diff options
author | Rob Herring <r.herring@freescale.com> | 2008-02-14 14:44:21 -0600 |
---|---|---|
committer | Daniel Schaeffer <daniel.schaeffer@timesys.com> | 2008-08-25 15:18:55 -0400 |
commit | 3f8ed3afb9cee6648f9650d5daf950bb9347cca6 (patch) | |
tree | c9e28cdb34524d44c5dc4dc0778ebdcb858af20c /drivers | |
parent | 49914084e797530d9baaf51df9eda77babc98fa8 (diff) |
ENGR00065563 Merge L2622-01 to 2.6.24
Merge L2622-01 release to 2.6.24 kernel.
Diffstat (limited to 'drivers')
433 files changed, 194545 insertions, 95 deletions
diff --git a/drivers/Makefile b/drivers/Makefile index 8cb37e3557d4..ca97a62a0217 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -76,6 +76,8 @@ obj-$(CONFIG_EISA) += eisa/ obj-$(CONFIG_LGUEST_GUEST) += lguest/ obj-$(CONFIG_CPU_FREQ) += cpufreq/ obj-$(CONFIG_CPU_IDLE) += cpuidle/ +obj-$(CONFIG_ARCH_MXC) += mxc/ +obj-y += otg/ obj-$(CONFIG_MMC) += mmc/ obj-$(CONFIG_NEW_LEDS) += leds/ obj-$(CONFIG_INFINIBAND) += infiniband/ diff --git a/drivers/char/Kconfig b/drivers/char/Kconfig index 2e3a0d4bc4c2..b77901481d37 100644 --- a/drivers/char/Kconfig +++ b/drivers/char/Kconfig @@ -411,6 +411,57 @@ config SGI_MBCS If you have an SGI Altix with an attached SABrick say Y or M here, otherwise say N. +config MXC_MU + bool "MXC Messaging Unit Driver" + depends on ARCH_MXC + depends on !ARCH_MX3 + depends on !ARCH_MX33 + depends on !ARCH_MX27 + depends on !ARCH_MX21 + select INPUT + default y + ---help--- + This module is called from other modules for + message transfers between MCU and DSP core. + +config MXC_IPC + bool "MXC IPC driver" + depends on MXC_MU && MXC_SDMA_API + depends on ARCH_MXC + depends on !ARCH_MX3 + depends on !ARCH_MX33 + depends on !ARCH_MX27 + depends on !ARCH_MX21 + default y + ---help--- + This module is called from other modules for + message and data transfers between MCU and DSP core. + +config MXC_SUPER_GEM + bool "MXC Super GEM" + depends on MXC_SDMA_API + depends on ARCH_MXC + depends on !ARCH_MX3 + depends on !ARCH_MX33 + depends on !ARCH_MX27 + depends on !ARCH_MX21 + default n + ---help--- + Super GEM module called for transfers between MCU and DSP core. + +config MXC_SDMA_TTY + tristate "MXC SDMA TTY Driver" + depends on ARCH_MXC && MXC_SDMA_API + depends on !ARCH_MX3 + depends on !ARCH_MX33 + depends on !ARCH_MX21 + depends on !ARCH_MX27 + select INPUT + default y + ---help--- + This module is called from other modules for + data transfers between MCU and DSP core. + source "drivers/serial/Kconfig" config UNIX98_PTYS diff --git a/drivers/char/Makefile b/drivers/char/Makefile index 07304d50e0cb..72ead21d2fc5 100644 --- a/drivers/char/Makefile +++ b/drivers/char/Makefile @@ -96,6 +96,9 @@ obj-$(CONFIG_CS5535_GPIO) += cs5535_gpio.o obj-$(CONFIG_GPIO_VR41XX) += vr41xx_giu.o obj-$(CONFIG_GPIO_TB0219) += tb0219.o obj-$(CONFIG_TELCLOCK) += tlclk.o +obj-$(CONFIG_MXC_MU) += mxc_mu.o +obj-$(CONFIG_MXC_IPC) += mxc_ipc.o +obj-$(CONFIG_MXC_SDMA_TTY) += mxc_sdma_tty.o obj-$(CONFIG_MWAVE) += mwave/ obj-$(CONFIG_AGP) += agp/ diff --git a/drivers/char/mxc_ipc.c b/drivers/char/mxc_ipc.c new file mode 100644 index 000000000000..a89f14c1ec20 --- /dev/null +++ b/drivers/char/mxc_ipc.c @@ -0,0 +1,2823 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mxc_ipc.c + * + * @brief This file provides all the kernel level and user level API + * definitions for all kind of transfers between MCU and DSP core. + * + * The Interfaces are with respect to the MCU core. Any driver or user-space + * application on the MCU side can transfer messages or data to the DSP side + * using the interfaces implemented here. + * + * @ingroup IPC + */ + +/* + * Include Files + */ + +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/wait.h> +#include <linux/init.h> +#include <linux/fs.h> +#include <linux/errno.h> +#include <linux/kdev_t.h> +#include <linux/major.h> +#include <linux/mm.h> +#include <linux/platform_device.h> +#include <linux/kthread.h> +#include <linux/circ_buf.h> +#include <linux/uio.h> +#include <linux/poll.h> +#include <linux/dma-mapping.h> +#include <asm/arch/mxc_mu.h> +#include <asm/arch/mxc_ipc.h> +#include <asm/dma.h> +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/semaphore.h> +#include <asm/atomic.h> +#include <asm/uaccess.h> + +#include "mxc_mu_reg.h" + +/*! + * Automatic major assignment + */ +#define MXC_IPC_MAJOR 0x0 + +/*! SDMA total receive buffer size, used by kernel space API */ +#define MAX_SDMA_RX_BUF_SIZE 8192 +/*! SDMA transmit buffer maximum size */ +#define MAX_SDMA_TX_BUF_SIZE 8192 +/*! + * Number of buffers used for SDMA receive, used by user API. This should + * be set to a power of 2. + */ +#define NUM_RX_SDMA_BUF 16 +/*! Size of each SDMA receive buffer */ +#define SDMA_RX_BUF_SIZE 512 +/*! MU buffer maximum size */ +#define MAX_MU_BUF_SIZE 512 + +/*! + * Max virtual channels available for this implementation + */ +#define IPC_MAX_VIRTUAL_CHANNELS 32 + +/*! + * Max ipc channels available for this implementation + */ +#define IPC_MAX_IPC_CHANNEL 10 + +/*! + * This defines an unused virtual channel + */ +#define IPC_DUMMY_CHANNEL IPC_MAX_VIRTUAL_CHANNELS + 1 + +/*! + * Max config index allowed for IPC channels. There are three types: + * read/write channel for messages + * read/write channel for data + * read only for data + */ +#define IPC_MAX_MU_CHANNEL_INDEX 3 +#define IPC_MAX_SDMA_BIDI_CHANNEL_INDEX 1 +#define IPC_MAX_SDMA_MONO_CHANNEL_INDEX 3 + +/*! + * States allowed for an IPC channel + */ +#define CHANNEL_CLOSED 0x0 +#define CHANNEL_OPEN 0x1 +#define CHANNEL_READ_ONGOING 0x2 +#define CHANNEL_WRITE_ONGOING 0x4 + +/*! + * Physical channels used to implement IPC channels. + * This defines depends on channels used on DSP side + * i.e. if the DSP uses SDMA channel 1 to do writes, + * we must use SDMA channel 1 to receive data. + * + * Currently we use 8 physical channels to implement the + * IPC abstraction. + * + * - MU channel 0 --> IPC channel 0 + * - MU channel 1 --> IPC channel 1 + * - MU channel 2 --> IPC channel 2 + * - MU channel 3 --> IPC channel 3 + * - SDMA Log channel 0 --> IPC channel 4 + * - SDMA Log channel 1 --> IPC channel 5 + * - SDMA Log channel 2 --> IPC channel 6 + * - SDMA Log channel 3 --> IPC channel 7 + * - SDMA Data channel 0 --> IPC channel 8 + * - SDMA Data channel 1 --> IPC channel 9 + * + * Note that IPC channels 8 and 9 uses two physical channels. This + * is because SDMA channels are unidirectional and IPC + * links must be bidirectional. + * + */ +#define SHORT_MESSAGE_CHANNEL0 0 +#define SHORT_MESSAGE_CHANNEL1 1 +#define SHORT_MESSAGE_CHANNEL2 2 +#define SHORT_MESSAGE_CHANNEL3 3 +#define LOGGING_CHANNEL0 4 +#define LOGGING_CHANNEL1 5 +#define LOGGING_CHANNEL2 6 +#define LOGGING_CHANNEL3 7 +#define PACKET_DATA_CHANNEL0 8 +#define PACKET_DATA_CHANNEL1 9 + +/*! + * SDMA physical channels used for the different IPC channels + */ +#define PACKET_DATA0_SDMA_RD_CHNL 1 +#define PACKET_DATA0_SDMA_WR_CHNL 2 +#define PACKET_DATA1_SDMA_RD_CHNL 3 +#define PACKET_DATA1_SDMA_WR_CHNL 4 +#define LOG0_SDMA_RD_CHNL 5 +#define LOG1_SDMA_RD_CHNL 6 +#define LOG2_SDMA_RD_CHNL 7 +#define LOG3_SDMA_RD_CHNL 8 + +/*! + * Operations allowed on IPC channels. These macros are + * used to tell the IPC driver-interface level which + * operation to execute. +*/ +#define IPC_READ 0x1 +#define IPC_WRITE 0x2 +#define IPC_RDWR IPC_READ | IPC_WRITE + +#define IPC_WRITE_BYTE_INIT_VAL -4 + +#define IPC_DEFAULT_MAX_CTRL_STRUCT_NUM 50 + +typedef void (*read_callback_t) (HW_CTRL_IPC_READ_STATUS_T *); +typedef void (*write_callback_t) (HW_CTRL_IPC_WRITE_STATUS_T *); +typedef void (*notify_callback_t) (HW_CTRL_IPC_NOTIFY_STATUS_T *); + +/*! + * This structure represents the IPC channels SDMA receive buffer. + */ +struct sdma_rbuf { + /*! Virtual addres of the read buffer */ + char *buf; + + /*! Physical address of the SDMA read buffers */ + dma_addr_t rpaddr; + + /*! Amount of data available to read */ + int count; + + /*! Offset within read buffer */ + int offset; +}; + +/*! + * This structure represents the SDMA channels used + * by the IPC abstraction. + */ +struct sdma_channel { + /*! SDMA read channel number */ + int read_channel; + + /*! SDMA write channel number */ + int write_channel; + + /*! SDMA write buffer */ + char *wbuf; + + /*! Physical address of the SDMA write buffer */ + dma_addr_t wpaddr; + + /*! DMA read buffers, uses muti-buffering scheme */ + struct sdma_rbuf rbuf[NUM_RX_SDMA_BUF]; + + /*! Head to keep track of DMA read buffers */ + int rbuf_head; + + /*! Tail to keep track of DMA read buffers */ + int rbuf_tail; + + /*! Head to keep track of DMA write buffers */ + int wbuf_head; + + /*! Tail to keep track of DMA write buffers */ + int wbuf_tail; + + /*! Tasklet to call SDMA read callback */ + struct tasklet_struct read_tasklet; + + /*! Tasklet to call SDMA write callback */ + struct tasklet_struct write_tasklet; +}; + +/*! + * This structure represents the MU channels used + * by the IPC abstraction. + */ +struct mu_channel { + /*! MU channel number */ + int index; + + /*! MU read buffer */ + struct circ_buf *rbuf; + + /*! MU write buffer */ + struct circ_buf *wbuf; + + /*! Number of bytes writen */ + int bytes_written; +}; + +/*! + * This structure represents an IPC channel. It is represented + * by one or even two physical channels, plus a control structure + */ +struct ipc_channel { + /*! Physical channel(s) used to implement an IPC channel */ + union { + struct sdma_channel sdma; + struct mu_channel mu; + } ch; + + /*! + * Control structure. It handles access and transfers on this + * IPC channel + */ + struct ipc_priv_data *priv; + + /*! Lock to protect the channel buffers */ + spinlock_t channel_lock; +}; + +/*! + * This structure represents a virtual channel. Multiple virtual channels + * could be connected to an IPC channel. IPC abstraction supports up to 32 + * virtual channels. + */ +struct virtual_channel { + /*! Controls access to the handle of this channel */ + struct semaphore sem; + + /*! Current state of the channel */ + atomic_t state; + /*!Added state variables for IPCv2 Read-while -write functionality */ + atomic_t read_state_ipcv2; + + atomic_t write_state_ipcv2; + + /*! + * + */ + char *data; + + /*! + * Pointer to the control structure of the IPC channel mapped by + * this virtual channel + */ + struct ipc_channel *ipc; +}; + +/*! + * Private data. Each time an IPC channel is opened, a private + * structure is allocated and stored in the priv + * field of the file descriptor. This data is + * used for further operations on the recently opened + * channel + */ +struct ipc_priv_data { + /*! Blocking mode (non-blocking, blocking) */ + atomic_t blockmode; + + /*! Physical channel to which the virtual channel is mapped to */ + unsigned short pchannel; + + /*! Index of virtual channel */ + unsigned short vchannel; + + /*! This stores number of bytes received */ + unsigned int read_count; + + /*! This stores number of bytes transmitted */ + unsigned int write_count; + + /*! Write waitqueue */ + wait_queue_head_t wq; + + /*! Read waitqueue */ + wait_queue_head_t rq; + + /*! Store the task_struct structure of the calling process. */ + struct task_struct *owner; + + /*! + * Virtual channel assigned to the IPC channel storing + * this structure. + */ + struct virtual_channel *vc; + + /*! + * Callbacks used to signal the end of a operation + * on an IPC channel. Only for kernel modules. + */ + struct kernel_callbacks *k_callbacks; +}; + +/*! + * This struct stores the callbacks pointers + * used to communicate the end of an operation + * on an IPC channel. These callbacks must be + * provided by the caller. This feature is available + * only for kernel modules. + */ +struct kernel_callbacks { + read_callback_t read; + write_callback_t write; + notify_callback_t notify; +}; + +/*! + * Array used to store the physical channels. + */ +static struct ipc_channel ipc_channels[IPC_MAX_IPC_CHANNEL]; + +/*! + * Array used to store the pointers to the virtual channels. + * These pointers will be allocated when trying to open a virtual channel. + */ +static struct virtual_channel virtual_channels[IPC_MAX_VIRTUAL_CHANNELS]; + +/*! + * Array used to store the channel handlers, correlated to the virtual_channels array. + * In future implementations, the handler could have been directly a + * virtual_channel pointer, but then it would need an harmonization between AP and BP + * structures to properly define what is a handler to a virtual channel on both sides. + */ +static HW_CTRL_IPC_CHANNEL_T channel_handlers[IPC_MAX_VIRTUAL_CHANNELS]; + +/*! + * Status registers of the MU channels + */ +static unsigned int wbits[4] = { AS_MUMSR_MTE0, AS_MUMSR_MTE1, AS_MUMSR_MTE2, + AS_MUMSR_MTE3 +}; +static unsigned int rbits[4] = { AS_MUMSR_MRF0, AS_MUMSR_MRF1, AS_MUMSR_MRF2, + AS_MUMSR_MRF3 +}; + +/*! + * Major number + */ +static int major_num = 0; + +static struct class *mxc_ipc_class; + +static void sdma_user_readcb(void *args); +static void sdma_user_writecb(void *args); + +/*! + * Locks the virtual channel located on the index channel. + * @param channel index of the virtual channel to lock + */ +static inline int lock_virtual_channel(unsigned short channel) +{ + return (down_interruptible(&virtual_channels[channel].sem)); +} + +/*! + * Try to locks the virtual channel located on the index channel. + * @param channel index of the virtual channel to (try)lock + */ +static inline int trylock_virtual_channel(unsigned short channel) +{ + return down_trylock(&virtual_channels[channel].sem); +} + +/*! + * Unlocks the virtual channel located on the index channel + * @param channel index of the virtual channel to unlock + */ +static inline void unlock_virtual_channel(unsigned short channel) +{ + up(&virtual_channels[channel].sem); +} + +/*! + * Finds an unused channel into the virtual channel array + * + * @return The function returns teh index of the first unused + * channel found, IPC_DUMMY_CHANNEL otherwise. + */ +inline int get_free_virtual_channel(void) +{ + struct virtual_channel *vch = NULL; + int i; + + for (i = 0; i < IPC_MAX_VIRTUAL_CHANNELS; i++) { + if (trylock_virtual_channel(i) == 1) { + continue; + } + + vch = &virtual_channels[i]; + + if (atomic_read(&vch->state) == CHANNEL_CLOSED) { + return i; + } + + unlock_virtual_channel(i); + } + + return IPC_DUMMY_CHANNEL; +} + +/*! + * The MU interrupt handler schedules this tasklet for + * execution to start processing a write request. + * This function writes data to chnum MU register and signals + * the end of the transfer to the servicing thread attached + * to the IPC channel. + * + * @param chnum MU channel number where the operation will occur + */ +static void mu_write_tasklet(int chnum) +{ + struct mu_channel *mu = NULL; + struct ipc_priv_data *priv = NULL; + char *p = NULL; + int bytes, error = 0; + + priv = ipc_channels[chnum].priv; + mu = &ipc_channels[chnum].ch.mu; + + /* Check for bytes in the IPC's MU channel write buffer */ + bytes = CIRC_CNT(mu->wbuf->head, mu->wbuf->tail, MAX_MU_BUF_SIZE); + + while ((bytes != 0) && (readl(AS_MUMSR) & wbits[chnum])) { + + pr_debug("remaining bytes = %d\n", bytes); + + p = &mu->wbuf->buf[mu->wbuf->tail]; + + if (mxc_mu_mcuwrite(mu->index, p)) { + pr_debug("Mu MCU write Failed\n"); + error = 1; + break; + } + mu->wbuf->tail = (mu->wbuf->tail + 4) & (MAX_MU_BUF_SIZE - 1); + bytes = CIRC_CNT(mu->wbuf->head, mu->wbuf->tail, + MAX_MU_BUF_SIZE); + } + + spin_lock(&ipc_channels[chnum].channel_lock); + bytes = CIRC_CNT(mu->wbuf->head, mu->wbuf->tail, MAX_MU_BUF_SIZE); + /* Disable interrupts if buffer is empty */ + if ((bytes == 0) && (error == 0)) { + pr_debug("(2)remaining bytes = %d\n", bytes); + mxc_mu_intdisable(mu->index, TX); + } + spin_unlock(&ipc_channels[chnum].channel_lock); + + if (waitqueue_active(&priv->wq)) { + wake_up_interruptible(&priv->wq); + } +} + +/*! + * The MU interrupt handler schedules this tasklet for + * execution to start processing a read request. + * This function reads data from chnum MU register and signals + * the end of the transfer to the servicing thread attached + * to the IPC channel. + * + * @param chnum MU channel number where the operation will occur + */ +static void mu_read_tasklet(int chnum) +{ + struct mu_channel *mu = NULL; + struct ipc_priv_data *priv = NULL; + int bytes = 0; + char p[4]; + int error = 0; + + priv = ipc_channels[chnum].priv; + mu = &ipc_channels[chnum].ch.mu; + + bytes = CIRC_SPACE(mu->rbuf->head, mu->rbuf->tail, MAX_MU_BUF_SIZE); + + while ((bytes >= 4) && (readl(AS_MUMSR) & rbits[chnum])) { + + pr_debug("space available = %d\n", bytes); + + if (mxc_mu_mcuread(mu->index, p)) { + pr_debug("MU MCU Read Failed\n"); + error = 1; + break; + } + + *((int *)(&(mu->rbuf->buf[mu->rbuf->head]))) = *((int *)p); + mu->rbuf->head = (mu->rbuf->head + 4) & (MAX_MU_BUF_SIZE - 1); + + bytes = CIRC_SPACE(mu->rbuf->head, mu->rbuf->tail, + MAX_MU_BUF_SIZE); + } + + /* Check if space available in read buffer */ + spin_lock(&ipc_channels[chnum].channel_lock); + bytes = CIRC_SPACE(mu->rbuf->head, mu->rbuf->tail, MAX_MU_BUF_SIZE); + if ((bytes < 4) && (error == 0)) { + mxc_mu_intdisable(mu->index, RX); + } else { + pr_debug("(2)space available = %d\n", bytes); + mxc_mu_intenable(mu->index, RX); + } + spin_unlock(&ipc_channels[chnum].channel_lock); + if (waitqueue_active(&priv->rq)) { + wake_up_interruptible(&priv->rq); + } +} + +/*! + * The MU interrupt handler schedules this tasklet for + * execution to start processing a read request. + * This function reads data from chnum MU register and signals + * the end of the transfer to the servicing thread attached + * to the IPC channel. + * + * This function is used when the caller is a kernel module + * + * @param chnum MU channel number where the operation will occur + */ +static void mu_read_tasklet_kernel(int chnum) +{ + struct ipc_priv_data *priv = NULL; + struct mu_channel *mu = NULL; + char p[4]; + int bytes = 0; + int error = 0; + + priv = ipc_channels[chnum].priv; + mu = &ipc_channels[chnum].ch.mu; + + while ((bytes < priv->read_count) && (readl(AS_MUMSR) & rbits[chnum])) { + pr_debug("space available = %d\n", bytes); + if (mxc_mu_mcuread(mu->index, p)) { + pr_debug("MU MCU Read Failed"); + error = 1; + break; + } + + *((int *)(&(priv->vc->data[bytes]))) = *((int *)p); + bytes += 4; + } + + if (error != 0) { + mxc_mu_intenable(mu->index, RX); + } else { + mxc_mu_intdisable(mu->index, RX); + mxc_mu_unbind(mu->index, RX); + atomic_set(&priv->vc->state, CHANNEL_OPEN); + + if (priv->k_callbacks->read != NULL) { + HW_CTRL_IPC_READ_STATUS_T status; + + status.channel = &channel_handlers[priv->vchannel]; + status.nb_bytes = bytes; + priv->k_callbacks->read(&status); + } + } +} + +/*! + * The MU interrupt handler schedules this tasklet for + * execution to start processing a write request. + * This function write data to chnum MU register and signals + * the end of the transfer to the servicing thread attached + * to the IPC channel. + * + * This function is used when the caller is a kernel module + * + * @param chnum MU channel number where the operation will occur + */ +static void mu_write_tasklet_kernel(int chnum) +{ + struct ipc_priv_data *priv = NULL; + struct mu_channel *mu = NULL; + char *p = NULL; + int error = 0; + + priv = ipc_channels[chnum].priv; + mu = &ipc_channels[chnum].ch.mu; + + /* Indication that previous write was successful */ + mu->bytes_written += 4; + + while (mu->bytes_written < priv->write_count) { + pr_debug("remaining bytes = %d\n", mu->bytes_written); + p = &priv->vc->data[mu->bytes_written]; + if (mxc_mu_mcuwrite(mu->index, p) != 0) { + error = 1; + break; + } + if ((readl(AS_MUMSR) & wbits[chnum]) == 1) { + mu->bytes_written += 4; + } else { + /* + * Data not yet read by DSP side, enable interrupts + * and exit + */ + break; + } + } + + /* Enable interrupts if there is data pending to be transmitted */ + if ((error == 0) && (mu->bytes_written < priv->write_count)) { + mxc_mu_intenable(mu->index, TX); + } else { + mxc_mu_unbind(mu->index, TX); + atomic_set(&priv->vc->state, CHANNEL_OPEN); + + if (priv->k_callbacks->write != NULL) { + HW_CTRL_IPC_WRITE_STATUS_T status; + + status.channel = &channel_handlers[priv->vchannel]; + status.nb_bytes = mu->bytes_written; + priv->k_callbacks->write(&status); + } + } +} + +/*! + * Enable MU channel interrupts + * + * @param chnum MU channel to enable + * @return returns 0 on success, negative value otherwise + */ +static int mxc_ipc_enable_muints(int chnum) +{ + int status = 0; + struct mu_channel *mu = NULL; + + mu = &ipc_channels[chnum].ch.mu; + /* Bind the default callback functions */ + status = mxc_mu_bind(mu->index, &mu_read_tasklet, RX); + if (status == 0) { + status = mxc_mu_bind(mu->index, &mu_write_tasklet, TX); + } + if (status == 0) { + status = mxc_mu_intdisable(mu->index, TX); + } + /* Enable the RX interrupt to start receiving data */ + if (status == 0) { + status = mxc_mu_intenable(mu->index, RX); + } + + /* Cleanup if we had an error at any step */ + if (status != 0) { + pr_debug("Error enabling MU channel %d interrupts \n", chnum); + mxc_mu_unbind(mu->index, RX); + mxc_mu_unbind(mu->index, TX); + } + + return status; +} + +/*! + * Allocate a MU channel + * + * @param chnum MU channel to allocate + * @return returns 0 on success, negative value otherwise + */ +static int allocate_mu_channel(int chnum) +{ + struct mu_channel *mu = NULL; + int num = 0, status = 0; + + num = mxc_mu_alloc_channel(chnum); + if (num < 0) { + pr_debug("Allocation of MU channel %d failed \n", chnum); + return num; + } + mu = &ipc_channels[chnum].ch.mu; + mu->index = num; + + status = mxc_mu_intdisable(mu->index, TX); + if (status == 0) { + status = mxc_mu_intdisable(mu->index, RX); + } + + if (status != 0) { + mxc_mu_dealloc_channel(mu->index); + return status; + } + /* Allocate the read and write buffers */ + mu->rbuf = kmalloc(sizeof(struct circ_buf), GFP_KERNEL); + if (mu->rbuf == NULL) { + goto err_alloc_rbuf; + } + mu->rbuf->buf = kmalloc(MAX_MU_BUF_SIZE, GFP_KERNEL); + if (mu->rbuf->buf == NULL) { + goto err_alloc_rbuf_buf; + } + mu->wbuf = kmalloc(sizeof(struct circ_buf), GFP_KERNEL); + if (mu->wbuf == NULL) { + goto err_alloc_wbuf; + } + mu->wbuf->buf = kmalloc(MAX_MU_BUF_SIZE, GFP_KERNEL); + if (mu->wbuf->buf == NULL) { + goto err_alloc_wbuf_buf; + } + spin_lock_init(&ipc_channels[chnum].channel_lock); + mu->wbuf->head = mu->wbuf->tail = 0; + mu->rbuf->head = mu->rbuf->tail = 0; + + return 0; + + err_alloc_wbuf_buf: + kfree(mu->wbuf); + err_alloc_wbuf: + kfree(mu->rbuf->buf); + err_alloc_rbuf_buf: + kfree(mu->rbuf); + err_alloc_rbuf: + mxc_mu_dealloc_channel(mu->index); + return -ENOMEM; +} + +/*! Deallocate a MU channel + * + * @param chnum MU channel to allocate + * @return returns 0 on success, negative value otherwise + */ +static int deallocate_mu_channel(int chnum) +{ + struct mu_channel *mu = NULL; + int ret = 0; + + mu = &ipc_channels[chnum].ch.mu; + + ret = mxc_mu_intdisable(mu->index, TX); + ret += mxc_mu_intdisable(mu->index, RX); + ret += mxc_mu_unbind(mu->index, TX); + ret += mxc_mu_unbind(mu->index, RX); + ret += mxc_mu_dealloc_channel(mu->index); + + mu->index = -1; + + /* Free the read and write buffers */ + kfree(mu->rbuf->buf); + kfree(mu->rbuf); + kfree(mu->wbuf->buf); + kfree(mu->wbuf); + + return ret; +} + +/*! + * Start the SDMA read channel accessed from kernel mode + * + * @param ipc_chnl SDMA channel to start + * @param num_od_bd number of BD's allocated for the channel + * + * @return 0 on succes, negative number if channel does not start + */ +static int initialize_sdma_read_channel_kernel(struct ipc_channel *ipc_chnl, + int num_of_bd) +{ + struct sdma_channel *read_chnl = NULL; + dma_channel_params sdma_params; + int result = 0; + + read_chnl = &ipc_chnl->ch.sdma; + + memset(&sdma_params, 0, sizeof(dma_channel_params)); + sdma_params.peripheral_type = DSP; + sdma_params.transfer_type = dsp_2_emi; + sdma_params.event_id = 0; + sdma_params.callback = NULL; + sdma_params.arg = 0; + sdma_params.bd_number = num_of_bd; + + result = mxc_dma_setup_channel(read_chnl->read_channel, &sdma_params); + if (result < 0) { + return result; + } + + return result; +} + +/*! + * Start the SDMA read channel accessed from user mode + * + * @param ipc_chnl SDMA channel to start + * + * @return 0 on succes, negative number if channel does not start + */ +static int initialize_sdma_read_channel_user(int ipc_chnl) +{ + dma_request_t sdma_request; + struct ipc_channel ipc_num; + struct sdma_channel *read_chnl = NULL; + dma_channel_params sdma_params; + int result = 0; + int i = 0; + + ipc_num = ipc_channels[ipc_chnl]; + read_chnl = &ipc_channels[ipc_chnl].ch.sdma; + + memset(&sdma_params, 0, sizeof(dma_channel_params)); + sdma_params.peripheral_type = DSP; + sdma_params.transfer_type = dsp_2_emi; + sdma_params.event_id = 0; + sdma_params.callback = NULL; + sdma_params.arg = 0; + sdma_params.bd_number = NUM_RX_SDMA_BUF; + + result = mxc_dma_setup_channel(read_chnl->read_channel, &sdma_params); + if (result < 0) { + return result; + } else { + for (i = 0; i < NUM_RX_SDMA_BUF; i++) { + /* Allocate the DMA'able buffers filled by SDMA engine */ + read_chnl->rbuf[i].buf = + dma_alloc_coherent(NULL, SDMA_RX_BUF_SIZE, + &read_chnl->rbuf[i].rpaddr, + GFP_DMA); + if (read_chnl->rbuf[i].buf == NULL) { + result = -1; + break; + } + read_chnl->rbuf[i].count = 0; + read_chnl->rbuf[i].offset = 0; + memset(&sdma_request, 0, sizeof(dma_request_t)); + sdma_request.count = SDMA_RX_BUF_SIZE; + sdma_request.destAddr = + (__u8 *) read_chnl->rbuf[i].rpaddr; + sdma_request.bd_cont = 1; + result = mxc_dma_set_config(read_chnl->read_channel, + &sdma_request, i); + if (result != 0) { + break; + } + } + } + + if (result != 0) { + return result; + } + + read_chnl->rbuf_head = 0; + read_chnl->rbuf_tail = 0; + + mxc_dma_set_callback(read_chnl->read_channel, sdma_user_readcb, + ipc_num.priv); + mxc_dma_start(read_chnl->read_channel); + return 0; +} + +/*! + * Setup the write channel for user mode and kernel mode + * + * @param ipc_chnl SDMA channel to start + * @param kernel_access flag indicates the access type, 1=kernel mode access. + * @param num_of_bd number of BD's allocated for the channel + * + * @return 0 on succes, negative number if channel does not start + */ +static int initialize_sdma_write_channel(struct ipc_channel *ipc_chnl, + int kernel_access, int num_of_bd) +{ + struct sdma_channel *write_chnl = NULL; + dma_channel_params sdma_params; + int result = 0; + + write_chnl = &ipc_chnl->ch.sdma; + memset(&sdma_params, 0, sizeof(dma_channel_params)); + sdma_params.peripheral_type = DSP; + sdma_params.transfer_type = emi_2_dsp; + sdma_params.event_id = 0; + sdma_params.callback = NULL; + sdma_params.arg = 0; + sdma_params.bd_number = num_of_bd; + + result = mxc_dma_setup_channel(write_chnl->write_channel, &sdma_params); + if (result < 0) { + pr_debug("Failed Setting up SDMA channel %d\n", + write_chnl->write_channel); + mxc_free_dma(write_chnl->write_channel); + write_chnl->write_channel = -1; + return result; + } + + /* + * Allocate the write buffer for user space, used to copy user-space + * data for DMA. + */ + if (kernel_access == 0) { + write_chnl->wbuf = + dma_alloc_coherent(NULL, MAX_SDMA_TX_BUF_SIZE, + &write_chnl->wpaddr, GFP_DMA); + if (write_chnl->wbuf == NULL) { + return -ENOMEM; + } + } + + return 0; +} + +/*! + * Allocate a SDMA channel + * + * @param ipc_chnl IPC channel to allocate + * @param chnum SDMA channel to allocate + * @param mode open mode for this channel (read, write, read/write) + * + * @return returns 0 on success, negative number otherwise. + */ +static int allocate_sdma_channel(int ipc_chnl, int chnum, int mode) +{ + struct ipc_channel ipc_num; + struct sdma_channel *sdma = NULL; + int result = 0; + + ipc_num = ipc_channels[ipc_chnl]; + sdma = &ipc_channels[ipc_chnl].ch.sdma; + if (mode == (IPC_RDWR)) { + sdma->write_channel = chnum; + sdma->read_channel = chnum - 1; + } else { + sdma->write_channel = -1; + sdma->read_channel = chnum; + } + + if (mode & IPC_WRITE) { + result = + mxc_request_dma(&sdma->write_channel, "IPC WRITE SDMA"); + if (result < 0) { + pr_debug("Failed Opening SDMA channel %d\n", + sdma->write_channel); + sdma->write_channel = -1; + return result; + } + } + + if (mode & IPC_READ) { + result = mxc_request_dma(&sdma->read_channel, "IPC READ SDMA"); + if (result < 0) { + pr_debug("Failed Opening SDMA channel %d\n", + sdma->read_channel); + sdma->read_channel = -1; + if (mode & IPC_WRITE) { + mxc_free_dma(sdma->write_channel); + sdma->write_channel = -1; + } + return result; + } + } + + spin_lock_init(&ipc_channels[chnum].channel_lock); + return result; +} + +static void mxc_ipc_free_readbuf_user(int chnum) +{ + int i = 0; + struct sdma_channel *sdma; + + sdma = &ipc_channels[chnum].ch.sdma; + + /* Free the DMA'able receive buffers */ + for (i = 0; i < NUM_RX_SDMA_BUF; i++) { + if (sdma->rbuf[i].buf != NULL) { + dma_free_coherent(NULL, SDMA_RX_BUF_SIZE, + sdma->rbuf[i].buf, + sdma->rbuf[i].rpaddr); + sdma->rbuf[i].buf = NULL; + } + } +} + +/*! + * Deallocate a SDMA channel + * + * @param chnum SDMA channel to allocate + */ +static void deallocate_sdma_channel(int chnum) +{ + struct sdma_channel *sdma; + + sdma = &ipc_channels[chnum].ch.sdma; + if (sdma->write_channel != -1) { + mxc_free_dma(sdma->write_channel); + } + if (sdma->read_channel != -1) { + mxc_free_dma(sdma->read_channel); + } + sdma->write_channel = -1; + sdma->read_channel = -1; + + /* Free the DMA'able transmit buffer */ + if (sdma->wbuf != NULL) { + dma_free_coherent(NULL, MAX_SDMA_TX_BUF_SIZE, + sdma->wbuf, sdma->wpaddr); + sdma->wbuf = NULL; + } +} + +/*! + * Copy bytes from the circular buffer to the user buffer + * + * @param buf buffer to store data + * @param count number of bytes to copy + * @param cbuf circular_buffer to copy the data from + * @param buf_size size of the circular buffer + * + * @return number of bytes copied into the user buffer + */ +static int mxc_ipc_copy_from_mubuf(char *buf, int count, struct circ_buf *cbuf, + int buf_size) +{ + int n = 0, ret = 0, result = 0; + + while (1) { + n = CIRC_CNT_TO_END(cbuf->head, cbuf->tail, buf_size); + pr_debug("bytes available in circular buffer = %d\n", n); + if (count < n) { + n = count; + } + if (n <= 0) { + break; + } + pr_debug("copying %d bytes into user's buffer\n", n); + result = copy_to_user(buf, cbuf->buf + cbuf->tail, n); + n -= result; + cbuf->tail = (cbuf->tail + n) & (buf_size - 1); + buf += n; + count -= n; + ret += n; + /* Check if there is problem copying data */ + if (result != 0) { + break; + } + } + + return ret; +} + +/*! + * Write bytes to the circular buffer from the user buffer + * + * + * @param buf buffer to copy data from + * @param count number of bytes to copy + * @param cbuf circular_buffer to copy the data to + * + * @return number of bytes copied into the circular buffer + */ +static int mxc_ipc_copy_to_buf(char **buf, int *count, struct circ_buf *cbuf) +{ + int n = 0; + int result; + int ret = 0; + + while (1) { + n = CIRC_SPACE_TO_END(cbuf->head, cbuf->tail, MAX_MU_BUF_SIZE); + if (*count < n) { + n = *count; + } + if (n < 4) { + break; + } + result = copy_from_user(cbuf->buf + cbuf->head, *buf, n); + if (result != 0) { + pr_debug("EINVAL error\n"); + ret = -EFAULT; + break; + } + n -= result; + cbuf->head = (cbuf->head + n) & (MAX_MU_BUF_SIZE - 1); + *buf += n; + *count -= n; + } + + return ret; +} + +/*! + * This function copies data from the MU buffer into the user read buffer. + * + * @param priv Private data. Used to synchronize the top half + * and the botton half as well as to keep track of + * the transfer + * @param count number of bytes to be transferred + * @param buf buffer where received bytes are saved + * + * @return number of transferred bytes + */ +static int mu_start_dsp_transfer(struct ipc_priv_data *priv, + int count, char *buf) +{ + int bytes = 0; + struct mu_channel *mu = &priv->vc->ipc->ch.mu; + + bytes = mxc_ipc_copy_from_mubuf(buf, count, mu->rbuf, MAX_MU_BUF_SIZE); + pr_debug("bytes read = %d\n", bytes); + + spin_lock(&priv->vc->ipc->channel_lock); + /* Enable intrs if space in receive buffer */ + if (CIRC_SPACE(mu->rbuf->head, mu->rbuf->tail, MAX_MU_BUF_SIZE)) { + mxc_mu_intenable(mu->index, RX); + } else { + mxc_mu_intdisable(mu->index, RX); + } + spin_unlock(&priv->vc->ipc->channel_lock); + + if (bytes == 0) { + pr_debug("Could not copy to user buffer error\n"); + return -EFAULT; + } + + return bytes; +} + +/*! + * This function starts a copies data into MU buffer and starts the transfer. + * + * @param priv Private data. Used to synchronize the top half + * and the botton half as well as to keep track of + * the transfer + * @param count number of bytes to be transferred + * @param buf buffer containing the bytes to be transferred + * + * @return number of transferred bytes. + */ +static int mu_start_mcu_transfer(struct ipc_priv_data *priv, + int count, const char *buf) +{ + struct mu_channel *mu = &priv->vc->ipc->ch.mu; + int retval = 0, orig_cnt; + + orig_cnt = count; + + /* Copy all the data into write buffer */ + while (count > 0) { + retval = mxc_ipc_copy_to_buf((char **)&buf, &count, mu->wbuf); + if (retval < 0) { + break; + } + + /* Enable TX intr, data available in buffer */ + spin_lock(&priv->vc->ipc->channel_lock); + mxc_mu_intenable(mu->index, TX); + spin_unlock(&priv->vc->ipc->channel_lock); + /* Check if non-blocking mode requested */ + if ((atomic_read(&priv->blockmode)) == 0) { + break; + } + if (count == 0) { + break; + } + if (wait_event_interruptible(priv->wq, + CIRC_SPACE(mu->wbuf->head, + mu->wbuf->tail, + MAX_MU_BUF_SIZE))) { + break; + } + } + + pr_debug("bytes written = %d\n", count); + + return orig_cnt - count; +} + +/*! + * This function starts a MCU->DSP transfer requested by a kernel module. + * This transfer uses a MU channel. + * + * @param mu the MU channel structure + * + * @return Zero + */ +static int mu_start_mcu_transfer_kernel(struct mu_channel *mu) +{ + pr_debug("writing bytes to DSP\n"); + mxc_mu_bind(mu->index, &mu_write_tasklet_kernel, TX); + mxc_mu_intenable(mu->index, TX); + + return 0; +} + +/*! + * This function starts a DSP->MCU transfer requested by a kernel module. + * This transfer uses a MU channel. + * + * @param priv Private data. Used to synchronize and to keep track of + * the transfer + * @param count number of bytes to be transferred + * @param buf buffer containing the bytes to be transferred + * + * @return Zero + */ +static int mu_start_dsp_transfer_kernel(struct ipc_priv_data *priv, + int count, char *buf) +{ + struct mu_channel *mu = &priv->vc->ipc->ch.mu; + + if (count > MAX_MU_BUF_SIZE) { + count = MAX_MU_BUF_SIZE; + } + + priv->read_count = count; + priv->vc->data = buf; + + mxc_mu_bind(mu->index, &mu_read_tasklet_kernel, RX); + mxc_mu_intenable(mu->index, RX); + + return 0; +} + +/*! + * This function is called by the SDMA's ISR whenever a MCU->DSP + * transfer has finished. This callback is used whenever a + * kernel module requests a transfer. + * + * @param args points to a priv structure. This structure contains + * fields to synchronize the IPC channel and the top half + * + * @return None + */ +static void sdma_kernel_writecb(void *args) +{ + dma_request_t sdma_request; + struct ipc_priv_data *priv = NULL; + struct sdma_channel *sdma = NULL; + + priv = (struct ipc_priv_data *)args; + sdma = &priv->vc->ipc->ch.sdma; + + mxc_dma_get_config(sdma->write_channel, &sdma_request, 0); + + priv->write_count = sdma_request.count; + + atomic_set(&priv->vc->state, CHANNEL_OPEN); + + tasklet_schedule(&sdma->write_tasklet); + pr_debug("Called SDMA write callback %d\n", priv->write_count); +} + +/*! + * This function is called by the SDMA's ISR whenever a MCU->DSP + * transfer has finished. This callback is used whenever a + * kernel module requests a transfer. + * + * @param args points to a priv structure. This structure contains + * fields to synchronize the IPC channel and the top half + * + * @return None + */ +static void sdma_kernel_writecb_ipcv2(void *args) +{ + struct ipc_priv_data *priv = NULL; + struct sdma_channel *sdma = NULL; + + priv = (struct ipc_priv_data *)args; + sdma = &priv->vc->ipc->ch.sdma; + + priv->write_count = 0; + + atomic_set(&priv->vc->write_state_ipcv2, CHANNEL_OPEN); + + tasklet_schedule(&sdma->write_tasklet); + pr_debug("Called SDMA write callback\n"); +} + +/*! + * This function starts a MCU->DSP transfer requested by a kernel module. + * This transfer uses a SDMA channel. + * + * @param ipc_chn IPC channel data structure + * @param count number of bytes to be transferred + * + * @return Zero + */ +static int sdma_start_mcu_transfer_kernel(struct ipc_channel *ipc_chn, + int count) +{ + dma_request_t sdma_request; + struct sdma_channel *sdma = NULL; + + sdma = &ipc_chn->ch.sdma; + memset(&sdma_request, 0, sizeof(dma_request_t)); + + pr_debug("writing %d bytes to DSP\n", count); + + sdma_request.sourceAddr = (__u8 *) sdma->wpaddr; + sdma_request.count = count; + sdma_request.bd_cont = 1; + mxc_dma_set_config(sdma->write_channel, &sdma_request, sdma->wbuf_head); + sdma->wbuf_head++; + if (sdma->wbuf_head >= sdma->wbuf_tail) { + sdma->wbuf_head = 0; + } + + return 0; +} + +/*! + * This function is called by the SDMA's ISR whenever a MCU->DSP + * transfer has finished. This callback is used whenever a + * user space program requests a transfer. + * + * @param args points to a priv structure. This structure contains + * fields to synchronize the IPC channel and the top half + * + * @return None + */ +static void sdma_user_writecb(void *args) +{ + dma_request_t sdma_request; + struct ipc_priv_data *priv = NULL; + struct sdma_channel *sdma = NULL; + + priv = (struct ipc_priv_data *)args; + sdma = &priv->vc->ipc->ch.sdma; + + mxc_dma_get_config(sdma->write_channel, &sdma_request, 0); + + pr_debug("Called SDMA write callback %d\n", sdma_request.count); + atomic_set(&priv->vc->state, CHANNEL_OPEN); + if (waitqueue_active(&priv->wq)) { + wake_up_interruptible(&priv->wq); + } +} + +/*! + * This function starts a MCU->DSP transfer requested by a user space + * program. This transfer uses a SDMA channel. + * + * @param priv Private data. Useful to synchronize and to keep track of + * the transfer + * @param count number of bytes to be transferred + * @param buf buffer containing the bytes to be transferred + * + * @return Zero + */ +static int sdma_start_mcu_transfer(struct ipc_priv_data *priv, + int count, const char *buf) +{ + dma_request_t sdma_request; + struct sdma_channel *sdma = NULL; + int result = 0; + + sdma = &priv->vc->ipc->ch.sdma; + + /* Check if the Channel is in use */ + while ((atomic_read(&priv->vc->state)) == CHANNEL_WRITE_ONGOING) { + pr_debug("Write Block till channel is available\n"); + if (wait_event_interruptible(priv->wq, + (atomic_read(&priv->vc->state) != + CHANNEL_WRITE_ONGOING))) { + return -ERESTARTSYS; + } + } + + result = copy_from_user(sdma->wbuf, buf, count); + if (result != 0) { + kfree(sdma->wbuf); + return -EFAULT; + } + + atomic_set(&priv->vc->state, CHANNEL_WRITE_ONGOING); + memset(&sdma_request, 0, sizeof(dma_request_t)); + + pr_debug("writing %d bytes to SDMA channel %d\n", count, + sdma->write_channel); + + sdma_request.sourceAddr = (__u8 *) sdma->wpaddr; + sdma_request.count = count; + + mxc_dma_set_callback(sdma->write_channel, sdma_user_writecb, priv); + mxc_dma_set_config(sdma->write_channel, &sdma_request, 0); + mxc_dma_start(sdma->write_channel); + + return count; +} + +/*! + * This function is called by the SDMA ISR whenever a DSP->MCU + * transfer has finished. This callback is used whenever a + * kernel module requests a transfer. + * + * @param args points to a priv structure. This structure contains + * fields to synchronize the IPC channel and the top half + * + * @return None + */ +static void sdma_kernel_readcb(void *args) +{ + dma_request_t sdma_request; + struct ipc_priv_data *priv; + struct sdma_channel *sdma = NULL; + + priv = (struct ipc_priv_data *)args; + sdma = &priv->vc->ipc->ch.sdma; + + mxc_dma_get_config(sdma->read_channel, &sdma_request, 0); + + priv->read_count = sdma_request.count; + + atomic_set(&priv->vc->state, CHANNEL_OPEN); + + tasklet_schedule(&sdma->read_tasklet); + pr_debug("Called SDMA read callback %d\n", priv->read_count); +} + +/*! + * This function is called by the SDMA ISR whenever a DSP->MCU + * transfer has finished. This callback is used whenever a + * kernel module requests a transfer. + * + * @param args points to a priv structure. This structure contains + * fields to synchronize the IPC channel and the top half + * + * @return None + */ +static void sdma_kernel_readcb_ipcv2(void *args) +{ + struct ipc_priv_data *priv; + struct sdma_channel *sdma = NULL; + + priv = (struct ipc_priv_data *)args; + sdma = &priv->vc->ipc->ch.sdma; + + priv->read_count = 0; + + atomic_set(&priv->vc->read_state_ipcv2, CHANNEL_OPEN); + + tasklet_schedule(&sdma->read_tasklet); + pr_debug("Called SDMA read callback\n"); +} + +/*! + * This function starts a DSP->MCU transfer requested by a kernel module. + * This transfer uses a SDMA channel. + * + * @param priv Private data. Used to synchronize and to keep track of + * the transfer + * @param count number of bytes to be transferred + * @param buf buffer containing the bytes to be transferred + * + * @return Zero + */ +static int sdma_start_dsp_transfer_kernel(struct ipc_priv_data *priv, + int count, char *buf) +{ + dma_request_t sdma_request; + struct sdma_channel *sdma = NULL; + + sdma = &priv->vc->ipc->ch.sdma; + + priv->vc->data = buf; + + memset(&sdma_request, 0, sizeof(dma_request_t)); + + pr_debug("writing %d bytes to DSP\n", count); + + sdma_request.destAddr = buf; + sdma_request.count = count; + + mxc_dma_set_callback(sdma->read_channel, sdma_kernel_readcb, priv); + mxc_dma_set_config(sdma->read_channel, &sdma_request, sdma->rbuf_head); + sdma->rbuf_head++; + if (sdma->rbuf_head >= sdma->rbuf_tail) { + sdma->rbuf_head = 0; + } + mxc_dma_start(sdma->read_channel); + + return 0; +} + +/*! + * This function is called by the SDMA's ISR whenever a DSP->MCU + * transfer has finished. This callback fills the SDMA channels read buffer + * + * @param args points to a priv structure. This structure contains + * fields to synchronize the IPC channel and the top half + * + * @return None + */ +static void sdma_user_readcb(void *args) +{ + dma_request_t sdma_request; + struct ipc_priv_data *priv = NULL; + struct sdma_channel *sdma = NULL; + + priv = (struct ipc_priv_data *)args; + sdma = &priv->vc->ipc->ch.sdma; + + memset(&sdma_request, 0, sizeof(dma_request_t)); + mxc_dma_get_config(sdma->read_channel, &sdma_request, sdma->rbuf_head); + + while ((sdma_request.bd_done == 0) + && (sdma->rbuf[sdma->rbuf_head].count == 0)) { + pr_debug("buf head=%d, cnt=%d\n", sdma->rbuf_head, + sdma_request.count); + sdma->rbuf[sdma->rbuf_head].count = sdma_request.count; + sdma->rbuf_head = (sdma->rbuf_head + 1) & (NUM_RX_SDMA_BUF - 1); + memset(&sdma_request, 0, sizeof(dma_request_t)); + mxc_dma_get_config(sdma->read_channel, &sdma_request, + sdma->rbuf_head); + } + + /* Data arrived in read chnl buffers, wake up any pending processes */ + if (waitqueue_active(&priv->rq)) { + wake_up_interruptible(&priv->rq); + } +} + +/*! + * Copy bytes from the SDMA read buffers to the user buffer + * + * @param sdma pointer to the sdma structure + * @param buf buffer to store data + * @param count number of bytes to copy + * + * @return number of bytes copied into the user buffer + */ +static int mxc_ipc_copy_from_sdmabuf(struct sdma_channel *sdma, char *buf, + int count) +{ + int n = 0; + int amt_cpy = 0; + int offset = 0; + int ret = 0; + char *tmp_buf = NULL; + dma_request_t sdma_request; + int result; + + while ((sdma->rbuf[sdma->rbuf_tail].count > 0) && (count > 0)) { + n = sdma->rbuf[sdma->rbuf_tail].count; + offset = sdma->rbuf[sdma->rbuf_tail].offset; + tmp_buf = sdma->rbuf[sdma->rbuf_tail].buf + offset; + + /* Use the minimum of the 2 counts to copy data */ + amt_cpy = (count < n) ? count : n; + + result = copy_to_user(buf, tmp_buf, amt_cpy); + + /* Check if there was problem copying data */ + if (result != 0) { + pr_debug("problem copying into user's buffer\n"); + break; + } + pr_debug("copied %d bytes into user's buffer\n", amt_cpy); + pr_debug("Buf Tail=%d\n", sdma->rbuf_tail); + /* Check if we emptied the SDMA read buffer */ + if (count < n) { + sdma->rbuf[sdma->rbuf_tail].count -= amt_cpy; + sdma->rbuf[sdma->rbuf_tail].offset += amt_cpy; + } else { + sdma->rbuf[sdma->rbuf_tail].count = 0; + sdma->rbuf[sdma->rbuf_tail].offset = 0; + memset(&sdma_request, 0, sizeof(dma_request_t)); + sdma_request.count = SDMA_RX_BUF_SIZE; + sdma_request.destAddr = + (__u8 *) sdma->rbuf[sdma->rbuf_tail].rpaddr; + sdma_request.bd_cont = 1; + result = mxc_dma_set_config(sdma->read_channel, + &sdma_request, + sdma->rbuf_tail); + sdma->rbuf_tail = + (sdma->rbuf_tail + 1) & (NUM_RX_SDMA_BUF - 1); + + /* Restart Sdma channel after data copy */ + + mxc_dma_start(sdma->read_channel); + } + buf += amt_cpy; + count -= amt_cpy; + ret += amt_cpy; + } + + return ret; +} + +/*! + * This function allocates a virtual channel. + * + * @param index index of virtual channel to be open + * @param config pointer to a structure containing the type + * of channel to open. + * + * @return 0 on success, negative value otherwise + */ +static int allocate_virtual_channel(unsigned short index, + const HW_CTRL_IPC_OPEN_T * config) +{ + struct kernel_callbacks *k_callbacks; + write_callback_t wcallback = NULL; + struct ipc_priv_data *priv; + unsigned short pchannel; + int ret = 0, sdma_chnl = -1; + + priv = (struct ipc_priv_data *) + kmalloc(sizeof(struct ipc_priv_data), GFP_KERNEL); + if (priv == NULL) { + return -1; + } + + k_callbacks = (struct kernel_callbacks *) + kmalloc(sizeof(struct kernel_callbacks), GFP_KERNEL); + if (k_callbacks == NULL) { + kfree(priv); + return -1; + } + + wcallback = config->write_callback; + priv->vc = &virtual_channels[index]; + + switch (config->type) { + case HW_CTRL_IPC_SHORT_MSG: + if (config->index > IPC_MAX_MU_CHANNEL_INDEX) { + kfree(priv); + kfree(k_callbacks); + return -1; + } + + pchannel = config->index; + priv->vc->ipc = &ipc_channels[pchannel]; + ret = allocate_mu_channel(pchannel); + if (ret < 0) { + kfree(priv); + kfree(k_callbacks); + return ret; + } + break; + case HW_CTRL_IPC_PACKET_DATA: + if (config->index > IPC_MAX_SDMA_BIDI_CHANNEL_INDEX) { + kfree(priv); + kfree(k_callbacks); + return -1; + } + if (config->index == 0) { + pchannel = PACKET_DATA_CHANNEL0; + sdma_chnl = PACKET_DATA0_SDMA_WR_CHNL; + } else { + pchannel = PACKET_DATA_CHANNEL1; + sdma_chnl = PACKET_DATA1_SDMA_WR_CHNL; + } + priv->vc->ipc = &ipc_channels[pchannel]; + ret = allocate_sdma_channel(pchannel, sdma_chnl, IPC_RDWR); + if (ret == 0) { + ret = + initialize_sdma_write_channel(priv->vc->ipc, 1, + IPC_DEFAULT_MAX_CTRL_STRUCT_NUM); + } + if (ret == 0) { + ret = + initialize_sdma_read_channel_kernel(priv->vc->ipc, + IPC_DEFAULT_MAX_CTRL_STRUCT_NUM); + } + if (ret < 0) { + deallocate_sdma_channel(pchannel); + kfree(priv); + kfree(k_callbacks); + return ret; + } + break; + case HW_CTRL_IPC_CHANNEL_LOG: + if (config->index > IPC_MAX_SDMA_MONO_CHANNEL_INDEX) { + kfree(priv); + kfree(k_callbacks); + return -1; + } + + if (config->index == 0) { + pchannel = LOGGING_CHANNEL0; + sdma_chnl = LOG0_SDMA_RD_CHNL; + } else if (config->index == 1) { + pchannel = LOGGING_CHANNEL1; + sdma_chnl = LOG1_SDMA_RD_CHNL; + } else if (config->index == 2) { + pchannel = LOGGING_CHANNEL2; + sdma_chnl = LOG2_SDMA_RD_CHNL; + } else { + pchannel = LOGGING_CHANNEL3; + sdma_chnl = LOG3_SDMA_RD_CHNL; + } + priv->vc->ipc = &ipc_channels[pchannel]; + ret = allocate_sdma_channel(pchannel, sdma_chnl, IPC_READ); + if (ret == 0) { + ret = + initialize_sdma_read_channel_kernel(priv->vc->ipc, + IPC_DEFAULT_MAX_CTRL_STRUCT_NUM); + } + if (ret < 0) { + deallocate_sdma_channel(pchannel); + kfree(priv); + kfree(k_callbacks); + return ret; + } + wcallback = NULL; + break; + default: + kfree(priv); + kfree(k_callbacks); + return -1; + } + + k_callbacks->write = wcallback; + k_callbacks->read = config->read_callback; + k_callbacks->notify = config->notify_callback; + + /* always non-blocking mode for kernel modules */ + atomic_set(&priv->blockmode, 0); + priv->vchannel = index; + priv->pchannel = pchannel; + priv->read_count = 0; + priv->write_count = 0; + priv->vc->ipc->priv = (void *)priv; + priv->k_callbacks = k_callbacks; + + return 0; +} + +/*! + * Verify that the handler points to the correct handler in the handler array + * if not, that mean either the passed handler is NULL, either it is not a correct handler + * + * @param channel handler to the virtual channel + * + * @return 1 when handler valid, 0 otherwise + */ + +static int is_channel_handler_valid(HW_CTRL_IPC_CHANNEL_T * channel) +{ + if (channel != NULL) { + if (channel != &channel_handlers[channel->channel_nb]) { + return 0; + } + } else { + return 0; + } + return 1; +} + +static void mxc_ipc_sdma_readtasklet(unsigned long arg) +{ + struct ipc_priv_data *priv = (struct ipc_priv_data *)arg; + + if (priv->k_callbacks->read != NULL) { + HW_CTRL_IPC_READ_STATUS_T status; + + status.channel = &channel_handlers[priv->vchannel]; + status.nb_bytes = priv->read_count; + + priv->k_callbacks->read(&status); + } +} + +static void mxc_ipc_sdma_writetasklet(unsigned long arg) +{ + struct ipc_priv_data *priv = (struct ipc_priv_data *)arg; + + if (priv->k_callbacks->write != NULL) { + HW_CTRL_IPC_WRITE_STATUS_T status; + + status.channel = &channel_handlers[priv->vchannel]; + status.nb_bytes = priv->write_count; + + priv->k_callbacks->write(&status); + } +} + +/*! + * Opens an IPC link. This functions can be called directly by kernel + * modules. POSIX implementation of the IPC Driver also calls it. + * + * @param config Pointer to a struct containing configuration para + * meters for the channel to open (type of channel, + * callbacks, etc) + * + * @return returns a virtual channel handler on success, a NULL + * pointer otherwise. + */ +HW_CTRL_IPC_CHANNEL_T *hw_ctrl_ipc_open(const HW_CTRL_IPC_OPEN_T * config) +{ + struct virtual_channel *v; + int channel_nb = 0; + int status = 0; + HW_CTRL_IPC_CHANNEL_T *channel; + + /* return a NULL handler if something goes wrong */ + channel = NULL; + + if (config == NULL) { + return NULL; + } + + /* Look for an empty virtual channel, if found, lock it */ + channel_nb = get_free_virtual_channel(); + + /* if free virtual channel not found, return NULL handler */ + if (channel_nb == IPC_DUMMY_CHANNEL) { + return NULL; + } + + status = allocate_virtual_channel(channel_nb, config); + if (status < 0) { + goto cleanup; + } + + v = &virtual_channels[channel_nb]; + atomic_set(&v->state, CHANNEL_OPEN); + /*! set state to open for read-while-write IPcv2 functionality */ + atomic_set(&v->read_state_ipcv2, CHANNEL_OPEN); + atomic_set(&v->write_state_ipcv2, CHANNEL_OPEN); + + channel = &channel_handlers[channel_nb]; + + /* + * set the channel_nb in the handler struct. This allows to + * find back the corresponding index + * in both channel_handlers and virtual_channels arrays + */ + channel->channel_nb = channel_nb; + if (v->ipc->priv->pchannel > 3) { + struct sdma_channel *sdma_chnl; + sdma_chnl = &v->ipc->ch.sdma; + + sdma_chnl->rbuf_head = 0; + sdma_chnl->rbuf_tail = IPC_DEFAULT_MAX_CTRL_STRUCT_NUM; + sdma_chnl->wbuf_head = 0; + sdma_chnl->wbuf_tail = IPC_DEFAULT_MAX_CTRL_STRUCT_NUM; + + tasklet_init(&sdma_chnl->read_tasklet, + mxc_ipc_sdma_readtasklet, + (unsigned long)v->ipc->priv); + + tasklet_init(&sdma_chnl->write_tasklet, + mxc_ipc_sdma_writetasklet, + (unsigned long)v->ipc->priv); + } + + pr_debug("Virtual channel %d opened\n", channel_nb); + + cleanup: + unlock_virtual_channel(channel_nb); + + return channel; +} + +/*! + * Close an IPC link. This functions can be called directly by kernel + * modules. + * + * @param channel handler to the virtual channel to close. + * + * @return returns HW_CTRL_IPC_STATUS_OK on success, an error code + * otherwise. + */ +HW_CTRL_IPC_STATUS_T hw_ctrl_ipc_close(HW_CTRL_IPC_CHANNEL_T * channel) +{ + struct virtual_channel *v; + int channel_nb; + if (is_channel_handler_valid(channel) == 0) { + return HW_CTRL_IPC_STATUS_ERROR; + } + + channel_nb = channel->channel_nb; + v = &virtual_channels[channel_nb]; + if (atomic_read(&v->state) == CHANNEL_CLOSED || + atomic_read(&v->read_state_ipcv2) == CHANNEL_CLOSED || + atomic_read(&v->write_state_ipcv2) == CHANNEL_CLOSED) { + return HW_CTRL_IPC_STATUS_OK; + } + if (lock_virtual_channel(channel_nb)) { + return HW_CTRL_IPC_STATUS_ERROR; + } + kfree(v->ipc->priv->k_callbacks); + v->ipc->priv->k_callbacks = NULL; + + if (v->ipc->priv->pchannel > 3) { + deallocate_sdma_channel(v->ipc->priv->pchannel); + } else { + deallocate_mu_channel(v->ipc->priv->pchannel); + } + kfree(v->ipc->priv); + v->ipc->priv = NULL; + v->ipc = NULL; + unlock_virtual_channel(channel_nb); + atomic_set(&v->state, CHANNEL_CLOSED); + atomic_set(&v->read_state_ipcv2, CHANNEL_CLOSED); + atomic_set(&v->write_state_ipcv2, CHANNEL_CLOSED); + + pr_debug("Virtual channel %d closed\n", channel_nb); + + return HW_CTRL_IPC_STATUS_OK; +} + +/*! + * Reads data from an IPC link. This functions can be called directly by kernel + * modules. POSIX implementation of the IPC Driver also calls it. + * + * @param channel handler to the virtual channel where read has been requested + * @param buf physical address of DMA'able read buffer to store data read from + * the channel. + * @param nb_bytes size of the buffer + * + * @return returns HW_CTRL_IPC_STATUS_OK on success, an error code + * otherwise. + */ +HW_CTRL_IPC_STATUS_T hw_ctrl_ipc_read(HW_CTRL_IPC_CHANNEL_T * channel, + unsigned char *buf, + unsigned short nb_bytes) +{ + struct virtual_channel *v; + int status = 0; + int channel_nb; + + if (is_channel_handler_valid(channel) == 0) { + return HW_CTRL_IPC_STATUS_ERROR; + } + + if (buf == NULL) { + return HW_CTRL_IPC_STATUS_ERROR; + } + + channel_nb = channel->channel_nb; + + v = &virtual_channels[channel_nb]; + + if (lock_virtual_channel(channel_nb)) { + return HW_CTRL_IPC_STATUS_ERROR; + } + + status = atomic_read(&v->state); + if (status == CHANNEL_READ_ONGOING) { + unlock_virtual_channel(channel_nb); + return HW_CTRL_IPC_STATUS_READ_ON_GOING; + } + + if (status == CHANNEL_CLOSED) { + unlock_virtual_channel(channel_nb); + return HW_CTRL_IPC_STATUS_CHANNEL_UNAVAILABLE; + } + + atomic_set(&v->state, CHANNEL_READ_ONGOING); + + v->ipc->priv->read_count = 0; + if (v->ipc->priv->pchannel > 3) { + struct sdma_channel *sdma_chnl; + sdma_chnl = &v->ipc->ch.sdma; + status = + sdma_start_dsp_transfer_kernel(v->ipc->priv, nb_bytes, buf); + } else { + status = + mu_start_dsp_transfer_kernel(v->ipc->priv, nb_bytes, buf); + } + + unlock_virtual_channel(channel_nb); + return (status == 0) ? HW_CTRL_IPC_STATUS_OK : HW_CTRL_IPC_STATUS_ERROR; +} + +/*! + * Writes data to an IPC link. This functions can be called directly by kernel + * modules. POSIX implementation of the IPC Driver also calls it. + * + * @param channel handler to the virtual channel where read has been requested. + * @param buf physical address of DMA'able write buffer containing data to + * be written on the channel. + * @param nb_bytes size of the buffer + * + * @return returns HW_CTRL_IPC_STATUS_OK on success, an error code + * otherwise. + */ +HW_CTRL_IPC_STATUS_T hw_ctrl_ipc_write(HW_CTRL_IPC_CHANNEL_T * channel, + unsigned char *buf, + unsigned short nb_bytes) +{ + struct virtual_channel *v; + struct sdma_channel *sdma_chnl; + struct mu_channel *mu_chnl; + int status = 0; + int channel_nb; + + if (is_channel_handler_valid(channel) == 0) { + return HW_CTRL_IPC_STATUS_ERROR; + } + + if (buf == NULL) { + return HW_CTRL_IPC_STATUS_ERROR; + } + + channel_nb = channel->channel_nb; + v = &virtual_channels[channel_nb]; + + if (lock_virtual_channel(channel_nb)) { + return HW_CTRL_IPC_STATUS_ERROR; + } + + status = atomic_read(&v->state); + + if (status == CHANNEL_CLOSED) { + unlock_virtual_channel(channel_nb); + return HW_CTRL_IPC_STATUS_CHANNEL_UNAVAILABLE; + } + + if (status == CHANNEL_WRITE_ONGOING) { + unlock_virtual_channel(channel_nb); + return HW_CTRL_IPC_STATUS_WRITE_ON_GOING; + } + + atomic_set(&v->state, CHANNEL_WRITE_ONGOING); + + v->ipc->priv->write_count = 0; + + if (v->ipc->priv->pchannel > 3) { + sdma_chnl = &v->ipc->ch.sdma; + sdma_chnl->wpaddr = (dma_addr_t) buf; + status = sdma_start_mcu_transfer_kernel(v->ipc, nb_bytes); + mxc_dma_set_callback(sdma_chnl->write_channel, + sdma_kernel_writecb, v->ipc->priv); + mxc_dma_start(sdma_chnl->write_channel); + } else { + mu_chnl = &v->ipc->ch.mu; + v->ipc->priv->vc->data = buf; + mu_chnl->bytes_written = IPC_WRITE_BYTE_INIT_VAL; + v->ipc->priv->write_count = nb_bytes; + status = mu_start_mcu_transfer_kernel(mu_chnl); + } + + pr_debug("transferred %d bytes on virtual channel %d\n", nb_bytes, + channel_nb); + + unlock_virtual_channel(channel_nb); + return (status == 0) ? HW_CTRL_IPC_STATUS_OK : HW_CTRL_IPC_STATUS_ERROR; +} + +/*! + * Writes data to an IPC link. This function can be called directly by kernel + * modules. It accepts a linked list or contiguous data. + * + * @param channel handler to the virtual channel where read has + * been requested. + * @param mem_ptr pointer of type HW_CTRL_IPC_WRITE_PARAMS_T. Each element + * points to the physical address of a DMA'able buffer + * + * @return returns HW_CTRL_IPC_STATUS_OK on success, an error code + * otherwise. + */ +HW_CTRL_IPC_STATUS_T hw_ctrl_ipc_write_ex(HW_CTRL_IPC_CHANNEL_T * channel, + HW_CTRL_IPC_WRITE_PARAMS_T * mem_ptr) +{ + struct virtual_channel *v; + struct sdma_channel *sdma_chnl = NULL; + int status = 0; + int channel_nb; + HW_CTRL_IPC_LINKED_LIST_T *elt_ptr; + + if (is_channel_handler_valid(channel) == 0) { + return HW_CTRL_IPC_STATUS_ERROR; + } + + channel_nb = channel->channel_nb; + + v = &virtual_channels[channel_nb]; + if (lock_virtual_channel(channel_nb)) { + return HW_CTRL_IPC_STATUS_ERROR; + } + + status = atomic_read(&v->state); + if (status == CHANNEL_CLOSED) { + unlock_virtual_channel(channel_nb); + return HW_CTRL_IPC_STATUS_CHANNEL_UNAVAILABLE; + } + if (status == CHANNEL_WRITE_ONGOING) { + unlock_virtual_channel(channel_nb); + return HW_CTRL_IPC_STATUS_WRITE_ON_GOING; + } + atomic_set(&v->state, CHANNEL_WRITE_ONGOING); + + sdma_chnl = &v->ipc->ch.sdma; + v->ipc->priv->write_count = 0; + + if (mem_ptr->ipc_memory_read_mode == HW_CTRL_IPC_MODE_CONTIGUOUS) { + sdma_chnl->wpaddr = + (dma_addr_t) mem_ptr->read.cont_ptr->data_ptr; + status = + sdma_start_mcu_transfer_kernel(v->ipc, + mem_ptr->read.cont_ptr-> + length); + } else { + elt_ptr = mem_ptr->read.list_ptr; + while (elt_ptr != NULL) { + sdma_chnl->wpaddr = (dma_addr_t) elt_ptr->data_ptr; + status = + sdma_start_mcu_transfer_kernel(v->ipc, + elt_ptr->length); + elt_ptr = elt_ptr->next; + } + } + mxc_dma_set_callback(sdma_chnl->write_channel, sdma_kernel_writecb, + v->ipc->priv); + mxc_dma_start(sdma_chnl->write_channel); + pr_debug("transferred bytes on virtual channel %d\n", channel_nb); + + unlock_virtual_channel(channel_nb); + return (status == 0) ? HW_CTRL_IPC_STATUS_OK : HW_CTRL_IPC_STATUS_ERROR; +} + +/*! + * Used to set various channel parameters + * + * @param channel handler to the virtual channel where read has + * been requested. + * @param action IPC driver control action to perform. + * @param param parameters required to complete the requested action + */ +HW_CTRL_IPC_STATUS_T hw_ctrl_ipc_ioctl(HW_CTRL_IPC_CHANNEL_T * channel, + HW_CTRL_IPC_IOCTL_ACTION_T action, + void *param) +{ + struct virtual_channel *v; + struct sdma_channel *sdma_chnl = NULL; + + if (is_channel_handler_valid(channel) == 0) { + return HW_CTRL_IPC_STATUS_ERROR; + } + + v = &virtual_channels[channel->channel_nb]; + switch (action) { + case HW_CTRL_IPC_SET_READ_CALLBACK: + v->ipc->priv->k_callbacks->read = param; + break; + case HW_CTRL_IPC_SET_WRITE_CALLBACK: + v->ipc->priv->k_callbacks->write = param; + break; + case HW_CTRL_IPC_SET_NOTIFY_CALLBACK: + v->ipc->priv->k_callbacks->notify = param; + break; + case HW_CTRL_IPC_SET_MAX_CTRL_STRUCT_NB: + sdma_chnl = &v->ipc->ch.sdma; + initialize_sdma_read_channel_kernel(v->ipc, (int)param); + /* Reinitialize the buff pointers to start at zero BD */ + sdma_chnl->rbuf_tail = (int)param; + sdma_chnl->rbuf_head = 0; + if (sdma_chnl->write_channel != -1) { + initialize_sdma_write_channel(v->ipc, 1, (int)param); + sdma_chnl->wbuf_tail = (int)param; + sdma_chnl->wbuf_head = 0; + } + break; + default: + return HW_CTRL_IPC_STATUS_ERROR; + } + + return HW_CTRL_IPC_STATUS_OK; +} + +/*! + * This function is a variant on the write() function, and is used to send a + * group of frames made of various pieces each to the IPC driver. + * It is mandatory to allow high throughput on IPC while minimizing the time + * spent in the drivers / interrupts. + * + * @param channel handler to the virtual channel where read has + * been requested. + * @param ctrl_ptr Pointer on the control structure. + * + * @return returns HW_CTRL_IPC_STATUS_OK on success, an error code + * otherwise. + */ +HW_CTRL_IPC_STATUS_T hw_ctrl_ipc_write_ex2(HW_CTRL_IPC_CHANNEL_T * channel, + HW_CTRL_IPC_DATA_NODE_DESCRIPTOR_T * + ctrl_ptr) +{ + int status = 0; + struct virtual_channel *v; + struct sdma_channel *sdma_chnl = NULL; + + int channel_nb; + + channel_nb = channel->channel_nb; + + v = &virtual_channels[channel_nb]; + + status = atomic_read(&v->write_state_ipcv2); + if (status == CHANNEL_WRITE_ONGOING) { + + return HW_CTRL_IPC_STATUS_WRITE_ON_GOING; + } + + if (status == CHANNEL_CLOSED) { + + return HW_CTRL_IPC_STATUS_CHANNEL_UNAVAILABLE; + } + + atomic_set(&v->write_state_ipcv2, CHANNEL_WRITE_ONGOING); + + v = &virtual_channels[channel->channel_nb]; + sdma_chnl = &v->ipc->ch.sdma; + mxc_dma_set_callback(sdma_chnl->write_channel, + sdma_kernel_writecb_ipcv2, v->ipc->priv); + status = mxc_sdma_write_ipcv2(sdma_chnl->write_channel, ctrl_ptr); + return (status == 0) ? HW_CTRL_IPC_STATUS_OK : HW_CTRL_IPC_STATUS_ERROR; +} + +/*! + * This function is used to give a set of buffers to the IPC and enable data + * transfers. + * + * @param channel handler to the virtual channel where read has + * been requested. + * @param ctrl_ptr Pointer on the control structure. + * + * @return returns HW_CTRL_IPC_STATUS_OK on success, an error code + * otherwise. + */ +HW_CTRL_IPC_STATUS_T hw_ctrl_ipc_read_ex2(HW_CTRL_IPC_CHANNEL_T * channel, + HW_CTRL_IPC_DATA_NODE_DESCRIPTOR_T * + ctrl_ptr) +{ + int status = 0; + struct virtual_channel *v; + struct sdma_channel *sdma_chnl = NULL; + + int channel_nb; + + channel_nb = channel->channel_nb; + + v = &virtual_channels[channel_nb]; + + status = atomic_read(&v->read_state_ipcv2); + if (status == CHANNEL_READ_ONGOING) { + + return HW_CTRL_IPC_STATUS_READ_ON_GOING; + } + + if (status == CHANNEL_CLOSED) { + return HW_CTRL_IPC_STATUS_CHANNEL_UNAVAILABLE; + } + + atomic_set(&v->read_state_ipcv2, CHANNEL_READ_ONGOING); + v = &virtual_channels[channel->channel_nb]; + sdma_chnl = &v->ipc->ch.sdma; + mxc_dma_set_callback(sdma_chnl->read_channel, sdma_kernel_readcb_ipcv2, + v->ipc->priv); + status = mxc_sdma_read_ipcv2(sdma_chnl->read_channel, ctrl_ptr); + return (status == 0) ? HW_CTRL_IPC_STATUS_OK : HW_CTRL_IPC_STATUS_ERROR; +} + +/*! + * This function is called when an IPC channel is opened. This function does + * the initialization of the device corresponding to the channel user want to + * open. + * + * @param inode Pointer to device inode + * @param file Pointer to device file structure + * + * @return The function returns 0 on success, -1 otherwise + */ +static int mxc_ipc_open(struct inode *inode, struct file *file) +{ + unsigned int minor = MINOR(inode->i_rdev); + struct ipc_priv_data *priv = NULL; + unsigned short vch_index; + int ret = 0, mode = IPC_READ, sdma_chnl = -1, ipc_chnl = 0; + + if (((minor == 4) || (minor == 5)) && + (file->f_flags & (O_WRONLY | O_RDWR))) { + return -ENXIO; + } + + /*look for an empty virtual channel, if found, lock it */ + vch_index = get_free_virtual_channel(); + /*if free virtual channel not found, return ENODEV */ + if (vch_index == IPC_DUMMY_CHANNEL) { + return -ENODEV; + } + + priv = (struct ipc_priv_data *) + kmalloc(sizeof(struct ipc_priv_data), GFP_KERNEL); + if (priv == NULL) { + unlock_virtual_channel(vch_index); + return -ENOMEM; + } + + priv->vc = &virtual_channels[vch_index]; + if (minor < 3) { + /* Allocate MU channel */ + ipc_chnl = minor; + priv->vc->ipc = &ipc_channels[ipc_chnl]; + ret = allocate_mu_channel(ipc_chnl); + if (ret < 0) { + deallocate_mu_channel(ipc_chnl); + kfree(priv); + unlock_virtual_channel(vch_index); + return ret; + } + } else { + /* Allocate SDMA channel */ + if (minor == 3) { + mode = IPC_RDWR; + sdma_chnl = PACKET_DATA1_SDMA_WR_CHNL; + ipc_chnl = PACKET_DATA_CHANNEL1; + } else if (minor == 4) { + mode = IPC_READ; + sdma_chnl = LOG0_SDMA_RD_CHNL; + ipc_chnl = LOGGING_CHANNEL0; + } else if (minor == 5) { + mode = IPC_READ; + sdma_chnl = LOG1_SDMA_RD_CHNL; + ipc_chnl = LOGGING_CHANNEL1; + } + ret = allocate_sdma_channel(ipc_chnl, sdma_chnl, mode); + if (ret < 0) { + deallocate_sdma_channel(ipc_chnl); + kfree(priv); + unlock_virtual_channel(vch_index); + return ret; + } + priv->vc->ipc = &ipc_channels[ipc_chnl]; + if (mode & IPC_WRITE) { + ret = + initialize_sdma_write_channel(priv->vc->ipc, 0, 1); + if (ret < 0) { + deallocate_sdma_channel(ipc_chnl); + kfree(priv); + unlock_virtual_channel(vch_index); + return ret; + } + } + } + + atomic_set(&priv->blockmode, ((file->f_flags & (O_NONBLOCK)) ? 0 : 1)); + priv->vchannel = vch_index; + priv->pchannel = ipc_chnl; + priv->owner = current; + + init_waitqueue_head(&priv->wq); + init_waitqueue_head(&priv->rq); + + priv->vc->ipc->priv = (void *)priv; + + /* Start the SDMA read channel */ + if (minor > 2) { + ret = initialize_sdma_read_channel_user(ipc_chnl); + if (ret < 0) { + deallocate_sdma_channel(ipc_chnl); + mxc_ipc_free_readbuf_user(ipc_chnl); + kfree(priv); + unlock_virtual_channel(vch_index); + return ret; + } + } else { + /* Start the MU read channel */ + ret = mxc_ipc_enable_muints(ipc_chnl); + if (ret < 0) { + deallocate_mu_channel(ipc_chnl); + kfree(priv); + unlock_virtual_channel(vch_index); + return ret; + } + } + atomic_set(&priv->vc->state, CHANNEL_OPEN); + + file->private_data = (void *)priv; + + unlock_virtual_channel(priv->vchannel); + + pr_debug("Channel successfully opened\n"); + + return 0; +} + +/*! + * This function is called to close an IPC channel. + * Any allocated buffers are freed + * + * @param inode Pointer to device inode + * @param file Pointer to device file structure + * + * @return The function returns 0 on success or + * returns NO_ACCESS if incorrect channel is accessed. + */ +static int mxc_ipc_close(struct inode *inode, struct file *file) +{ + struct ipc_priv_data *priv = NULL; + unsigned int minor; + int do_sleep = 0; + + minor = MINOR(file->f_dentry->d_inode->i_rdev); + priv = (struct ipc_priv_data *)file->private_data; + + if (lock_virtual_channel(priv->vchannel)) { + return -EINTR; + } + + if (minor < 3) { + struct mu_channel *mu = NULL; + mu = &priv->vc->ipc->ch.mu; + + /* Wait to complete any pending writes */ + /* Block until the write buffer is empty */ + while (CIRC_CNT(mu->wbuf->head, mu->wbuf->tail, MAX_MU_BUF_SIZE) + != 0) { + if (wait_event_interruptible + (priv->wq, + (CIRC_CNT + (mu->wbuf->head, mu->wbuf->tail, + MAX_MU_BUF_SIZE) == 0))) { + return -ERESTARTSYS; + } + } + deallocate_mu_channel(priv->pchannel); + } else { + struct sdma_channel *sdma = NULL; + sdma = &priv->vc->ipc->ch.sdma; + + /* Wait to complete any pending writes */ + /* Block until the write buffer is empty */ + while (atomic_read(&priv->vc->state) == CHANNEL_WRITE_ONGOING) { + if (wait_event_interruptible(priv->wq, + (atomic_read + (&priv->vc->state) != + CHANNEL_WRITE_ONGOING))) { + return -ERESTARTSYS; + } + } + deallocate_sdma_channel(priv->pchannel); + mxc_ipc_free_readbuf_user(priv->pchannel); + } + + /* Wake up all waiting processes on the wait queue */ + while (1) { + do_sleep = 0; + + if (waitqueue_active(&priv->wq)) { + wake_up(&priv->wq); + do_sleep++; + } + if (waitqueue_active(&priv->rq)) { + wake_up(&priv->rq); + do_sleep++; + } + if (!do_sleep) { + break; + } + schedule(); + } + + priv->vc->ipc = NULL; + atomic_set(&priv->vc->state, CHANNEL_CLOSED); + priv->vc = NULL; + + unlock_virtual_channel(priv->vchannel); + + kfree(file->private_data); + file->private_data = NULL; + + pr_debug("Channel %d closed\n", minor); + + return 0; +} + +/*! + * The write function is available to the user-space to perform + * a write operation + * + * @param file Pointer to device file structure + * @param buf User buffer, where write data is placed. + * @param bytes Size of the requested data transfer + * @param off File position where the user is accessing. + * + * @return Returns number of bytes written or + * Returns COUNT_ESIZE if the number of bytes + * requested is not a multiple of channel + * receive or transmit size or + * Returns -EAGAIN for a non-block write when there is + * no data in the buffer or + * Returns -EFAULT if copy_to_user failed + */ +static ssize_t mxc_ipc_write(struct file *file, const char *buf, + size_t bytes, loff_t * off) +{ + struct ipc_priv_data *priv; + unsigned int minor; + int count = 0; + + minor = MINOR(file->f_dentry->d_inode->i_rdev); + + if ((minor <= 2) && ((bytes % 4) != 0)) { + pr_debug("EINVAL error\n"); + return -EINVAL; + } + + if (((minor == 4) || (minor == 5)) && + (file->f_flags & (O_WRONLY | O_RDWR))) { + pr_debug("ENXIO error\n"); + return -ENXIO; /*log channel is read-only */ + } + + priv = (struct ipc_priv_data *)file->private_data; + + if (lock_virtual_channel(priv->vchannel)) { + return -EINTR; + } + + pr_debug("bytes to write = %d\n", bytes); + + if (minor > 2) { + if (atomic_read(&priv->vc->state) == CHANNEL_WRITE_ONGOING) { + /* If non-blocking mode return */ + if (atomic_read(&priv->blockmode) == 0) { + unlock_virtual_channel(priv->vchannel); + return -EAGAIN; + } + } + if (bytes > MAX_SDMA_TX_BUF_SIZE) { + bytes = MAX_SDMA_TX_BUF_SIZE; + } + count = sdma_start_mcu_transfer(priv, bytes, buf); + } else { + count = mu_start_mcu_transfer(priv, bytes, buf); + } + + pr_debug("bytes written = %d\n", count); + + unlock_virtual_channel(priv->vchannel); + + return count; +} + +/*! + * Check to see if there is data available in the internal buffers + * + * @param minor minor number used to differentiate between MU and + * SDMA buffers + * @param priv private data. Useful to synchronize and to keep track of + * the transfer + * @return returns 0 if no data available or a positive value when data is + * available + */ +static int mxc_ipc_data_available(int minor, struct ipc_priv_data *priv) +{ + int cnt = 0; + + if (minor > 2) { + struct sdma_channel *sdma = NULL; + + sdma = &priv->vc->ipc->ch.sdma; + cnt = sdma->rbuf[sdma->rbuf_tail].count; + pr_debug("bytes in SDMA read buf=%d\n", cnt); + } else { + struct mu_channel *mu = NULL; + mu = &priv->vc->ipc->ch.mu; + cnt = CIRC_CNT(mu->rbuf->head, mu->rbuf->tail, MAX_MU_BUF_SIZE); + pr_debug("bytes in MU read buf = %d\n", cnt); + } + + return cnt; +} + +/*! + * The read function is available to the user-space to perform + * a read operation + * + * @param file Pointer to device file structure + * @param buf User buffer, where read data to be placed. + * @param bytes Size of the requested data transfer + * @param off File position where the user is accessing. + * + * @return Returns number of bytes read or + * Returns COUNT_ESIZE if the number of bytes + * requested is not a multiple of channel + * receive or transmit size or + * Returns -EAGAIN for a non-block read when no data + * is ready in the buffer or in the register or + * Returns -EFAULT if copy_to_user failed + */ +static ssize_t mxc_ipc_read(struct file *file, char *buf, + size_t bytes, loff_t * off) +{ + struct ipc_priv_data *priv; + unsigned int minor; + int blockmode; + int status; + int count = 0; + + minor = MINOR(file->f_dentry->d_inode->i_rdev); + + if ((minor <= 2) && ((bytes % 4) != 0)) { + return -EINVAL; + } + + priv = (struct ipc_priv_data *)file->private_data; + + status = atomic_read(&priv->vc->state); + blockmode = atomic_read(&priv->blockmode); + + /* + * If non-blocking mode has been requested and no data is available + * read channel buffers, return -EAGAIN. + */ + if ((blockmode == 0) && (!mxc_ipc_data_available(minor, priv))) { + return -EAGAIN; + } + + if (lock_virtual_channel(priv->vchannel)) { + return -EINTR; + } + + pr_debug("bytes to read = %d\n", bytes); + + /* Block till data is available */ + while ((mxc_ipc_data_available(minor, priv)) == 0) { + pr_debug("No data to read, BLOCK\n"); + if (wait_event_interruptible(priv->rq, + (mxc_ipc_data_available + (minor, priv) > 0))) { + unlock_virtual_channel(priv->vchannel); + return -ERESTARTSYS; + } + } + + if (minor > 2) { + struct sdma_channel *sdma = NULL; + sdma = &priv->vc->ipc->ch.sdma; + + count = mxc_ipc_copy_from_sdmabuf(sdma, buf, bytes); + } else { + count = mu_start_dsp_transfer(priv, bytes, buf); + } + + pr_debug("bytes read = %d\n", count); + + atomic_set(&priv->vc->state, CHANNEL_OPEN); + unlock_virtual_channel(priv->vchannel); + + return count; +} + +/*! + * This function allows the caller to determine whether it can + * read from or write to one or more open files without blocking. + * + * @param filp Pointer to device file structure + * @param wait used by poll_wait to indicate a change in the + * poll status. + * + * @return the bit mask describing which operations could + * be completed immediately. + */ +unsigned int mxc_ipc_poll(struct file *filp, poll_table * wait) +{ + struct ipc_priv_data *priv; + unsigned int minor; + unsigned int mask = 0; + int state = 0; + + minor = MINOR(filp->f_dentry->d_inode->i_rdev); + priv = (struct ipc_priv_data *)filp->private_data; + + poll_wait(filp, &priv->wq, wait); + poll_wait(filp, &priv->rq, wait); + + if (minor > 2) { + struct sdma_channel *sdma; + sdma = &priv->vc->ipc->ch.sdma; + state = atomic_read(&priv->vc->state); + /* Check if SDMA Write channel is available */ + if (state == CHANNEL_OPEN) { + mask |= POLLOUT | POLLWRNORM; + } + /* Check if data is available in SDMA channel read buffer */ + if ((mxc_ipc_data_available(minor, priv)) > 0) { + mask |= POLLIN | POLLRDNORM; + } + } else { + struct mu_channel *mu; + mu = &priv->vc->ipc->ch.mu; + /* Check for space in MU channel write buffer */ + if (CIRC_SPACE(mu->wbuf->head, mu->wbuf->tail, MAX_MU_BUF_SIZE)) { + mask |= POLLOUT | POLLWRNORM; + } + + /* Check if data is available in MU channel read buffer */ + if (CIRC_CNT(mu->rbuf->head, mu->rbuf->tail, MAX_MU_BUF_SIZE)) { + mask |= POLLIN | POLLRDNORM; + } + } + pr_debug("Poll mask = %d\n", mask); + return mask; +} + +static struct file_operations mxc_ipc_fops = { + .owner = THIS_MODULE, + .read = mxc_ipc_read, + .write = mxc_ipc_write, +// .writev = mxc_ipc_writev, + .poll = mxc_ipc_poll, + .open = mxc_ipc_open, + .release = mxc_ipc_close, +}; + +/*! + * This function is used to unload the module. + */ +static void ipc_cleanup_module(void) +{ + int i; + for (i = 0; i <= 5; i++) { + class_device_destroy(mxc_ipc_class, MKDEV(major_num, i)); + } + class_destroy(mxc_ipc_class); + unregister_chrdev(major_num, "mxc_ipc"); + + pr_debug("IPC Driver Module Unloaded\n"); +} + +/*! + * This function is used to load the module. All initializations and + * resources requesting is done here + * + * @return Returns 0 on success, -1 otherwise + */ +int __init ipc_init_module(void) +{ + int res = 0; + int i; + struct class_device *temp_class; + + res = register_chrdev(MXC_IPC_MAJOR, "mxc_ipc", &mxc_ipc_fops); + if (res < 0) { + pr_debug("IPC Driver Module was not Loaded successfully\n"); + return res; + } + + major_num = res; + + mxc_ipc_class = class_create(THIS_MODULE, "mxc_ipc"); + if (IS_ERR(mxc_ipc_class)) { + printk(KERN_ERR "Error creating mxc_ipc class.\n"); + unregister_chrdev(major_num, "mxc_ipc"); + return PTR_ERR(mxc_ipc_class); + } + + for (i = 0; i <= 5; i++) { + temp_class = + class_device_create(mxc_ipc_class, NULL, + MKDEV(major_num, i), + NULL, "mxc_ipc%u", i); + + if (IS_ERR(temp_class)) + goto err_out; + } + + for (i = 0; i < IPC_MAX_VIRTUAL_CHANNELS; i++) { + virtual_channels[i].ipc = NULL; + sema_init(&virtual_channels[i].sem, 1); + atomic_set(&virtual_channels[i].state, CHANNEL_CLOSED); + /* Initialize the channel_handlers array to map the virtual channel indexes */ + channel_handlers[i].channel_nb = i; + } + + printk(KERN_INFO "IPC driver successfully loaded.\n"); + return major_num; + + err_out: + printk(KERN_ERR "Error creating mxc_ipc class device.\n"); + for (i = 0; i <= 5; i++) { + class_device_destroy(mxc_ipc_class, MKDEV(major_num, i)); + } + class_destroy(mxc_ipc_class); + unregister_chrdev(major_num, "mxc_ipc"); + return -1; +} + +module_init(ipc_init_module); +module_exit(ipc_cleanup_module); +EXPORT_SYMBOL(hw_ctrl_ipc_open); +EXPORT_SYMBOL(hw_ctrl_ipc_close); +EXPORT_SYMBOL(hw_ctrl_ipc_read); +EXPORT_SYMBOL(hw_ctrl_ipc_write); +EXPORT_SYMBOL(hw_ctrl_ipc_write_ex); +EXPORT_SYMBOL(hw_ctrl_ipc_write_ex2); +EXPORT_SYMBOL(hw_ctrl_ipc_read_ex2); +EXPORT_SYMBOL(hw_ctrl_ipc_ioctl); diff --git a/drivers/char/mxc_mu.c b/drivers/char/mxc_mu.c new file mode 100644 index 000000000000..5d16f62a6246 --- /dev/null +++ b/drivers/char/mxc_mu.c @@ -0,0 +1,1646 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mxc_mu.c + * + * @brief This file provides all the kernel level and user level API + * definitions for the message transfer between MCU and DSP core. + * + * The Interfaces are with respect to the MCU core. Any driver on the MCU side + * can transfer messages to the DSP side using the interfaces implemented here. + * + * @ingroup MU + */ + +/* + * Include Files + */ +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/init.h> +#include <linux/fs.h> +#include <linux/errno.h> +#include <linux/circ_buf.h> +#include <linux/platform_device.h> +#include <linux/poll.h> +#include <linux/clk.h> +#include <asm/arch/mxc_mu.h> +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/uaccess.h> +#include "mxc_mu_reg.h" + +#define DVR_VER "2.0" +static struct class *mxc_mu_class; +static int mu_major; + +static struct clk *mu_clkp; + +/* + * Used to calculate number of bytes in the read buffer + */ +#define GET_READBYTES(a) CIRC_CNT(mu_rbuf[a].cbuf.head, \ + mu_rbuf[a].cbuf.tail, MAX_BUF_SIZE) + +/* + * Used to calculate number of bytes in the write buffer + */ +#define GET_WRITEBYTES(a) CIRC_CNT(mu_wbuf[a].cbuf.head, \ + mu_wbuf[a].cbuf.tail, MAX_BUF_SIZE) + +/* + * Used to calculate empty space in the read buffer + */ +#define GET_READSPACE(a) CIRC_SPACE(mu_rbuf[a].cbuf.head, \ + mu_rbuf[a].cbuf.tail, MAX_BUF_SIZE) + +/* + * Used to calculate empty space in the write buffer + */ +#define GET_WRITESPACE(a) CIRC_SPACE(mu_wbuf[a].cbuf.head, \ + mu_wbuf[a].cbuf.tail, MAX_BUF_SIZE) + +/*! + * There are 4 callback functions, one for each receive + * interrupt. When 'bind' function is called the callback + * functions are stored in an array and retrieved in the + * tasklets when a receive interrupt arrives + */ +static callbackfn rxcallback[NUM_MU_CHANNELS]; + +/*! + * There are 4 callback functions, one for each transmit + * interrupt. When 'bind' function is called the callback + * functions are stored in an array and retrieved in the + * tasklets when a transmit interrupt arrives + */ +static callbackfn txcallback[NUM_MU_CHANNELS]; + +/*! + * There are 4 callback functions, one for each general purpose + * interrupt. When 'bind' function is called the callback + * functions are stored in an array and retrieved in the + * tasklets when a general purpose interrupt arrives + */ +static callbackfn gpcallback[NUM_MU_CHANNELS]; + +/*! + * Declaring lock for mutual exclusion + */ +static DEFINE_SPINLOCK(mu_lock); + +/*! + * Declaring lock to prevent access to + * allocated registers + */ +static DEFINE_SPINLOCK(alloc_lock); + +/*! + * Number of users waiting in suspendq + */ +static int swait = 0; + +/*! + * To indicate whether any of the MU devices are suspending + */ +static int suspend_flag = 0; + +/*! + * To restore the state of control register while resuming the device + * to the state it was before suspending it. + */ +static unsigned int mcr_suspend_state = 0; + +/*! + * Indicate block mode - 0 and non block mode - 1. Default is non-block mode + */ +static int blockmode[NUM_MU_CHANNELS] = { 1, 1, 1, 1 }; + +/*! + * Device Kernel Buffers. Each channel has one read and write buffer. + */ +typedef struct { + /*! + * Buffer used to store data that was read or the data that is + * to be written + */ + struct circ_buf cbuf; + /*! + * to indicate number of users waiting on blocking mode of the device + */ + int pwait; +} ring_buffer; + +ring_buffer mu_rbuf[NUM_MU_CHANNELS], mu_wbuf[NUM_MU_CHANNELS]; + +/*! + * reg_allocated[] stores channel handlers for the allocated registers. + */ +static int reg_allocated[NUM_MU_CHANNELS] = { -1, -1, -1, -1 }; + +static unsigned int wbits[NUM_MU_CHANNELS] = { AS_MUMSR_MTE0, AS_MUMSR_MTE1, + AS_MUMSR_MTE2, AS_MUMSR_MTE3 +}; + +static unsigned int rbits[NUM_MU_CHANNELS] = { AS_MUMSR_MRF0, AS_MUMSR_MRF1, + AS_MUMSR_MRF2, AS_MUMSR_MRF3 +}; + +static unsigned int read_en[NUM_MU_CHANNELS] = { AS_MUMCR_MRIE0, AS_MUMCR_MRIE1, + AS_MUMCR_MRIE2, AS_MUMCR_MRIE3 +}; + +static unsigned int write_en[NUM_MU_CHANNELS] = + { AS_MUMCR_MTIE0, AS_MUMCR_MTIE1, + AS_MUMCR_MTIE2, AS_MUMCR_MTIE3 +}; + +static unsigned int genp_en[NUM_MU_CHANNELS] = { AS_MUMCR_MGIE0, AS_MUMCR_MGIE1, + AS_MUMCR_MGIE2, AS_MUMCR_MGIE3 +}; + +static unsigned int genp_pend[NUM_MU_CHANNELS] = + { AS_MUMSR_MGIP0, AS_MUMSR_MGIP1, + AS_MUMSR_MGIP2, AS_MUMSR_MGIP3 +}; + +static unsigned int genp_req[NUM_MU_CHANNELS] = + { AS_MUMCR_MGIR0, AS_MUMCR_MGIR1, + AS_MUMCR_MGIR2, AS_MUMCR_MGIR3 +}; + +static unsigned int read_reg[NUM_MU_CHANNELS] = { AS_MUMRR0, AS_MUMRR1, + AS_MUMRR2, AS_MUMRR3 +}; + +static unsigned int write_reg[NUM_MU_CHANNELS] = { AS_MUMTR0, AS_MUMTR1, + AS_MUMTR2, AS_MUMTR3 +}; + +/*! + * The readq and waitq are used by blocking read/write + */ +static wait_queue_head_t readq, writeq, suspendq, wait_before_closeq; + +/*! + * To enable or disable byte swapping + */ +static bool byte_swapping_rd[NUM_MU_CHANNELS] = { false, false, false, false }; +static bool byte_swapping_wr[NUM_MU_CHANNELS] = { false, false, false, false }; + +/* + * Declaring Bottom-half handlers - one for GP, RX and TX interrupt lines + */ +static void mxc_mu_rxhandler(unsigned long someval); +static void mxc_mu_txhandler(unsigned long someval); +static void mxc_mu_gphandler(unsigned long someval); + +/* + * Tasklets Declarations + */ +/* + * Declaring tasklet to handle receive interrupts + */ +DECLARE_TASKLET(rxch0tasklet, (void *)mxc_mu_rxhandler, (unsigned long)0); +DECLARE_TASKLET(rxch1tasklet, (void *)mxc_mu_rxhandler, (unsigned long)1); +DECLARE_TASKLET(rxch2tasklet, (void *)mxc_mu_rxhandler, (unsigned long)2); +DECLARE_TASKLET(rxch3tasklet, (void *)mxc_mu_rxhandler, (unsigned long)3); + +/* + * Declaring tasklet to handle transmit interrupts + */ +DECLARE_TASKLET(txch0tasklet, (void *)mxc_mu_txhandler, (unsigned long)0); +DECLARE_TASKLET(txch1tasklet, (void *)mxc_mu_txhandler, (unsigned long)1); +DECLARE_TASKLET(txch2tasklet, (void *)mxc_mu_txhandler, (unsigned long)2); +DECLARE_TASKLET(txch3tasklet, (void *)mxc_mu_txhandler, (unsigned long)3); + +/* + * Declaring tasklet to handle general purpose interrupts + */ +DECLARE_TASKLET(gpch0tasklet, (void *)mxc_mu_gphandler, (unsigned long)0); +DECLARE_TASKLET(gpch1tasklet, (void *)mxc_mu_gphandler, (unsigned long)1); +DECLARE_TASKLET(gpch2tasklet, (void *)mxc_mu_gphandler, (unsigned long)2); +DECLARE_TASKLET(gpch3tasklet, (void *)mxc_mu_gphandler, (unsigned long)3); + +/*! + * Allocating Resources: + * Allocating channels required to the caller if they are free. + * 'reg_allocated' variable holds all the registers that have been allocated + * + * @param chnum The channel number required is passed + * + * @return Returns unique channel handler associated with channel. This + * channel handler is used to validate the user accessing the + * channel. + * Returns channel handler - on success + * Returns negative value - on error + * If invalid channel is passed, it returns -ENODEV error. + * If no channel is available, returns -EBUSY. + */ +int mxc_mu_alloc_channel(int chnum) +{ + unsigned long flags; + int ret_val = -EBUSY; + + if (chnum < 0 || chnum > 3) { + return -ENODEV; + } + if (suspend_flag == 1) { + return -EBUSY; + } + /* Acquiring Lock */ + spin_lock_irqsave(&alloc_lock, flags); + if (reg_allocated[chnum] == -1) { + /* + * If channel requested is available, create + * channel handler and store in the array + */ + reg_allocated[chnum] = BASE_NUM + chnum; + ret_val = BASE_NUM + chnum; + } + spin_unlock_irqrestore(&alloc_lock, flags); + return ret_val; +} + +/*! + * Deallocating Resources: + * + * @param chand The channel handler associated with the channel + * that is to be freed is passed. + * + * @return Returns 0 on success and -1 if channel was not + * already allocated or ENODEV error if invalid + * channel is obtained from the channel handler + */ +int mxc_mu_dealloc_channel(int chand) +{ + unsigned long flags; + int retval = -1; + int chnum = chand - BASE_NUM; + + if (chnum < 0 || chnum > 3) { + return -ENODEV; + } + if (suspend_flag == 1) { + return -EBUSY; + } + spin_lock_irqsave(&alloc_lock, flags); + if (reg_allocated[chnum] == chand) { + /* deallocating */ + reg_allocated[chnum] = -1; + /* clearing callback arrays */ + rxcallback[chnum] = NULL; + txcallback[chnum] = NULL; + gpcallback[chnum] = NULL; + retval = 0; + } + spin_unlock_irqrestore(&alloc_lock, flags); + return retval; +} + +/* NON BLOCKING READ/WRITE */ + +/*! + * This function is called by other modules to read from one of the + * receive registers on the MCU side. + * + * @param chand The channel handler associated with the channel + * to be accessed is passed + * @param mu_msg Buffer where the read data has to be stored + * is passed by pointer as an argument + * + * @return Returns 0 on success or + * Returns NO_DATA error if there is no data to read or + * Returns NO_ACCESS error if the incorrect channel + * is accessed or ENODEV error if invalid + * channel is obtained from the channel handler + * Returns -1 if the buffer passed is not allocated + */ +int mxc_mu_mcuread(int chand, char *mu_msg) +{ + /* mu_msg should be 4 bytes long */ + int chnum = chand - BASE_NUM; + + if (chnum < 0 || chnum > 3) { + return -ENODEV; + } + if (suspend_flag == 1) { + return -EBUSY; + } + if (mu_msg == NULL) { + return -1; + } + if ((readl(AS_MUMSR) & rbits[chnum]) == 0) { + return NO_DATA; + } + if (reg_allocated[chnum] == chand) { + *(unsigned int *)mu_msg = readl(read_reg[chnum]); + } else { + return NO_ACCESS; + } + if (byte_swapping_rd[chnum] == true) { + *(unsigned int *)mu_msg = swab32(*(unsigned int *)mu_msg); + } + return 0; +} + +/*! + * This function is called by other modules to write to one of the + * transmit registers on the MCU side + * + * @param chand The channel handler associated with the channel + * to be accessed is passed + * @param mu_msg Buffer where the write data has to be stored + * is passed by pointer as an argument + * + * @return Returns 0 on success or + * Returns NO_DATA error if the register not empty or + * Returns NO_ACCESS error if the incorrect channel + * is accessed or ENODEV error if invalid + * channel is obtained from the channel handler + * Returns -1 if the buffer passed is not allocated + */ +int mxc_mu_mcuwrite(int chand, char *mu_msg) +{ + int chnum = chand - BASE_NUM; + + if (chnum < 0 || chnum > 3) { + return -ENODEV; + } + if (suspend_flag == 1) { + return -EBUSY; + } + if (mu_msg == NULL) { + return -1; + } + if (byte_swapping_wr[chnum] == true) { + /* Swap the bytes */ + *(unsigned int *)mu_msg = swab32(*(unsigned int *)mu_msg); + } + if ((readl(AS_MUMSR) & wbits[chnum]) == 0) { + return NO_DATA; + } + /* Check whether the register is allocated */ + if (reg_allocated[chnum] == chand) { + writel(*(unsigned int *)mu_msg, write_reg[chnum]); + } else { + return NO_ACCESS; + } + return 0; +} + +/*! + * Tasklet used by the Interrupt service routine to handle the muxed + * receive interrupts. When an interrupt occurs, control from the top main + * interrupt service routine is transferred to this tasklet which sends the + * channel number to the call back function + * + * @param chnum index value indicating channel number + * + */ +static void mxc_mu_rxhandler(unsigned long chnum) +{ + if (rxcallback[chnum] != NULL) { + rxcallback[chnum] (chnum); + } +} + +/*! + * Tasklet used by the Interrupt service routine to handle the muxed + * transmit interrupts. When an interrupt occurs, control from the top main + * interrupt service routine is transferred to this tasklet which sends the + * channel number to the call back function + * + * @param chnum index value indicating channel number + * + */ +static void mxc_mu_txhandler(unsigned long chnum) +{ + if (txcallback[chnum] != NULL) { + txcallback[chnum] (chnum); + } +} + +/*! + * Tasklet used by the Interrupt service routine to handle the general + * purpose interrupts. When an interrupt occurs, control from the top main + * interrupt service routine is transferred to this tasklet which sends the + * channel number to the call back function + * + * @param chnum index value indicating channel number + * + */ +static void mxc_mu_gphandler(unsigned long chnum) +{ + if (gpcallback[chnum] != NULL) { + gpcallback[chnum] (chnum); + } +} + +/*! + * Interrupt service routine registered to handle the individual general purpose + * interrupts or muxed. Interrupts are cleared in ISR before scheduling tasklet + * + * @param irq the interrupt number + * @param dev_id driver private data + * + * @return The function returns \b IRQ_RETVAL(1) if interrupt was + * handled, returns \b IRQ_RETVAL(0) if the interrupt was + * not handled. + * \b IRQ_RETVAL is defined in \b include/linux/interrupt.h. + */ +static irqreturn_t mxc_mu_mcugphand(int irq, void *dev_id) +{ + int handled = 0; + unsigned int sreg1, sreg2, status = 0; + + sreg1 = readl(AS_MUMSR); + sreg2 = readl(AS_MUMCR); + /* When General Purpose Interrupt occurs */ + if ((sreg1 & AS_MUMSR_MGIP0) && (sreg2 & AS_MUMCR_MGIE0)) { + status |= (AS_MUMSR_MGIP0); + tasklet_schedule(&gpch0tasklet); + handled = 1; + } + if ((sreg1 & AS_MUMSR_MGIP1) && (sreg2 & AS_MUMCR_MGIE1)) { + status |= (AS_MUMSR_MGIP1); + tasklet_schedule(&gpch1tasklet); + handled = 1; + } + if ((sreg1 & AS_MUMSR_MGIP2) && (sreg2 & AS_MUMCR_MGIE2)) { + status |= (AS_MUMSR_MGIP2); + tasklet_schedule(&gpch2tasklet); + handled = 1; + } + if ((sreg1 & AS_MUMSR_MGIP3) && (sreg2 & AS_MUMCR_MGIE3)) { + status |= (AS_MUMSR_MGIP3); + tasklet_schedule(&gpch3tasklet); + handled = 1; + } + writel(status, AS_MUMSR); + return IRQ_RETVAL(handled); +} + +/*! + * Interrupt service routine registered to handle the muxed receive + * interrupts. Interrupts disabled inside ISR before scheduling tasklet + * + * @param irq the interrupt number + * @param dev_id driver private data + * + * @return The function returns \b IRQ_RETVAL(1) if interrupt was + * handled, returns \b IRQ_RETVAL(0) if the interrupt was + * not handled. + * \b IRQ_RETVAL is defined in \b include/linux/interrupt.h. + */ +static irqreturn_t mxc_mu_mcurxhand(int irq, void *dev_id) +{ + int handled = 0; + unsigned int sreg1, sreg2, control; + + sreg1 = readl(AS_MUMSR); + sreg2 = readl(AS_MUMCR); + control = sreg2; + /* When Receive Interrupt occurs */ + if ((sreg1 & AS_MUMSR_MRF0) && (sreg2 & AS_MUMCR_MRIE0)) { + control &= ~(AS_MUMCR_MRIE0); + tasklet_schedule(&rxch0tasklet); + handled = 1; + } + if ((sreg1 & AS_MUMSR_MRF1) && (sreg2 & AS_MUMCR_MRIE1)) { + control &= ~(AS_MUMCR_MRIE1); + tasklet_schedule(&rxch1tasklet); + handled = 1; + } + if ((sreg1 & AS_MUMSR_MRF2) && (sreg2 & AS_MUMCR_MRIE2)) { + control &= ~(AS_MUMCR_MRIE2); + tasklet_schedule(&rxch2tasklet); + handled = 1; + } + if ((sreg1 & AS_MUMSR_MRF3) && (sreg2 & AS_MUMCR_MRIE3)) { + control &= ~(AS_MUMCR_MRIE3); + tasklet_schedule(&rxch3tasklet); + handled = 1; + } + writel(control, AS_MUMCR); + return IRQ_RETVAL(handled); +} + +/*! + * Interrupt service routine registered to handle the muxed transmit interrupts + * Interrupts disabled inside ISR before scheduling tasklet + * + * @param irq the interrupt number + * @param dev_id driver private data + * + * @return The function returns \b IRQ_RETVAL(1) if interrupt was + * handled, returns \b IRQ_RETVAL(0) if the interrupt was + * not handled. + * \b IRQ_RETVAL is defined in \b include/linux/interrupt.h. + */ +static irqreturn_t mxc_mu_mcutxhand(int irq, void *dev_id) +{ + int handled = 0; + unsigned int sreg1, sreg2, control; + + sreg1 = readl(AS_MUMSR); + sreg2 = readl(AS_MUMCR); + control = sreg2; + /* When Transmit Interrupt occurs */ + if ((sreg1 & AS_MUMSR_MTE0) && (sreg2 & AS_MUMCR_MTIE0)) { + control &= ~(AS_MUMCR_MTIE0); + tasklet_schedule(&txch0tasklet); + handled = 1; + } + if ((sreg1 & AS_MUMSR_MTE1) && (sreg2 & AS_MUMCR_MTIE1)) { + control &= ~(AS_MUMCR_MTIE1); + tasklet_schedule(&txch1tasklet); + handled = 1; + } + if ((sreg1 & AS_MUMSR_MTE2) && (sreg2 & AS_MUMCR_MTIE2)) { + control &= ~(AS_MUMCR_MTIE2); + tasklet_schedule(&txch2tasklet); + handled = 1; + } + if ((sreg1 & AS_MUMSR_MTE3) && (sreg2 & AS_MUMCR_MTIE3)) { + control &= ~(AS_MUMCR_MTIE3); + tasklet_schedule(&txch3tasklet); + handled = 1; + } + writel(control, AS_MUMCR); + return IRQ_RETVAL(handled); +} + +/*! + * This function is used by other modules to issue DSP hardware reset. + * + */ +int mxc_mu_dsp_reset(void) +{ + unsigned long flags; + + if (suspend_flag == 1) { + return -EBUSY; + } + spin_lock_irqsave(&mu_lock, flags); + writel((readl(AS_MUMCR) | AS_MUMCR_DHR), AS_MUMCR); + spin_unlock_irqrestore(&mu_lock, flags); + return 0; +} + +/*! + * This function is used by other modules to deassert DSP hardware reset + */ +int mxc_mu_dsp_deassert(void) +{ + unsigned long flags; + + if (suspend_flag == 1) { + return -EBUSY; + } + spin_lock_irqsave(&mu_lock, flags); + writel((readl(AS_MUMCR) & (~AS_MUMCR_DHR)), AS_MUMCR); + spin_unlock_irqrestore(&mu_lock, flags); + return 0; +} + +/*! + * This function is used by other modules to issue DSP Non-Maskable + * Interrupt to the DSP + */ +int mxc_mu_dsp_nmi(void) +{ + unsigned long flags; + + if (suspend_flag == 1) { + return -EBUSY; + } + spin_lock_irqsave(&mu_lock, flags); + writel((readl(AS_MUMCR) | AS_MUMCR_DNMI), AS_MUMCR); + spin_unlock_irqrestore(&mu_lock, flags); + return 0; +} + +/*! + * This function is used by other modules to retrieve the DSP reset state. + * + * @return Returns 1 if DSP in reset state and 0 otherwise + */ +int mxc_mu_dsp_reset_status(void) +{ + if (suspend_flag == 1) { + return -EBUSY; + } + if ((readl(AS_MUMSR) & AS_MUMSR_DRS) != 0) { + /* 1 implies the DSP side of MU is in reset state */ + return 1; + } else { + /* 0 implies the DSP side of MU is not in reset state */ + return 0; + } +} + +/*! + * This function is used by other modules to retrieve the DSP Power Mode. + * + * @return Returns a value from which power mode of the DSP side of + * of MU unit can be inferred + * 0 - Run mode, 1 - Wait mode + * 2 - Stop mode, 3 - DSM mode + */ +unsigned int mxc_mu_dsp_pmode_status(void) +{ + if (suspend_flag == 1) { + return -EBUSY; + } + return (readl(AS_MUMSR) & (AS_MUMSR_DPM1 | AS_MUMSR_DPM0) >> 5); +} + +/*! + * This function is used by other modules to reset the MU Unit. This would reset + * both the MCU side and the DSP side + * + */ +int mxc_mu_reset(void) +{ + unsigned long flags; + + if (suspend_flag == 1) { + return -EBUSY; + } + spin_lock_irqsave(&mu_lock, flags); + writel((readl(AS_MUMCR) | AS_MUMCR_MMUR), AS_MUMCR); + spin_unlock_irqrestore(&mu_lock, flags); + return 0; +} + +/*! + * This function is called by unbind function of this module and + * can also be called from other modules to enable desired + * receive Interrupt + * + * @param chand The channel handler associated with the channel + * whose interrupt to be disabled is passed + * @param muoper The value passed is TX, RX, GP + * + * @return Returns 0 on success or ENODEV error if invalid + * channel is obtained from the channel handler + * Returns -1 if muoper is other than RX, TX or GP + */ +int mxc_mu_intdisable(int chand, enum mu_oper muoper) +{ + unsigned int status; + unsigned long flags; + int chnum = chand - BASE_NUM; + + if (chnum < 0 || chnum > 3) { + return -ENODEV; + } + if (suspend_flag == 1) { + return -EBUSY; + } + spin_lock_irqsave(&mu_lock, flags); + status = readl(AS_MUMCR); + switch (muoper) { + case RX: + status &= ~(read_en[chnum]); + break; + case TX: + status &= ~(write_en[chnum]); + break; + case GP: + status &= ~(genp_en[chnum]); + break; + default: + spin_unlock_irqrestore(&mu_lock, flags); + return -1; + } + writel(status, AS_MUMCR); + spin_unlock_irqrestore(&mu_lock, flags); + return 0; +} + +/*! + * This function is called by other modules to unbind their + * call back functions + * + * @param chand The channel handler associated with the channel + * to be accessed is passed + * @param muoper The value passed is TX, RX, GP + * + * @return Returns 0 on success or ENODEV error if invalid + * channel is obtained from the channel handler + * Returns -1 if muoper is other than RX, TX or GP, or + * if disabling interrupt failed + */ +int mxc_mu_unbind(int chand, enum mu_oper muoper) +{ + int result = 0; + int chnum = chand - BASE_NUM; + + if (chnum < 0 || chnum > 3) { + return -ENODEV; + } + if (suspend_flag == 1) { + return -EBUSY; + } + if (reg_allocated[chnum] == chand) { + if (mxc_mu_intdisable(chand, muoper) != 0) { + return -1; + } + switch (muoper) { + case RX: + rxcallback[chnum] = NULL; + break; + case TX: + txcallback[chnum] = NULL; + break; + case GP: + gpcallback[chnum] = NULL; + break; + default: + result = -1; + } + } + return result; +} + +/*! + * This function is called by the user and other modules to enable desired + * interrupt + * + * @param chand The channel handler associated with the channel + * to be accessed is passed + * @param muoper The value passed is TX, RX, GP + * + * @return Returns 0 on success or ENODEV error if invalid + * channel is obtained from the channel handler + * Returns -1 if muoper is other than RX, TX or GP + */ +int mxc_mu_intenable(int chand, enum mu_oper muoper) +{ + unsigned long status = 0; + unsigned long flags; + int chnum = chand - BASE_NUM; + + if (chnum < 0 || chnum > 3) { + return -ENODEV; + } + if (suspend_flag == 1) { + return -EBUSY; + } + spin_lock_irqsave(&mu_lock, flags); + status = readl(AS_MUMCR); + switch (muoper) { + case RX: + status |= (read_en[chnum]); + break; + case TX: + status |= (write_en[chnum]); + break; + case GP: + status |= (genp_en[chnum]); + break; + default: + spin_unlock_irqrestore(&mu_lock, flags); + return -1; + } + writel(status, AS_MUMCR); + spin_unlock_irqrestore(&mu_lock, flags); + return 0; +} + +/*! + * This function is called by other modules to bind their + * call back functions. + * + * @param chand The channel handler associated with the channel + * to be accessed is passed + * @param callback the caller's callback function + * @param muoper The value passed is TX, RX, GP + * + * @return Returns 0 on success or ENODEV error if invalid + * channel is obtained from the channel handler + * Returns -1 if muoper is other than RX, TX or GP + */ +int mxc_mu_bind(int chand, callbackfn callback, enum mu_oper muoper) +{ + + int chnum = chand - BASE_NUM; + + if (chnum < 0 || chnum > 3) { + return -ENODEV; + } + if (suspend_flag == 1) { + return -EBUSY; + } + if (reg_allocated[chnum] == chand) { + switch (muoper) { + case RX: + rxcallback[chnum] = callback; + break; + case TX: + txcallback[chnum] = callback; + break; + case GP: + gpcallback[chnum] = callback; + break; + default: + return -1; + } + } + return 0; +} + +/* + * This function is used to copy data from kernel space buffer + * to user space buffer + */ +static int mcu_copytouser(int count, int minor, char *userbuff) +{ + int bytes_available = 0, actual_bytes = 0, checkval = 0; + + while (1) { + bytes_available = CIRC_CNT_TO_END(mu_rbuf[minor].cbuf.head, + mu_rbuf[minor].cbuf.tail, + MAX_BUF_SIZE); + /* + * If the number of bytes already in the buffer is greater than + * count then read count from buffer else read what is in the buffer + */ + if (count < bytes_available) { + bytes_available = count; + } + if (bytes_available <= 0) { + break; + } + checkval = copy_to_user(userbuff, + &mu_rbuf[minor].cbuf.buf[mu_rbuf[minor]. + cbuf.tail], + bytes_available); + if (checkval) { + break; + } + mu_rbuf[minor].cbuf.tail = (mu_rbuf[minor].cbuf.tail + + bytes_available) & (MAX_BUF_SIZE - + 1); + + userbuff += bytes_available; + count -= bytes_available; + actual_bytes += bytes_available; + } + /* + * Return the number of bytes copied + */ + return actual_bytes; +} + +/* + * This function is used to copy data from user space buffer + * to kernel space buffer + */ +static int mcu_copyfromuser(int count, int minor, const char *userbuff) +{ + int space_available = 0, bytes_copied = 0, checkval = 0; + + while (1) { + space_available = CIRC_SPACE_TO_END(mu_wbuf[minor].cbuf.head, + mu_wbuf[minor].cbuf.tail, + MAX_BUF_SIZE); + /* + * If the space available in the kernel space buffer + * is more than required i.e., count then number of bytes + * copied from the user space buffer is count else available empty + * space for transmission + */ + if (space_available >= count) { + space_available = count; + } + if ((space_available % CH_SIZE) != 0) { + space_available = space_available - + (space_available % CH_SIZE); + } + if (space_available < CH_SIZE) { + break; + } + checkval = + copy_from_user(&mu_wbuf[minor].cbuf. + buf[mu_wbuf[minor].cbuf.head], userbuff, + space_available); + if (checkval) { + break; + } + mu_wbuf[minor].cbuf.head = + (mu_wbuf[minor].cbuf.head + + space_available) & (MAX_BUF_SIZE - 1); + + userbuff += space_available; + count -= space_available; + bytes_copied += space_available; + } + + return bytes_copied; +} + +/*! + * This function allows the caller to determine whether it can + * read from or write to one or more open files without blocking. + * + * @param filp Pointer to device file structure + * @param wait used by poll_wait to indicate a change in the + * poll status. + * + * @return the bit mask describing which operations could + * be completed immediately. + */ +static unsigned int mxc_mu_poll(struct file *filep, poll_table * wait) +{ + unsigned int minor; + unsigned int mask = 0; + + minor = MINOR(filep->f_dentry->d_inode->i_rdev); + + poll_wait(filep, &writeq, wait); + poll_wait(filep, &readq, wait); + + if (GET_READBYTES(minor)) { + mask |= POLLIN | POLLRDNORM; + } + if (GET_WRITESPACE(minor) >= CH_SIZE) { + mask |= POLLOUT | POLLWRNORM; + } + return mask; +} + +/*! + * The read function is available to the user-space to perform + * a read operation + * + * @param filp Pointer to device file structure + * @param userbuff User buffer, where read data to be placed. + * @param count Size of the requested data transfer + * @param offp File position where the user is accessing. + * + * @return Returns number of bytes read or + * Returns -EINVAL if the number of bytes + * requested is not a multiple of channel + * receive or transmit size or + * Returns -EAGAIN for a non-block read when no data + * is ready in the buffer or in the register or + * Returns -EFAULT if copy_to_user failed + */ +static ssize_t mxc_mu_read(struct file *filp, + char *userbuff, size_t count, loff_t * offp) +{ + int total_bytestocopy = 0; + struct inode *inode = filp->f_dentry->d_inode; + unsigned int minor = MINOR(inode->i_rdev); + + while (suspend_flag == 1) { + swait++; + /* Block if the device is suspended */ + if (wait_event_interruptible(suspendq, (suspend_flag == 0))) { + return -ERESTARTSYS; + } + } + /* + * If the count value requested is not multiple of channel size then + * return error + */ + if (((count % CH_SIZE) != 0) || (count == 0)) { + return -EINVAL; /* error */ + } + total_bytestocopy = mcu_copytouser(count, minor, userbuff); + /* + * Enable the interrupt to read from the register + */ + mxc_mu_intenable(reg_allocated[minor], RX); + if (total_bytestocopy != 0) { + return total_bytestocopy; + } + /* + * If there is no data in buffer and number of bytes + * copied to user space buffer is 0, then block until at least + * 4 bytes are read incase of block read else, return immediately + * incase of nonblock read + */ + if (!(filp->f_flags & O_NONBLOCK)) { + while (GET_READBYTES(minor) == 0) { + mu_rbuf[minor].pwait++; + /* Block */ + if (wait_event_interruptible(readq, + (GET_READBYTES(minor) != + 0))) { + return -ERESTARTSYS; + } + } + } else { + /* Return error for Non-Block read */ + return -EAGAIN; + } + /* + * Some data is now available in the register, + * read the data and copy to the user + */ + return (mcu_copytouser(count, minor, userbuff)); +} + +/*! + * The write function is available to the user-space to perform + * a write operation + * + * @param filp Pointer to device file structure + * @param userbuff User buffer, where read data to be placed. + * @param count Size of the requested data transfer + * @param offp File position where the user is accessing. + * + * @return Returns number of bytes read or + * Returns -EINVAL if the number of bytes + * requested is not a multiple of channel + * receive or transmit size or + * Returns -EAGAIN for a non-block read when no data + * is ready in the buffer or in the register or + * Returns -EFAULT if copy_to_user failed + */ +static ssize_t mxc_mu_write(struct file *filp, const char *userbuff, size_t + count, loff_t * offp) +{ + int totalbytes_copied = 0; + struct inode *inode = filp->f_dentry->d_inode; + unsigned int minor = MINOR(inode->i_rdev); + + while (suspend_flag == 1) { + swait++; + /* Block if the device is suspended */ + if (wait_event_interruptible(suspendq, (suspend_flag == 0))) { + return -ERESTARTSYS; + } + } + /* + * If the count value requested is not multiple of channel size then + * return error + */ + if (((count % CH_SIZE) != 0) || (count == 0)) { + return -EINVAL; /* error */ + } + totalbytes_copied = mcu_copyfromuser(count, minor, userbuff); + /* Enable the interrupt before return so that data is transmitted */ + mxc_mu_intenable(reg_allocated[minor], TX); + /* + * If number of bytes copied to the kernel space buffer is greater than + * 0, return immediately + */ + if (totalbytes_copied != 0) { + return totalbytes_copied; + } + /* If there is no space in buffer and number of bytes + * copied from user space buffer is 0, then block until at least + * 4 bytes are written incase of block write else, return + * immediately incase of nonblock write + */ + if (!(filp->f_flags & O_NONBLOCK)) { + while (GET_WRITESPACE(minor) < CH_SIZE) { + mu_wbuf[minor].pwait++; + if (wait_event_interruptible(writeq, + (GET_WRITESPACE(minor) >= + CH_SIZE))) { + return -ERESTARTSYS; + } + } + } else { + /* Return error incase of non-block write */ + return -EAGAIN; + } + /* + * Some space is now available since some + * data has been transmitted. So, copy data from + * the user space buffer to kernel space buffer, + */ + return (mcu_copyfromuser(count, minor, userbuff)); +} + +/*! + * The write callback function is used by this module to perform any + * write requests from user-space + * + * @param chnum channel number whose register has to be accessed + */ +static void mxc_mu_writecb(int chnum) +{ + char *message; + + /* + * While there are more bytes to transfer and + * register is empty, continue transmitting + */ + while ((GET_WRITEBYTES(chnum) != 0) && (readl(AS_MUMSR) & wbits[chnum])) { + /* Maximum of 4 bytes everytime */ + message = &mu_wbuf[chnum].cbuf.buf[mu_wbuf[chnum].cbuf.tail]; + mu_wbuf[chnum].cbuf.tail = + (mu_wbuf[chnum].cbuf.tail + CH_SIZE) & (MAX_BUF_SIZE - 1); + mxc_mu_mcuwrite(reg_allocated[chnum], message); + /* Emptying write buffer */ + } + /* + * Enable the interrupt if more data has to be transmitted. + * Since interrupts are disabled inside ISR. + */ + if (GET_WRITEBYTES(chnum) != 0) { + mxc_mu_intenable(reg_allocated[chnum], TX); + } + /* Wake up the sleeping process if the buffer gets emptied */ + if (blockmode[chnum] == 0 && mu_wbuf[chnum].pwait > 0 && + GET_WRITESPACE(chnum) >= CH_SIZE) { + mu_wbuf[chnum].pwait--; + /* Wake up */ + wake_up_interruptible(&writeq); + } +} + +/*! + * The read callback function is used by this module to perform any + * read requests from user-space + * + * @param chnum channel number whose register has to be accessed + */ +static void mxc_mu_readcb(int chnum) +{ + char message[CH_SIZE]; + int index; + + /* + * While there more bytes can be read and if + * buffer is empty + */ + while ((GET_READSPACE(chnum) >= CH_SIZE) + && (readl(AS_MUMSR) & rbits[chnum])) { + mxc_mu_mcuread(reg_allocated[chnum], message); + index = mu_rbuf[chnum].cbuf.head; + *(int *)(&(mu_rbuf[chnum].cbuf.buf[index])) = *(int *)message; + mu_rbuf[chnum].cbuf.head = + (mu_rbuf[chnum].cbuf.head + CH_SIZE) & (MAX_BUF_SIZE - 1); + } + /* + * If no empty space in buffer to store the data that + * has been read, then disable the interrupt else + * enable the interrupt + */ + (GET_READSPACE(chnum) < + CH_SIZE) ? mxc_mu_intdisable(reg_allocated[chnum], + RX) : +mxc_mu_intenable(reg_allocated[chnum], RX); + /* Wake up the sleeping process if data is available */ + if (blockmode[chnum] == 0 && mu_rbuf[chnum].pwait > 0 && + GET_READBYTES(chnum) != 0) { + mu_rbuf[chnum].pwait--; + /* Wake up */ + wake_up_interruptible(&readq); + } +} + +/*! + * The general purpose callback function is used by this module to + * perform any general purpose interrupt requests from user-space + * + * @param chnum channel number whose register has to be accessed + */ +static void mxc_mu_genpurposecb(int chnum) +{ + writel((readl(AS_MUMSR) | (genp_pend[chnum])), AS_MUMSR); +} + +/*! + * This function is called when an ioctl call is made from user space. + * + * @param inode Pointer to device inode + * @param file Pointer to device file structure + * @param cmd Ioctl command + * @param arg Ioctl argument + * + * @return The function returns 0 on success and -EINVAL on + * failure when cmd is other than what is specified + */ +int mxc_mu_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct inode *i_node = file->f_dentry->d_inode; + unsigned int minor = MINOR(i_node->i_rdev); + int i = 0; + + while (suspend_flag == 1) { + swait++; + /* Block if the device is suspended */ + if (wait_event_interruptible(suspendq, (suspend_flag == 0))) { + return -ERESTARTSYS; + } + } + switch (cmd) { + case SENDINT: + writel(genp_req[minor], AS_MUMCR); + break; + case SENDNMI: + mxc_mu_dsp_nmi(); + break; + case SENDDSPRESET: + mxc_mu_dsp_reset(); + for (i = 0; i < 20; i++) ; + mxc_mu_dsp_deassert(); + while (mxc_mu_dsp_reset_status() != 0) ; + break; + case SENDMURESET: + mxc_mu_reset(); + break; + case RXENABLE: + mxc_mu_intenable(reg_allocated[minor], RX); + break; + case TXENABLE: + mxc_mu_intenable(reg_allocated[minor], TX); + break; + case GPENABLE: + mxc_mu_intenable(reg_allocated[minor], GP); + break; + case BYTESWAP_RD: + byte_swapping_rd[minor] = true; + break; + case BYTESWAP_WR: + byte_swapping_wr[minor] = true; + break; + default: + return -EINVAL; + } + return 0; +} + +/*! + * This function is called to close the device. The MCU side of MU interrupts + * are disabled. Any allocated buffers are freed + * + * @param inode Pointer to device inode + * @param filp Pointer to device file structure + * + * @return The function returns 0 on success or + * returns -ENODEV if incorrect channel is accessed + * + */ +static int mxc_mu_close(struct inode *inode, struct file *filp) +{ + unsigned int minor = MINOR(inode->i_rdev); + + while (suspend_flag == 1) { + swait++; + /* Block if the device is suspended */ + if (wait_event_interruptible(suspendq, (suspend_flag == 0))) { + return -ERESTARTSYS; + } + } + /* Wait to complete any pending writes */ + /* Block until the write buffer is empty */ + while (GET_WRITEBYTES(minor) != 0) { + if (wait_event_interruptible_timeout + (wait_before_closeq, (GET_WRITEBYTES(minor) == 0), + MAX_SCHEDULE_TIMEOUT)) { + return -ERESTARTSYS; + } + } + if (mxc_mu_dealloc_channel(reg_allocated[minor]) != 0) { + return -ENODEV; + } + kfree(mu_rbuf[minor].cbuf.buf); + return 0; +} + +/*! + * This function is called when the MU driver is opened. This function does + * the initialization of the device. + * + * @param inode Pointer to device inode + * @param filp Pointer to device file structure + * + * @return The function returns 0 on success or a negative value if + * channel was not allocated or if kmalloc failed + */ +static int mxc_mu_open(struct inode *inode, struct file *filp) +{ + int chand; + unsigned int minor = MINOR(inode->i_rdev); + + while (suspend_flag == 1) { + swait++; + /* Block if the device is suspended */ + if (wait_event_interruptible(suspendq, (suspend_flag == 0))) { + return -ERESTARTSYS; + } + } + chand = mxc_mu_alloc_channel(minor); + if (chand < 0) { + return chand; + } + mxc_mu_bind(reg_allocated[minor], &mxc_mu_readcb, RX); + mxc_mu_bind(reg_allocated[minor], &mxc_mu_writecb, TX); + mxc_mu_bind(reg_allocated[minor], &mxc_mu_genpurposecb, GP); + mxc_mu_intdisable(reg_allocated[minor], TX); + /* + * Read buffer from 0 to MAX_BUF_SIZE + * Write buffer from MAX_BUF_SIZE to 2*MAX_BUF_SIZE + */ + mu_rbuf[minor].cbuf.buf = kmalloc((2 * MAX_BUF_SIZE), GFP_KERNEL); + mu_wbuf[minor].cbuf.buf = mu_rbuf[minor].cbuf.buf + MAX_BUF_SIZE; + if (mu_rbuf[minor].cbuf.buf == NULL) { + reg_allocated[minor] = -1; + return -1; + } + mu_rbuf[minor].cbuf.tail = mu_rbuf[minor].cbuf.head = 0; + mu_wbuf[minor].cbuf.tail = mu_wbuf[minor].cbuf.head = 0; + + byte_swapping_rd[minor] = false; + byte_swapping_wr[minor] = false; + + mu_rbuf[minor].pwait = mu_rbuf[minor].pwait = 0; + if (filp->f_flags & O_NONBLOCK) { + blockmode[minor] = 1; + } else { + blockmode[minor] = 0; + } + mxc_mu_intenable(reg_allocated[minor], RX); + return 0; +} + +/*! + * This function is called to put the MU in a low power state. + * + * @param pdev the device structure used to give information on which MU + * device (0 through 3 channels) to suspend + * @param state the power state the device is entering + * + * @return The function always returns 0. + */ +static int mxc_mu_suspend(struct platform_device *pdev, pm_message_t state) +{ + unsigned long flags; + + suspend_flag = 1; + spin_lock_irqsave(&mu_lock, flags); + mcr_suspend_state = readl(AS_MUMCR); + /* Disabling all receive, transmit, gen. purpose interrupts */ + writel((mcr_suspend_state & 0x0000FFFF), AS_MUMCR); + spin_unlock_irqrestore(&mu_lock, flags); + + /* Turn off clock */ + clk_disable(mu_clkp); + + return 0; +} + +/*! + * This function is called to resume the MU from a low power state. + * + * @param dev the device structure used to give information on which MU + * device (0 through 3 channels) to suspend + * @param level the stage in device suspension process that we want the + * device to be put in + * + * @return The function always returns 0. + */ +static int mxc_mu_resume(struct platform_device *pdev) +{ + unsigned long flags; + + /* Turn on clock */ + clk_enable(mu_clkp); + + spin_lock_irqsave(&mu_lock, flags); + /* Re-enable interrupts or restore state of interrupts */ + writel(mcr_suspend_state, AS_MUMCR); + while (swait > 0) { + swait--; + wake_up_interruptible(&suspendq); + } + spin_unlock_irqrestore(&mu_lock, flags); + suspend_flag = 0; + + return 0; +} + +static struct file_operations mu_fops = { + .owner = THIS_MODULE, + .read = mxc_mu_read, + .write = mxc_mu_write, + .ioctl = mxc_mu_ioctl, + .open = mxc_mu_open, + .release = mxc_mu_close, + .poll = mxc_mu_poll, +}; + +/*! + * This function is called while loading the module to initiate message + * transfers. This function initializes all the registers. + */ +static void mxc_mu_load_mod(void) +{ + /* Initializing wait queues */ + init_waitqueue_head(&readq); + init_waitqueue_head(&writeq); + init_waitqueue_head(&suspendq); + init_waitqueue_head(&wait_before_closeq); + /* Setting the status and control registers to default values */ + writel(0xffff, AS_MUMSR); + writel(0, AS_MUMCR); +} + +/*! + * This function is called while unloading driver. + * This resets all the registers and disables + * interrupts. + */ +static void mxc_mu_unload_mod(void) +{ + /* setting the status and control registers to default values */ + writel(0xffff, AS_MUMSR); + writel(0, AS_MUMCR); +} + +/*! + * This function is used to load the module and all the interrupt lines + * are requested. + * + * @return Returns an Integer on success + */ +static int __init mxc_mu_probe(struct platform_device *pdev) +{ + /* Initializing resources allocated */ + int rx_irq, tx_irq, irq; + int max_res; + int i; + struct class_device *temp_class; + + max_res = pdev->num_resources; + rx_irq = platform_get_irq(pdev, 0); + tx_irq = platform_get_irq(pdev, 1); + if (rx_irq == NO_IRQ || tx_irq == NO_IRQ) + return -ENXIO; + + mu_clkp = clk_get(&pdev->dev, "mu_clk"); + if (IS_ERR(mu_clkp)) + return -ENXIO; + clk_enable(mu_clkp); + + if ((mu_major = register_chrdev(0, "mxc_mu", &mu_fops)) < 0) { + printk(KERN_NOTICE + "Can't allocate major number for MU Devices.\n"); + return -EAGAIN; + } + + mxc_mu_class = class_create(THIS_MODULE, "mxc_mu"); + + if (IS_ERR(mxc_mu_class)) { + printk(KERN_ERR "Error creating mu class.\n"); + unregister_chrdev(mu_major, "mxc_mu"); + return PTR_ERR(mxc_mu_class); + } + + mxc_mu_load_mod(); + + for (i = 0; i <= 3; i++) { + temp_class = + class_device_create(mxc_mu_class, NULL, MKDEV(mu_major, i), + NULL, "mxc_mu%u", i); + + if (IS_ERR(temp_class)) + goto err_out1; + } + + if (request_irq(rx_irq, mxc_mu_mcurxhand, 0, "mxc_mu_rx", NULL) != 0) + goto err_out1; + + if (request_irq(tx_irq, mxc_mu_mcutxhand, 0, "mxc_mu_tx", NULL) != 0) + goto err_out2; + + for (i = 2; i < max_res; i++) { + irq = platform_get_irq(pdev, i); + if (irq == NO_IRQ) + goto err_out3; + + if (request_irq(irq, mxc_mu_mcugphand, 0, "mxc_mu_mcug", NULL) + != 0) { + printk(KERN_ERR "mxc_mu: request_irq for %d failed\n", + irq); + goto err_out3; + } + } + + return 0; + + err_out3: + for (--i; i >= 2; i--) { + irq = platform_get_irq(pdev, i); + free_irq(irq, NULL); + } + free_irq(tx_irq, NULL); + + err_out2: + free_irq(rx_irq, NULL); + + err_out1: + for (i = 0; i <= 3; i++) { + class_device_destroy(mxc_mu_class, MKDEV(mu_major, i)); + } + mxc_mu_unload_mod(); + class_destroy(mxc_mu_class); + unregister_chrdev(mu_major, "mxc_mu"); + + return -1; +} + +/*! + * This function is used to unload the module and all interrupt lines are freed + */ +static void __exit mxc_mu_remove(struct platform_device *pdev) +{ + int irq, max_res, i; + max_res = pdev->num_resources; + + /* Freeing the resources allocated */ + mxc_mu_unload_mod(); + clk_disable(mu_clkp); + clk_put(mu_clkp); + for (i = 0; i < max_res; i++) { + irq = platform_get_irq(pdev, i); + free_irq(irq, NULL); + } + for (i = 0; i <= 3; i++) { + class_device_destroy(mxc_mu_class, MKDEV(mu_major, i)); + } + class_destroy(mxc_mu_class); + unregister_chrdev(mu_major, "mxc_mu"); + +} + +/*! + * This structure contains pointers to the power management callback functions. + */ +static struct platform_driver mxc_mu_driver = { + .driver = { + .name = "mxc_mu", + }, + .probe = mxc_mu_probe, + .remove = __exit_p(mxc_mu_remove), + .suspend = mxc_mu_suspend, + .resume = mxc_mu_resume, +}; + +/* + * Main initialization routine + */ +static int __init mxc_mu_init(void) +{ + /* Register the device driver structure. */ + pr_info("MXC MU Driver %s\n", DVR_VER); + if (platform_driver_register(&mxc_mu_driver) != 0) { + printk(KERN_ERR "Driver register failed for mxc_mu_driver\n"); + return -ENODEV; + } + return 0; +} + +/* + * Clean up routine + */ +static void __exit mxc_mu_cleanup(void) +{ + /* Unregister the device structure */ + platform_driver_unregister(&mxc_mu_driver); + printk(KERN_INFO "MU Driver Module Unloaded\n"); +} + +module_init(mxc_mu_init); +module_exit(mxc_mu_cleanup); + +EXPORT_SYMBOL(mxc_mu_bind); +EXPORT_SYMBOL(mxc_mu_unbind); +EXPORT_SYMBOL(mxc_mu_alloc_channel); +EXPORT_SYMBOL(mxc_mu_dealloc_channel); +EXPORT_SYMBOL(mxc_mu_mcuread); +EXPORT_SYMBOL(mxc_mu_mcuwrite); +EXPORT_SYMBOL(mxc_mu_intenable); +EXPORT_SYMBOL(mxc_mu_intdisable); +EXPORT_SYMBOL(mxc_mu_reset); +EXPORT_SYMBOL(mxc_mu_dsp_reset); +EXPORT_SYMBOL(mxc_mu_dsp_deassert); +EXPORT_SYMBOL(mxc_mu_dsp_nmi); +EXPORT_SYMBOL(mxc_mu_dsp_reset_status); +EXPORT_SYMBOL(mxc_mu_dsp_pmode_status); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("Messaging Unit Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/char/mxc_mu_reg.h b/drivers/char/mxc_mu_reg.h new file mode 100644 index 000000000000..2723e8707ed4 --- /dev/null +++ b/drivers/char/mxc_mu_reg.h @@ -0,0 +1,105 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/*! + * @file mxc_mu_reg.h + * + * @brief This file provides all the Register Addresses, Bit definitions. + * + * @ingroup MU + */ +#ifndef __MXC_MU_REG_H__ + +#define __MXC_MU_REG_H__ + +#include <asm/hardware.h> + +/* + * Address offsets of the MU MCU side transmit registers + */ +#define AS_MUMTR0 IO_ADDRESS(MU_BASE_ADDR + 0x000) /* Transmit register 0 */ +#define AS_MUMTR1 IO_ADDRESS(MU_BASE_ADDR + 0x004) /* Transmit register 1 */ +#define AS_MUMTR2 IO_ADDRESS(MU_BASE_ADDR + 0x008) /* Transmit register 2 */ +#define AS_MUMTR3 IO_ADDRESS(MU_BASE_ADDR + 0x00c) /* Transmit register 3 */ + +/* + * Address offsets of the MU MCU side receive registers + */ +#define AS_MUMRR0 IO_ADDRESS(MU_BASE_ADDR + 0x010) /* Receive register 0 */ +#define AS_MUMRR1 IO_ADDRESS(MU_BASE_ADDR + 0x014) /* Receive register 1 */ +#define AS_MUMRR2 IO_ADDRESS(MU_BASE_ADDR + 0x018) /* Receive register 2 */ +#define AS_MUMRR3 IO_ADDRESS(MU_BASE_ADDR + 0x01c) /* Receive register 3 */ + +/* + * Address offset of the MU MCU side status register + */ +#define AS_MUMSR IO_ADDRESS(MU_BASE_ADDR + 0x020) /* Status register */ + +/* + * Address offset of the MU MCU side control register + */ +#define AS_MUMCR IO_ADDRESS(MU_BASE_ADDR + 0x024) /* Control register */ + +/* + * Bit definitions of MSR + */ +#define AS_MUMSR_MGIP0 0x80000000 +#define AS_MUMSR_MGIP1 0x40000000 +#define AS_MUMSR_MGIP2 0x20000000 +#define AS_MUMSR_MGIP3 0x10000000 +#define AS_MUMSR_MRF0 0x08000000 +#define AS_MUMSR_MRF1 0x04000000 +#define AS_MUMSR_MRF2 0x02000000 +#define AS_MUMSR_MRF3 0x01000000 +#define AS_MUMSR_MTE0 0x00800000 +#define AS_MUMSR_MTE1 0x00400000 +#define AS_MUMSR_MTE2 0x00200000 +#define AS_MUMSR_MTE3 0x00100000 +#define AS_MUMSR_MFUP 0x00000100 +#define AS_MUMSR_DRS 0x00000080 +#define AS_MUMSR_DPM1 0x00000040 +#define AS_MUMSR_DPM0 0x00000020 +#define AS_MUMSR_MEP 0x00000010 +#define AS_MUMSR_MNMIC 0x00000008 +#define AS_MUMSR_MF2 0x00000004 +#define AS_MUMSR_MF1 0x00000002 +#define AS_MUMSR_MF0 0x00000001 + +/* + * Bit definitions of MCR + */ +#define AS_MUMCR_MGIE0 0x80000000 +#define AS_MUMCR_MGIE1 0x40000000 +#define AS_MUMCR_MGIE2 0x20000000 +#define AS_MUMCR_MGIE3 0x10000000 +#define AS_MUMCR_MRIE0 0x08000000 +#define AS_MUMCR_MRIE1 0x04000000 +#define AS_MUMCR_MRIE2 0x02000000 +#define AS_MUMCR_MRIE3 0x01000000 +#define AS_MUMCR_MTIE0 0x00800000 +#define AS_MUMCR_MTIE1 0x00400000 +#define AS_MUMCR_MTIE2 0x00200000 +#define AS_MUMCR_MTIE3 0x00100000 +#define AS_MUMCR_MGIR0 0x00080000 +#define AS_MUMCR_MGIR1 0x00040000 +#define AS_MUMCR_MGIR2 0x00020000 +#define AS_MUMCR_MGIR3 0x00010000 +#define AS_MUMCR_MMUR 0x00000020 +#define AS_MUMCR_DHR 0x00000010 +#define AS_MUMCR_DNMI 0x00000008 +#define AS_MUMCR_MDF2 0x00000004 +#define AS_MUMCR_MDF1 0x00000002 +#define AS_MUMCR_MDF0 0x00000001 + +#define BASE_NUM 1222 + +#endif /* __MXC_MU_REG_H__ */ diff --git a/drivers/char/mxc_sdma_tty.c b/drivers/char/mxc_sdma_tty.c new file mode 100644 index 000000000000..d0b59e4ff69e --- /dev/null +++ b/drivers/char/mxc_sdma_tty.c @@ -0,0 +1,744 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mxc_sdma_tty.c + * @brief This file contains functions for SDMA TTY driver + * + * SDMA TTY driver is used for moving data between MCU and DSP using line discipline API + * + * @ingroup IPC + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/types.h> +#include <linux/mm.h> +#include <asm/dma.h> +#include <asm/mach/dma.h> +#include <linux/interrupt.h> +#include <asm/irq.h> +#include <asm/hardware.h> +#include <asm/semaphore.h> +#include <asm/system.h> /* save/local_irq_restore */ +#include <asm/uaccess.h> /* For copy_from_user */ +#include <linux/sched.h> /* For schedule */ +#include <linux/device.h> + +#include <linux/tty.h> +#include <linux/tty_flip.h> +#include <linux/circ_buf.h> + +#include <asm/arch/mxc_sdma_tty.h> + +#define DEBUG 0 + +#if DEBUG +#define DPRINTK(fmt, args...) printk("%s: " fmt, __FUNCTION__ , ## args) +#else +#define DPRINTK(fmt, args...) +#endif + +#define SDMA_TTY_NORMAL_MAJOR 0 + +#define WRITE_ROOM 512 + +/*! + * This define returns the number of read SDMA channel + */ +#define READ_CHANNEL(line) (line < IPC_NB_CH_BIDIR) ? 2 * line + 1 : \ + (line < (IPC_NB_CH_BIDIR + IPC_NB_CH_DSPMCU)) ? \ + (line + 3) : -1 + +/*! + * This define returns the number of write SDMA channel + */ +#define WRITE_CHANNEL(line) (line < IPC_NB_CH_BIDIR) ? 2 * line + 2 : -1 + +/*! + * This define returns 1 if the device is unidirectional + * DSP to MCU + */ +#define DSPMCU_DEVICE(line) line < (IPC_NB_CH_BIDIR + IPC_NB_CH_DSPMCU) && \ + line >= IPC_NB_CH_BIDIR + +/*! + * This holds tty driver definitions + */ +static struct tty_driver *sdma_tty_driver; + +/*! + * This holds the transmit tasklet + */ +static struct tasklet_struct sdma_tty_tasklet; + +static struct class *sdma_tty_class; + +/*! + * Structure containing sdma tty line status + */ +typedef struct { + struct tty_struct *tty; /*!< pointer to tty struct of the line */ + int loopback_mode; /*!< Loopback mode: 0 - disable, 1 - enable */ + struct circ_buf write_buf; /*!< Write buffer */ + char *write_buf_phys; /*!< Write buffer dma pointer */ + char *read_buf; /*!< Read buffer */ + char *read_buf_phys; /*!< Read buffer dma pointer */ + int chars_in_buffer; /*!< Number of characters in write buffer */ + int sending; /*!< Sending flag */ + int sending_count; /*!< Number of characters sent + in last write operation */ + int f_mode; /*!< File mode (read/write) */ +} sdma_tty_struct; + +static sdma_tty_struct sdma_tty_data[IPC_NB_CH_BIDIR + IPC_NB_CH_DSPMCU]; + +static void sdma_tty_write_tasklet(unsigned long arg) +{ + int line, tx_num; + struct tty_struct *tty; + dma_request_t writechnl_request; + struct circ_buf *circ; + + tty = (struct tty_struct *)arg; + line = tty->index; + DPRINTK("SDMA %s on line %d\n", __FUNCTION__, line); + + if (sdma_tty_data[line].chars_in_buffer > 0) { + circ = &sdma_tty_data[line].write_buf; + tx_num = CIRC_CNT_TO_END(circ->head, circ->tail, WRITE_ROOM); + writechnl_request.sourceAddr = + sdma_tty_data[line].write_buf_phys + circ->tail; + writechnl_request.count = tx_num; + mxc_dma_set_config(WRITE_CHANNEL(line), &writechnl_request, 0); + mxc_dma_start(WRITE_CHANNEL(line)); + } else { + sdma_tty_data[line].sending = 0; + } +} + +/*! + * This function called on SDMA write channel interrupt. + * + * @param arg number of SDMA channel + */ +static void sdma_tty_write_callback(void *arg) +{ + dma_request_t sdma_read_request, sdma_write_request; + int line, count = 0; + struct tty_struct *tty; + struct circ_buf *circ; + + tty = (struct tty_struct *)arg; + line = tty->index; + + mxc_dma_get_config(WRITE_CHANNEL(line), &sdma_write_request, 0); + + count = sdma_write_request.count; + circ = &sdma_tty_data[line].write_buf; + + DPRINTK("SDMA %s on line %d count %d\n", __FUNCTION__, line, count); + + if (sdma_tty_data[line].loopback_mode) { + sdma_tty_data[line].sending_count = count; + + mxc_dma_get_config(READ_CHANNEL(line), &sdma_read_request, 0); + sdma_read_request.count = count; + sdma_read_request.destAddr = sdma_tty_data[line].read_buf_phys; + + if (count <= tty_buffer_request_room(tty, count)) { + tty_flip_buffer_push(tty); + } + + mxc_dma_set_config(READ_CHANNEL(line), &sdma_read_request, 0); + mxc_dma_start(READ_CHANNEL(line)); + } + sdma_tty_data[line].chars_in_buffer -= count; + circ->tail = (circ->tail + count) & (WRITE_ROOM - 1); + DPRINTK("SDMA %s on head %x tail %x\n", __FUNCTION__, circ->head, + circ->tail); + wake_up_interruptible(&tty->write_wait); + + if (sdma_tty_data[line].chars_in_buffer > 0) { + tasklet_schedule(&sdma_tty_tasklet); + } else { + sdma_tty_data[line].sending = 0; + } +} + +/*! + * This function called on SDMA read channel interrupt. + * + * @param arg number of SDMA channel + */ +static void sdma_tty_read_callback(void *arg) +{ + dma_request_t read_request; + struct tty_struct *tty; + int line; + int count; + + tty = (struct tty_struct *)arg; + line = tty->index; + + if (sdma_tty_data[line].loopback_mode && + sdma_tty_data[line].sending_count <= 0) { + return; + } + + mxc_dma_get_config(READ_CHANNEL(line), &read_request, 0); + + if (read_request.bd_done == 1) { + /* BD is not done yet */ + return; + } + + count = read_request.count; + /* Check for space availability in the TTY Flip buffer */ + count = tty_buffer_request_room(tty, count); + if (!count) { + goto drop_data; + } + + /* Set the real_raw flag to prevent processing on the received chars */ + tty->real_raw = 1; + tty_insert_flip_string(tty, sdma_tty_data[line].read_buf, count); + tty_flip_buffer_push(tty); + + wake_up_interruptible(&tty->read_wait); + + drop_data: + if (!sdma_tty_data[line].loopback_mode) { + read_request.count = WRITE_ROOM; + read_request.destAddr = sdma_tty_data[line].read_buf_phys; + + mxc_dma_set_config(READ_CHANNEL(line), &read_request, 0); + mxc_dma_start(READ_CHANNEL(line)); + } else { + sdma_tty_data[line].sending_count = 0; + } + + DPRINTK("Exit\n"); +} + +/*! + * This function changes loopback mode + * + * @param tty pointer to current tty line structure + * @param mode loopback mode: 0 - disable, 1 enable + * @param line tty line + * @return 0 on success, error code on fail + */ +static int sdma_tty_change_mode(struct tty_struct *tty, int mode, int line) +{ + dma_channel_params read_sdma_params, write_sdma_params; + dma_request_t sdma_read_request; + int res = 0; + + if (!(mode == 0 || mode == 1)) { + printk(KERN_WARNING "Illegal loopback mode value\n"); + return -EINVAL; + } + + if ((mode == 1) && (!((sdma_tty_data[line].f_mode & FMODE_READ) && + (sdma_tty_data[line].f_mode & FMODE_WRITE)))) { + printk(KERN_WARNING + "Loopback mode requires channel to be have read and write modes\n"); + return -EINVAL; + } + + /* Mode not changed */ + if (sdma_tty_data[line].loopback_mode == mode) { + return 0; + } + + sdma_tty_data[line].loopback_mode = mode; + + if (sdma_tty_data[line].f_mode & FMODE_READ) { + mxc_dma_stop(READ_CHANNEL(line)); + } + if (sdma_tty_data[line].f_mode & FMODE_WRITE) { + mxc_dma_stop(WRITE_CHANNEL(line)); + } + + if (sdma_tty_data[line].f_mode & FMODE_READ) { + /* SDMA read channel setup */ + memset(&read_sdma_params, 0, sizeof(dma_channel_params)); + read_sdma_params.peripheral_type = DSP; + read_sdma_params.transfer_type = + (mode == 0) ? dsp_2_emi : dsp_2_emi_loop; + read_sdma_params.event_id = 0; + read_sdma_params.callback = sdma_tty_read_callback; + read_sdma_params.arg = tty; + res = + mxc_dma_setup_channel(READ_CHANNEL(line), + &read_sdma_params); + if (res < 0) { + return res; + } + tty_flip_buffer_push(tty); + + /* SDMA read request setup */ + memset(&sdma_read_request, 0, sizeof(dma_request_t)); + sdma_read_request.destAddr = sdma_tty_data[line].read_buf_phys; + + mxc_dma_set_config(READ_CHANNEL(line), &sdma_read_request, 0); + + if (!sdma_tty_data[line].loopback_mode) { + mxc_dma_start(READ_CHANNEL(line)); + } + + } + + if (sdma_tty_data[line].f_mode & FMODE_WRITE) { + /* Write SDMA channel setup */ + memset(&write_sdma_params, 0, sizeof(dma_channel_params)); + write_sdma_params.peripheral_type = DSP; + write_sdma_params.transfer_type = + (mode == 0) ? emi_2_dsp : emi_2_dsp_loop; + write_sdma_params.event_id = 0; + write_sdma_params.callback = sdma_tty_write_callback; + write_sdma_params.arg = tty; + res = + mxc_dma_setup_channel(WRITE_CHANNEL(line), + &write_sdma_params); + if (res < 0) { + return res; + } + } + + if (mode) { + /* Echo off to avoid infinite loop */ + tty->termios->c_lflag = 0; + } + + return 0; +} + +/*! + * This function opens tty line + * + * @param tty pointer to current tty line structure + * @param filp pointer to file structure + * @return 0 on success, error code on fail + */ +static int sdma_tty_open(struct tty_struct *tty, struct file *filp) +{ + int channels[2]; + int line; + int res; + + line = tty->index; + + if (DSPMCU_DEVICE(line) && (filp->f_mode & FMODE_WRITE)) { + printk(KERN_WARNING + "Error: Cannot open this channel for writing.\n"); + return -EINVAL; + } + + memset(&sdma_tty_data[line], 0, sizeof(sdma_tty_struct)); + + channels[0] = READ_CHANNEL(line); + channels[1] = WRITE_CHANNEL(line); + + sdma_tty_data[line].tty = tty; + sdma_tty_data[line].f_mode = filp->f_mode; + + if (sdma_tty_data[line].f_mode & FMODE_READ) { + res = mxc_request_dma(channels, "SDMA TTY"); + if (res < 0) { // For read + printk(KERN_WARNING + "Error: SDMA DSP read channel busy\n"); + goto read_dma_req_failed; + } + } + if (sdma_tty_data[line].f_mode & FMODE_WRITE) { + res = mxc_request_dma(channels + 1, "SDMA TTY"); + if (res < 0) { // For write + printk(KERN_WARNING + "Error: SDMA DSP write channel busy\n"); + goto write_dma_req_failed; + } + } + + sdma_tty_data[line].chars_in_buffer = 0; + sdma_tty_data[line].sending = 0; + sdma_tty_data[line].loopback_mode = -1; + + if (sdma_tty_data[line].f_mode & FMODE_WRITE) { + sdma_tty_data[line].write_buf.buf = sdma_malloc(WRITE_ROOM); + sdma_tty_data[line].write_buf.head = 0; + sdma_tty_data[line].write_buf.tail = 0; + sdma_tty_data[line].write_buf_phys = + (char *)sdma_virt_to_phys(sdma_tty_data[line].write_buf. + buf); + } + if (sdma_tty_data[line].f_mode & FMODE_READ) { + sdma_tty_data[line].read_buf = sdma_malloc(WRITE_ROOM); + sdma_tty_data[line].read_buf_phys = + (char *)sdma_virt_to_phys(sdma_tty_data[line].read_buf); + } + + tty->low_latency = 1; // High rate + + res = sdma_tty_change_mode(tty, DEFAULT_LOOPBACK_MODE, line); + if (res < 0) { + goto change_mode_failed; + } + tasklet_init(&sdma_tty_tasklet, sdma_tty_write_tasklet, + (unsigned long)tty); + DPRINTK("SDMA %s raw = %d real_raw = %d\n", __FUNCTION__, tty->raw, + tty->real_raw); + + return 0; + + change_mode_failed: + if (sdma_tty_data[line].f_mode & FMODE_WRITE) { + sdma_free(sdma_tty_data[line].write_buf.buf); + } + if (sdma_tty_data[line].f_mode & FMODE_READ) { + sdma_free(sdma_tty_data[line].read_buf); + } + write_dma_req_failed: + if (sdma_tty_data[line].f_mode & FMODE_READ) { + mxc_free_dma(READ_CHANNEL(line)); + } + read_dma_req_failed: + return res; +} + +/*! + * This function closes tty line + * + * @param tty pointer to current tty line structure + * @param filp pointer to file structure + */ +static void sdma_tty_close(struct tty_struct *tty, struct file *filp) +{ + int line; + + line = tty->index; + + if (sdma_tty_data[line].f_mode & FMODE_READ) { + mxc_free_dma(READ_CHANNEL(line)); + sdma_free(sdma_tty_data[line].read_buf); + } + + if (sdma_tty_data[line].f_mode & FMODE_WRITE) { + mxc_free_dma(WRITE_CHANNEL(line)); + sdma_free(sdma_tty_data[line].write_buf.buf); + sdma_tty_data[line].write_buf.buf = NULL; + } +} + +/*! + * This function performs ioctl commands + * + * The driver supports 2 ioctls: TIOCLPBACK and TCGETS + * Other ioctls are supported by upper tty layers. + * + * @param tty pointer to current tty line structure + * @param file pointer to file structure + * @param cmd command + * @param arg pointer to argument + * @return 0 on success, error code on fail + */ +static int sdma_tty_ioctl(struct tty_struct *tty, struct file *file, + unsigned int cmd, unsigned long arg) +{ + int res; + int line; + + line = tty->index; + + switch (cmd) { + case TIOCLPBACK: + res = sdma_tty_change_mode(tty, *((int *)arg), line); + + return res; + break; + case TCGETS: + if (copy_to_user((struct termios *)arg, + tty->termios, sizeof(struct termios))) { + return -EFAULT; + } + return (0); + break; + default: + /* printk(KERN_WARNING "Unsupported %d ioctl\n", cmd); */ + break; + } + + return -ENOIOCTLCMD; +} + +/*! + * This function starts SDMA for sending data from write buffer + * + * @param tty pointer to current tty line structure + * @param count number of characters + * @return number of characters copied to write buffer + */ +static ssize_t sdma_tty_write_to_device(struct tty_struct *tty, int count) +{ + dma_request_t sdma_write_request; + int line; + + line = tty->index; + + DPRINTK("SDMA %s on line %d %d\n", __FUNCTION__, line, count); + + memset(&sdma_write_request, 0, sizeof(dma_request_t)); + + sdma_tty_data[line].sending_count = count; + + sdma_write_request.sourceAddr = sdma_tty_data[line].write_buf_phys + + sdma_tty_data[line].write_buf.tail; + sdma_write_request.count = count; + + mxc_dma_set_config(WRITE_CHANNEL(line), &sdma_write_request, 0); + + mxc_dma_start(WRITE_CHANNEL(line)); + + return count; +} + +/*! + * This function flushes chars from write buffer + * + * @param tty pointer to current tty line structure + */ +static void sdma_tty_flush_chars(struct tty_struct *tty) +{ + int line, count = 0; + struct circ_buf *circ; + + line = tty->index; + circ = &sdma_tty_data[line].write_buf; + + if (sdma_tty_data[line].sending == 1) { + return; + } + + if (sdma_tty_data[line].chars_in_buffer == 0) { + return; + } + + sdma_tty_data[line].sending = 1; + count = CIRC_CNT_TO_END(circ->head, circ->tail, WRITE_ROOM); + sdma_tty_write_to_device(tty, count); +} + +/*! + * This function writes characters to write buffer + * + * @param tty pointer to current tty line structure + * @param buf pointer to buffer + * @param count number of characters + * @return number of characters copied to write buffer + */ +static ssize_t sdma_tty_write(struct tty_struct *tty, + const unsigned char *buf, int count) +{ + int line; + int write_room, ret = 0; + struct circ_buf *circ; + line = tty->index; + + DPRINTK("SDMA %s on line %d %d\n", __FUNCTION__, line, count); + circ = &sdma_tty_data[line].write_buf; + + if (circ->head == circ->tail) { + circ->head = 0; + circ->tail = 0; + } + + while (1) { + write_room = + CIRC_SPACE_TO_END(circ->head, circ->tail, WRITE_ROOM); + if (count < write_room) { + write_room = count; + } + if (write_room <= 0) { + break; + } + + memcpy(circ->buf + circ->head, buf, write_room); + circ->head = (circ->head + write_room) & (WRITE_ROOM - 1); + buf += write_room; + count -= write_room; + ret += write_room; + } + DPRINTK("SDMA %s on head %x tail %x\n", __FUNCTION__, circ->head, + circ->tail); + sdma_tty_data[line].chars_in_buffer += ret; + sdma_tty_flush_chars(tty); + + return ret; +} + +/*! + * This function returns the number of characters in write buffer + * + * @param tty pointer to current tty line structure + * @return number of characters in write buffer + */ +static int sdma_tty_chars_in_buffer(struct tty_struct *tty) +{ + int res; + int line; + + line = tty->index; + + res = sdma_tty_data[line].chars_in_buffer; + + return res; +} + +/*! + * This function returns how much room the tty driver has available in the write buffer + * + * @param tty pointer to current tty line structure + * @return how much room the tty driver has available in the write buffer + */ +static int sdma_tty_write_room(struct tty_struct *tty) +{ + int res; + int line; + struct circ_buf *circ; + + line = tty->index; + circ = &sdma_tty_data[line].write_buf; + + res = CIRC_SPACE(circ->head, circ->tail, WRITE_ROOM); + + return res; +} + +/*! + * TTY operation structure + */ +static struct tty_operations sdma_tty_ops = { + .open = sdma_tty_open, + .close = sdma_tty_close, + .write_room = sdma_tty_write_room, + .write = sdma_tty_write, + .flush_chars = sdma_tty_flush_chars, + .ioctl = sdma_tty_ioctl, + .chars_in_buffer = sdma_tty_chars_in_buffer, +}; + +/*! + * This function registers the tty driver + */ +static int __init sdma_tty_init(void) +{ + int error; + int dev_id, dev_number; + int dev_mode; + struct class_device *temp_class; + + dev_number = IPC_NB_CH_BIDIR + IPC_NB_CH_DSPMCU; + + sdma_tty_driver = alloc_tty_driver(dev_number); + + if (!sdma_tty_driver) { + return -ENOMEM; + } + + sdma_tty_driver->owner = THIS_MODULE; + sdma_tty_driver->name = "mxc_sdma_tty"; + sdma_tty_driver->name_base = 0; + sdma_tty_driver->major = SDMA_TTY_NORMAL_MAJOR; + sdma_tty_driver->minor_start = 0; + sdma_tty_driver->num = dev_number; + sdma_tty_driver->type = TTY_DRIVER_TYPE_SERIAL; + sdma_tty_driver->subtype = SERIAL_TYPE_NORMAL; + sdma_tty_driver->init_termios = tty_std_termios; + sdma_tty_driver->flags = TTY_DRIVER_DYNAMIC_DEV; + sdma_tty_driver->flags |= TTY_DRIVER_REAL_RAW; /* lpj */ + tty_set_operations(sdma_tty_driver, &sdma_tty_ops); + + if ((error = tty_register_driver(sdma_tty_driver))) { + printk(KERN_ERR "SDMA_TTY: Couldn't register SDMA_TTY driver,\ + error = %d\n", error); + put_tty_driver(sdma_tty_driver); + + return error; + } + + sdma_tty_class = class_create(THIS_MODULE, "sdma"); + if (IS_ERR(sdma_tty_class)) { + printk(KERN_ERR "Error creating sdma tty class.\n"); + return PTR_ERR(sdma_tty_class); + } + + dev_mode = S_IFCHR | S_IRUGO | S_IWUGO; + for (dev_id = 0; dev_id < dev_number; dev_id++) { + temp_class = + class_device_create(sdma_tty_class, NULL, + MKDEV(sdma_tty_driver->major, + dev_id), NULL, "sdma%u", dev_id); + if (IS_ERR(temp_class)) + goto err_out; + + if (dev_id == IPC_NB_CH_BIDIR) { + dev_mode = S_IFCHR | S_IRUGO; + } + if (dev_id == IPC_NB_CH_BIDIR + IPC_NB_CH_DSPMCU) { + dev_mode = S_IFCHR | S_IWUGO; + } + } + + printk("SDMA TTY Driver initialized\n"); + return error; + + err_out: + printk(KERN_ERR "Error creating sdma class or class device.\n"); + for (dev_id = 0; dev_id < dev_number; dev_id++) { + class_device_destroy(sdma_tty_class, MKDEV + (sdma_tty_driver->major, dev_id)); + } + class_destroy(sdma_tty_class); + return -1; +} + +/*! + * This function unregisters the tty driver + */ +static void __exit sdma_tty_exit(void) +{ + + int dev_id, dev_number; + + dev_number = IPC_NB_CH_BIDIR + IPC_NB_CH_DSPMCU; + + if (!sdma_tty_driver) { + for (dev_id = 0; dev_id < dev_number; dev_id++) { + class_device_destroy(sdma_tty_class, + MKDEV(sdma_tty_driver->major, + dev_id)); + } + class_destroy(sdma_tty_class); + } + + tty_unregister_driver(sdma_tty_driver); + put_tty_driver(sdma_tty_driver); +} + +module_init(sdma_tty_init); +module_exit(sdma_tty_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MXC SDMA TTY driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/char/watchdog/mxc_wdt.c b/drivers/char/watchdog/mxc_wdt.c new file mode 100644 index 000000000000..126b14357d5a --- /dev/null +++ b/drivers/char/watchdog/mxc_wdt.c @@ -0,0 +1,385 @@ +/* + * linux/drivers/char/watchdog/mxc_wdt.c + * + * Watchdog driver for FSL MXC. It is based on omap1610_wdt.c + * + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + * 2005 (c) MontaVista Software, Inc. All Rights Reserved. + + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * History: + * + * 20051207: <AKuster@mvista.com> + * Full rewrite based on + * linux-2.6.15-rc5/drivers/char/watchdog/omap_wdt.c + * Add platform resource support + * + */ + +/*! + * @defgroup WDOG Watchdog Timer (WDOG) Driver + */ +/*! + * @file mxc_wdt.c + * + * @brief Watchdog timer driver + * + * @ingroup WDOG + */ + +#include <linux/module.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/fs.h> +#include <linux/mm.h> +#include <linux/miscdevice.h> +#include <linux/watchdog.h> +#include <linux/reboot.h> +#include <linux/smp_lock.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/moduleparam.h> +#include <linux/clk.h> + +#include <asm/io.h> +#include <asm/uaccess.h> +#include <asm/hardware.h> +#include <asm/irq.h> +#include <asm/bitops.h> + +#include <asm/hardware.h> +#include "mxc_wdt.h" +#define DVR_VER "2.0" + +#define WDOG_SEC_TO_COUNT(s) ((s * 2) << 8) +#define WDOG_COUNT_TO_SEC(c) ((c >> 8) / 2) + +static u32 wdt_base_reg; +static int mxc_wdt_users; +static struct clk *mxc_wdt_clk; + +static unsigned timer_margin = TIMER_MARGIN_DEFAULT; +module_param(timer_margin, uint, 0); +MODULE_PARM_DESC(timer_margin, "initial watchdog timeout (in seconds)"); + +static unsigned dev_num = 0; + +static void mxc_wdt_ping(u32 base) +{ + /* issue the service sequence instructions */ + __raw_writew(WDT_MAGIC_1, base + MXC_WDT_WSR); + __raw_writew(WDT_MAGIC_2, base + MXC_WDT_WSR); +} + +static void mxc_wdt_config(u32 base) +{ + u16 val; + + val = __raw_readw(base + MXC_WDT_WCR); + val |= 0xFF00 | WCR_WOE_BIT | WCR_WDA_BIT | WCR_SRS_BIT; + /* enable suspend WDT */ + val |= WCR_WDZST_BIT | WCR_WDBG_BIT; + /* generate reset if wdog times out */ + val &= ~WCR_WRE_BIT; + + __raw_writew(val, base + MXC_WDT_WCR); +} + +static void mxc_wdt_enable(u32 base) +{ + u16 val; + + val = __raw_readw(base + MXC_WDT_WCR); + val |= WCR_WDE_BIT; + __raw_writew(val, base + MXC_WDT_WCR); +} + +static void mxc_wdt_disable(u32 base) +{ + /* disable not supported by this chip */ +} + +static void mxc_wdt_adjust_timeout(unsigned new_timeout) +{ + if (new_timeout < TIMER_MARGIN_MIN) + new_timeout = TIMER_MARGIN_DEFAULT; + if (new_timeout > TIMER_MARGIN_MAX) + new_timeout = TIMER_MARGIN_MAX; + timer_margin = new_timeout; +} + +static u16 mxc_wdt_get_timeout(u32 base) +{ + u16 val; + + val = __raw_readw(base + MXC_WDT_WCR); + return WDOG_COUNT_TO_SEC(val); +} + +static u16 mxc_wdt_get_bootreason(u32 base) +{ + u16 val; + + val = __raw_readw(base + MXC_WDT_WRSR); + return val; +} + +static void mxc_wdt_set_timeout(u32 base) +{ + u16 val; + val = __raw_readw(base + MXC_WDT_WCR); + val = (val & 0x00FF) | WDOG_SEC_TO_COUNT(timer_margin); + __raw_writew(val, base + MXC_WDT_WCR); + val = __raw_readw(base + MXC_WDT_WCR); + timer_margin = WDOG_COUNT_TO_SEC(val); +} + +/* + * Allow only one task to hold it open + */ + +static int mxc_wdt_open(struct inode *inode, struct file *file) +{ + + if (test_and_set_bit(1, (unsigned long *)&mxc_wdt_users)) + return -EBUSY; + + mxc_wdt_config(wdt_base_reg); + mxc_wdt_set_timeout(wdt_base_reg); + mxc_wdt_enable(wdt_base_reg); + mxc_wdt_ping(wdt_base_reg); + + return 0; +} + +static int mxc_wdt_release(struct inode *inode, struct file *file) +{ + /* + * Shut off the timer unless NOWAYOUT is defined. + */ +#ifndef CONFIG_WATCHDOG_NOWAYOUT + mxc_wdt_disable(wdt_base_reg); + +#else + printk(KERN_CRIT "mxc_wdt: Unexpected close, not stopping!\n"); +#endif + mxc_wdt_users = 0; + return 0; +} + +static ssize_t +mxc_wdt_write(struct file *file, const char __user * data, + size_t len, loff_t * ppos) +{ + /* Refresh LOAD_TIME. */ + if (len) + mxc_wdt_ping(wdt_base_reg); + return len; +} + +static int +mxc_wdt_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + int new_margin; + int bootr; + + static struct watchdog_info ident = { + .identity = "MXC Watchdog", + .options = WDIOF_SETTIMEOUT, + .firmware_version = 0, + }; + + switch (cmd) { + default: + return -ENOIOCTLCMD; + case WDIOC_GETSUPPORT: + return copy_to_user((struct watchdog_info __user *)arg, &ident, + sizeof(ident)); + case WDIOC_GETSTATUS: + return put_user(0, (int __user *)arg); + case WDIOC_GETBOOTSTATUS: + bootr = mxc_wdt_get_bootreason(wdt_base_reg); + return put_user(bootr, (int __user *)arg); + case WDIOC_KEEPALIVE: + mxc_wdt_ping(wdt_base_reg); + return 0; + case WDIOC_SETTIMEOUT: + if (get_user(new_margin, (int __user *)arg)) + return -EFAULT; + + mxc_wdt_adjust_timeout(new_margin); + mxc_wdt_disable(wdt_base_reg); + mxc_wdt_set_timeout(wdt_base_reg); + mxc_wdt_enable(wdt_base_reg); + mxc_wdt_ping(wdt_base_reg); + return 0; + + case WDIOC_GETTIMEOUT: + mxc_wdt_ping(wdt_base_reg); + new_margin = mxc_wdt_get_timeout(wdt_base_reg); + return put_user(new_margin, (int __user *)arg); + } +} + +static struct file_operations mxc_wdt_fops = { + .owner = THIS_MODULE, + .write = mxc_wdt_write, + .ioctl = mxc_wdt_ioctl, + .open = mxc_wdt_open, + .release = mxc_wdt_release, +}; + +static struct miscdevice mxc_wdt_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &mxc_wdt_fops +}; + +static int __init mxc_wdt_probe(struct platform_device *pdev) +{ + struct resource *res, *mem; + int ret; + + /* reserve static register mappings */ + res = platform_get_resource(pdev, IORESOURCE_MEM, dev_num); + if (!res) + return -ENOENT; + + mem = request_mem_region(res->start, res->end - res->start + 1, + pdev->name); + if (mem == NULL) + return -EBUSY; + + platform_set_drvdata(pdev, mem); + + wdt_base_reg = IO_ADDRESS(res->start); + mxc_wdt_disable(wdt_base_reg); + mxc_wdt_adjust_timeout(timer_margin); + + mxc_wdt_users = 0; + + mxc_wdt_miscdev.this_device = &pdev->dev; + + mxc_wdt_clk = clk_get(NULL, "wdog_clk"); + clk_enable(mxc_wdt_clk); + + ret = misc_register(&mxc_wdt_miscdev); + if (ret) + goto fail; + + pr_info("MXC Watchdog # %d Timer: initial timeout %d sec\n", dev_num, + timer_margin); + + return 0; + + fail: + release_resource(mem); + pr_info("MXC Watchdog Probe failed\n"); + return ret; +} + +static void mxc_wdt_shutdown(struct platform_device *pdev) +{ + struct resource *res = platform_get_drvdata(pdev); + mxc_wdt_disable(res->start); + pr_info("MXC Watchdog # %d shutdown\n", dev_num); +} + +static int __exit mxc_wdt_remove(struct platform_device *pdev) +{ + struct resource *mem = platform_get_drvdata(pdev); + misc_deregister(&mxc_wdt_miscdev); + release_resource(mem); + pr_info("MXC Watchdog # %d removed\n", dev_num); + return 0; +} + +#ifdef CONFIG_PM + +/* REVISIT ... not clear this is the best way to handle system suspend; and + * it's very inappropriate for selective device suspend (e.g. suspending this + * through sysfs rather than by stopping the watchdog daemon). Also, this + * may not play well enough with NOWAYOUT... + */ + +static int mxc_wdt_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct resource *res = platform_get_drvdata(pdev); + + if (mxc_wdt_users) { + mxc_wdt_disable(res->start); + } + return 0; +} + +static int mxc_wdt_resume(struct platform_device *pdev) +{ + struct resource *res = platform_get_drvdata(pdev); + if (mxc_wdt_users) { + mxc_wdt_enable(res->start); + mxc_wdt_ping(res->start); + } + return 0; +} + +#else +#define mxc_wdt_suspend NULL +#define mxc_wdt_resume NULL +#endif + +static struct platform_driver mxc_wdt_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "mxc_wdt", + }, + .probe = mxc_wdt_probe, + .shutdown = mxc_wdt_shutdown, + .remove = __exit_p(mxc_wdt_remove), + .suspend = mxc_wdt_suspend, + .resume = mxc_wdt_resume, +}; + +static int __init mxc_wdt_init(void) +{ + pr_info("MXC WatchDog Driver %s\n", DVR_VER); + + if ((timer_margin < TIMER_MARGIN_MIN) || + (timer_margin > TIMER_MARGIN_MAX)) { + pr_info("MXC watchdog error. wrong timer_margin %d\n", + timer_margin); + pr_info(" Range: %d to %d seconds\n", TIMER_MARGIN_MIN, + TIMER_MARGIN_MAX); + return -EINVAL; + } + + return platform_driver_register(&mxc_wdt_driver); +} + +static void __exit mxc_wdt_exit(void) +{ + platform_driver_unregister(&mxc_wdt_driver); + pr_info("MXC WatchDog Driver removed\n"); +} + +module_init(mxc_wdt_init); +module_exit(mxc_wdt_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/char/watchdog/mxc_wdt.h b/drivers/char/watchdog/mxc_wdt.h new file mode 100644 index 000000000000..cd09b9acf99f --- /dev/null +++ b/drivers/char/watchdog/mxc_wdt.h @@ -0,0 +1,37 @@ +/* + * linux/drivers/char/watchdog/mxc_wdt.h + * + * BRIEF MODULE DESCRIPTION + * MXC Watchdog timer register definitions + * + * Author: MontaVista Software, Inc. + * <AKuster@mvista.com> or <source@mvista.com> + * + * 2005 (c) MontaVista Software, Inc. + * Copyright 2007 Freescale Semiconductor, Inc. All Rights Reserved. + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ + +#ifndef __MXC_WDT_H__ +#define __MXC_WDT_H__ + +#define MXC_WDT_WCR 0x00 +#define MXC_WDT_WSR 0x02 +#define MXC_WDT_WRSR 0x04 +#define WCR_WOE_BIT (1 << 6) +#define WCR_WDA_BIT (1 << 5) +#define WCR_SRS_BIT (1 << 4) +#define WCR_WRE_BIT (1 << 3) +#define WCR_WDE_BIT (1 << 2) +#define WCR_WDBG_BIT (1 << 1) +#define WCR_WDZST_BIT (1 << 0) +#define WDT_MAGIC_1 0x5555 +#define WDT_MAGIC_2 0xAAAA + +#define TIMER_MARGIN_MAX 127 +#define TIMER_MARGIN_DEFAULT 60 /* 60 secs */ +#define TIMER_MARGIN_MIN 1 + +#endif /* __MXC_WDT_H__ */ diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig index c466c6cfc2e5..c5742313704f 100644 --- a/drivers/i2c/busses/Kconfig +++ b/drivers/i2c/busses/Kconfig @@ -311,6 +311,15 @@ config I2C_MPC This driver can also be built as a module. If so, the module will be called i2c-mpc. +config I2C_MXC + tristate "MXC I2C support" + depends on I2C && ARCH_MXC + help + If you say yes to this option, support will be included for Freescale + MXC I2C modules. + + This driver can also be built as a module. + config I2C_NFORCE2 tristate "Nvidia nForce2, nForce3 and nForce4" depends on PCI diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile index 81d43c27cf93..c7dfff1cbd79 100644 --- a/drivers/i2c/busses/Makefile +++ b/drivers/i2c/busses/Makefile @@ -53,6 +53,7 @@ obj-$(CONFIG_I2C_VIAPRO) += i2c-viapro.o obj-$(CONFIG_I2C_VOODOO3) += i2c-voodoo3.o obj-$(CONFIG_SCx200_ACB) += scx200_acb.o obj-$(CONFIG_SCx200_I2C) += scx200_i2c.o +obj-$(CONFIG_I2C_MXC) += mxc_i2c.o ifeq ($(CONFIG_I2C_DEBUG_BUS),y) EXTRA_CFLAGS += -DDEBUG diff --git a/drivers/i2c/busses/mxc_i2c.c b/drivers/i2c/busses/mxc_i2c.c new file mode 100644 index 000000000000..677b252815cd --- /dev/null +++ b/drivers/i2c/busses/mxc_i2c.c @@ -0,0 +1,762 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mxc_i2c.c + * + * @brief Driver for the Freescale Semiconductor MXC I2C buses. + * + * Based on i2c driver algorithm for PCF8584 adapters + * + * @ingroup MXCI2C + */ + +/* + * Include Files + */ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/i2c.h> +#include <linux/clk.h> +#include <asm/irq.h> +#include <asm/io.h> +#include "mxc_i2c_reg.h" + +/*! + * In case the MXC device has multiple I2C modules, this structure is used to + * store information specific to each I2C module. + */ +typedef struct { + /*! + * This structure is used to identify the physical i2c bus along with + * the access algorithms necessary to access it. + */ + struct i2c_adapter adap; + + /*! + * This waitqueue is used to wait for the data transfer to complete. + */ + wait_queue_head_t wq; + + /*! + * The base address of the I2C device. + */ + unsigned long membase; + + /*! + * The interrupt number used by the I2C device. + */ + int irq; + + /*! + * The default clock divider value to be used. + */ + unsigned int clkdiv; + + /*! + * The clock source for the device. + */ + struct clk *clk; + + /*! + * The current power state of the device + */ + bool low_power; +} mxc_i2c_device; + +/*! + * Boolean to indicate if data was transferred + */ +static bool transfer_done = false; + +/*! + * Boolean to indicate if we received an ACK for the data transmitted + */ +static bool tx_success = false; + +struct clk_div_table { + int reg_value; + int div; +}; + +static const struct clk_div_table i2c_clk_table[] = { + {0x20, 22}, {0x21, 24}, {0x22, 26}, {0x23, 28}, + {0, 30}, {1, 32}, {0x24, 32}, {2, 36}, + {0x25, 36}, {0x26, 40}, {3, 42}, {0x27, 44}, + {4, 48}, {0x28, 48}, {5, 52}, {0x29, 56}, + {6, 60}, {0x2A, 64}, {7, 72}, {0x2B, 72}, + {8, 80}, {0x2C, 80}, {9, 88}, {0x2D, 96}, + {0xA, 104}, {0x2E, 112}, {0xB, 128}, {0x2F, 128}, + {0xC, 144}, {0xD, 160}, {0x30, 160}, {0xE, 192}, + {0x31, 192}, {0x32, 224}, {0xF, 240}, {0x33, 256}, + {0x10, 288}, {0x11, 320}, {0x34, 320}, {0x12, 384}, + {0x35, 384}, {0x36, 448}, {0x13, 480}, {0x37, 512}, + {0x14, 576}, {0x15, 640}, {0x38, 640}, {0x16, 768}, + {0x39, 768}, {0x3A, 896}, {0x17, 960}, {0x3B, 1024}, + {0x18, 1152}, {0x19, 1280}, {0x3C, 1280}, {0x1A, 1536}, + {0x3D, 1536}, {0x3E, 1792}, {0x1B, 1920}, {0x3F, 2048}, + {0x1C, 2304}, {0x1D, 2560}, {0x1E, 3072}, {0x1F, 3840}, + {0, 0} +}; + +extern void gpio_i2c_active(int i2c_num); +extern void gpio_i2c_inactive(int i2c_num); + +/*! + * Transmit a \b STOP signal to the slave device. + * + * @param dev the mxc i2c structure used to get to the right i2c device + */ +static void mxc_i2c_stop(mxc_i2c_device * dev) +{ + volatile unsigned int cr; + + cr = readw(dev->membase + MXC_I2CR); + cr &= ~(MXC_I2CR_MSTA | MXC_I2CR_MTX); + writew(cr, dev->membase + MXC_I2CR); +} + +/*! + * Wait for the transmission of the data byte to complete. This function waits + * till we get a signal from the interrupt service routine indicating completion + * of the address cycle or we time out. + * + * @param dev the mxc i2c structure used to get to the right i2c device + * @param trans_flag transfer flag + * + * + * @return The function returns 0 on success or -1 if an ack was not received + */ + +static int mxc_i2c_wait_for_tc(mxc_i2c_device * dev, int trans_flag) +{ + int retry = 16; + + while (retry-- && !transfer_done) { + wait_event_interruptible_timeout(dev->wq, + transfer_done, + dev->adap.timeout); + } + transfer_done = false; + + if (retry <= 0) { + /* Unable to send data */ + printk(KERN_DEBUG "Data not transmitted\n"); + return -1; + } + + if (!tx_success) { + /* An ACK was not received for transmitted byte */ + printk(KERN_DEBUG "ACK not received \n"); + return -1; + } + + return 0; +} + +/*! + * Transmit a \b START signal to the slave device. + * + * @param dev the mxc i2c structure used to get to the right i2c device + * @param *msg pointer to a message structure that contains the slave + * address + */ +static void mxc_i2c_start(mxc_i2c_device * dev, struct i2c_msg *msg) +{ + volatile unsigned int cr, sr; + unsigned int addr_trans; + int retry = 16; + + /* + * Set the slave address and the requested transfer mode + * in the data register + */ + addr_trans = msg->addr << 1; + if (msg->flags & I2C_M_RD) { + addr_trans |= 0x01; + } + + /* Set the Master bit */ + cr = readw(dev->membase + MXC_I2CR); + cr |= MXC_I2CR_MSTA; + writew(cr, dev->membase + MXC_I2CR); + + /* Wait till the Bus Busy bit is set */ + sr = readw(dev->membase + MXC_I2SR); + while (retry-- && (!(sr & MXC_I2SR_IBB))) { + udelay(3); + sr = readw(dev->membase + MXC_I2SR); + } + if (retry <= 0) { + printk(KERN_DEBUG "Could not grab Bus ownership\n"); + } + + /* Set the Transmit bit */ + cr = readw(dev->membase + MXC_I2CR); + cr |= MXC_I2CR_MTX; + writew(cr, dev->membase + MXC_I2CR); + + writew(addr_trans, dev->membase + MXC_I2DR); +} + +/*! + * Transmit a \b REPEAT START to the slave device + * + * @param dev the mxc i2c structure used to get to the right i2c device + * @param *msg pointer to a message structure that contains the slave + * address + */ +static void mxc_i2c_repstart(mxc_i2c_device * dev, struct i2c_msg *msg) +{ + volatile unsigned int cr; + unsigned int addr_trans; + + /* + * Set the slave address and the requested transfer mode + * in the data register + */ + addr_trans = msg->addr << 1; + if (msg->flags & I2C_M_RD) { + addr_trans |= 0x01; + } + cr = readw(dev->membase + MXC_I2CR); + cr |= MXC_I2CR_RSTA; + writew(cr, dev->membase + MXC_I2CR); + udelay(3); + writew(addr_trans, dev->membase + MXC_I2DR); +} + +/*! + * Read the received data. The function waits till data is available or times + * out. Generates a stop signal if this is the last message to be received. + * Sends an ack for all the bytes received except the last byte. + * + * @param dev the mxc i2c structure used to get to the right i2c device + * @param *msg pointer to a message structure that contains the slave + * address and a pointer to the receive buffer + * @param last indicates that this is the last message to be received + * @param addr_comp flag indicates that we just finished the address cycle + * + * @return The function returns the number of bytes read or -1 on time out. + */ +static int mxc_i2c_readbytes(mxc_i2c_device * dev, struct i2c_msg *msg, + int last, int addr_comp) +{ + int i; + char *buf = msg->buf; + int len = msg->len; + volatile unsigned int cr; + + cr = readw(dev->membase + MXC_I2CR); + /* + * Clear MTX to switch to receive mode. + */ + cr &= ~MXC_I2CR_MTX; + /* + * Clear the TXAK bit to gen an ack when receiving only one byte. + */ + if (len == 1) { + cr |= MXC_I2CR_TXAK; + } else { + cr &= ~MXC_I2CR_TXAK; + } + writew(cr, dev->membase + MXC_I2CR); + /* + * Dummy read only at the end of an address cycle + */ + if (addr_comp > 0) { + readw(dev->membase + MXC_I2DR); + } + + for (i = 0; i < len; i++) { + /* + * Wait for data transmission to complete + */ + if (mxc_i2c_wait_for_tc(dev, msg->flags)) { + mxc_i2c_stop(dev); + return -1; + } + /* + * Do not generate an ACK for the last byte + */ + if (i == (len - 2)) { + cr = readw(dev->membase + MXC_I2CR); + cr |= MXC_I2CR_TXAK; + writew(cr, dev->membase + MXC_I2CR); + } else if (i == (len - 1)) { + if (last) { + mxc_i2c_stop(dev); + } + } + /* Read the data */ + *buf++ = readw(dev->membase + MXC_I2DR); + } + + return i; +} + +/*! + * Write the data to the data register. Generates a stop signal if this is + * the last message to be sent or if no ack was received for the data sent. + * + * @param dev the mxc i2c structure used to get to the right i2c device + * @param *msg pointer to a message structure that contains the slave + * address and data to be sent + * @param last indicates that this is the last message to be received + * + * @return The function returns the number of bytes written or -1 on time out + * or if no ack was received for the data that was sent. + */ +static int mxc_i2c_writebytes(mxc_i2c_device * dev, struct i2c_msg *msg, + int last) +{ + int i; + char *buf = msg->buf; + int len = msg->len; + volatile unsigned int cr; + + cr = readw(dev->membase + MXC_I2CR); + /* Set MTX to switch to transmit mode */ + cr |= MXC_I2CR_MTX; + writew(cr, dev->membase + MXC_I2CR); + + for (i = 0; i < len; i++) { + /* + * Write the data + */ + writew(*buf++, dev->membase + MXC_I2DR); + if (mxc_i2c_wait_for_tc(dev, msg->flags)) { + mxc_i2c_stop(dev); + return -1; + } + } + if (last > 0) { + mxc_i2c_stop(dev); + } + + return i; +} + +/*! + * Function enables the I2C module and initializes the registers. + * + * @param dev the mxc i2c structure used to get to the right i2c device + * @param trans_flag transfer flag + */ +static void mxc_i2c_module_en(mxc_i2c_device * dev, int trans_flag) +{ + clk_enable(dev->clk); + /* Set the frequency divider */ + writew(dev->clkdiv, dev->membase + MXC_IFDR); + /* Clear the status register */ + writew(0x0, dev->membase + MXC_I2SR); + /* Enable I2C and its interrupts */ + writew(MXC_I2CR_IEN, dev->membase + MXC_I2CR); + writew(MXC_I2CR_IEN | MXC_I2CR_IIEN, dev->membase + MXC_I2CR); +} + +/*! + * Disables the I2C module. + * + * @param dev the mxc i2c structure used to get to the right i2c device + */ +static void mxc_i2c_module_dis(mxc_i2c_device * dev) +{ + writew(0x0, dev->membase + MXC_I2CR); + clk_disable(dev->clk); +} + +/*! + * The function is registered in the adapter structure. It is called when an MXC + * driver wishes to transfer data to a device connected to the I2C device. + * + * @param adap adapter structure for the MXC i2c device + * @param msgs[] array of messages to be transferred to the device + * @param num number of messages to be transferred to the device + * + * @return The function returns the number of messages transferred, + * \b -EREMOTEIO on I2C failure and a 0 if the num argument is + * less than 0. + */ +static int mxc_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], + int num) +{ + mxc_i2c_device *dev = (mxc_i2c_device *) (i2c_get_adapdata(adap)); + int i, ret = 0, addr_comp = 0; + volatile unsigned int sr; + + if (dev->low_power) { + printk(KERN_ERR "I2C Device in low power mode\n"); + return -EREMOTEIO; + } + + if (num < 1) { + return 0; + } + + mxc_i2c_module_en(dev, msgs[0].flags); + sr = readw(dev->membase + MXC_I2SR); + /* + * Check bus state + */ + if (sr & MXC_I2SR_IBB) { + mxc_i2c_module_dis(dev); + printk(KERN_DEBUG "Bus busy\n"); + return -EREMOTEIO; + } + //gpio_i2c_active(dev->adap.id); + transfer_done = false; + tx_success = false; + for (i = 0; i < num && ret >= 0; i++) { + addr_comp = 0; + /* + * Send the slave address and transfer direction in the + * address cycle + */ + if (i == 0) { + /* + * Send a start or repeat start signal + */ + mxc_i2c_start(dev, &msgs[0]); + /* Wait for the address cycle to complete */ + if (mxc_i2c_wait_for_tc(dev, msgs[0].flags)) { + mxc_i2c_stop(dev); + //gpio_i2c_inactive(dev->adap.id); + mxc_i2c_module_dis(dev); + return -EREMOTEIO; + } + addr_comp = 1; + } else { + /* + * Generate repeat start only if required i.e the address + * changed or the transfer direction changed + */ + if ((msgs[i].addr != msgs[i - 1].addr) || + ((msgs[i].flags & I2C_M_RD) != + (msgs[i - 1].flags & I2C_M_RD))) { + mxc_i2c_repstart(dev, &msgs[i]); + /* Wait for the address cycle to complete */ + if (mxc_i2c_wait_for_tc(dev, msgs[i].flags)) { + mxc_i2c_stop(dev); + //gpio_i2c_inactive(dev->adap.id); + mxc_i2c_module_dis(dev); + return -EREMOTEIO; + } + addr_comp = 1; + } + } + + /* Transfer the data */ + if (msgs[i].flags & I2C_M_RD) { + /* Read the data */ + ret = mxc_i2c_readbytes(dev, &msgs[i], (i + 1 == num), + addr_comp); + if (ret < 0) { + printk(KERN_ERR "mxc_i2c_readbytes: fail.\n"); + break; + } + } else { + /* Write the data */ + ret = mxc_i2c_writebytes(dev, &msgs[i], (i + 1 == num)); + if (ret < 0) { + printk(KERN_ERR "mxc_i2c_writebytes: fail.\n"); + break; + } + } + } + + //gpio_i2c_inactive(dev->adap.id); + mxc_i2c_module_dis(dev); + /* + * Decrease by 1 as we do not want Start message to be included in + * the count + */ + return (i - 1); +} + +/*! + * Returns the i2c functionality supported by this driver. + * + * @param adap adapter structure for this i2c device + * + * @return Returns the functionality that is supported. + */ +static u32 mxc_i2c_func(struct i2c_adapter *adap) +{ + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; +} + +/*! + * Stores the pointers for the i2c algorithm functions. The algorithm functions + * is used by the i2c bus driver to talk to the i2c bus + */ +static struct i2c_algorithm mxc_i2c_algorithm = { + .master_xfer = mxc_i2c_xfer, + .functionality = mxc_i2c_func +}; + +/*! + * Interrupt Service Routine. It signals to the process about the data transfer + * completion. Also sets a flag if bus arbitration is lost. + * @param irq the interrupt number + * @param dev_id driver private data + * + * @return The function returns \b IRQ_HANDLED. + */ +static irqreturn_t mxc_i2c_handler(int irq, void *dev_id) +{ + mxc_i2c_device *dev = dev_id; + volatile unsigned int sr, cr; + + sr = readw(dev->membase + MXC_I2SR); + cr = readw(dev->membase + MXC_I2CR); + + /* + * Clear the interrupt bit + */ + writew(0x0, dev->membase + MXC_I2SR); + + if (sr & MXC_I2SR_IAL) { + printk(KERN_DEBUG "Bus Arbitration lost\n"); + } else { + /* Interrupt due byte transfer completion */ + tx_success = true; + /* Check if RXAK is received in Transmit mode */ + if ((cr & MXC_I2CR_MTX) && (sr & MXC_I2SR_RXAK)) { + tx_success = false; + } + transfer_done = true; + wake_up_interruptible(&dev->wq); + } + + return IRQ_HANDLED; +} + +/*! + * This function is called to put the I2C adapter in a low power state. Refer to the + * document driver-model/driver.txt in the kernel source tree for more + * information. + * + * @param pdev the device structure used to give information on which I2C + * to suspend + * @param state the power state the device is entering + * + * @return The function returns 0 on success and -1 on failure. + */ +static int mxci2c_suspend(struct platform_device *pdev, pm_message_t state) +{ + mxc_i2c_device *mxcdev = platform_get_drvdata(pdev); + volatile unsigned int sr = 0; + + if (mxcdev == NULL) { + return -1; + } + + /* Prevent further calls to be processed */ + mxcdev->low_power = true; + /* Wait till we finish the current transfer */ + sr = readw(mxcdev->membase + MXC_I2SR); + while (sr & MXC_I2SR_IBB) { + msleep(10); + sr = readw(mxcdev->membase + MXC_I2SR); + } + gpio_i2c_inactive(mxcdev->adap.id); + + return 0; +} + +/*! + * This function is called to bring the I2C adapter back from a low power state. Refer + * to the document driver-model/driver.txt in the kernel source tree for more + * information. + * + * @param pdev the device structure used to give information on which I2C + * to resume + * + * @return The function returns 0 on success and -1 on failure + */ +static int mxci2c_resume(struct platform_device *pdev) +{ + mxc_i2c_device *mxcdev = platform_get_drvdata(pdev); + + if (mxcdev == NULL) + return -1; + + mxcdev->low_power = false; + gpio_i2c_active(mxcdev->adap.id); + + return 0; +} + +/*! + * This function is called during the driver binding process. + * + * @param pdev the device structure used to store device specific + * information that is used by the suspend, resume and remove + * functions + * + * @return The function always returns 0. + */ +static int mxci2c_probe(struct platform_device *pdev) +{ + mxc_i2c_device *mxc_i2c; + struct mxc_i2c_platform_data *i2c_plat_data = pdev->dev.platform_data; + struct resource *res; + int id = pdev->id; + u32 clk_freq; + int ret = 0; + int i; + + mxc_i2c = kzalloc(sizeof(mxc_i2c_device), GFP_KERNEL); + if (!mxc_i2c) { + return -ENOMEM; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) { + ret = -ENODEV; + goto err1; + } + mxc_i2c->membase = IO_ADDRESS(res->start); + + /* + * Request the I2C interrupt + */ + mxc_i2c->irq = platform_get_irq(pdev, 0); + if (mxc_i2c->irq < 0) { + ret = mxc_i2c->irq; + goto err1; + } + + ret = request_irq(mxc_i2c->irq, mxc_i2c_handler, + 0, pdev->name, mxc_i2c); + if (ret < 0) { + goto err1; + } + + init_waitqueue_head(&mxc_i2c->wq); + + mxc_i2c->low_power = false; + + gpio_i2c_active(id); + + mxc_i2c->clk = clk_get(&pdev->dev, "i2c_clk"); + clk_freq = clk_get_rate(mxc_i2c->clk); + mxc_i2c->clkdiv = -1; + if (i2c_plat_data->i2c_clk) { + /* Calculate divider and round up any fractional part */ + int div = (clk_freq + i2c_plat_data->i2c_clk - 1) / + i2c_plat_data->i2c_clk; + for (i = 0; i2c_clk_table[i].div != 0; i++) { + if (i2c_clk_table[i].div >= div) { + mxc_i2c->clkdiv = i2c_clk_table[i].reg_value; + break; + } + } + } + if (mxc_i2c->clkdiv == -1) { + i--; + mxc_i2c->clkdiv = 0x1F; /* Use max divider */ + } + dev_dbg(&pdev->dev, "i2c speed is %d/%d = %d bps, reg val = 0x%02X\n", + clk_freq, i2c_clk_table[i].div, + clk_freq / i2c_clk_table[i].div, mxc_i2c->clkdiv); + + /* + * Set the adapter information + */ + strcpy(mxc_i2c->adap.name, pdev->name); + mxc_i2c->adap.id = id; + mxc_i2c->adap.algo = &mxc_i2c_algorithm; + mxc_i2c->adap.timeout = 1; + platform_set_drvdata(pdev, mxc_i2c); + i2c_set_adapdata(&mxc_i2c->adap, mxc_i2c); + if ((ret = i2c_add_adapter(&mxc_i2c->adap)) < 0) { + goto err2; + } + + printk(KERN_INFO "MXC I2C driver\n"); + return 0; + + err2: + free_irq(mxc_i2c->irq, mxc_i2c); + gpio_i2c_inactive(id); + err1: + dev_err(&pdev->dev, "failed to probe i2c adapter\n"); + kfree(mxc_i2c); + return ret; +} + +/*! + * Dissociates the driver from the I2C device. + * + * @param pdev the device structure used to give information on which I2C + * to remove + * + * @return The function always returns 0. + */ +static int mxci2c_remove(struct platform_device *pdev) +{ + mxc_i2c_device *mxc_i2c = platform_get_drvdata(pdev); + int id = pdev->id; + + free_irq(mxc_i2c->irq, mxc_i2c); + i2c_del_adapter(&mxc_i2c->adap); + gpio_i2c_inactive(id); + clk_put(mxc_i2c->clk); + platform_set_drvdata(pdev, NULL); + return 0; +} + +/*! + * This structure contains pointers to the power management callback functions. + */ +static struct platform_driver mxci2c_driver = { + .driver = { + .name = "mxc_i2c", + .owner = THIS_MODULE, + }, + .probe = mxci2c_probe, + .remove = mxci2c_remove, + .suspend = mxci2c_suspend, + .resume = mxci2c_resume, +}; + +/*! + * Function requests the interrupts and registers the i2c adapter structures. + * + * @return The function returns 0 on success and a non-zero value on failure. + */ +static int __init mxc_i2c_init(void) +{ + /* Register the device driver structure. */ + return platform_driver_register(&mxci2c_driver); +} + +/*! + * This function is used to cleanup all resources before the driver exits. + */ +static void __exit mxc_i2c_exit(void) +{ + platform_driver_unregister(&mxci2c_driver); +} + +subsys_initcall(mxc_i2c_init); +module_exit(mxc_i2c_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MXC I2C driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/i2c/busses/mxc_i2c_reg.h b/drivers/i2c/busses/mxc_i2c_reg.h new file mode 100644 index 000000000000..4fc76c8aa811 --- /dev/null +++ b/drivers/i2c/busses/mxc_i2c_reg.h @@ -0,0 +1,40 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef __MXC_I2C_REG_H__ +#define __MXC_I2C_REG_H__ + +/* Address offsets of the I2C registers */ +#define MXC_IADR 0x00 /* Address Register */ +#define MXC_IFDR 0x04 /* Freq div register */ +#define MXC_I2CR 0x08 /* Control regsiter */ +#define MXC_I2SR 0x0C /* Status register */ +#define MXC_I2DR 0x10 /* Data I/O register */ + +/* Bit definitions of I2CR */ +#define MXC_I2CR_IEN 0x0080 +#define MXC_I2CR_IIEN 0x0040 +#define MXC_I2CR_MSTA 0x0020 +#define MXC_I2CR_MTX 0x0010 +#define MXC_I2CR_TXAK 0x0008 +#define MXC_I2CR_RSTA 0x0004 + +/* Bit definitions of I2SR */ +#define MXC_I2SR_ICF 0x0080 +#define MXC_I2SR_IAAS 0x0040 +#define MXC_I2SR_IBB 0x0020 +#define MXC_I2SR_IAL 0x0010 +#define MXC_I2SR_SRW 0x0004 +#define MXC_I2SR_IIF 0x0002 +#define MXC_I2SR_RXAK 0x0001 + +#endif /* __MXC_I2C_REG_H__ */ diff --git a/drivers/ide/Kconfig b/drivers/ide/Kconfig index fb06555708a8..94e1958b952d 100644 --- a/drivers/ide/Kconfig +++ b/drivers/ide/Kconfig @@ -887,6 +887,13 @@ config BLK_DEV_IDE_BAST Say Y here if you want to support the onboard IDE channels on the Simtec BAST or the Thorcom VR1000 +config BLK_DEV_IDE_MXC + tristate "Freescale MXC IDE support" + depends on ARM && ( ARCH_MX3 || ARCH_MX27 || ARCH_MX33) + help + Say Y here if you want to support the IDE controller on the + Freescale iMX3 processor. + config ETRAX_IDE bool "ETRAX IDE support" depends on CRIS && BROKEN @@ -1111,7 +1118,7 @@ config BLK_DEV_UMC8672 endif config BLK_DEV_IDEDMA - def_bool BLK_DEV_IDEDMA_PCI || BLK_DEV_IDEDMA_PMAC || BLK_DEV_IDEDMA_ICS || BLK_DEV_IDE_AU1XXX_MDMA2_DBDMA + def_bool BLK_DEV_IDEDMA_PCI || BLK_DEV_IDEDMA_PMAC || BLK_DEV_IDEDMA_ICS || BLK_DEV_IDE_AU1XXX_MDMA2_DBDMA || BLK_DEV_IDE_MXC config IDE_ARCH_OBSOLETE_INIT def_bool ALPHA || (ARM && !ARCH_L7200) || BLACKFIN || X86 || IA64 || M32R || MIPS || PARISC || PPC || (SUPERH64 && BLK_DEV_IDEPCI) || SPARC diff --git a/drivers/ide/arm/Makefile b/drivers/ide/arm/Makefile index 6a78f0755f26..0b116dac847d 100644 --- a/drivers/ide/arm/Makefile +++ b/drivers/ide/arm/Makefile @@ -2,5 +2,6 @@ obj-$(CONFIG_BLK_DEV_IDE_ICSIDE) += icside.o obj-$(CONFIG_BLK_DEV_IDE_RAPIDE) += rapide.o obj-$(CONFIG_BLK_DEV_IDE_BAST) += bast-ide.o +obj-$(CONFIG_BLK_DEV_IDE_MXC) += mxc_ide.o EXTRA_CFLAGS := -Idrivers/ide diff --git a/drivers/ide/arm/mxc_ide.c b/drivers/ide/arm/mxc_ide.c new file mode 100644 index 000000000000..142702245a5f --- /dev/null +++ b/drivers/ide/arm/mxc_ide.c @@ -0,0 +1,1122 @@ +/* + * linux/drivers/ide/arm/mxc_ide.c + * + * Based on Simtec BAST IDE driver + * Copyright (c) 2003-2004 Simtec Electronics + * Ben Dooks <ben@simtec.co.uk> + * + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/*! + * @file mxc_ide.c + * + * @brief ATA driver + * + * @ingroup ATA + */ + +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/ide.h> +#include <linux/init.h> +#include <linux/ide.h> +#include <linux/clk.h> + +#include <asm/mach-types.h> + +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/delay.h> +#include <asm/hardware.h> +#include <asm/arch/dma.h> +#include "mxc_ide.h" + +extern void gpio_ata_active(void); +extern void gpio_ata_inactive(void); +static int mxc_ide_config_drive(ide_drive_t * drive, u8 xfer_mode); +static void mxc_ide_dma_callback(void *arg, int error, unsigned int count); + +static struct clk *ata_clk; + +/* List of registered interfaces */ +static ide_hwif_t *ifs[1]; + +/* + * This structure contains the timing parameters for + * ATA bus timing in the 5 PIO modes. The timings + * are in nanoseconds, and are converted to clock + * cycles before being stored in the ATA controller + * timing registers. + */ +static struct { + short t0, t1, t2_8, t2_16, t2i, t4, t9, tA; +} pio_specs[] = { + [0] = { + .t0 = 600,.t1 = 70,.t2_8 = 290,.t2_16 = 165,.t2i = 0,.t4 = + 30,.t9 = 20,.tA = 50},[1] = { + .t0 = 383,.t1 = 50,.t2_8 = 290,.t2_16 = 125,.t2i = 0,.t4 = + 20,.t9 = 15,.tA = 50},[2] = { + .t0 = 240,.t1 = 30,.t2_8 = 290,.t2_16 = 100,.t2i = 0,.t4 = + 15,.t9 = 10,.tA = 50},[3] = { + .t0 = 180,.t1 = 30,.t2_8 = 80,.t2_16 = 80,.t2i = 0,.t4 = + 10,.t9 = 10,.tA = 50},[4] = { + .t0 = 120,.t1 = 25,.t2_8 = 70,.t2_16 = 70,.t2i = 0,.t4 = + 10,.t9 = 10,.tA = 50} +}; + +#define NR_PIO_SPECS (sizeof pio_specs / sizeof pio_specs[0]) + +/* + * This structure contains the timing parameters for + * ATA bus timing in the 3 MDMA modes. The timings + * are in nanoseconds, and are converted to clock + * cycles before being stored in the ATA controller + * timing registers. + */ +static struct { + short t0M, tD, tH, tJ, tKW, tM, tN, tJNH; +} mdma_specs[] = { + [0] = { + .t0M = 480,.tD = 215,.tH = 20,.tJ = 20,.tKW = 215,.tM = 50,.tN = + 15,.tJNH = 20},[1] = { + .t0M = 150,.tD = 80,.tH = 15,.tJ = 5,.tKW = 50,.tM = 30,.tN = + 10,.tJNH = 15},[2] = { + .t0M = 120,.tD = 70,.tH = 10,.tJ = 5,.tKW = 25,.tM = 25,.tN = + 10,.tJNH = 10} +}; + +#define NR_MDMA_SPECS (sizeof mdma_specs / sizeof mdma_specs[0]) + +/* + * This structure contains the timing parameters for + * ATA bus timing in the 6 UDMA modes. The timings + * are in nanoseconds, and are converted to clock + * cycles before being stored in the ATA controller + * timing registers. + */ +static struct { + short t2CYC, tCYC, tDS, tDH, tDVS, tDVH, tCVS, tCVH, tFS_min, tLI_max, + tMLI, tAZ, tZAH, tENV_min, tSR, tRFS, tRP, tACK, tSS, tDZFS; +} udma_specs[] = { + [0] = { + .t2CYC = 235,.tCYC = 114,.tDS = 15,.tDH = 5,.tDVS = 70,.tDVH = + 6,.tCVS = 70,.tCVH = 6,.tFS_min = 0,.tLI_max = + 100,.tMLI = 20,.tAZ = 10,.tZAH = 20,.tENV_min = + 20,.tSR = 50,.tRFS = 75,.tRP = 160,.tACK = 20,.tSS = + 50,.tDZFS = 80},[1] = { + .t2CYC = 156,.tCYC = 75,.tDS = 10,.tDH = 5,.tDVS = 48,.tDVH = + 6,.tCVS = 48,.tCVH = 6,.tFS_min = 0,.tLI_max = + 100,.tMLI = 20,.tAZ = 10,.tZAH = 20,.tENV_min = + 20,.tSR = 30,.tRFS = 70,.tRP = 125,.tACK = 20,.tSS = + 50,.tDZFS = 63},[2] = { + .t2CYC = 117,.tCYC = 55,.tDS = 7,.tDH = 5,.tDVS = 34,.tDVH = + 6,.tCVS = 34,.tCVH = 6,.tFS_min = 0,.tLI_max = + 100,.tMLI = 20,.tAZ = 10,.tZAH = 20,.tENV_min = + 20,.tSR = 20,.tRFS = 60,.tRP = 100,.tACK = 20,.tSS = + 50,.tDZFS = 47},[3] = { + .t2CYC = 86,.tCYC = 39,.tDS = 7,.tDH = 5,.tDVS = 20,.tDVH = + 6,.tCVS = 20,.tCVH = 6,.tFS_min = 0,.tLI_max = + 100,.tMLI = 20,.tAZ = 10,.tZAH = 20,.tENV_min = + 20,.tSR = 20,.tRFS = 60,.tRP = 100,.tACK = 20,.tSS = + 50,.tDZFS = 35},[4] = { + .t2CYC = 57,.tCYC = 25,.tDS = 5,.tDH = 5,.tDVS = 7,.tDVH = + 6,.tCVS = 7,.tCVH = 6,.tFS_min = 0,.tLI_max = + 100,.tMLI = 20,.tAZ = 10,.tZAH = 20,.tENV_min = + 20,.tSR = 50,.tRFS = 60,.tRP = 100,.tACK = 20,.tSS = + 50,.tDZFS = 25},[5] = { + .t2CYC = 38,.tCYC = 17,.tDS = 4,.tDH = 5,.tDVS = 5,.tDVH = + 6,.tCVS = 10,.tCVH = 10,.tFS_min = 0,.tLI_max = + 75,.tMLI = 20,.tAZ = 10,.tZAH = 20,.tENV_min = + 20,.tSR = 20,.tRFS = 50,.tRP = 85,.tACK = 20,.tSS = + 50,.tDZFS = 40} +}; + +#define NR_UDMA_SPECS (sizeof udma_specs / sizeof udma_specs[0]) + +/*! + * Calculate values for the ATA bus timing registers and store + * them into the hardware. + * + * @param mode Selects PIO, MDMA or UDMA modes + * + * @param speed Specifies the sub-mode number + * + * @return EINVAL speed out of range, or illegal mode + */ +static int set_ata_bus_timing(int speed, enum ata_mode mode) +{ + /* get the bus clock cycle time, in ns */ + int T = 1 * 1000 * 1000 * 1000 / clk_get_rate(ata_clk); + mxc_ide_time_cfg_t cfg0, cfg1, cfg2, cfg3, cfg4, cfg5; + /* every mode gets the same t_off and t_on */ + + GET_TIME_CFG(&cfg0, MXC_IDE_TIME_OFF); + cfg0.bytes.field1 = 3; + cfg0.bytes.field2 = 3; + SET_TIME_CFG(&cfg0, 3, MXC_IDE_TIME_OFF); + + switch (mode) { + case PIO: + if (speed < 0 || speed >= NR_PIO_SPECS) { + return -EINVAL; + } + cfg0.bytes.field3 = (pio_specs[speed].t1 + T) / T; + cfg0.bytes.field4 = (pio_specs[speed].t2_8 + T) / T; + + cfg1.bytes.field1 = (pio_specs[speed].t2_8 + T) / T; + cfg1.bytes.field2 = (pio_specs[speed].tA + T) / T + 2; + cfg1.bytes.field3 = 1; + cfg1.bytes.field4 = (pio_specs[speed].t4 + T) / T; + + GET_TIME_CFG(&cfg2, MXC_IDE_TIME_9); + cfg2.bytes.field1 = (pio_specs[speed].t9 + T) / T; + + SET_TIME_CFG(&cfg0, 0x0C, MXC_IDE_TIME_OFF); + SET_TIME_CFG(&cfg1, 0x0F, MXC_IDE_TIME_2r); + SET_TIME_CFG(&cfg2, 0x01, MXC_IDE_TIME_9); + break; + case MDMA: + if (speed < 0 || speed >= NR_MDMA_SPECS) { + return -EINVAL; + } + GET_TIME_CFG(&cfg2, MXC_IDE_TIME_9); + GET_TIME_CFG(&cfg3, MXC_IDE_TIME_K); + + cfg2.bytes.field2 = (mdma_specs[speed].tM + T) / T; + cfg2.bytes.field3 = (mdma_specs[speed].tJNH + T) / T; + cfg2.bytes.field4 = (mdma_specs[speed].tD + T) / T; + + cfg3.bytes.field1 = (mdma_specs[speed].tKW + T) / T; + + SET_TIME_CFG(&cfg2, 0x0E, MXC_IDE_TIME_9); + SET_TIME_CFG(&cfg3, 0x01, MXC_IDE_TIME_K); + break; + case UDMA: + if (speed < 0 || speed >= NR_UDMA_SPECS) { + return -EINVAL; + } + + GET_TIME_CFG(&cfg3, MXC_IDE_TIME_K); + + cfg3.bytes.field2 = (udma_specs[speed].tACK + T) / T; + cfg3.bytes.field3 = (udma_specs[speed].tENV_min + T) / T; + cfg3.bytes.field4 = (udma_specs[speed].tRP + T) / T + 2; + + cfg4.bytes.field1 = (udma_specs[speed].tZAH + T) / T; + cfg4.bytes.field2 = (udma_specs[speed].tMLI + T) / T; + cfg4.bytes.field3 = (udma_specs[speed].tDVH + T) / T + 1; + cfg4.bytes.field4 = (udma_specs[speed].tDZFS + T) / T; + + cfg5.bytes.field1 = (udma_specs[speed].tDVS + T) / T; + cfg5.bytes.field2 = (udma_specs[speed].tCVH + T) / T; + cfg5.bytes.field3 = (udma_specs[speed].tSS + T) / T; + cfg5.bytes.field4 = (udma_specs[speed].tCYC + T) / T; + + SET_TIME_CFG(&cfg3, 0x0E, MXC_IDE_TIME_K); + SET_TIME_CFG(&cfg4, 0x0F, MXC_IDE_TIME_ZAH); + SET_TIME_CFG(&cfg5, 0x0F, MXC_IDE_TIME_DVS); + break; + default: + return -EINVAL; + } + return 0; +} + +/*! + * Placeholder for code to make any hardware tweaks + * necessary to select a drive. Currently we are + * not aware of any. + */ +static void mxc_ide_selectproc(ide_drive_t * drive) +{ +} + +/*! + * Called to set the PIO mode. + * + * @param drive Specifies the drive + * @param pio Specifies the PIO mode number desired + */ +static void mxc_ide_tune(ide_drive_t * drive, u8 pio) +{ + set_ata_bus_timing(pio, PIO); +} + +/*! + * Hardware-specific interrupt service routine for the ATA driver, + * called mainly just to dismiss the interrupt at the hardware, and + * to indicate whether there actually was an interrupt pending. + * + * The generic IDE related interrupt handling is done by the IDE layer. + */ +static int mxc_ide_ack_intr(struct hwif_s *hw) +{ + unsigned char status = ATA_RAW_READ(MXC_IDE_INTR_PENDING); + unsigned char enable = ATA_RAW_READ(MXC_IDE_INTR_ENABLE); + + /* + * The only interrupts we can actually dismiss are the FIFO conditions. + * INTRQ comes from the bus, and must be dismissed according to IDE + * protocol, which will be done by the IDE layer, even when DMA + * is invovled (DMA can see it, but can't dismiss it). + */ + ATA_RAW_WRITE(status, MXC_IDE_INTR_CLEAR); + + if (status & enable & ~MXC_IDE_INTR_ATA_INTRQ2) { + printk(KERN_ERR "mxc_ide_ack_intr: unexpected interrupt, " + "status=0x%02X\n", status); + } + + return status ? 1 : 0; +} + +/*! + * Decodes the specified transfer mode and sets both timing and ultra modes + * + * @param drive Specifies the drive + * + * @param xfer_mode Specifies the desired transfer mode + * + * @return EINVAL Illegal mode specified + */ +static int mxc_ide_set_speed(ide_drive_t * drive, u8 xfer_mode) +{ + mxc_ide_private_t *priv = (mxc_ide_private_t *) HWIF(drive)->hwif_data; + + switch (xfer_mode) { + case XFER_UDMA_7: + case XFER_UDMA_6: + case XFER_UDMA_5: + case XFER_UDMA_4: + case XFER_UDMA_3: + case XFER_UDMA_2: + case XFER_UDMA_1: + case XFER_UDMA_0: + priv->ultra = 1; + return set_ata_bus_timing(xfer_mode - XFER_UDMA_0, UDMA); + break; + case XFER_MW_DMA_2: + case XFER_MW_DMA_1: + case XFER_MW_DMA_0: + priv->ultra = 0; + return set_ata_bus_timing(xfer_mode - XFER_MW_DMA_0, MDMA); + break; + case XFER_PIO_4: + case XFER_PIO_3: + case XFER_PIO_2: + case XFER_PIO_1: + case XFER_PIO_0: + return set_ata_bus_timing(xfer_mode - XFER_PIO_0, PIO); + break; + } + + return -EINVAL; +} + +/*! + * Called when the IDE layer is disabling DMA on a drive. + * + * @param drive Specifies the drive + */ +static void mxc_ide_dma_off_quietly(ide_drive_t * drive) +{ + drive->using_dma = 0; +} + +/*! + * Called by the IDE layer when something goes wrong + * + * @param drive Specifies the drive + * + */ +static void mxc_ide_resetproc(ide_drive_t * drive) +{ + printk(KERN_INFO "%s: resetting ATA controller\n", __func__); + + ATA_RAW_WRITE(0x00, MXC_IDE_ATA_CONTROL); + udelay(100); + ATA_RAW_WRITE(MXC_IDE_CTRL_ATA_RST_B, MXC_IDE_ATA_CONTROL); + udelay(100); +} + +/*! + * Turn on DMA for a drive. + * + * @param drive Specifies the drive + * + * @return 0 if successful + */ +static int mxc_ide_dma_on(ide_drive_t * drive) +{ + /* consult the list of known "bad" drives */ + if (__ide_dma_bad_drive(drive)) + return 1; + + /* + * Go to UDMA3 mode. Beyond that you'll no doubt + * need an Ultra-100 cable (the 80 pin type with + * ground leads between all the signals) + */ + printk(KERN_INFO "%s: enabling UDMA3 mode\n", drive->name); + mxc_ide_config_drive(drive, XFER_UDMA_3); + drive->using_dma = 1; + + blk_queue_max_hw_segments(drive->queue, MXC_IDE_DMA_BD_NR); + blk_queue_max_hw_segments(drive->queue, MXC_IDE_DMA_BD_NR); + blk_queue_max_segment_size(drive->queue, MXC_IDE_DMA_BD_SIZE_MAX); + + HWIF(drive)->dma_host_on(drive); + return 0; +} + +/*! + * Turn on DMA if the drive can handle it. + * + * @param drive Specifies the drive + * + * @return 0 if successful + */ +static int mxc_ide_dma_check(ide_drive_t * drive) +{ + struct hd_driveid *id = drive->id; + if (id && (id->capability & 1)) { + /* + * Enable DMA on any drive that has + * UltraDMA (mode 0/1/2/3/4/5/6) enabled + */ + if ((id->field_valid & 4) && ((id->dma_ultra >> 8) & 0x7f)) + return HWIF(drive)->ide_dma_on(drive); + /* + * Enable DMA on any drive that has mode2 DMA + * (multi or single) enabled + */ + if (id->field_valid & 2) /* regular DMA */ + if ((id->dma_mword & 0x404) == 0x404 || + (id->dma_1word & 0x404) == 0x404) + return HWIF(drive)->ide_dma_on(drive); + + /* Consult the list of known "good" drives */ + if (__ide_dma_good_drive(drive)) + return HWIF(drive)->ide_dma_on(drive); + } + HWIF(drive)->dma_off_quietly(drive); + return 0; +} + +/*! + * The DMA is done, and the drive is done. We'll check the BD array for + * errors, and unmap the scatter-gather list. + * + * @param drive The drive we're servicing + * + * @return 0 means all is well, others means DMA signalled an error , + */ +static int mxc_ide_dma_end(ide_drive_t * drive) +{ + ide_hwif_t *hwif = HWIF(drive); + mxc_ide_private_t *priv = (mxc_ide_private_t *) hwif->hwif_data; + int dma_stat = priv->dma_stat; + + BUG_ON(drive->waiting_for_dma == 0); + drive->waiting_for_dma = 0; + + /* + * We'll unmap the sg table regardless of status. + */ + dma_unmap_sg(priv->dev, hwif->sg_table, hwif->sg_nents, + hwif->sg_dma_direction); + + return dma_stat; +} + +/*! + * The end-of-DMA interrupt handler + * + * @param drive Specifies the drive + * + * @return ide_stopped or ide_started + */ +static ide_startstop_t mxc_ide_dma_intr(ide_drive_t * drive) +{ + u8 stat, dma_stat; + struct request *rq = HWGROUP(drive)->rq; + ide_hwif_t *hwif = HWIF(drive); + u8 fifo_fill; + + if (!rq) + return ide_stopped; + + fifo_fill = ATA_RAW_READ(MXC_IDE_FIFO_FILL); + BUG_ON(fifo_fill); + + dma_stat = hwif->ide_dma_end(drive); + stat = hwif->INB(IDE_STATUS_REG); /* get drive status */ + if (OK_STAT(stat, DRIVE_READY, drive->bad_wstat | DRQ_STAT)) { + if (dma_stat == MXC_DMA_DONE) { + ide_end_request(drive, 1, rq->nr_sectors); + return ide_stopped; + } + printk(KERN_ERR "%s: mxc_ide_dma_intr: bad DMA status (0x%x)\n", + drive->name, dma_stat); + } + + return ide_error(drive, "mxc_ide_dma_intr", stat); +} + +/*! + * Directs the IDE INTRQ signal to DMA or to the CPU + * + * @param hwif Specifies the IDE controller + * + * @param which \b INTRQ_DMA or \b INTRQ_MCU + * + */ +static void mxc_ide_set_intrq(ide_hwif_t * hwif, intrq_which_t which) +{ + mxc_ide_private_t *priv = (mxc_ide_private_t *) hwif->hwif_data; + unsigned char enable = ATA_RAW_READ(MXC_IDE_INTR_ENABLE); + + switch (which) { + case INTRQ_DMA: + enable &= ~MXC_IDE_INTR_ATA_INTRQ2; + enable |= MXC_IDE_INTR_ATA_INTRQ1; + break; + case INTRQ_MCU: + enable &= ~MXC_IDE_INTR_ATA_INTRQ1; + enable |= MXC_IDE_INTR_ATA_INTRQ2; + break; + } + priv->enable = enable; + + ATA_RAW_WRITE(enable, MXC_IDE_INTR_ENABLE); +} + +/*! + * Helper routine to configure drive speed + * + * @param drive Specifies the drive + * + * @param xfer_mode Specifies the desired transfer mode + * + * @return 0 = success, non-zero otherwise + */ +static int mxc_ide_config_drive(ide_drive_t * drive, u8 xfer_mode) +{ + int err; + ide_hwif_t *hwif = HWIF(drive); + mxc_ide_private_t *priv = (mxc_ide_private_t *) (hwif->hwif_data); + u8 prev = priv->enable; + + mxc_ide_set_speed(drive, xfer_mode); + + /* + * Work around an ADS hardware bug: + * + * OK, ide_config_drive_speed() is the right thing to call but it's + * going to leave an interrupt pending. We have to jump through hoops + * because the ADS board doesn't correctly terminate INTRQ. Instead + * of pulling it low, it pulls it high (asserted), so when the drive + * tri-states it, the MX31 sees it as an interrupt. So we'll disable + * the hardware interrupt here, and restore it a bit later, after we've + * selected the drive again and let it drive INTRQ low for a bit. + * + * On later ADS boards, when presumably the correct INTRQ termination + * is in place, these extra hoops won't be necessary, but they + * shouldn't hurt, either. + */ + priv->enable = 0; + ATA_RAW_WRITE(0, MXC_IDE_INTR_ENABLE); + + err = ide_config_drive_speed(drive, xfer_mode); + + if (ATA_RAW_READ(MXC_IDE_INTR_PENDING) & + (MXC_IDE_INTR_ATA_INTRQ2 | MXC_IDE_INTR_ATA_INTRQ1)) { + /* + * OK, the 'bad thing' is happening, so we'll clear nIEN to + * get the drive to drive INTRQ, then we'll read status to + * clear any pending interrupt. This sequence gets us by + * the spurious and stuck interrupt we see otherwise. + */ + SELECT_DRIVE(drive); + udelay(2); + hwif->OUTB(0, IDE_CONTROL_REG); + udelay(2); + hwif->INB(IDE_STATUS_REG); + udelay(2); + } + + priv->enable = prev; + ATA_RAW_WRITE(prev, MXC_IDE_INTR_ENABLE); + + return err; +} + +/*! + * Masks drive interrupts temporarily at the hardware level + * + * @param drive Specifies the drive + * + * @param mask 1 = disable interrupts, 0 = re-enable + * + */ +static void mxc_ide_maskproc(ide_drive_t * drive, int mask) +{ + mxc_ide_private_t *priv = + (mxc_ide_private_t *) (HWIF(drive)->hwif_data); + BUG_ON(!priv); + + if (mask) { + ATA_RAW_WRITE(0, MXC_IDE_INTR_ENABLE); + } else { + ATA_RAW_WRITE(priv->enable, MXC_IDE_INTR_ENABLE); + } +} + +/*! + * DMA completion callback routine. This gets called after the DMA request + * has completed or aborted.All we need to do here is return ownership of + * the drive's INTRQ signal back to the CPU, which will immediately raise + * the interrupt to the IDE layer. + * + * @param arg The drive we're servicing + * @param error The error number of DMA transfer. + * @param count The transfered length. + */ +static void mxc_ide_dma_callback(void *arg, int error, unsigned int count) +{ + ide_hwif_t *hwif = HWIF((ide_drive_t *) arg); + mxc_ide_private_t *priv = (mxc_ide_private_t *) (hwif->hwif_data); + unsigned long fifo_fill; + + /* + * clean the fifo if the fill register is non-zero. + * If the fill register is non-zero, it is incorrect state. + */ + fifo_fill = ATA_RAW_READ(MXC_IDE_FIFO_FILL); + while (fifo_fill) { + ATA_RAW_READ(MXC_IDE_FIFO_DATA_32); + fifo_fill = ATA_RAW_READ(MXC_IDE_FIFO_FILL); + } + + priv->dma_stat = error; + /* + * Redirect ata_intrq back to us instead of the DMA. + */ + mxc_ide_set_intrq(hwif, INTRQ_MCU); +} + +/*! + * DMA set up. This is called once per DMA request to the drive. It delivers + * the scatter-gather list into the DMA channel and prepares both the ATA + * controller and the DMA engine for the DMA + * transfer. + * + * @param drive The drive we're servicing + * + * @return 0 on success, non-zero otherwise + */ +static int mxc_ide_dma_setup(ide_drive_t * drive) +{ + struct request *rq = HWGROUP(drive)->rq; + ide_hwif_t *hwif = HWIF(drive); + struct scatterlist *sg = hwif->sg_table; + mxc_ide_private_t *priv = (mxc_ide_private_t *) hwif->hwif_data; + int dma_ultra = priv->ultra ? MXC_IDE_CTRL_DMA_ULTRA : 0; + int dma_mode = 0; + int chan; + u8 ata_control; + u8 fifo_fill; + + BUG_ON(!rq); + BUG_ON(!priv); + BUG_ON(drive->waiting_for_dma); + + /*Initialize the dma state */ + priv->dma_stat = MXC_DMA_TRANSFER_ERROR; + + /* + * Prepare the ATA controller for the DMA + */ + if (rq_data_dir(rq)) { + chan = priv->dma_write_chan; + ata_control = MXC_IDE_CTRL_FIFO_RST_B | + MXC_IDE_CTRL_ATA_RST_B | + MXC_IDE_CTRL_FIFO_TX_EN | + MXC_IDE_CTRL_DMA_PENDING | + dma_ultra | MXC_IDE_CTRL_DMA_WRITE; + + dma_mode = DMA_MODE_WRITE; + } else { + chan = priv->dma_read_chan; + ata_control = MXC_IDE_CTRL_FIFO_RST_B | + MXC_IDE_CTRL_ATA_RST_B | + MXC_IDE_CTRL_FIFO_RCV_EN | + MXC_IDE_CTRL_DMA_PENDING | dma_ultra; + + dma_mode = DMA_MODE_READ; + } + + /* + * Set up the DMA interrupt callback + */ + mxc_dma_callback_set(chan, mxc_ide_dma_callback, (void *)drive); + + /* + * If the ATA FIFO isn't empty, we shouldn't even be here + */ + fifo_fill = ATA_RAW_READ(MXC_IDE_FIFO_FILL); + BUG_ON(fifo_fill); // $$$ TODO: need better recovery here + + ide_map_sg(drive, rq); + + hwif->sg_dma_direction = rq_data_dir(rq) ? DMA_TO_DEVICE : + DMA_FROM_DEVICE; + + hwif->sg_nents = dma_map_sg(priv->dev, sg, hwif->sg_nents, + hwif->sg_dma_direction); + BUG_ON(!hwif->sg_nents); + BUG_ON(hwif->sg_nents > MXC_IDE_DMA_BD_NR); + + mxc_dma_sg_config(chan, sg, hwif->sg_nents, 0, dma_mode); + + ATA_RAW_WRITE(ata_control, MXC_IDE_ATA_CONTROL); + ATA_RAW_WRITE(MXC_IDE_DMA_WATERMARK / 2, MXC_IDE_FIFO_ALARM); + + /* + * Route ata_intrq to the DMA engine, and not to us. + */ + mxc_ide_set_intrq(hwif, INTRQ_DMA); + + /* + * The DMA and ATA controller are ready to go. + * mxc_ide_dma_start() will start the DMA transfer, + * and mxc_ide_dma_exec_cmd() will tickle the drive, which + * actually initiates the DMA transfer on the ATA bus. + * The ATA controller is DMA slave for both read and write. + */ + BUG_ON(drive->waiting_for_dma); + drive->waiting_for_dma = 1; + + return 0; +} + +/*! + * DMA timeout notification. This gets called when the IDE layer above + * us times out waiting for a request. + * + * @param drive The drive we're servicing + * + * @return 0 to attempt recovery, otherwise, an additional tick count + * to keep waiting + */ +static int mxc_ide_dma_timer_expiry(ide_drive_t * drive) +{ + printk(KERN_ERR "%s: fifo_fill=%d\n", drive->name, + readb(MXC_IDE_FIFO_FILL)); + + mxc_ide_resetproc(NULL); + + if (drive->waiting_for_dma) { + HWIF(drive)->ide_dma_end(drive); + } + + return 0; +} + +/*! + * Called by the IDE layer to start a DMA request on the specified drive. + * The request has already been prepared by \b mxc_ide_dma_setup(). All + * we need to do is pass the command through while specifying our timeout + * handler. + * + * @param drive The drive we're servicing + * + * @param cmd The IDE command for the drive + * + */ +static void mxc_ide_dma_exec_cmd(ide_drive_t * drive, u8 cmd) +{ + ide_execute_command(drive, cmd, mxc_ide_dma_intr, 2 * WAIT_CMD, + mxc_ide_dma_timer_expiry); +} + +/*! + * Called by the IDE layer to start the DMA controller. The request has + * already been prepared by \b mxc_ide_dma_setup(). All we do here + * is tickle the DMA channel. + * + * @param drive The drive we're servicing + * + */ +static void mxc_ide_dma_start(ide_drive_t * drive) +{ + struct request *rq = HWGROUP(drive)->rq; + ide_hwif_t *hwif = HWIF(drive); + mxc_ide_private_t *priv = (mxc_ide_private_t *) hwif->hwif_data; + int chan = rq_data_dir(rq) ? priv->dma_write_chan : priv->dma_read_chan; + + BUG_ON(chan < 0); + + /* + * Tickle the DMA channel. This starts the channel, but it is likely + * that DMA will yield and go idle before the DMA request arrives from + * the drive. Nonetheless, at least the context will be hot. + */ + mxc_dma_enable(chan); +} + +/*! + * There is a race between the DMA interrupt and the timeout interrupt. This + * gets called during the IDE layer's timeout interrupt to see if the DMA + * interrupt has also occured or is pending. + * + * @param drive The drive we're servicing + * + * @return 1 means there is a DMA interrupt pending, 0 otherwise + */ +static int mxc_ide_dma_test_irq(ide_drive_t * drive) +{ + unsigned char status = ATA_RAW_READ(MXC_IDE_INTR_PENDING); + + /* + * We need to test the interrupt status without dismissing any. + */ + + return status & (MXC_IDE_INTR_ATA_INTRQ1 + | MXC_IDE_INTR_ATA_INTRQ2) ? 1 : 0; +} + +/*! + * Called to turn off DMA on this drive's controller, such as when a DMA error + * occured and the IDE layer wants to fail back to PIO mode. We won't do + * anything special, leaving the DMA channel allocated for future attempts. + * + * @param drive The drive we're servicing + */ +static void mxc_ide_dma_host_off(ide_drive_t * drive) +{ +} + +/*! + * Called to turn on DMA on this drive's controller. + * + * @param drive The drive we're servicing + */ +static void mxc_ide_dma_host_on(ide_drive_t * drive) +{ +} + +/*! + * Called for special handling on timeouts. + * + * @param drive The drive we're servicing + * + * @return 0 if successful, non-zero otherwise + */ +static int mxc_ide_dma_timeout(ide_drive_t * drive) +{ + return 0; +} + +/*! + * Called for special handling on lost irq's. + * + * @param drive The drive we're servicing + * + * @return 0 if successful, non-zero otherwise + */ +static int mxc_ide_dma_lost_irq(ide_drive_t * drive) +{ + return 0; +} + +/*! + * Called once per controller to set up DMA + * + * @param hwif Specifies the IDE controller + * + */ +static void mxc_ide_dma_init(ide_hwif_t * hwif) +{ + mxc_ide_private_t *priv = (mxc_ide_private_t *) hwif->hwif_data; + + hwif->dmatable_cpu = NULL; + hwif->dmatable_dma = 0; + hwif->speedproc = mxc_ide_set_speed; + hwif->resetproc = mxc_ide_resetproc; + hwif->autodma = 0; + + /* + * Allocate and setup the DMA channels + */ + priv->dma_read_chan = mxc_dma_request(MXC_DMA_ATA_RX, "MXC ATA RX"); + if (priv->dma_read_chan < 0) { + printk(KERN_ERR "%s: couldn't get RX DMA channel\n", + hwif->name); + goto err_out; + } + + priv->dma_write_chan = mxc_dma_request(MXC_DMA_ATA_TX, "MXC ATA TX"); + if (priv->dma_write_chan < 0) { + printk(KERN_ERR "%s: couldn't get TX DMA channel\n", + hwif->name); + goto err_out; + } + + printk(KERN_INFO "%s: read chan=%d , write chan=%d \n", + hwif->name, priv->dma_read_chan, priv->dma_write_chan); + + set_ata_bus_timing(0, UDMA); + + /* + * All ready now + */ + hwif->atapi_dma = 1; + hwif->ultra_mask = 0x7f; + hwif->mwdma_mask = 0x07; + hwif->swdma_mask = 0x07; + hwif->udma_four = 1; + + hwif->dma_off_quietly = mxc_ide_dma_off_quietly; + hwif->ide_dma_on = mxc_ide_dma_on; + hwif->ide_dma_check = mxc_ide_dma_check; + hwif->dma_setup = mxc_ide_dma_setup; + hwif->dma_exec_cmd = mxc_ide_dma_exec_cmd; + hwif->dma_start = mxc_ide_dma_start; + hwif->ide_dma_end = mxc_ide_dma_end; + hwif->ide_dma_test_irq = mxc_ide_dma_test_irq; + hwif->dma_host_off = mxc_ide_dma_host_off; + hwif->dma_host_on = mxc_ide_dma_host_on; + hwif->ide_dma_timeout = mxc_ide_dma_timeout; + hwif->ide_dma_lostirq = mxc_ide_dma_lost_irq; + + hwif->hwif_data = (void *)priv; + return; + + err_out: + hwif->atapi_dma = 0; + if (priv->dma_read_chan >= 0) + mxc_dma_free(priv->dma_read_chan); + if (priv->dma_write_chan >= 0) + mxc_dma_free(priv->dma_write_chan); + kfree(priv); + return; +} + +/*! + * MXC-specific IDE registration helper routine. Among other things, + * we tell the IDE layer where our standard IDE drive registers are, + * through the \b io_ports array. The IDE layer sends commands to and + * reads status directly from attached IDE drives through these drive + * registers. + * + * @param base Base address of memory mapped IDE standard drive registers + * + * @param aux Address of the auxilliary ATA control register + * + * @param irq IRQ number for our hardware + * + * @param hwifp Pointer to hwif structure to be updated by the IDE layer + * + * @param priv Pointer to private structure + * + * @return ENODEV if no drives are present, 0 otherwise + */ +static int __init +mxc_ide_register(unsigned long base, unsigned int aux, int irq, + ide_hwif_t ** hwifp, mxc_ide_private_t * priv) +{ + int i = 0; + hw_regs_t hw; + ide_hwif_t *hwif = &ide_hwifs[0]; + const int regsize = 4; + + memset(&hw, 0, sizeof(hw)); + + for (i = IDE_DATA_OFFSET; i <= IDE_STATUS_OFFSET; i++) { + hw.io_ports[i] = (unsigned long)base; + base += regsize; + } + hw.io_ports[IDE_CONTROL_OFFSET] = aux; + hw.irq = irq; + hw.ack_intr = &mxc_ide_ack_intr; + + *hwifp = hwif; + ide_register_hw(&hw, 0, hwifp); + + hwif->selectproc = &mxc_ide_selectproc; + hwif->tuneproc = &mxc_ide_tune; + hwif->maskproc = &mxc_ide_maskproc; + hwif->hwif_data = (void *)priv; + mxc_ide_set_intrq(hwif, INTRQ_MCU); + + /* + * The IDE layer will set hwif->present if we have devices attached, + * if we don't, discard the interface reset the ports. + */ + if (!hwif->present) { + printk(KERN_INFO "ide%d: Bus empty, interface released.\n", + hwif->index); + for (i = IDE_DATA_OFFSET; i <= IDE_CONTROL_OFFSET; ++i) + hwif->io_ports[i] = 0; + hwif->chipset = ide_unknown; + hwif->noprobe = 1; + return -ENODEV; + } + + mxc_ide_dma_init(hwif); + + for (i = 0; i < MAX_DRIVES; i++) { + mxc_ide_dma_check(hwif->drives + i); + } + + return 0; +} + +/*! + * Driver initialization routine. Prepares the hardware for ATA activity. + */ +static int __init mxc_ide_init(void) +{ + int index = 0; + mxc_ide_private_t *priv; + + printk(KERN_INFO + "MXC: IDE driver, (c) 2004-2006 Freescale Semiconductor\n"); + + ata_clk = clk_get(NULL, "ata_clk"); + clk_enable(ata_clk); + ATA_RAW_WRITE(MXC_IDE_CTRL_ATA_RST_B, MXC_IDE_ATA_CONTROL); + + /* Select group B pins, and enable the interface */ + gpio_ata_active(); + + /* Set initial timing and mode */ + + set_ata_bus_timing(4, PIO); + + /* Reset the interface */ + + mxc_ide_resetproc(NULL); + + /* + * Enable hardware interrupts. + * INTRQ2 goes to us, so we enable it here, but we'll need to ignore + * it when DMA is doing the transfer. + */ + ATA_RAW_WRITE(MXC_IDE_INTR_ATA_INTRQ2, MXC_IDE_INTR_ENABLE); + + /* + * Allocate a private structure + */ + priv = (mxc_ide_private_t *) kmalloc(sizeof *priv, GFP_KERNEL); + if (priv == NULL) { + ATA_RAW_WRITE(0, MXC_IDE_INTR_ENABLE); + gpio_ata_inactive(); + return ENOMEM; + } + + memset(priv, 0, sizeof *priv); + priv->dev = NULL; // dma_map_sg() ignores it anyway + priv->dma_read_chan = -1; + priv->dma_write_chan = -1; + + /* + * Now register + */ + + index = + mxc_ide_register(IDE_ARM_IO, IDE_ARM_CTL, IDE_ARM_IRQ, &ifs[0], + priv); + if (index == -1) { + printk(KERN_ERR "Unable to register the MXC IDE driver\n"); + ATA_RAW_WRITE(0, MXC_IDE_INTR_ENABLE); + gpio_ata_inactive(); + kfree(priv); + return ENODEV; + } +#ifdef ATA_USE_IORDY + /* turn on IORDY protocol */ + + udelay(25); + ATA_RAW_WRITE(MXC_IDE_CTRL_ATA_RST_B | MXC_IDE_CTRL_IORDY_EN, + MXC_IDE_ATA_CONTROL); +#endif + + return 0; +} + +/*! + * Driver exit routine. Clean up. + */ +static void __exit mxc_ide_exit(void) +{ + ide_hwif_t *hwif = ifs[0]; + mxc_ide_private_t *priv; + + BUG_ON(!hwif); + priv = (mxc_ide_private_t *) hwif->hwif_data; + BUG_ON(!priv); + + /* + * Unregister the interface at the IDE layer. This should shut + * down any drives and pending I/O. + */ + ide_unregister(hwif->index); + + /* + * Disable hardware interrupts. + */ + ATA_RAW_WRITE(0, MXC_IDE_INTR_ENABLE); + + /* + * Deallocate DMA channels. Free's BDs and everything. + */ + if (priv->dma_read_chan >= 0) + mxc_dma_free(priv->dma_read_chan); + if (priv->dma_write_chan >= 0) + mxc_dma_free(priv->dma_write_chan); + + /* + * Turn off the clock + */ + clk_disable(ata_clk); + clk_put(ata_clk); + + /* + * Disable the interface + * Free the pins + */ + gpio_ata_inactive(); + + /* + * Free the private structure. + */ + kfree(priv); + hwif->hwif_data = NULL; + +} + +module_init(mxc_ide_init); +module_exit(mxc_ide_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION + ("Freescale MXC IDE (based on Simtec BAST IDE driver by Ben Dooks <ben@simtec.co.uk>)"); diff --git a/drivers/ide/arm/mxc_ide.h b/drivers/ide/arm/mxc_ide.h new file mode 100644 index 000000000000..cf1100b047f1 --- /dev/null +++ b/drivers/ide/arm/mxc_ide.h @@ -0,0 +1,195 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef _MXC_IDE_H_ +#define _MXC_IDE_H_ + +/*! + * @defgroup ATA ATA/IDE Driver + */ + +/*! + * @file mxc_ide.h + * + * @brief MXC ATA/IDE hardware register and bit definitions. + * + * @ingroup ATA + */ + +#define IDE_ARM_IO IO_ADDRESS((ATA_BASE_ADDR + 0xA0) ) +#define IDE_ARM_CTL IO_ADDRESS((ATA_BASE_ADDR + 0xD8) ) +#define IDE_ARM_IRQ INT_ATA + +/* + * Interface control registers + */ + +#define MXC_IDE_FIFO_DATA_32 IO_ADDRESS((ATA_BASE_ADDR + 0x18) ) +#define MXC_IDE_FIFO_DATA_16 IO_ADDRESS((ATA_BASE_ADDR + 0x1C) ) +#define MXC_IDE_FIFO_FILL IO_ADDRESS((ATA_BASE_ADDR + 0x20) ) +#define MXC_IDE_ATA_CONTROL IO_ADDRESS((ATA_BASE_ADDR + 0x24) ) +#define MXC_IDE_INTR_PENDING IO_ADDRESS((ATA_BASE_ADDR + 0x28) ) +#define MXC_IDE_INTR_ENABLE IO_ADDRESS((ATA_BASE_ADDR + 0x2C) ) +#define MXC_IDE_INTR_CLEAR IO_ADDRESS((ATA_BASE_ADDR + 0x30) ) +#define MXC_IDE_FIFO_ALARM IO_ADDRESS((ATA_BASE_ADDR + 0x34) ) + +/* + * Control register bit definitions + */ + +#define MXC_IDE_CTRL_FIFO_RST_B 0x80 +#define MXC_IDE_CTRL_ATA_RST_B 0x40 +#define MXC_IDE_CTRL_FIFO_TX_EN 0x20 +#define MXC_IDE_CTRL_FIFO_RCV_EN 0x10 +#define MXC_IDE_CTRL_DMA_PENDING 0x08 +#define MXC_IDE_CTRL_DMA_ULTRA 0x04 +#define MXC_IDE_CTRL_DMA_WRITE 0x02 +#define MXC_IDE_CTRL_IORDY_EN 0x01 + +/* + * Interrupt registers bit definitions + */ + +#define MXC_IDE_INTR_ATA_INTRQ1 0x80 +#define MXC_IDE_INTR_FIFO_UNDERFLOW 0x40 +#define MXC_IDE_INTR_FIFO_OVERFLOW 0x20 +#define MXC_IDE_INTR_CTRL_IDLE 0x10 +#define MXC_IDE_INTR_ATA_INTRQ2 0x08 + +/* + * timing registers + */ + +#define MXC_IDE_TIME_OFF IO_ADDRESS((ATA_BASE_ADDR + 0x00) ) +#define MXC_IDE_TIME_ON IO_ADDRESS((ATA_BASE_ADDR + 0x01) ) +#define MXC_IDE_TIME_1 IO_ADDRESS((ATA_BASE_ADDR + 0x02) ) +#define MXC_IDE_TIME_2w IO_ADDRESS((ATA_BASE_ADDR + 0x03) ) + +#define MXC_IDE_TIME_2r IO_ADDRESS((ATA_BASE_ADDR + 0x04) ) +#define MXC_IDE_TIME_AX IO_ADDRESS((ATA_BASE_ADDR + 0x05) ) +#define MXC_IDE_TIME_PIO_RDX IO_ADDRESS((ATA_BASE_ADDR + 0x06) ) +#define MXC_IDE_TIME_4 IO_ADDRESS((ATA_BASE_ADDR + 0x07) ) + +#define MXC_IDE_TIME_9 IO_ADDRESS((ATA_BASE_ADDR + 0x08) ) +#define MXC_IDE_TIME_M IO_ADDRESS((ATA_BASE_ADDR + 0x09) ) +#define MXC_IDE_TIME_JN IO_ADDRESS((ATA_BASE_ADDR + 0x0A) ) +#define MXC_IDE_TIME_D IO_ADDRESS((ATA_BASE_ADDR + 0x0B) ) + +#define MXC_IDE_TIME_K IO_ADDRESS((ATA_BASE_ADDR + 0x0C) ) +#define MXC_IDE_TIME_ACK IO_ADDRESS((ATA_BASE_ADDR + 0x0D) ) +#define MXC_IDE_TIME_ENV IO_ADDRESS((ATA_BASE_ADDR + 0x0E) ) +#define MXC_IDE_TIME_RPX IO_ADDRESS((ATA_BASE_ADDR + 0x0F) ) + +#define MXC_IDE_TIME_ZAH IO_ADDRESS((ATA_BASE_ADDR + 0x10) ) +#define MXC_IDE_TIME_MLIX IO_ADDRESS((ATA_BASE_ADDR + 0x11) ) +#define MXC_IDE_TIME_DVH IO_ADDRESS((ATA_BASE_ADDR + 0x12) ) +#define MXC_IDE_TIME_DZFS IO_ADDRESS((ATA_BASE_ADDR + 0x13) ) + +#define MXC_IDE_TIME_DVS IO_ADDRESS((ATA_BASE_ADDR + 0x14) ) +#define MXC_IDE_TIME_CVH IO_ADDRESS((ATA_BASE_ADDR + 0x15) ) +#define MXC_IDE_TIME_SS IO_ADDRESS((ATA_BASE_ADDR + 0x16) ) +#define MXC_IDE_TIME_CYC IO_ADDRESS((ATA_BASE_ADDR + 0x17) ) + +/* + * other facts + */ +#define MXC_IDE_FIFO_SIZE 64 /* DMA FIFO size in halfwords */ +#define MXC_IDE_DMA_BD_SIZE_MAX 0xFC00 /* max size of scatterlist segment */ + +/*! Private data for the drive structure. */ +typedef struct { + struct device *dev; /*!< The device */ + int dma_read_chan; /*!< DMA channel sdma api gave us for reads */ + int dma_write_chan; /*!< DMA channel sdma api gave us for writes */ + int ultra; /*!< Remember when we're in ultra mode */ + int dma_stat; /*!< the state of DMA request */ + u8 enable; /*!< Current hardware interrupt mask */ +} mxc_ide_private_t; + +/*! ATA transfer mode for set_ata_bus_timing() */ +enum ata_mode { + PIO, /*!< Specifies PIO mode */ + MDMA, /*!< Specifies MDMA mode */ + UDMA /*!< Specifies UDMA mode */ +}; + +/*! + * The struct defines the interrupt type which will issued by interrupt singal. + */ +typedef enum { + /*! Enable ATA_INTRQ on the CPU */ + INTRQ_MCU, + + /*! Enable ATA_INTRQ on the DMA engine */ + INTRQ_DMA +} intrq_which_t; + +/*! defines the structure for timing configurations */ + +/*! + * This structure defines the bits in the ATA TIME_CONFIGx + */ +typedef union { + unsigned long config; /* the 32bits fields in TIME_CONFIGx */ + struct { + unsigned char field1; /* the 8bits field for 8bits accessing */ + unsigned char field2; /* the 8bits field for 8bits accessing */ + unsigned char field3; /* the 8bits field for 8bits accessing */ + unsigned char field4; /* the 8bits field for 8bits accessing */ + } bytes; /* the 8bits fields in TIME_CONFIGx */ +} mxc_ide_time_cfg_t; + +#ifdef MXC_ATA_USE_8_BIT_ACCESS /* use 8 bit access */ + +/*!defines the macro for accessing the register */ +#define ATA_RAW_WRITE(v, addr) __raw_writeb(v, addr) +#define ATA_RAW_READ(addr) __raw_readb(addr) + +/*! Get the configuration of TIME_CONFIG0 */ +#define GET_TIME_CFG(t, base) +/*! Set the configuration of TIME_CONFIG0. + * And mask is the mask of valid fields. + * base is the start address of this cofniguration. + */ +#define SET_TIME_CFG(t, mask, base) \ + { \ + if ((mask) & 1) \ + ATA_RAW_WRITE((t)->bytes.field1, (unsigned long)(base)); \ + if ((mask) & 2) \ + ATA_RAW_WRITE((t)->bytes.field2, (unsigned long)(base) + 1); \ + if ((mask) & 4) \ + ATA_RAW_WRITE((t)->bytes.field3, (unsigned long)(base) + 2); \ + if ((mask) & 8) \ + ATA_RAW_WRITE((t)->bytes.field4, (unsigned long)(base) + 3); \ + } + +#else /*MXC_ATA_USE_8_BIT_ACCESS */ + +/*!defines the macro for accessing the register */ +#define ATA_RAW_WRITE(v, addr) __raw_writel(v, addr) +#define ATA_RAW_READ(addr) __raw_readl(addr) +/*! Get the configuration of TIME_CONFIG0 */ +#define GET_TIME_CFG(t, base) \ + { \ + (t)->config = ATA_RAW_READ(base); \ + } + +/*! Set the configuration of TIME_CONFIG0. + * And mask is ignored. base is the start address of this configuration. + */ +#define SET_TIME_CFG(t, mask, base) \ + { \ + ATA_RAW_WRITE((t)->config, base); \ + } +#endif /*MXC_ATA_USE_8_BIT_ACCESS */ + +#endif /* !_MXC_IDE_H_ */ diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig index 086d58c0ccbe..290254d23a29 100644 --- a/drivers/input/keyboard/Kconfig +++ b/drivers/input/keyboard/Kconfig @@ -247,6 +247,13 @@ config KEYBOARD_PXA27x To compile this driver as a module, choose M here: the module will be called pxa27x_keyboard. +config KEYBOARD_MXC + tristate "MXC Keypad Driver" + depends on ARCH_MXC + help + This is the Keypad driver for the Freescale MXC application + processors. + config KEYBOARD_AAED2000 tristate "AAED-2000 keyboard" depends on MACH_AAED2000 diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile index e97455fdcc83..b2b9abe5e645 100644 --- a/drivers/input/keyboard/Makefile +++ b/drivers/input/keyboard/Makefile @@ -17,6 +17,7 @@ obj-$(CONFIG_KEYBOARD_CORGI) += corgikbd.o obj-$(CONFIG_KEYBOARD_SPITZ) += spitzkbd.o obj-$(CONFIG_KEYBOARD_HIL) += hil_kbd.o obj-$(CONFIG_KEYBOARD_HIL_OLD) += hilkbd.o +obj-$(CONFIG_KEYBOARD_MXC) += mxc_keyb.o obj-$(CONFIG_KEYBOARD_OMAP) += omap-keypad.o obj-$(CONFIG_KEYBOARD_PXA27x) += pxa27x_keyboard.o obj-$(CONFIG_KEYBOARD_AAED2000) += aaed2000_kbd.o diff --git a/drivers/input/keyboard/mxc_keyb.c b/drivers/input/keyboard/mxc_keyb.c new file mode 100644 index 000000000000..cbc7141e7e39 --- /dev/null +++ b/drivers/input/keyboard/mxc_keyb.c @@ -0,0 +1,1024 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mxc_keyb.c + * + * @brief Driver for the Freescale Semiconductor MXC keypad port. + * + * The keypad driver is designed as a standard Input driver which interacts + * with low level keypad port hardware. Upon opening, the Keypad driver + * initializes the keypad port. When the keypad interrupt happens the driver + * calles keypad polling timer and scans the keypad matrix for key + * press/release. If all key press/release happened it comes out of timer and + * waits for key press interrupt. The scancode for key press and release events + * are passed to Input subsytem. + * + * @ingroup keypad + */ + +/*! + * Comment KPP_DEBUG to disable debug messages + */ +#define KPP_DEBUG 0 + +#ifdef KPP_DEBUG +#define DEBUG +#include <linux/kernel.h> +#endif + +#include <linux/module.h> +#include <linux/sched.h> +#include <linux/mm.h> +#include <linux/init.h> +#include <asm/io.h> +#include <asm/uaccess.h> +#include <asm/arch/hardware.h> +#include <linux/kd.h> +#include <linux/fs.h> +#include <linux/kbd_kern.h> +#include <linux/ioctl.h> +#include <linux/poll.h> +#include <linux/interrupt.h> +#include <linux/timer.h> +#include <linux/input.h> +#include <linux/miscdevice.h> +#include <linux/platform_device.h> +#include <linux/delay.h> +#include <linux/clk.h> +#include <asm/mach/keypad.h> + +/* + * Module header file + */ +#include "mxc_keyb.h" + +/*! + * This structure holds the keypad private data structure. + */ +static struct keypad_priv kpp_dev; + +/*! Indicates if the key pad device is enabled. */ +static unsigned int key_pad_enabled; + +/*! Input device structure. */ +static struct input_dev *mxckbd_dev = NULL; + +/*! KPP clock handle. */ +static struct clk *kpp_clk; + +/*! This static variable indicates whether a key event is pressed/released. */ +static unsigned short KPress; + +/*! cur_rcmap and prev_rcmap array is used to detect key press and release. */ +static unsigned short *cur_rcmap; /* max 64 bits (8x8 matrix) */ +static unsigned short *prev_rcmap; + +/*! + * Debounce polling period(10ms) in system ticks. + */ +static unsigned short KScanRate = (10 * HZ) / 1000; + +static struct keypad_data *keypad; + +static int has_leaning_key; +/*! + * These arrays are used to store press and release scancodes. + */ +static short **press_scancode; +static short **release_scancode; + +static const unsigned short *mxckpd_keycodes; +static unsigned short mxckpd_keycodes_size; + +#define press_left_code 30 +#define press_right_code 29 +#define press_up_code 28 +#define press_down_code 27 + +#define rel_left_code 158 +#define rel_right_code 157 +#define rel_up_code 156 +#define rel_down_code 155 +/*! + * These functions are used to configure and the GPIO pins for keypad to + * activate and deactivate it. + */ +extern void gpio_keypad_active(void); +extern void gpio_keypad_inactive(void); + +/*! + * This function is called for generating scancodes for key press and + * release on keypad for the board. + * + * @param row Keypad row pressed on the keypad matrix. + * @param col Keypad col pressed on the keypad matrix. + * @param press Indicated key press/release. + * + * @return Key press/release Scancode. + */ +static signed short mxc_scan_matrix_leaning_key(int row, int col, int press) +{ + static unsigned first_row; + static unsigned first_set = 0, flag = 0; + signed short scancode = -1; + + if (press) { + if ((3 == col) && ((3 == row) || + (4 == row) || (5 == row) || (6 == row))) { + if (first_set == 0) { + first_set = 1; + first_row = row; + } else { + first_set = 0; + if (((first_row == 6) || (first_row == 3)) + && ((row == 6) || (row == 3))) + scancode = press_down_code; + else if (((first_row == 3) || (first_row == 5)) + && ((row == 3) || (row == 5))) + scancode = press_left_code; + else if (((first_row == 6) || (first_row == 4)) + && ((row == 6) || (row == 4))) + scancode = press_right_code; + else if (((first_row == 4) || (first_row == 5)) + && ((row == 4) || (row == 5))) + scancode = press_up_code; + KPress = 1; + kpp_dev.iKeyState = KStateUp; + pr_debug("Press (%d, %d) scan=%d Kpress=%d\n", + row, col, scancode, KPress); + } + } else { + /* + * check for other keys only + * if the cursor key presses + * are not detected may be + * this needs better logic + */ + if ((0 == (cur_rcmap[3] & BITSET(0, 3))) && + (0 == (cur_rcmap[4] & BITSET(0, 3))) && + (0 == (cur_rcmap[5] & BITSET(0, 3))) && + (0 == (cur_rcmap[6] & BITSET(0, 3)))) { + scancode = ((col * kpp_dev.kpp_rows) + row); + KPress = 1; + kpp_dev.iKeyState = KStateUp; + flag = 1; + pr_debug("Press (%d, %d) scan=%d Kpress=%d\n", + row, col, scancode, KPress); + } + } + } else { + if ((flag == 0) && (3 == col) + && ((3 == row) || (4 == row) || (5 == row) + || (6 == row))) { + if (first_set == 0) { + first_set = 1; + first_row = row; + } else { + first_set = 0; + if (((first_row == 6) || (first_row == 3)) + && ((row == 6) || (row == 3))) + scancode = rel_down_code; + else if (((first_row == 3) || (first_row == 5)) + && ((row == 3) || (row == 5))) + scancode = rel_left_code; + else if (((first_row == 6) || (first_row == 4)) + && ((row == 6) || (row == 4))) + scancode = rel_right_code; + else if (((first_row == 4) || (first_row == 5)) + && ((row == 4) || (row == 5))) + scancode = rel_up_code; + KPress = 0; + kpp_dev.iKeyState = KStateDown; + pr_debug("Release (%d, %d) scan=%d Kpress=%d\n", + row, col, scancode, KPress); + } + } else { + /* + * check for other keys only + * if the cursor key presses + * are not detected may be + * this needs better logic + */ + if ((0 == (prev_rcmap[3] & BITSET(0, 3))) && + (0 == (prev_rcmap[4] & BITSET(0, 3))) && + (0 == (cur_rcmap[5] & BITSET(0, 3))) && + (0 == (cur_rcmap[6] & BITSET(0, 3)))) { + scancode = ((col * kpp_dev.kpp_rows) + row) + + MXC_KEYRELEASE; + KPress = 0; + flag = 0; + kpp_dev.iKeyState = KStateDown; + pr_debug("Release (%d, %d) scan=%d Kpress=%d\n", + row, col, scancode, KPress); + } + } + } + return scancode; +} + +/*! + * This function is called to scan the keypad matrix to find out the key press + * and key release events. Make scancode and break scancode are generated for + * key press and key release events. + * + * The following scanning sequence are done for + * keypad row and column scanning, + * -# Write 1's to KPDR[15:8], setting column data to 1's + * -# Configure columns as totem pole outputs(for quick discharging of keypad + * capacitance) + * -# Configure columns as open-drain + * -# Write a single column to 0, others to 1. + * -# Sample row inputs and save data. Multiple key presses can be detected on + * a single column. + * -# Repeat steps the above steps for remaining columns. + * -# Return all columns to 0 in preparation for standby mode. + * -# Clear KPKD and KPKR status bit(s) by writing to a 1, + * Set the KPKR synchronizer chain by writing "1" to KRSS register, + * Clear the KPKD synchronizer chain by writing "1" to KDSC register + * + * @result Number of key pressed/released. + */ +static int mxc_kpp_scan_matrix(void) +{ + unsigned short reg_val; + int col, row; + short scancode = 0; + int keycnt = 0; /* How many keys are still pressed */ + + /* + * wmb() linux kernel function which guarantees orderings in write + * operations + */ + wmb(); + + /* save cur keypad matrix to prev */ + + memcpy(prev_rcmap, cur_rcmap, kpp_dev.kpp_rows * sizeof(prev_rcmap[0])); + memset(cur_rcmap, 0, kpp_dev.kpp_rows * sizeof(cur_rcmap[0])); + + for (col = 0; col < kpp_dev.kpp_cols; col++) { /* Col */ + /* 2. Write 1.s to KPDR[15:8] setting column data to 1.s */ + reg_val = __raw_readw(KPDR); + reg_val |= 0xff00; + __raw_writew(reg_val, KPDR); + + /* + * 3. Configure columns as totem pole outputs(for quick + * discharging of keypad capacitance) + */ + reg_val = __raw_readw(KPCR); + reg_val &= 0x00ff; + __raw_writew(reg_val, KPCR); + + udelay(2); + + /* + * 4. Configure columns as open-drain + */ + reg_val = __raw_readw(KPCR); + reg_val |= ((1 << MAXCOL) - 1) << 8; + __raw_writew(reg_val, KPCR); + + /* + * 5. Write a single column to 0, others to 1. + * 6. Sample row inputs and save data. Multiple key presses + * can be detected on a single column. + * 7. Repeat steps 2 - 6 for remaining columns. + */ + + /* Col bit starts at 8th bit in KPDR */ + reg_val = __raw_readw(KPDR); + reg_val &= ~(1 << (8 + col)); + __raw_writew(reg_val, KPDR); + + /* Delay added to avoid propagating the 0 from column to row + * when scanning. */ + + udelay(5); + + /* Read row input */ + reg_val = __raw_readw(KPDR); + for (row = 0; row < kpp_dev.kpp_rows; row++) { /* sample row */ + if (TEST_BIT(reg_val, row) == 0) { + cur_rcmap[row] = BITSET(cur_rcmap[row], col); + keycnt++; + } + } + } + + /* + * 8. Return all columns to 0 in preparation for standby mode. + * 9. Clear KPKD and KPKR status bit(s) by writing to a .1., + * set the KPKR synchronizer chain by writing "1" to KRSS register, + * clear the KPKD synchronizer chain by writing "1" to KDSC register + */ + reg_val = 0x00; + __raw_writew(reg_val, KPDR); + reg_val = __raw_readw(KPDR); + reg_val = __raw_readw(KPSR); + reg_val |= KBD_STAT_KPKD | KBD_STAT_KPKR | KBD_STAT_KRSS | + KBD_STAT_KDSC; + __raw_writew(reg_val, KPSR); + + /* Check key press status change */ + + /* + * prev_rcmap array will contain the previous status of the keypad + * matrix. cur_rcmap array will contains the present status of the + * keypad matrix. If a bit is set in the array, that (row, col) bit is + * pressed, else it is not pressed. + * + * XORing these two variables will give us the change in bit for + * particular row and column. If a bit is set in XOR output, then that + * (row, col) has a change of status from the previous state. From + * the diff variable the key press and key release of row and column + * are found out. + * + * If the key press is determined then scancode for key pressed + * can be generated using the following statement: + * scancode = ((row * 8) + col); + * + * If the key release is determined then scancode for key release + * can be generated using the following statement: + * scancode = ((row * 8) + col) + MXC_KEYRELEASE; + */ + for (row = 0; row < kpp_dev.kpp_rows; row++) { + unsigned char diff; + + /* + * Calculate the change in the keypad row status + */ + diff = prev_rcmap[row] ^ cur_rcmap[row]; + + for (col = 0; col < kpp_dev.kpp_cols; col++) { + if ((diff >> col) & 0x1) { + /* There is a status change on col */ + if ((prev_rcmap[row] & BITSET(0, col)) == 0) { + /* + * Previous state is 0, so now + * a key is pressed + */ + if (has_leaning_key) { + scancode = + mxc_scan_matrix_leaning_key + (row, col, 1); + } else { + scancode = + ((row * kpp_dev.kpp_cols) + + col); + KPress = 1; + kpp_dev.iKeyState = KStateUp; + } + pr_debug("Press (%d, %d) scan=%d " + "Kpress=%d\n", + row, col, scancode, KPress); + press_scancode[row][col] = + (short)scancode; + } else { + /* + * Previous state is not 0, so + * now a key is released + */ + if (has_leaning_key) { + scancode = + mxc_scan_matrix_leaning_key + (row, col, 0); + } else { + scancode = + (row * kpp_dev.kpp_cols) + + col + MXC_KEYRELEASE; + KPress = 0; + kpp_dev.iKeyState = KStateDown; + } + + pr_debug + ("Release (%d, %d) scan=%d Kpress=%d\n", + row, col, scancode, KPress); + release_scancode[row][col] = + (short)scancode; + keycnt++; + } + } + } + } + + /* + * This switch case statement is the + * implementation of state machine of debounce + * logic for key press/release. + * The explaination of state machine is as + * follows: + * + * KStateUp State: + * This is in intial state of the state machine + * this state it checks for any key presses. + * The key press can be checked using the + * variable KPress. If KPress is set, then key + * press is identified and switches the to + * KStateFirstDown state for key press to + * debounce. + * + * KStateFirstDown: + * After debounce delay(10ms), if the KPress is + * still set then pass scancode generated to + * input device and change the state to + * KStateDown, else key press debounce is not + * satisfied so change the state to KStateUp. + * + * KStateDown: + * In this state it checks for any key release. + * If KPress variable is cleared, then key + * release is indicated and so, switch the + * state to KStateFirstUp else to state + * KStateDown. + * + * KStateFirstUp: + * After debounce delay(10ms), if the KPress is + * still reset then pass the key release + * scancode to input device and change + * the state to KStateUp else key release is + * not satisfied so change the state to + * KStateDown. + */ + switch (kpp_dev.iKeyState) { + case KStateUp: + if (KPress) { + /* First Down (must debounce). */ + kpp_dev.iKeyState = KStateFirstDown; + } else { + /* Still UP.(NO Changes) */ + kpp_dev.iKeyState = KStateUp; + } + break; + + case KStateFirstDown: + if (KPress) { + for (row = 0; row < kpp_dev.kpp_rows; row++) { + for (col = 0; col < kpp_dev.kpp_cols; col++) { + if ((press_scancode[row][col] != -1)) { + /* Still Down, so add scancode */ + scancode = + press_scancode[row][col]; + input_event(mxckbd_dev, EV_KEY, + mxckpd_keycodes + [scancode], 1); + if (mxckpd_keycodes[scancode] == + KEY_LEFTSHIFT) { + input_event(mxckbd_dev, + EV_KEY, + KEY_3, 1); + } + kpp_dev.iKeyState = KStateDown; + press_scancode[row][col] = -1; + } + } + } + } else { + /* Just a bounce */ + kpp_dev.iKeyState = KStateUp; + } + break; + + case KStateDown: + if (KPress) { + /* Still down (no change) */ + kpp_dev.iKeyState = KStateDown; + } else { + /* First Up. Must debounce */ + kpp_dev.iKeyState = KStateFirstUp; + } + break; + + case KStateFirstUp: + if (KPress) { + /* Just a bounce */ + kpp_dev.iKeyState = KStateDown; + } else { + for (row = 0; row < kpp_dev.kpp_rows; row++) { + for (col = 0; col < kpp_dev.kpp_cols; col++) { + if ((release_scancode[row][col] != -1)) { + scancode = + release_scancode[row][col]; + scancode = + scancode - MXC_KEYRELEASE; + input_event(mxckbd_dev, EV_KEY, + mxckpd_keycodes + [scancode], 0); + if (mxckpd_keycodes[scancode] == + KEY_LEFTSHIFT) { + input_event(mxckbd_dev, + EV_KEY, + KEY_3, 0); + } + kpp_dev.iKeyState = KStateUp; + release_scancode[row][col] = -1; + } + } + } + } + break; + + default: + return -EBADRQC; + break; + } + + return keycnt; +} + +/*! + * This function is called to start the timer for scanning the keypad if there + * is any key press. Currently this interval is set to 10 ms. When there are + * no keys pressed on the keypad we return back, waiting for a keypad key + * press interrupt. + * + * @param data Opaque data passed back by kernel. Not used. + */ +static void mxc_kpp_handle_timer(unsigned long data) +{ + unsigned short reg_val; + int i; + + if (key_pad_enabled == 0) { + return; + } + if (mxc_kpp_scan_matrix() == 0) { + /* + * Stop scanning and wait for interrupt. + * Enable press interrupt and disable release interrupt. + */ + __raw_writew(0x00FF, KPDR); + reg_val = __raw_readw(KPSR); + reg_val |= (KBD_STAT_KPKR | KBD_STAT_KPKD); + reg_val |= KBD_STAT_KRSS | KBD_STAT_KDSC; + __raw_writew(reg_val, KPSR); + reg_val |= KBD_STAT_KDIE; + reg_val &= ~KBD_STAT_KRIE; + __raw_writew(reg_val, KPSR); + + /* + * No more keys pressed... make sure unwanted key codes are + * not given upstairs + */ + for (i = 0; i < kpp_dev.kpp_rows; i++) { + memset(press_scancode[i], -1, + sizeof(press_scancode[0][0]) * kpp_dev.kpp_cols); + memset(release_scancode[i], -1, + sizeof(release_scancode[0][0]) * + kpp_dev.kpp_cols); + } + return; + } + + /* + * There are still some keys pressed, continue to scan. + * We shall scan again in 10 ms. This has to be tuned according + * to the requirement. + */ + kpp_dev.poll_timer.expires = jiffies + KScanRate; + kpp_dev.poll_timer.function = mxc_kpp_handle_timer; + add_timer(&kpp_dev.poll_timer); +} + +/*! + * This function is the keypad Interrupt handler. + * This function checks for keypad status register (KPSR) for key press + * and interrupt. If key press interrupt has occurred, then the key + * press interrupt in the KPSR are disabled. + * It then calls mxc_kpp_scan_matrix to check for any key pressed/released. + * If any key is found to be pressed, then a timer is set to call + * mxc_kpp_scan_matrix function for every 10 ms. + * + * @param irq The Interrupt number + * @param dev_id Driver private data + * + * @result The function returns \b IRQ_RETVAL(1) if interrupt was handled, + * returns \b IRQ_RETVAL(0) if the interrupt was not handled. + * \b IRQ_RETVAL is defined in include/linux/interrupt.h. + */ +static irqreturn_t mxc_kpp_interrupt(int irq, void *dev_id) +{ + unsigned short reg_val; + + /* Delete the polling timer */ + del_timer(&kpp_dev.poll_timer); + reg_val = __raw_readw(KPSR); + + /* Check if it is key press interrupt */ + if (reg_val & KBD_STAT_KPKD) { + /* + * Disable key press(KDIE status bit) interrupt + */ + reg_val &= ~KBD_STAT_KDIE; + __raw_writew(reg_val, KPSR); + } else { + /* spurious interrupt */ + return IRQ_RETVAL(0); + } + /* + * Check if any keys are pressed, if so start polling. + */ + mxc_kpp_handle_timer(0); + + return IRQ_RETVAL(1); +} + +/*! + * This function is called when the keypad driver is opened. + * Since keypad initialization is done in __init, nothing is done in open. + * + * @param dev Pointer to device inode + * + * @result The function always return 0 + */ +static int mxc_kpp_open(struct input_dev *dev) +{ + return 0; +} + +/*! + * This function is called close the keypad device. + * Nothing is done in this function, since every thing is taken care in + * __exit function. + * + * @param dev Pointer to device inode + * + */ +static void mxc_kpp_close(struct input_dev *dev) +{ +} + +#ifdef CONFIG_PM +/*! + * This function puts the Keypad controller in low-power mode/state. + * If Keypad is enabled as a wake source(i.e. it can resume the system + * from suspend mode), the Keypad controller doesn't enter low-power state. + * + * @param pdev the device structure used to give information on Keypad + * to suspend + * @param state the power state the device is entering + * + * @return The function always returns 0. + */ +static int mxc_kpp_suspend(struct platform_device *pdev, pm_message_t state) +{ + del_timer(&kpp_dev.poll_timer); + + if (device_may_wakeup(&pdev->dev)) { + enable_irq_wake(keypad->irq); + } else { + disable_irq(keypad->irq); + key_pad_enabled = 0; + clk_disable(kpp_clk); + gpio_keypad_inactive(); + } + + return 0; +} + +/*! + * This function brings the Keypad controller back from low-power state. + * If Keypad is enabled as a wake source(i.e. it can resume the system + * from suspend mode), the Keypad controller doesn't enter low-power state. + * + * @param pdev the device structure used to give information on Keypad + * to resume + * + * @return The function always returns 0. + */ +static int mxc_kpp_resume(struct platform_device *pdev) +{ + if (device_may_wakeup(&pdev->dev)) { + disable_irq_wake(keypad->irq); + } else { + gpio_keypad_active(); + clk_enable(kpp_clk); + key_pad_enabled = 1; + enable_irq(keypad->irq); + } + + init_timer(&kpp_dev.poll_timer); + + return 0; +} + +#else +#define mxc_kpp_suspend NULL +#define mxc_kpp_resume NULL +#endif /* CONFIG_PM */ + +/*! + * This function is called to free the allocated memory for local arrays + */ +static void mxc_kpp_free_allocated(void) +{ + + int i; + + if (press_scancode) { + for (i = 0; i < kpp_dev.kpp_rows; i++) { + if (press_scancode[i]) + kfree(press_scancode[i]); + } + kfree(press_scancode); + } + + if (release_scancode) { + for (i = 0; i < kpp_dev.kpp_rows; i++) { + if (release_scancode[i]) + kfree(release_scancode[i]); + } + kfree(release_scancode); + } + + if (cur_rcmap) + kfree(cur_rcmap); + + if (prev_rcmap) + kfree(prev_rcmap); + + if (mxckbd_dev) + input_free_device(mxckbd_dev); +} + +/*! + * This function is called during the driver binding process. + * + * @param pdev the device structure used to store device specific + * information that is used by the suspend, resume and remove + * functions. + * + * @return The function returns 0 on successful registration. Otherwise returns + * specific error code. + */ +static int mxc_kpp_probe(struct platform_device *pdev) +{ + int i, irq; + int retval; + unsigned int reg_val; + + keypad = (struct keypad_data *)pdev->dev.platform_data; + + kpp_dev.kpp_cols = keypad->colmax; + kpp_dev.kpp_rows = keypad->rowmax; + key_pad_enabled = 0; + + /* + * Request for IRQ number for keypad port. The Interrupt handler + * function (mxc_kpp_interrupt) is called when ever interrupt occurs on + * keypad port. + */ + irq = platform_get_irq(pdev, 0); + keypad->irq = irq; + retval = request_irq(irq, mxc_kpp_interrupt, 0, MOD_NAME, MOD_NAME); + if (retval) { + pr_debug("KPP: request_irq(%d) returned error %d\n", INT_KPP, + retval); + return -1; + } + + /* Enable keypad clock */ + kpp_clk = clk_get(&pdev->dev, "kpp_clk"); + clk_enable(kpp_clk); + + /* IOMUX configuration for keypad */ + gpio_keypad_active(); + + /* Configure keypad */ + + /* Enable number of rows in keypad (KPCR[7:0]) + * Configure keypad columns as open-drain (KPCR[15:8]) + * + * Configure the rows/cols in KPP + * LSB nibble in KPP is for 8 rows + * MSB nibble in KPP is for 8 cols + */ + reg_val = __raw_readw(KPCR); + reg_val |= (1 << keypad->rowmax) - 1; /* LSB */ + reg_val |= ((1 << keypad->colmax) - 1) << 8; /* MSB */ + __raw_writew(reg_val, KPCR); + + /* Write 0's to KPDR[15:8] */ + reg_val = __raw_readw(KPDR); + reg_val &= 0x00ff; + __raw_writew(reg_val, KPDR); + + /* Configure columns as output, rows as input (KDDR[15:0]) */ + reg_val = __raw_readw(KDDR); + reg_val |= 0xff00; + reg_val &= 0xff00; + __raw_writew(reg_val, KDDR); + + reg_val = __raw_readw(KPSR); + reg_val &= ~(KBD_STAT_KPKR | KBD_STAT_KPKD); + reg_val |= KBD_STAT_KPKD; + reg_val |= KBD_STAT_KRSS | KBD_STAT_KDSC; + __raw_writew(reg_val, KPSR); + reg_val |= KBD_STAT_KDIE; + reg_val &= ~KBD_STAT_KRIE; + __raw_writew(reg_val, KPSR); + + has_leaning_key = keypad->learning; + mxckpd_keycodes = keypad->matrix; + mxckpd_keycodes_size = keypad->rowmax * keypad->colmax; + + if ((keypad->matrix == (void *)0) + || (mxckpd_keycodes_size == 0)) { + free_irq(irq, MOD_NAME); + return -ENODEV; + } + + mxckbd_dev = input_allocate_device(); + if (!mxckbd_dev) { + printk(KERN_ERR + "mxckbd_dev: not enough memory for input device\n"); + free_irq(irq, MOD_NAME); + return -ENOMEM; + } + + mxckbd_dev->keycode = &mxckpd_keycodes; + mxckbd_dev->keycodesize = sizeof(unsigned char); + mxckbd_dev->keycodemax = mxckpd_keycodes_size; + mxckbd_dev->name = "mxckpd"; + mxckbd_dev->id.bustype = BUS_HOST; + mxckbd_dev->open = mxc_kpp_open; + mxckbd_dev->close = mxc_kpp_close; + + /* allocate required memory */ + press_scancode = kmalloc(kpp_dev.kpp_rows * sizeof(press_scancode[0]), + GFP_KERNEL); + release_scancode = + kmalloc(kpp_dev.kpp_rows * sizeof(release_scancode[0]), GFP_KERNEL); + + if (!press_scancode || !release_scancode) { + free_irq(irq, MOD_NAME); + mxc_kpp_free_allocated(); + return -1; + } + + for (i = 0; i < kpp_dev.kpp_rows; i++) { + press_scancode[i] = kmalloc(kpp_dev.kpp_cols + * sizeof(press_scancode[0][0]), + GFP_KERNEL); + release_scancode[i] = + kmalloc(kpp_dev.kpp_cols * sizeof(release_scancode[0][0]), + GFP_KERNEL); + + if (!press_scancode[i] || !release_scancode[i]) { + free_irq(irq, MOD_NAME); + mxc_kpp_free_allocated(); + return -1; + } + } + + cur_rcmap = + kmalloc(kpp_dev.kpp_rows * sizeof(cur_rcmap[0]), GFP_KERNEL); + prev_rcmap = + kmalloc(kpp_dev.kpp_rows * sizeof(prev_rcmap[0]), GFP_KERNEL); + + if (!cur_rcmap || !prev_rcmap) { + free_irq(irq, MOD_NAME); + mxc_kpp_free_allocated(); + return -1; + } + + __set_bit(EV_KEY, mxckbd_dev->evbit); + + for (i = 0; i < mxckpd_keycodes_size; i++) + __set_bit(mxckpd_keycodes[i], mxckbd_dev->keybit); + + for (i = 0; i < kpp_dev.kpp_rows; i++) { + memset(press_scancode[i], -1, + sizeof(press_scancode[0][0]) * kpp_dev.kpp_cols); + memset(release_scancode[i], -1, + sizeof(release_scancode[0][0]) * kpp_dev.kpp_cols); + } + memset(cur_rcmap, 0, kpp_dev.kpp_rows * sizeof(cur_rcmap[0])); + memset(prev_rcmap, 0, kpp_dev.kpp_rows * sizeof(prev_rcmap[0])); + + key_pad_enabled = 1; + /* Initialize the polling timer */ + init_timer(&kpp_dev.poll_timer); + + input_register_device(mxckbd_dev); + + /* By default, devices should wakeup if they can */ + /* So keypad is set as "should wakeup" as it can */ + device_init_wakeup(&pdev->dev, 1); + + return 0; +} + +/*! + * Dissociates the driver from the kpp device. + * + * @param pdev the device structure used to give information on which SDHC + * to remove + * + * @return The function always returns 0. + */ +static int mxc_kpp_remove(struct platform_device *pdev) +{ + unsigned short reg_val; + + /* + * Clear the KPKD status flag (write 1 to it) and synchronizer chain. + * Set KDIE control bit, clear KRIE control bit (avoid false release + * events. Disable the keypad GPIO pins. + */ + __raw_writew(0x00, KPCR); + __raw_writew(0x00, KPDR); + __raw_writew(0x00, KDDR); + + reg_val = __raw_readw(KPSR); + reg_val |= KBD_STAT_KPKD; + reg_val &= ~KBD_STAT_KRSS; + reg_val |= KBD_STAT_KDIE; + reg_val &= ~KBD_STAT_KRIE; + __raw_writew(reg_val, KPSR); + + gpio_keypad_inactive(); + clk_disable(kpp_clk); + clk_put(kpp_clk); + + KPress = 0; + + del_timer(&kpp_dev.poll_timer); + + free_irq(keypad->irq, MOD_NAME); + input_unregister_device(mxckbd_dev); + + mxc_kpp_free_allocated(); + + return 0; +} + +/*! + * This structure contains pointers to the power management callback functions. + */ +static struct platform_driver mxc_kpd_driver = { + .driver = { + .name = "mxc_keypad", + .bus = &platform_bus_type, + }, + .suspend = mxc_kpp_suspend, + .resume = mxc_kpp_resume, + .probe = mxc_kpp_probe, + .remove = mxc_kpp_remove +}; + +/*! + * This function is called for module initialization. + * It registers keypad char driver and requests for KPP irq number. This + * function does the initialization of the keypad device. + * + * The following steps are used for keypad configuration,\n + * -# Enable number of rows in the keypad control register (KPCR[7:0}).\n + * -# Write 0's to KPDR[15:8]\n + * -# Configure keypad columns as open-drain (KPCR[15:8])\n + * -# Configure columns as output, rows as input (KDDR[15:0])\n + * -# Clear the KPKD status flag (write 1 to it) and synchronizer chain\n + * -# Set KDIE control bit, clear KRIE control bit\n + * In this function the keypad queue initialization is done. + * The keypad IOMUX configuration are done here.* + + * + * @return 0 on success and a non-zero value on failure. + */ +static int __init mxc_kpp_init(void) +{ + printk(KERN_INFO "MXC keypad loaded\n"); + platform_driver_register(&mxc_kpd_driver); + return 0; +} + +/*! + * This function is called whenever the module is removed from the kernel. It + * unregisters the keypad driver from kernel and frees the irq number. + * This function puts the keypad to standby mode. The keypad interrupts are + * disabled. It calls gpio_keypad_inactive function to switch gpio + * configuration into default state. + * + */ +static void __exit mxc_kpp_cleanup(void) +{ + platform_driver_unregister(&mxc_kpd_driver); +} + +module_init(mxc_kpp_init); +module_exit(mxc_kpp_cleanup); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MXC Keypad Controller Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/keyboard/mxc_keyb.h b/drivers/input/keyboard/mxc_keyb.h new file mode 100644 index 000000000000..853c0005e50e --- /dev/null +++ b/drivers/input/keyboard/mxc_keyb.h @@ -0,0 +1,191 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @defgroup keypad Keypad Driver + */ + +/*! + * @file mxc_keyb.h + * + * @brief MXC keypad header file. + * + * @ingroup keypad + */ +#ifndef __MXC_KEYB_H__ +#define __MXC_KEYB_H__ + +/*! + * Keypad Module Name + */ +#define MOD_NAME "mxckpd" + +/*! + * Keypad irq number + */ +#define KPP_IRQ INT_KPP + +/*! + * XLATE mode selection + */ +#define KEYPAD_XLATE 0 + +/*! + * RAW mode selection + */ +#define KEYPAD_RAW 1 + +/*! + * Maximum number of keys. + */ +#define MAXROW 8 +#define MAXCOL 8 +#define MXC_MAXKEY (MAXROW * MAXCOL) + +/*! + * This define indicates break scancode for every key release. A constant + * of 128 is added to the key press scancode. + */ +#define MXC_KEYRELEASE 128 + +/* + * _reg_KPP_KPCR _reg_KPP_KPSR _reg_KPP_KDDR _reg_KPP_KPDR + * Keypad Control Register Address + */ +#define KPCR IO_ADDRESS(KPP_BASE_ADDR + 0x00) + +/* + * Keypad Status Register Address + */ +#define KPSR IO_ADDRESS(KPP_BASE_ADDR + 0x02) + +/* + * Keypad Data Direction Address + */ +#define KDDR IO_ADDRESS(KPP_BASE_ADDR + 0x04) + +/* + * Keypad Data Register + */ +#define KPDR IO_ADDRESS(KPP_BASE_ADDR + 0x06) + +/* + * Key Press Interrupt Status bit + */ +#define KBD_STAT_KPKD 0x01 + +/* + * Key Release Interrupt Status bit + */ +#define KBD_STAT_KPKR 0x02 + +/* + * Key Depress Synchronizer Chain Status bit + */ +#define KBD_STAT_KDSC 0x04 + +/* + * Key Release Synchronizer Status bit + */ +#define KBD_STAT_KRSS 0x08 + +/* + * Key Depress Interrupt Enable Status bit + */ +#define KBD_STAT_KDIE 0x100 + +/* + * Key Release Interrupt Enable + */ +#define KBD_STAT_KRIE 0x200 + +/* + * Keypad Clock Enable + */ +#define KBD_STAT_KPPEN 0x400 + +/*! + * Buffer size of keypad queue. Should be a power of 2. + */ +#define KPP_BUF_SIZE 128 + +/*! + * Test whether bit is set for integer c + */ +#define TEST_BIT(c, n) ((c) & (0x1 << (n))) + +/*! + * Set nth bit in the integer c + */ +#define BITSET(c, n) ((c) | (1 << (n))) + +/*! + * Reset nth bit in the integer c + */ +#define BITRESET(c, n) ((c) & ~(1 << (n))) + +/*! + * This enum represents the keypad state machine to maintain debounce logic + * for key press/release. + */ +enum KeyState { + + /*! + * Key press state. + */ + KStateUp, + + /*! + * Key press debounce state. + */ + KStateFirstDown, + + /*! + * Key release state. + */ + KStateDown, + + /*! + * Key release debounce state. + */ + KStateFirstUp +}; + +/*! + * Keypad Private Data Structure + */ +typedef struct keypad_priv { + + /*! + * Keypad state machine. + */ + enum KeyState iKeyState; + + /*! + * Number of rows configured in the keypad matrix + */ + unsigned long kpp_rows; + + /*! + * Number of Columns configured in the keypad matrix + */ + unsigned long kpp_cols; + + /*! + * Timer used for Keypad polling. + */ + struct timer_list poll_timer; + +} keypad_priv; + +#endif /* __MXC_KEYB_H__ */ diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig index 90e8e92dfe47..9b0692a19618 100644 --- a/drivers/input/touchscreen/Kconfig +++ b/drivers/input/touchscreen/Kconfig @@ -134,6 +134,18 @@ config TOUCHSCREEN_HP7XX To compile this driver as a module, choose M here: the module will be called jornada720_ts. +config TOUCHSCREEN_MXC + tristate "MXC touchscreen input driver" + depends on MXC_MC13783_ADC + help + Say Y here if you have an MXC based board with touchscreen + attached to it. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called mxc_ts. + config TOUCHSCREEN_PENMOUNT tristate "Penmount serial touchscreen" select SERIO diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile index 35d4097df35a..364ae57f3e58 100644 --- a/drivers/input/touchscreen/Makefile +++ b/drivers/input/touchscreen/Makefile @@ -14,6 +14,7 @@ obj-$(CONFIG_TOUCHSCREEN_MTOUCH) += mtouch.o obj-$(CONFIG_TOUCHSCREEN_MK712) += mk712.o obj-$(CONFIG_TOUCHSCREEN_HP600) += hp680_ts_input.o obj-$(CONFIG_TOUCHSCREEN_HP7XX) += jornada720_ts.o +obj-$(CONFIG_TOUCHSCREEN_MXC) += mxc_ts.o obj-$(CONFIG_TOUCHSCREEN_USB_COMPOSITE) += usbtouchscreen.o obj-$(CONFIG_TOUCHSCREEN_PENMOUNT) += penmount.o obj-$(CONFIG_TOUCHSCREEN_TOUCHRIGHT) += touchright.o diff --git a/drivers/input/touchscreen/mxc_ts.c b/drivers/input/touchscreen/mxc_ts.c new file mode 100644 index 000000000000..a2c2a3bb6e09 --- /dev/null +++ b/drivers/input/touchscreen/mxc_ts.c @@ -0,0 +1,104 @@ +/* + * Copyright 2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mxc_ts.c + * + * @brief Driver for the Freescale Semiconductor MXC touchscreen. + * + * The touchscreen driver is designed as a standard input driver which is a + * wrapper over low level PMIC driver. Most of the hardware configuration and + * touchscreen functionality is implemented in the low level PMIC driver. During + * initialization, this driver creates a kernel thread. This thread then calls + * PMIC driver to obtain touchscreen values continously. These values are then + * passed to the input susbsystem. + * + * @ingroup touchscreen + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/sched.h> +#include <linux/input.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/freezer.h> +#include <asm/arch/pmic_external.h> +#include <asm/arch/pmic_adc.h> + +#define MXC_TS_NAME "mxc_ts" + +static struct input_dev *mxc_inputdev = NULL; +static u32 input_ts_installed; + +static int ts_thread(void *arg) +{ + t_touch_screen ts_sample; + s32 wait = 0; + daemonize("mxc_ts"); + while (input_ts_installed) { + try_to_freeze(); + memset(&ts_sample, 0, sizeof(t_touch_screen)); + pmic_adc_get_touch_sample(&ts_sample, !wait); + + input_report_abs(mxc_inputdev, ABS_X, ts_sample.x_position); + input_report_abs(mxc_inputdev, ABS_Y, ts_sample.y_position); + input_report_abs(mxc_inputdev, ABS_PRESSURE, + ts_sample.contact_resistance); + input_sync(mxc_inputdev); + + wait = ts_sample.contact_resistance; + msleep(20); + } + + return 0; +} + +static int __init mxc_ts_init(void) +{ + mxc_inputdev = input_allocate_device(); + if (!mxc_inputdev) { + printk(KERN_ERR + "mxc_ts_init: not enough memory for input device\n"); + return -ENOMEM; + } + + mxc_inputdev->name = MXC_TS_NAME; + mxc_inputdev->evbit[0] = BIT(EV_KEY) | BIT(EV_ABS); + mxc_inputdev->keybit[LONG(BTN_TOUCH)] |= BIT(BTN_TOUCH); + mxc_inputdev->absbit[0] = BIT(ABS_X) | BIT(ABS_Y) | BIT(ABS_PRESSURE); + input_register_device(mxc_inputdev); + + input_ts_installed = 1; + kernel_thread(ts_thread, NULL, CLONE_VM | CLONE_FS); + printk("mxc input touchscreen loaded\n"); + return 0; +} + +static void __exit mxc_ts_exit(void) +{ + input_ts_installed = 0; + input_unregister_device(mxc_inputdev); + + if (mxc_inputdev) { + input_free_device(mxc_inputdev); + mxc_inputdev = NULL; + } +} + +late_initcall(mxc_ts_init); +module_exit(mxc_ts_exit); + +MODULE_DESCRIPTION("MXC input touchscreen driver"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/Kconfig b/drivers/media/video/Kconfig index c9f14bfc8544..8820407ea87f 100644 --- a/drivers/media/video/Kconfig +++ b/drivers/media/video/Kconfig @@ -441,6 +441,33 @@ config VIDEO_W9966 Check out <file:Documentation/video4linux/w9966.txt> for more information. +config VIDEO_MXC_CAMERA + tristate "MXC Video For Linux Camera" + depends on VIDEO_DEV && ARCH_MXC + default y + ---help--- + This is the video4linux2 capture driver based on MXC IPU/eMMA module. + +source "drivers/media/video/mxc/capture/Kconfig" + +config VIDEO_MXC_OUTPUT + tristate "MXC Video For Linux Video Output" + depends on VIDEO_DEV && ARCH_MXC + default y + ---help--- + This is the video4linux2 output driver based on MXC IPU/eMMA module. + +source "drivers/media/video/mxc/output/Kconfig" + +config VIDEO_MXC_OPL + tristate + depends on VIDEO_DEV && ARCH_MXC + default n + ---help--- + This is the ARM9-optimized OPL (Open Primitives Library) software + rotation/mirroring implementation. It may be used by eMMA video + capture or output device. + config VIDEO_CPIA tristate "CPiA Video For Linux" depends on VIDEO_V4L1 diff --git a/drivers/media/video/Makefile b/drivers/media/video/Makefile index b5a064163e03..30884338c398 100644 --- a/drivers/media/video/Makefile +++ b/drivers/media/video/Makefile @@ -55,6 +55,11 @@ obj-$(CONFIG_VIDEO_PMS) += pms.o obj-$(CONFIG_VIDEO_PLANB) += planb.o obj-$(CONFIG_VIDEO_VINO) += vino.o indycam.o obj-$(CONFIG_VIDEO_STRADIS) += stradis.o +obj-$(CONFIG_VIDEO_MXC_IPU_CAMERA) += mxc/capture/ +obj-$(CONFIG_VIDEO_MXC_EMMA_CAMERA) += mxc/capture/ +obj-$(CONFIG_VIDEO_MXC_IPU_OUTPUT) += mxc/output/ +obj-$(CONFIG_VIDEO_MXC_EMMA_OUTPUT) += mxc/output/ +obj-$(CONFIG_VIDEO_MXC_OPL) += mxc/opl/ obj-$(CONFIG_VIDEO_CPIA) += cpia.o obj-$(CONFIG_VIDEO_CPIA_PP) += cpia_pp.o obj-$(CONFIG_VIDEO_CPIA_USB) += cpia_usb.o diff --git a/drivers/media/video/mxc/capture/Kconfig b/drivers/media/video/mxc/capture/Kconfig new file mode 100644 index 000000000000..352871961fe3 --- /dev/null +++ b/drivers/media/video/mxc/capture/Kconfig @@ -0,0 +1,90 @@ +if VIDEO_MXC_CAMERA + +menu "MXC Camera/V4L2 PRP Features support" +config VIDEO_MXC_IPU_CAMERA + bool + depends on VIDEO_MXC_CAMERA && MXC_IPU + default y + +config VIDEO_MXC_EMMA_CAMERA + tristate "MX27 eMMA support" + depends on VIDEO_MXC_CAMERA && MXC_EMMA && FB_MXC_SYNC_PANEL && (MXC_CAMERA_MICRON111 || MXC_CAMERA_MC521DA) + select VIDEO_MXC_OPL + default y + +config VIDEO_MXC_CSI_DMA + bool "CSI-DMA Still Image Capture support" + depends on VIDEO_MXC_EMMA_CAMERA + default n + ---help--- + Use CSI-DMA method instead of CSI-PrP link to capture still image. This allows + to use less physical contiguous memory to capture big resolution still image. But + with this method the CSC (Color Space Conversion) and resize are not supported. + If unsure, say N. + +choice + prompt "Select Camera" + default MXC_CAMERA_MICRON111 + depends on (VIDEO_MXC_CAMERA && I2C_MXC) + +config MXC_CAMERA_MC521DA + tristate "Magnachip mc521da camera support" + depends on ((!MACH_I30030ADS) && (!MACH_MXC30030ADS)) + ---help--- + If you plan to use the mc521da Camera with your MXC system, say Y here. + +config MXC_CAMERA_MICRON111 + tristate "Micron mt9v111 camera support" + depends on ((!MACH_I30030ADS) && (!MACH_MXC30030ADS)) + ---help--- + If you plan to use the mt9v111 Camera with your MXC system, say Y here. + +config MXC_CAMERA_S5K3AAEX + tristate "Sumsung s5k3aaex camera support" + depends on ((MACH_I30030ADS || MACH_MXC30030ADS)) + ---help--- + If you plan to use the s5k3aaex Camera with your MXC system, say Y here. + Will be replaced by Magna hv7161. + +config MXC_CAMERA_HV7161 + tristate "Magna Hv7161 camera support" + depends on ((MACH_I30030ADS || MACH_MXC30030ADS)) + ---help--- + If you plan to use the magna hv7161 Camera with your MXC system, say Y here. +endchoice + +config MXC_IPU_PRP_VF_SDC + tristate "Pre-Processor VF SDC library" + depends on (VIDEO_MXC_IPU_CAMERA && FB_MXC_SYNC_PANEL && (MXC_CAMERA_MC521DA || MXC_CAMERA_MICRON111 || MXC_CAMERA_S5K3AAEX || MXC_CAMERA_HV7161)) + default y + ---help--- + Use case PRP_VF_SDC: + Preprocessing image from smart sensor for viewfinder and + displaying it on synchronous display with SDC use case. + If SDC BG is selected, Rotation will not be supported. + CSI -> IC (PRP VF) -> MEM + MEM -> IC (ROT) -> MEM + MEM -> SDC (FG/BG) + +config MXC_IPU_PRP_VF_ADC + tristate "Pre-Processor VF ADC library" + depends on (VIDEO_MXC_IPU_CAMERA && FB_MXC_ASYNC_PANEL && (MXC_CAMERA_MC521DA || MXC_CAMERA_MICRON111 || MXC_CAMERA_S5K3AAEX || MXC_CAMERA_HV7161)) + default y + ---help--- + Use case PRP_VF_ADC: + Preprocessing image from smart sensor for viewfinder and + displaying it on asynchronous display. + CSI -> IC (PRP VF) -> ADC2 + +config MXC_IPU_PRP_ENC + tristate "Pre-processor Encoder library" + depends on (VIDEO_MXC_IPU_CAMERA && (MXC_CAMERA_MC521DA || MXC_CAMERA_MICRON111 || MXC_CAMERA_S5K3AAEX || MXC_CAMERA_HV7161)) + default y + ---help--- + Use case PRP_ENC: + Preprocessing image from smart sensor for encoder. + CSI -> IC (PRP ENC) -> MEM + +endmenu + +endif diff --git a/drivers/media/video/mxc/capture/Makefile b/drivers/media/video/mxc/capture/Makefile new file mode 100644 index 000000000000..4e7c903a4896 --- /dev/null +++ b/drivers/media/video/mxc/capture/Makefile @@ -0,0 +1,21 @@ +ifeq ($(CONFIG_VIDEO_MXC_IPU_CAMERA),y) + obj-$(CONFIG_VIDEO_MXC_CAMERA) += mxc_v4l2_capture.o + obj-$(CONFIG_MXC_IPU_PRP_VF_ADC) += ipu_prp_vf_adc.o + obj-$(CONFIG_MXC_IPU_PRP_VF_SDC) += ipu_prp_vf_sdc.o ipu_prp_vf_sdc_bg.o + obj-$(CONFIG_MXC_IPU_PRP_ENC) += ipu_prp_enc.o ipu_still.o +endif + +mx27_capture-objs := mx27_prphw.o mx27_prpsw.o mx27_v4l2_capture.o +obj-$(CONFIG_VIDEO_MXC_EMMA_CAMERA) += mx27_csi.o mx27_capture.o + +mc521da_camera-objs := mc521da.o sensor_clock.o +obj-$(CONFIG_MXC_CAMERA_MC521DA) += mc521da_camera.o + +mt9v111_camera-objs := mt9v111.o sensor_clock.o +obj-$(CONFIG_MXC_CAMERA_MICRON111) += mt9v111_camera.o + +hv7161_camera-objs := hv7161.o sensor_clock.o +obj-$(CONFIG_MXC_CAMERA_HV7161) += hv7161_camera.o + +s5k3aaex_camera-objs := s5k3aaex.o sensor_clock.o +obj-$(CONFIG_MXC_CAMERA_S5K3AAEX) += s5k3aaex_camera.o diff --git a/drivers/media/video/mxc/capture/hv7161.c b/drivers/media/video/mxc/capture/hv7161.c new file mode 100644 index 000000000000..2ba0f92776ec --- /dev/null +++ b/drivers/media/video/mxc/capture/hv7161.c @@ -0,0 +1,434 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file hv7161.c + * + * @brief hv7161 camera driver functions + * + * @ingroup Camera + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/ctype.h> +#include <linux/types.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/i2c.h> +#include "asm/arch/mxc_i2c.h" +#include "hv7161.h" +#include "mxc_v4l2_capture.h" + +#define HV7161_TERM 0xFF + +static sensor_interface *interface_param = NULL; +static int reset_frame_rate = 30; + +static hv7161_image_format format[2] = { + { + .index = 0, + .width = 1280, + .height = 960, + }, + { + .index = 1, + .width = 640, + .height = 480, + }, +}; + +const static struct hv7161_reg hv7161_common[] = { + {0x31, 0x20}, {0x32, 0x3}, {0xee, 0x3}, + {HV7161_TERM, HV7161_TERM} +}; + +static int hv7161_attach(struct i2c_adapter *adapter); +static int hv7161_detach(struct i2c_client *client); + +static struct i2c_driver hv7161_i2c_driver = { + .owner = THIS_MODULE, + .name = "HV7161 Client", + .flags = I2C_DF_NOTIFY, + .attach_adapter = hv7161_attach, + .detach_client = hv7161_detach, +}; + +static struct i2c_client hv7161_i2c_client = { + .name = "hv7161 I2C dev", + .id = 1, + .addr = HV7161_I2C_ADDRESS, + .driver = &hv7161_i2c_driver, +}; + +extern void gpio_sensor_setup(void); +extern void gpio_sensor_reset(bool flag); +extern void gpio_sensor_suspend(bool flag); + +/* + * Function definitions + */ +static int hv7161_i2c_client_xfer(unsigned int addr, char *reg, + int reg_len, char *buf, int num, + int tran_flag) +{ + struct i2c_msg msg[2]; + int ret; + + msg[0].addr = addr; + msg[0].len = reg_len; + msg[0].buf = reg; + msg[0].flags = tran_flag; + msg[0].flags &= ~I2C_M_RD; + + msg[1].addr = addr; + msg[1].len = num; + msg[1].buf = buf; + msg[1].flags = tran_flag; + + if (tran_flag & MXC_I2C_FLAG_READ) { + msg[1].flags |= I2C_M_RD; + } else { + msg[1].flags &= ~I2C_M_RD; + } + + ret = i2c_transfer(hv7161_i2c_client.adapter, msg, 2); + if (ret >= 0) + return 0; + + return ret; +} + +static int hv7161_read_reg(u8 * reg, u8 * val) +{ + return hv7161_i2c_client_xfer(HV7161_I2C_ADDRESS, reg, 1, val, 1, + MXC_I2C_FLAG_READ); +} + +static int hv7161_write_reg(u8 reg, u8 val) +{ + u8 temp1, temp2; + temp1 = reg; + temp2 = val; + return hv7161_i2c_client_xfer(HV7161_I2C_ADDRESS, &temp1, 1, &temp2, + 1, 0); +} + +static int hv7161_write_regs(const struct hv7161_reg reglist[]) +{ + int err; + const struct hv7161_reg *next = reglist; + + while (!((next->reg == HV7161_TERM) && (next->val == HV7161_TERM))) { + err = hv7161_write_reg(next->reg, next->val); + if (err) { + return err; + } + next++; + } + return 0; +} + +/*! + * hv7161 sensor downscale function + * @param downscale bool + * @return Error code indicating success or failure + */ +static u8 hv7161_sensor_downscale(bool downscale) +{ + u8 error = 0; + u8 reg, data; + + if (downscale == true) { + reg = 0x1; + data = 0x16; + hv7161_write_reg(reg, data); + } else { + reg = 0x1; + data = 0x13; + hv7161_write_reg(reg, data); + } + + hv7161_write_regs(hv7161_common); + + return error; +} + +/*! + * hv7161 sensor interface Initialization + * @param param sensor_interface * + * @param width u32 + * @param height u32 + * @return None + */ +static void hv7161_interface(sensor_interface * param, u32 width, u32 height) +{ + param->clk_mode = 0x0; //gated + param->pixclk_pol = 0x0; + param->data_width = 0x1; + param->data_pol = 0x0; + param->ext_vsync = 0x1; + param->Vsync_pol = 0x1; + param->Hsync_pol = 0x1; + param->width = width - 1; + param->height = height - 1; + param->pixel_fmt = IPU_PIX_FMT_UYVY; +} + +/*! + * hv7161 sensor configuration + * + * @param frame_rate int * + * @param high_quality int + * @return sensor_interface * + */ +static sensor_interface *hv7161_config(int *frame_rate, int high_quality) +{ + u8 reg, data; + int num_clock_per_row; + u16 h_blank; + int max_rate = 0; + int index = 1; + + if (high_quality == 1) + index = 0; + + hv7161_interface(interface_param, format[index].width, + format[index].height); + + if (index == 0) { + pr_info("SXGA\n"); + hv7161_sensor_downscale(false); + } else { + pr_info("VGA\n"); + hv7161_sensor_downscale(true); + } + + num_clock_per_row = format[0].width + 208; + max_rate = interface_param->mclk / (num_clock_per_row * + (format[0].height + 8)); + + if ((*frame_rate > max_rate) || (*frame_rate == 0)) { + *frame_rate = max_rate; + } + + num_clock_per_row = interface_param->mclk / *frame_rate; + num_clock_per_row /= format[0].height + 8; + h_blank = num_clock_per_row - format[0].width; + reg = 0x11; + data = (u8) (h_blank & 0xff); + hv7161_write_reg(reg, data); + reg = 0x10; + data = (u8) ((h_blank >> 8) & 0xff); + hv7161_write_reg(reg, data); + + reset_frame_rate = *frame_rate; + + return interface_param; +} + +/*! + * hv7161 sensor set color configuration + * + * @param bright int + * @param saturation int + * @param red int + * @param green int + * @param blue int + * @return None + */ +static void +hv7161_set_color(int bright, int saturation, int red, int green, int blue) +{ + u8 reg; + u8 data; + + // set Brightness + reg = 0x5b; + data = (u8) bright; + hv7161_write_reg(reg, data); + // set Saturation + reg = 0x5c; + data = (u8) saturation; + hv7161_write_reg(reg, data); + // set Red + reg = 0x14; + data = (u8) red; + hv7161_write_reg(reg, data); + // set Green + reg = 0x15; + data = (u8) green; + hv7161_write_reg(reg, data); + // set Blue + reg = 0x16; + data = (u8) blue; + hv7161_write_reg(reg, data); +} + +/*! + * hv7161 sensor get color configuration + * + * @param bright int * + * @param saturation int * + * @param red int * + * @param green int * + * @param blue int * + * @return None + */ +static void +hv7161_get_color(int *bright, int *saturation, int *red, int *green, int *blue) +{ + u8 reg[1]; + u8 *pdata; + + // get Brightness + reg[0] = 0x5b; + pdata = (u8 *) bright; + hv7161_read_reg(reg, pdata); + // get saturation + reg[0] = 0x5c; + pdata = (u8 *) saturation; + hv7161_read_reg(reg, pdata); + // get Red + reg[0] = 0x14; + pdata = (u8 *) red; + hv7161_read_reg(reg, pdata); + // get Green + reg[0] = 0x15; + pdata = (u8 *) red; + hv7161_read_reg(reg, pdata); + // get Blue + reg[0] = 0x16; + pdata = (u8 *) blue; + hv7161_read_reg(reg, pdata); +} + +/*! + * hv7161 Reset function + * + * @return None + */ +static sensor_interface *hv7161_reset(void) +{ + set_mclk_rate(&interface_param->mclk); + + /* Reset for at least 4 cycles */ + gpio_sensor_reset(true); + msleep(10); + gpio_sensor_reset(false); + msleep(10); + + hv7161_config(&reset_frame_rate, 0); + return interface_param; +} + +struct camera_sensor camera_sensor_if = { + set_color:hv7161_set_color, + get_color:hv7161_get_color, + config:hv7161_config, + reset:hv7161_reset, +}; + +/*! + * hv7161 I2C attach function + * + * @param adapter struct i2c_adapter * + * @return Error code indicating success or failure + */ +static int hv7161_attach(struct i2c_adapter *adapter) +{ + if (strcmp(adapter->name, MXC_ADAPTER_NAME) != 0) { + printk(KERN_ERR "hv7161_attach: %s\n", adapter->name); + return -1; + } + + hv7161_i2c_client.adapter = adapter; + if (i2c_attach_client(&hv7161_i2c_client)) { + hv7161_i2c_client.adapter = NULL; + printk(KERN_ERR "hv7161_attach: i2c_attach_client failed\n"); + return -1; + } + + interface_param = (sensor_interface *) + kmalloc(sizeof(sensor_interface), GFP_KERNEL); + if (!interface_param) { + printk(KERN_ERR "hv7161_attach: kmalloc failed \n"); + return -1; + } + + gpio_sensor_setup(); + + gpio_sensor_suspend(false); + + interface_param->mclk = 0x2a00000; + + return 0; +} + +/*! + * hv7161 I2C detach function + * + * @param client struct i2c_client * + * @return Error code indicating success or failure + */ +static int hv7161_detach(struct i2c_client *client) +{ + int err; + + if (!hv7161_i2c_client.adapter) + return -1; + + err = i2c_detach_client(&hv7161_i2c_client); + hv7161_i2c_client.adapter = NULL; + + if (interface_param) + kfree(interface_param); + interface_param = NULL; + + return err; +} + +/*! + * hv7161 init function + * + * @return Error code indicating success or failure + */ +static __init int hv7161_init(void) +{ + u8 err; + + err = i2c_add_driver(&hv7161_i2c_driver); + + return err; +} + +/*! + * hv7161 cleanup function + * + * @return Error code indicating success or failure + */ +static void __exit hv7161_clean(void) +{ + i2c_del_driver(&hv7161_i2c_driver); +} + +module_init(hv7161_init); +module_exit(hv7161_clean); + +/* Exported symbols for modules. */ +EXPORT_SYMBOL(camera_sensor_if); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("hv7161 Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/mxc/capture/hv7161.h b/drivers/media/video/mxc/capture/hv7161.h new file mode 100644 index 000000000000..26c5bd60cdd3 --- /dev/null +++ b/drivers/media/video/mxc/capture/hv7161.h @@ -0,0 +1,38 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file hv7161.h + * + * @brief HV7161 Camera Header file + * + * @ingroup Camera + */ + +#ifndef __HV7161_H__ +#define __HV7161_H__ + +#define HV7161_I2C_ADDRESS 0x11 + +typedef struct { + u8 index; + u16 width; + u16 height; +} hv7161_image_format; + +struct hv7161_reg { + u8 reg; + u8 val; +}; + +#endif /* __HV7161_H__ */ diff --git a/drivers/media/video/mxc/capture/ipu_prp_enc.c b/drivers/media/video/mxc/capture/ipu_prp_enc.c new file mode 100644 index 000000000000..31fe635c0dd9 --- /dev/null +++ b/drivers/media/video/mxc/capture/ipu_prp_enc.c @@ -0,0 +1,438 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file ipu_prp_enc.c + * + * @brief IPU Use case for PRP-ENC + * + * @ingroup IPU + */ + +#include "mxc_v4l2_capture.h" +#include <asm/arch/ipu.h> +#include "ipu_prp_sw.h" +#include <linux/dma-mapping.h> + +static ipu_rotate_mode_t grotation = IPU_ROTATE_NONE; + +/* + * Function definitions + */ + +/*! + * IPU ENC callback function. + * + * @param irq int irq line + * @param dev_id void * device id + * + * @return status IRQ_HANDLED for handled + */ +static irqreturn_t prp_enc_callback(int irq, void *dev_id) +{ + cam_data *cam = (cam_data *) dev_id; + + if (cam->enc_callback == NULL) + return IRQ_HANDLED; + + cam->enc_callback(irq, dev_id); + + return IRQ_HANDLED; +} + +/*! + * PrpENC enable channel setup function + * + * @param cam struct cam_data * mxc capture instance + * + * @return status + */ +static int prp_enc_setup(cam_data * cam) +{ + ipu_channel_params_t enc; + int err = 0; + dma_addr_t dummy = 0xdeadbeaf; + + if (!cam) { + printk(KERN_ERR "cam private is NULL\n"); + return -ENXIO; + } + + ipu_csi_get_window_size(&enc.csi_prp_enc_mem.in_width, + &enc.csi_prp_enc_mem.in_height); + enc.csi_prp_enc_mem.in_pixel_fmt = IPU_PIX_FMT_UYVY; + enc.csi_prp_enc_mem.out_width = cam->v2f.fmt.pix.width; + enc.csi_prp_enc_mem.out_height = cam->v2f.fmt.pix.height; + if (cam->rotation >= IPU_ROTATE_90_RIGHT) { + enc.csi_prp_enc_mem.out_width = cam->v2f.fmt.pix.height; + enc.csi_prp_enc_mem.out_height = cam->v2f.fmt.pix.width; + } + if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_YUV420) { + enc.csi_prp_enc_mem.out_pixel_fmt = IPU_PIX_FMT_YUV420P; + pr_info("YUV420\n"); + } else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_YUV422P) { + enc.csi_prp_enc_mem.out_pixel_fmt = IPU_PIX_FMT_YUV422P; + pr_info("YUV422P\n"); + } else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_BGR24) { + enc.csi_prp_enc_mem.out_pixel_fmt = IPU_PIX_FMT_BGR24; + pr_info("BGR24\n"); + } else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_RGB24) { + enc.csi_prp_enc_mem.out_pixel_fmt = IPU_PIX_FMT_RGB24; + pr_info("RGB24\n"); + } else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_RGB565) { + enc.csi_prp_enc_mem.out_pixel_fmt = IPU_PIX_FMT_RGB565; + pr_info("RGB565\n"); + } else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_BGR32) { + enc.csi_prp_enc_mem.out_pixel_fmt = IPU_PIX_FMT_BGR32; + pr_info("BGR32\n"); + } else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_RGB32) { + enc.csi_prp_enc_mem.out_pixel_fmt = IPU_PIX_FMT_RGB32; + pr_info("RGB32\n"); + } else { + printk(KERN_ERR "format not supported\n"); + return -EINVAL; + } + + err = ipu_init_channel(CSI_PRP_ENC_MEM, &enc); + if (err != 0) { + printk(KERN_ERR "ipu_init_channel %d\n", err); + return err; + } + ipu_csi_enable_mclk(CSI_MCLK_ENC, true, true); + + grotation = cam->rotation; + if (cam->rotation >= IPU_ROTATE_90_RIGHT) { + if (cam->rot_enc_bufs_vaddr[0]) { + dma_free_coherent(0, cam->rot_enc_buf_size[0], + cam->rot_enc_bufs_vaddr[0], + cam->rot_enc_bufs[0]); + } + if (cam->rot_enc_bufs_vaddr[1]) { + dma_free_coherent(0, cam->rot_enc_buf_size[1], + cam->rot_enc_bufs_vaddr[1], + cam->rot_enc_bufs[1]); + } + cam->rot_enc_buf_size[0] = + PAGE_ALIGN(cam->v2f.fmt.pix.sizeimage); + cam->rot_enc_bufs_vaddr[0] = + (void *)dma_alloc_coherent(0, cam->rot_enc_buf_size[0], + &cam->rot_enc_bufs[0], + GFP_DMA | GFP_KERNEL); + if (!cam->rot_enc_bufs_vaddr[0]) { + printk(KERN_ERR "alloc enc_bufs0\n"); + return -ENOMEM; + } + cam->rot_enc_buf_size[1] = + PAGE_ALIGN(cam->v2f.fmt.pix.sizeimage); + cam->rot_enc_bufs_vaddr[1] = + (void *)dma_alloc_coherent(0, cam->rot_enc_buf_size[1], + &cam->rot_enc_bufs[1], + GFP_DMA | GFP_KERNEL); + if (!cam->rot_enc_bufs_vaddr[1]) { + dma_free_coherent(0, cam->rot_enc_buf_size[0], + cam->rot_enc_bufs_vaddr[0], + cam->rot_enc_bufs[0]); + cam->rot_enc_bufs_vaddr[0] = NULL; + cam->rot_enc_bufs[0] = 0; + printk(KERN_ERR "alloc enc_bufs1\n"); + return -ENOMEM; + } + + err = ipu_init_channel_buffer(CSI_PRP_ENC_MEM, + IPU_OUTPUT_BUFFER, + enc.csi_prp_enc_mem.out_pixel_fmt, + enc.csi_prp_enc_mem.out_width, + enc.csi_prp_enc_mem.out_height, + enc.csi_prp_enc_mem.out_width, + IPU_ROTATE_NONE, + cam->rot_enc_bufs[0], + cam->rot_enc_bufs[1], 0, 0); + if (err != 0) { + printk(KERN_ERR "CSI_PRP_ENC_MEM err\n"); + return err; + } + + err = ipu_init_channel(MEM_ROT_ENC_MEM, NULL); + if (err != 0) { + printk(KERN_ERR "MEM_ROT_ENC_MEM channel err\n"); + return err; + } + + err = ipu_init_channel_buffer(MEM_ROT_ENC_MEM, IPU_INPUT_BUFFER, + enc.csi_prp_enc_mem.out_pixel_fmt, + enc.csi_prp_enc_mem.out_width, + enc.csi_prp_enc_mem.out_height, + enc.csi_prp_enc_mem.out_width, + cam->rotation, + cam->rot_enc_bufs[0], + cam->rot_enc_bufs[1], 0, 0); + if (err != 0) { + printk(KERN_ERR "MEM_ROT_ENC_MEM input buffer\n"); + return err; + } + + err = + ipu_init_channel_buffer(MEM_ROT_ENC_MEM, IPU_OUTPUT_BUFFER, + enc.csi_prp_enc_mem.out_pixel_fmt, + enc.csi_prp_enc_mem.out_height, + enc.csi_prp_enc_mem.out_width, + cam->v2f.fmt.pix.bytesperline / + bytes_per_pixel(enc.csi_prp_enc_mem. + out_pixel_fmt), + IPU_ROTATE_NONE, dummy, dummy, + cam->offset.u_offset, + cam->offset.v_offset); + if (err != 0) { + printk(KERN_ERR "MEM_ROT_ENC_MEM output buffer\n"); + return err; + } + + err = ipu_link_channels(CSI_PRP_ENC_MEM, MEM_ROT_ENC_MEM); + if (err < 0) { + printk(KERN_ERR + "link CSI_PRP_ENC_MEM-MEM_ROT_ENC_MEM\n"); + return err; + } + + err = ipu_enable_channel(CSI_PRP_ENC_MEM); + if (err < 0) { + printk(KERN_ERR "ipu_enable_channel CSI_PRP_ENC_MEM\n"); + return err; + } + err = ipu_enable_channel(MEM_ROT_ENC_MEM); + if (err < 0) { + printk(KERN_ERR "ipu_enable_channel MEM_ROT_ENC_MEM\n"); + return err; + } + + ipu_select_buffer(CSI_PRP_ENC_MEM, IPU_OUTPUT_BUFFER, 0); + ipu_select_buffer(CSI_PRP_ENC_MEM, IPU_OUTPUT_BUFFER, 1); + } else { + err = + ipu_init_channel_buffer(CSI_PRP_ENC_MEM, IPU_OUTPUT_BUFFER, + enc.csi_prp_enc_mem.out_pixel_fmt, + enc.csi_prp_enc_mem.out_width, + enc.csi_prp_enc_mem.out_height, + cam->v2f.fmt.pix.bytesperline / + bytes_per_pixel(enc.csi_prp_enc_mem. + out_pixel_fmt), + cam->rotation, dummy, dummy, + cam->offset.u_offset, + cam->offset.v_offset); + if (err != 0) { + printk(KERN_ERR "CSI_PRP_ENC_MEM output buffer\n"); + return err; + } + err = ipu_enable_channel(CSI_PRP_ENC_MEM); + if (err < 0) { + printk(KERN_ERR "ipu_enable_channel CSI_PRP_ENC_MEM\n"); + return err; + } + } + return err; +} + +/*! + * function to update physical buffer address for encorder IDMA channel + * + * @param eba physical buffer address for encorder IDMA channel + * @param buffer_num int buffer 0 or buffer 1 + * + * @return status + */ +static int prp_enc_eba_update(dma_addr_t eba, int *buffer_num) +{ + int err = 0; + + pr_debug("eba %x\n", eba); + if (grotation >= IPU_ROTATE_90_RIGHT) { + err = ipu_update_channel_buffer(MEM_ROT_ENC_MEM, + IPU_OUTPUT_BUFFER, *buffer_num, + eba); + } else { + err = ipu_update_channel_buffer(CSI_PRP_ENC_MEM, + IPU_OUTPUT_BUFFER, *buffer_num, + eba); + } + if (err != 0) { + printk(KERN_ERR "err %d buffer_num %d\n", err, *buffer_num); + return err; + } + + if (grotation >= IPU_ROTATE_90_RIGHT) { + ipu_select_buffer(MEM_ROT_ENC_MEM, IPU_OUTPUT_BUFFER, + *buffer_num); + } else { + ipu_select_buffer(CSI_PRP_ENC_MEM, IPU_OUTPUT_BUFFER, + *buffer_num); + } + + *buffer_num = (*buffer_num == 0) ? 1 : 0; + return 0; +} + +/*! + * Enable encoder task + * @param private struct cam_data * mxc capture instance + * + * @return status + */ +static int prp_enc_enabling_tasks(void *private) +{ + cam_data *cam = (cam_data *) private; + int err = 0; + + if (cam->rotation >= IPU_ROTATE_90_RIGHT) { + err = ipu_request_irq(IPU_IRQ_PRP_ENC_ROT_OUT_EOF, + prp_enc_callback, 0, "Mxc Camera", cam); + } else { + err = ipu_request_irq(IPU_IRQ_PRP_ENC_OUT_EOF, + prp_enc_callback, 0, "Mxc Camera", cam); + } + if (err != 0) { + printk(KERN_ERR "Error registering rot irq\n"); + return err; + } + + err = prp_enc_setup(cam); + if (err != 0) { + printk(KERN_ERR "prp_enc_setup %d\n", err); + return err; + } + + return err; +} + +/*! + * Disable encoder task + * @param private struct cam_data * mxc capture instance + * + * @return int + */ +static int prp_enc_disabling_tasks(void *private) +{ + cam_data *cam = (cam_data *) private; + int err = 0; + + if (cam->rotation >= IPU_ROTATE_90_RIGHT) { + ipu_free_irq(IPU_IRQ_PRP_ENC_ROT_OUT_EOF, cam); + } else { + ipu_free_irq(IPU_IRQ_PRP_ENC_OUT_EOF, cam); + } + + if (cam->rotation >= IPU_ROTATE_90_RIGHT) { + ipu_unlink_channels(CSI_PRP_ENC_MEM, MEM_ROT_ENC_MEM); + } + + err = ipu_disable_channel(CSI_PRP_ENC_MEM, true); + if (cam->rotation >= IPU_ROTATE_90_RIGHT) { + err |= ipu_disable_channel(MEM_ROT_ENC_MEM, true); + } + + ipu_uninit_channel(CSI_PRP_ENC_MEM); + if (cam->rotation >= IPU_ROTATE_90_RIGHT) { + ipu_uninit_channel(MEM_ROT_ENC_MEM); + } + + ipu_csi_enable_mclk(CSI_MCLK_ENC, false, false); + + return err; +} + +/*! + * function to select PRP-ENC as the working path + * + * @param private struct cam_data * mxc capture instance + * + * @return int + */ +int prp_enc_select(void *private) +{ + cam_data *cam = (cam_data *) private; + int err = 0; + + if (cam) { + cam->enc_update_eba = prp_enc_eba_update; + cam->enc_enable = prp_enc_enabling_tasks; + cam->enc_disable = prp_enc_disabling_tasks; + } else { + err = -EIO; + } + + return err; +} + +/*! + * function to de-select PRP-ENC as the working path + * + * @param private struct cam_data * mxc capture instance + * + * @return int + */ +int prp_enc_deselect(void *private) +{ + cam_data *cam = (cam_data *) private; + int err = 0; + + //err = prp_enc_disabling_tasks(cam); + + if (cam) { + cam->enc_update_eba = NULL; + cam->enc_enable = NULL; + cam->enc_disable = NULL; + if (cam->rot_enc_bufs_vaddr[0]) { + dma_free_coherent(0, cam->rot_enc_buf_size[0], + cam->rot_enc_bufs_vaddr[0], + cam->rot_enc_bufs[0]); + cam->rot_enc_bufs_vaddr[0] = NULL; + cam->rot_enc_bufs[0] = 0; + } + if (cam->rot_enc_bufs_vaddr[1]) { + dma_free_coherent(0, cam->rot_enc_buf_size[1], + cam->rot_enc_bufs_vaddr[1], + cam->rot_enc_bufs[1]); + cam->rot_enc_bufs_vaddr[1] = NULL; + cam->rot_enc_bufs[1] = 0; + } + } + + return err; +} + +/*! + * Init the Encorder channels + * + * @return Error code indicating success or failure + */ +__init int prp_enc_init(void) +{ + return 0; +} + +/*! + * Deinit the Encorder channels + * + */ +void __exit prp_enc_exit(void) +{ +} + +module_init(prp_enc_init); +module_exit(prp_enc_exit); + +EXPORT_SYMBOL(prp_enc_select); +EXPORT_SYMBOL(prp_enc_deselect); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("IPU PRP ENC Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/mxc/capture/ipu_prp_sw.h b/drivers/media/video/mxc/capture/ipu_prp_sw.h new file mode 100644 index 000000000000..1aa53e69b477 --- /dev/null +++ b/drivers/media/video/mxc/capture/ipu_prp_sw.h @@ -0,0 +1,36 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file ipu_prp_sw.h + * + * @brief This file contains the IPU PRP use case driver header. + * + * @ingroup IPU + */ + +#ifndef _INCLUDE_IPU__PRP_SW_H_ +#define _INCLUDE_IPU__PRP_SW_H_ + +int prp_enc_select(void *private); +int prp_enc_deselect(void *private); +int prp_vf_adc_select(void *private); +int prp_vf_sdc_select(void *private); +int prp_vf_sdc_select_bg(void *private); +int prp_vf_adc_deselect(void *private); +int prp_vf_sdc_deselect(void *private); +int prp_vf_sdc_deselect_bg(void *private); +int prp_still_select(void *private); +int prp_still_deselect(void *private); + +#endif diff --git a/drivers/media/video/mxc/capture/ipu_prp_vf_adc.c b/drivers/media/video/mxc/capture/ipu_prp_vf_adc.c new file mode 100644 index 000000000000..56a82fba71a8 --- /dev/null +++ b/drivers/media/video/mxc/capture/ipu_prp_vf_adc.c @@ -0,0 +1,601 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file ipu_prp_vf_adc.c + * + * @brief IPU Use case for PRP-VF + * + * @ingroup IPU + */ + +#include "mxc_v4l2_capture.h" +#include "ipu_prp_sw.h" +#include <asm/arch/mxcfb.h> +#include <asm/arch/ipu.h> +#include <linux/dma-mapping.h> + +/* + * Function definitions + */ + +/*! + * prpvf_start - start the vf task + * + * @param private cam_data * mxc v4l2 main structure + * + */ +static int prpvf_start(void *private) +{ + cam_data *cam = (cam_data *) private; + ipu_channel_params_t vf; + ipu_channel_params_t params; + u32 format = IPU_PIX_FMT_RGB565; + u32 size = 2; + int err = 0; + + if (!cam) { + printk(KERN_ERR "prpvf_start private is NULL\n"); + return -ENXIO; + } + + if (cam->overlay_active == true) { + printk(KERN_ERR "prpvf_start already start.\n"); + return 0; + } + + mxcfb_set_refresh_mode(cam->overlay_fb, MXCFB_REFRESH_OFF, 0); + + memset(&vf, 0, sizeof(ipu_channel_params_t)); + ipu_csi_get_window_size(&vf.csi_prp_vf_adc.in_width, + &vf.csi_prp_vf_adc.in_height); + vf.csi_prp_vf_adc.in_pixel_fmt = IPU_PIX_FMT_UYVY; + vf.csi_prp_vf_adc.out_width = cam->win.w.width; + vf.csi_prp_vf_adc.out_height = cam->win.w.height; + vf.csi_prp_vf_adc.graphics_combine_en = 0; + vf.csi_prp_vf_adc.out_left = cam->win.w.left; + + /* hope to be removed when those offset taken cared by adc driver. */ +#ifdef CONFIG_FB_MXC_EPSON_QVGA_PANEL + vf.csi_prp_vf_adc.out_left += 12; +#endif +#ifdef CONFIG_FB_MXC_EPSON_PANEL + vf.csi_prp_vf_adc.out_left += 2; +#endif + + vf.csi_prp_vf_adc.out_top = cam->win.w.top; + + if (cam->rotation >= IPU_ROTATE_90_RIGHT) { + vf.csi_prp_vf_adc.out_width = cam->win.w.height; + vf.csi_prp_vf_adc.out_height = cam->win.w.width; + + size = cam->win.w.width * cam->win.w.height * size; + vf.csi_prp_vf_adc.out_pixel_fmt = format; + err = ipu_init_channel(CSI_PRP_VF_MEM, &vf); + if (err != 0) + return err; + + ipu_csi_enable_mclk(CSI_MCLK_VF, true, true); + + if (cam->vf_bufs_vaddr[0]) { + dma_free_coherent(0, cam->vf_bufs_size[0], + cam->vf_bufs_vaddr[0], + cam->vf_bufs[0]); + } + if (cam->vf_bufs_vaddr[1]) { + dma_free_coherent(0, cam->vf_bufs_size[1], + cam->vf_bufs_vaddr[1], + cam->vf_bufs[1]); + } + cam->vf_bufs_size[0] = size; + cam->vf_bufs_vaddr[0] = (void *)dma_alloc_coherent(0, + cam-> + vf_bufs_size + [0], + &cam-> + vf_bufs[0], + GFP_DMA | + GFP_KERNEL); + if (cam->vf_bufs_vaddr[0] == NULL) { + printk(KERN_ERR + "prpvf_start: Error to allocate vf buffer\n"); + err = -ENOMEM; + goto out_3; + } + cam->vf_bufs_size[1] = size; + cam->vf_bufs_vaddr[1] = (void *)dma_alloc_coherent(0, + cam-> + vf_bufs_size + [1], + &cam-> + vf_bufs[1], + GFP_DMA | + GFP_KERNEL); + if (cam->vf_bufs_vaddr[1] == NULL) { + printk(KERN_ERR + "prpvf_start: Error to allocate vf buffer\n"); + err = -ENOMEM; + goto out_3; + } + + err = ipu_init_channel_buffer(CSI_PRP_VF_MEM, IPU_OUTPUT_BUFFER, + format, + vf.csi_prp_vf_mem.out_width, + vf.csi_prp_vf_mem.out_height, + vf.csi_prp_vf_mem.out_width, + IPU_ROTATE_NONE, + cam->vf_bufs[0], cam->vf_bufs[1], + 0, 0); + if (err != 0) + goto out_3; + + if (cam->rot_vf_bufs[0]) { + dma_free_coherent(0, cam->rot_vf_buf_size[0], + cam->rot_vf_bufs_vaddr[0], + cam->rot_vf_bufs[0]); + } + if (cam->rot_vf_bufs[1]) { + dma_free_coherent(0, cam->rot_vf_buf_size[1], + cam->rot_vf_bufs_vaddr[1], + cam->rot_vf_bufs[1]); + } + cam->rot_vf_buf_size[0] = PAGE_ALIGN(size); + cam->rot_vf_bufs_vaddr[0] = (void *)dma_alloc_coherent(0, + cam-> + rot_vf_buf_size + [0], + &cam-> + rot_vf_bufs + [0], + GFP_DMA | + GFP_KERNEL); + if (cam->rot_vf_bufs_vaddr[0] == NULL) { + printk(KERN_ERR + "prpvf_start: Error to allocate rot_vf_bufs\n"); + err = -ENOMEM; + goto out_3; + } + cam->rot_vf_buf_size[1] = PAGE_ALIGN(size); + cam->rot_vf_bufs_vaddr[1] = (void *)dma_alloc_coherent(0, + cam-> + rot_vf_buf_size + [1], + &cam-> + rot_vf_bufs + [1], + GFP_DMA | + GFP_KERNEL); + if (cam->rot_vf_bufs_vaddr[1] == NULL) { + printk(KERN_ERR + "prpvf_start: Error to allocate rot_vf_bufs\n"); + err = -ENOMEM; + goto out_3; + } + err = ipu_init_channel(MEM_ROT_VF_MEM, NULL); + if (err != 0) { + printk(KERN_ERR "prpvf_start :Error " + "MEM_ROT_VF_MEM channel\n"); + goto out_3; + } + + err = ipu_init_channel_buffer(MEM_ROT_VF_MEM, IPU_INPUT_BUFFER, + format, + vf.csi_prp_vf_mem.out_width, + vf.csi_prp_vf_mem.out_height, + vf.csi_prp_vf_mem.out_width, + cam->rotation, cam->vf_bufs[0], + cam->vf_bufs[1], 0, 0); + if (err != 0) { + printk(KERN_ERR "prpvf_start: Error " + "MEM_ROT_VF_MEM input buffer\n"); + goto out_2; + } + + err = ipu_init_channel_buffer(MEM_ROT_VF_MEM, IPU_OUTPUT_BUFFER, + format, + vf.csi_prp_vf_mem.out_height, + vf.csi_prp_vf_mem.out_width, + vf.csi_prp_vf_mem.out_height, + IPU_ROTATE_NONE, + cam->rot_vf_bufs[0], + cam->rot_vf_bufs[1], 0, 0); + if (err != 0) { + printk(KERN_ERR "prpvf_start: Error " + "MEM_ROT_VF_MEM output buffer\n"); + goto out_2; + } + + err = ipu_link_channels(CSI_PRP_VF_MEM, MEM_ROT_VF_MEM); + if (err < 0) { + printk(KERN_ERR "prpvf_start: Error " + "linking CSI_PRP_VF_MEM-MEM_ROT_VF_MEM\n"); + goto out_2; + } + + ipu_disable_channel(ADC_SYS2, false); + ipu_uninit_channel(ADC_SYS2); + + params.adc_sys2.disp = DISP0; + params.adc_sys2.ch_mode = WriteTemplateNonSeq; + params.adc_sys2.out_left = cam->win.w.left; + /* going to be removed when those offset taken cared by adc driver. */ +#ifdef CONFIG_FB_MXC_EPSON_QVGA_PANEL + params.adc_sys2.out_left += 12; +#endif +#ifdef CONFIG_FB_MXC_EPSON_PANEL + params.adc_sys2.out_left += 2; +#endif + params.adc_sys2.out_top = cam->win.w.top; + err = ipu_init_channel(ADC_SYS2, ¶ms); + if (err != 0) { + printk(KERN_ERR + "prpvf_start: Error initializing ADC SYS1\n"); + goto out_2; + } + + err = ipu_init_channel_buffer(ADC_SYS2, IPU_INPUT_BUFFER, + format, + vf.csi_prp_vf_mem.out_height, + vf.csi_prp_vf_mem.out_width, + vf.csi_prp_vf_mem.out_height, + IPU_ROTATE_NONE, + cam->rot_vf_bufs[0], + cam->rot_vf_bufs[1], 0, 0); + if (err != 0) { + printk(KERN_ERR "Error initializing ADC SYS1 buffer\n"); + goto out_1; + } + + err = ipu_link_channels(MEM_ROT_VF_MEM, ADC_SYS2); + if (err < 0) { + printk(KERN_ERR + "Error linking MEM_ROT_VF_MEM-ADC_SYS2\n"); + goto out_1; + } + + ipu_enable_channel(CSI_PRP_VF_MEM); + ipu_enable_channel(MEM_ROT_VF_MEM); + ipu_enable_channel(ADC_SYS2); + + ipu_select_buffer(CSI_PRP_VF_MEM, IPU_OUTPUT_BUFFER, 0); + ipu_select_buffer(CSI_PRP_VF_MEM, IPU_OUTPUT_BUFFER, 1); + ipu_select_buffer(MEM_ROT_VF_MEM, IPU_OUTPUT_BUFFER, 0); + ipu_select_buffer(MEM_ROT_VF_MEM, IPU_OUTPUT_BUFFER, 1); + } +#ifndef CONFIG_MXC_IPU_PRP_VF_SDC + else if (cam->rotation == IPU_ROTATE_NONE) { + vf.csi_prp_vf_adc.out_pixel_fmt = IPU_PIX_FMT_BGR32; + err = ipu_init_channel(CSI_PRP_VF_ADC, &vf); + if (err != 0) { + printk(KERN_ERR "prpvf_start: Error " + "initializing CSI_PRP_VF_ADC\n"); + return err; + } + ipu_csi_enable_mclk(CSI_MCLK_VF, true, true); + err = ipu_init_channel_buffer(CSI_PRP_VF_ADC, IPU_OUTPUT_BUFFER, + format, cam->win.w.width, + cam->win.w.height, + cam->win.w.width, IPU_ROTATE_NONE, + 0, 0, 0, 0); + if (err != 0) { + printk(KERN_ERR "prpvf_start: Error " + "initializing CSI_PRP_VF_MEM\n"); + return err; + } + ipu_enable_channel(CSI_PRP_VF_ADC); + } +#endif + else { + size = cam->win.w.width * cam->win.w.height * size; + vf.csi_prp_vf_adc.out_pixel_fmt = format; + err = ipu_init_channel(CSI_PRP_VF_MEM, &vf); + if (err != 0) { + printk(KERN_ERR "prpvf_start: Error " + "initializing CSI_PRP_VF_MEM\n"); + return err; + } + + ipu_csi_enable_mclk(CSI_MCLK_VF, true, true); + + if (cam->vf_bufs[0]) { + dma_free_coherent(0, cam->vf_bufs_size[0], + cam->vf_bufs_vaddr[0], + cam->vf_bufs[0]); + } + if (cam->vf_bufs[1]) { + dma_free_coherent(0, cam->vf_bufs_size[1], + cam->vf_bufs_vaddr[1], + cam->vf_bufs[1]); + } + cam->vf_bufs_size[0] = PAGE_ALIGN(size); + cam->vf_bufs_vaddr[0] = (void *)dma_alloc_coherent(0, + cam-> + vf_bufs_size + [0], + &cam-> + vf_bufs[0], + GFP_DMA | + GFP_KERNEL); + if (cam->vf_bufs_vaddr[0] == NULL) { + printk(KERN_ERR + "prpvf_start: Error to allocate vf_bufs\n"); + err = -ENOMEM; + goto out_3; + } + cam->vf_bufs_size[1] = PAGE_ALIGN(size); + cam->vf_bufs_vaddr[1] = (void *)dma_alloc_coherent(0, + cam-> + vf_bufs_size + [1], + &cam-> + vf_bufs[1], + GFP_DMA | + GFP_KERNEL); + if (cam->vf_bufs_vaddr[1] == NULL) { + printk(KERN_ERR + "prpvf_start: Error to allocate vf_bufs\n"); + err = -ENOMEM; + goto out_3; + } + err = ipu_init_channel_buffer(CSI_PRP_VF_MEM, IPU_OUTPUT_BUFFER, + format, + vf.csi_prp_vf_mem.out_width, + vf.csi_prp_vf_mem.out_height, + vf.csi_prp_vf_mem.out_width, + cam->rotation, + cam->vf_bufs[0], cam->vf_bufs[1], + 0, 0); + if (err != 0) { + printk(KERN_ERR "prpvf_start: Error " + "initializing CSI_PRP_VF_MEM\n"); + goto out_3; + } + + ipu_disable_channel(ADC_SYS2, false); + ipu_uninit_channel(ADC_SYS2); + + params.adc_sys2.disp = DISP0; + params.adc_sys2.ch_mode = WriteTemplateNonSeq; + params.adc_sys2.out_left = cam->win.w.left; + // going to be removed when those offset taken cared by adc driver. +#ifdef CONFIG_FB_MXC_EPSON_QVGA_PANEL + params.adc_sys2.out_left += 12; +#endif +#ifdef CONFIG_FB_MXC_EPSON_PANEL + params.adc_sys2.out_left += 2; +#endif + params.adc_sys2.out_top = cam->win.w.top; + err = ipu_init_channel(ADC_SYS2, ¶ms); + if (err != 0) { + printk(KERN_ERR "prpvf_start: Error " + "initializing ADC_SYS2\n"); + goto out_3; + } + + err = ipu_init_channel_buffer(ADC_SYS2, IPU_INPUT_BUFFER, + format, + vf.csi_prp_vf_mem.out_width, + vf.csi_prp_vf_mem.out_height, + vf.csi_prp_vf_mem.out_width, + IPU_ROTATE_NONE, cam->vf_bufs[0], + cam->vf_bufs[1], 0, 0); + if (err != 0) { + printk(KERN_ERR "prpvf_start: Error " + "initializing ADC SYS1 buffer\n"); + goto out_1; + } + + err = ipu_link_channels(CSI_PRP_VF_MEM, ADC_SYS2); + if (err < 0) { + printk(KERN_ERR "prpvf_start: Error " + "linking MEM_ROT_VF_MEM-ADC_SYS2\n"); + goto out_1; + } + + ipu_enable_channel(CSI_PRP_VF_MEM); + ipu_enable_channel(ADC_SYS2); + + ipu_select_buffer(CSI_PRP_VF_MEM, IPU_OUTPUT_BUFFER, 0); + ipu_select_buffer(CSI_PRP_VF_MEM, IPU_OUTPUT_BUFFER, 1); + } + + cam->overlay_active = true; + return err; + + out_1: + ipu_uninit_channel(ADC_SYS2); + out_2: + if (cam->rotation >= IPU_ROTATE_90_RIGHT) { + ipu_uninit_channel(MEM_ROT_VF_MEM); + } + out_3: + ipu_uninit_channel(CSI_PRP_VF_MEM); + if (cam->rot_vf_bufs_vaddr[0]) { + dma_free_coherent(0, cam->rot_vf_buf_size[0], + cam->rot_vf_bufs_vaddr[0], + cam->rot_vf_bufs[0]); + cam->rot_vf_bufs_vaddr[0] = NULL; + cam->rot_vf_bufs[0] = 0; + } + if (cam->rot_vf_bufs_vaddr[1]) { + dma_free_coherent(0, cam->rot_vf_buf_size[1], + cam->rot_vf_bufs_vaddr[1], + cam->rot_vf_bufs[1]); + cam->rot_vf_bufs_vaddr[1] = NULL; + cam->rot_vf_bufs[1] = 0; + } + if (cam->vf_bufs_vaddr[0]) { + dma_free_coherent(0, cam->vf_bufs_size[0], + cam->vf_bufs_vaddr[0], cam->vf_bufs[0]); + cam->vf_bufs_vaddr[0] = NULL; + cam->vf_bufs[0] = 0; + } + if (cam->vf_bufs_vaddr[1]) { + dma_free_coherent(0, cam->vf_bufs_size[1], + cam->vf_bufs_vaddr[1], cam->vf_bufs[1]); + cam->vf_bufs_vaddr[1] = NULL; + cam->vf_bufs[1] = 0; + } + return err; +} + +/*! + * prpvf_stop - stop the vf task + * + * @param private cam_data * mxc v4l2 main structure + * + */ +static int prpvf_stop(void *private) +{ + cam_data *cam = (cam_data *) private; + int err = 0; + + if (cam->overlay_active == false) + return 0; + + if (cam->rotation >= IPU_ROTATE_90_RIGHT) { + ipu_unlink_channels(CSI_PRP_VF_MEM, MEM_ROT_VF_MEM); + ipu_unlink_channels(MEM_ROT_VF_MEM, ADC_SYS2); + + ipu_disable_channel(CSI_PRP_VF_MEM, true); + ipu_disable_channel(MEM_ROT_VF_MEM, true); + ipu_disable_channel(ADC_SYS2, true); + + ipu_uninit_channel(CSI_PRP_VF_MEM); + ipu_uninit_channel(MEM_ROT_VF_MEM); + ipu_uninit_channel(ADC_SYS2); + + ipu_csi_enable_mclk(CSI_MCLK_VF, false, false); + } +#ifndef CONFIG_MXC_IPU_PRP_VF_SDC + else if (cam->rotation == IPU_ROTATE_NONE) { + ipu_disable_channel(CSI_PRP_VF_ADC, false); + ipu_uninit_channel(CSI_PRP_VF_ADC); + ipu_csi_enable_mclk(CSI_MCLK_VF, false, false); + } +#endif + else { + ipu_unlink_channels(CSI_PRP_VF_MEM, ADC_SYS2); + + ipu_disable_channel(CSI_PRP_VF_MEM, true); + ipu_disable_channel(ADC_SYS2, true); + + ipu_uninit_channel(CSI_PRP_VF_MEM); + ipu_uninit_channel(ADC_SYS2); + + ipu_csi_enable_mclk(CSI_MCLK_VF, false, false); + } + + if (cam->vf_bufs_vaddr[0]) { + dma_free_coherent(0, cam->vf_bufs_size[0], + cam->vf_bufs_vaddr[0], cam->vf_bufs[0]); + cam->vf_bufs_vaddr[0] = NULL; + cam->vf_bufs[0] = 0; + } + if (cam->vf_bufs_vaddr[1]) { + dma_free_coherent(0, cam->vf_bufs_size[1], + cam->vf_bufs_vaddr[1], cam->vf_bufs[1]); + cam->vf_bufs_vaddr[1] = NULL; + cam->vf_bufs[1] = 0; + } + if (cam->rot_vf_bufs_vaddr[0]) { + dma_free_coherent(0, cam->rot_vf_buf_size[0], + cam->rot_vf_bufs_vaddr[0], + cam->rot_vf_bufs[0]); + cam->rot_vf_bufs_vaddr[0] = NULL; + cam->rot_vf_bufs[0] = 0; + } + if (cam->rot_vf_bufs_vaddr[1]) { + dma_free_coherent(0, cam->rot_vf_buf_size[1], + cam->rot_vf_bufs_vaddr[1], + cam->rot_vf_bufs[1]); + cam->rot_vf_bufs_vaddr[1] = NULL; + cam->rot_vf_bufs[1] = 0; + } + + cam->overlay_active = false; + + mxcfb_set_refresh_mode(cam->overlay_fb, MXCFB_REFRESH_PARTIAL, 0); + return err; +} + +/*! + * function to select PRP-VF as the working path + * + * @param private cam_data * mxc v4l2 main structure + * + * @return status + */ +int prp_vf_adc_select(void *private) +{ + cam_data *cam; + if (private) { + cam = (cam_data *) private; + cam->vf_start_adc = prpvf_start; + cam->vf_stop_adc = prpvf_stop; + cam->overlay_active = false; + } else { + return -EIO; + } + return 0; +} + +/*! + * function to de-select PRP-VF as the working path + * + * @param private cam_data * mxc v4l2 main structure + * + * @return status + */ +int prp_vf_adc_deselect(void *private) +{ + cam_data *cam; + int err = 0; + err = prpvf_stop(private); + + if (private) { + cam = (cam_data *) private; + cam->vf_start_adc = NULL; + cam->vf_stop_adc = NULL; + } + return err; +} + +/*! + * Init viewfinder task. + * + * @return Error code indicating success or failure + */ +__init int prp_vf_adc_init(void) +{ + return 0; +} + +/*! + * Deinit viewfinder task. + * + * @return Error code indicating success or failure + */ +void __exit prp_vf_adc_exit(void) +{ +} + +module_init(prp_vf_adc_init); +module_exit(prp_vf_adc_exit); + +EXPORT_SYMBOL(prp_vf_adc_select); +EXPORT_SYMBOL(prp_vf_adc_deselect); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("IPU PRP VF ADC Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/mxc/capture/ipu_prp_vf_sdc.c b/drivers/media/video/mxc/capture/ipu_prp_vf_sdc.c new file mode 100644 index 000000000000..def68818c693 --- /dev/null +++ b/drivers/media/video/mxc/capture/ipu_prp_vf_sdc.c @@ -0,0 +1,462 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file ipu_prp_vf_sdc.c + * + * @brief IPU Use case for PRP-VF + * + * @ingroup IPU + */ + +#include "mxc_v4l2_capture.h" +#include <asm/arch/ipu.h> +#include "ipu_prp_sw.h" +#include <linux/dma-mapping.h> + +/* + * Function definitions + */ + +/*! + * prpvf_start - start the vf task + * + * @param private cam_data * mxc v4l2 main structure + * + */ +static int prpvf_start(void *private) +{ + cam_data *cam = (cam_data *) private; + ipu_channel_params_t vf; + u32 format = IPU_PIX_FMT_RGB565; + u32 size = 2; + int err = 0; + + if (!cam) { + printk(KERN_ERR "private is NULL\n"); + return -EIO; + } + + if (cam->overlay_active == true) { + pr_debug("already started.\n"); + return 0; + } + + memset(&vf, 0, sizeof(ipu_channel_params_t)); + ipu_csi_get_window_size(&vf.csi_prp_vf_mem.in_width, + &vf.csi_prp_vf_mem.in_height); + vf.csi_prp_vf_mem.in_pixel_fmt = IPU_PIX_FMT_UYVY; + vf.csi_prp_vf_mem.out_width = cam->win.w.width; + vf.csi_prp_vf_mem.out_height = cam->win.w.height; + if (cam->rotation >= IPU_ROTATE_90_RIGHT) { + vf.csi_prp_vf_mem.out_width = cam->win.w.height; + vf.csi_prp_vf_mem.out_height = cam->win.w.width; + } + vf.csi_prp_vf_mem.out_pixel_fmt = format; + size = cam->win.w.width * cam->win.w.height * size; + + err = ipu_init_channel(CSI_PRP_VF_MEM, &vf); + if (err != 0) + goto out_4; + + ipu_csi_enable_mclk(CSI_MCLK_VF, true, true); + + if (cam->vf_bufs_vaddr[0]) { + dma_free_coherent(0, cam->vf_bufs_size[0], + cam->vf_bufs_vaddr[0], + (dma_addr_t) cam->vf_bufs[0]); + } + if (cam->vf_bufs_vaddr[1]) { + dma_free_coherent(0, cam->vf_bufs_size[1], + cam->vf_bufs_vaddr[1], + (dma_addr_t) cam->vf_bufs[1]); + } + cam->vf_bufs_size[0] = size; + cam->vf_bufs_vaddr[0] = (void *)dma_alloc_coherent(0, + cam->vf_bufs_size[0], + (dma_addr_t *) & + cam->vf_bufs[0], + GFP_DMA | + GFP_KERNEL); + if (cam->vf_bufs_vaddr[0] == NULL) { + printk(KERN_ERR "Error to allocate vf buffer\n"); + err = -ENOMEM; + goto out_3; + } + cam->vf_bufs_size[1] = size; + cam->vf_bufs_vaddr[1] = (void *)dma_alloc_coherent(0, + cam->vf_bufs_size[1], + (dma_addr_t *) & + cam->vf_bufs[1], + GFP_DMA | + GFP_KERNEL); + if (cam->vf_bufs_vaddr[1] == NULL) { + printk(KERN_ERR "Error to allocate vf buffer\n"); + err = -ENOMEM; + goto out_3; + } + pr_debug("vf_bufs %x %x\n", cam->vf_bufs[0], cam->vf_bufs[1]); + + if (cam->rotation >= IPU_ROTATE_90_RIGHT) { + err = ipu_init_channel_buffer(CSI_PRP_VF_MEM, IPU_OUTPUT_BUFFER, + format, + vf.csi_prp_vf_mem.out_width, + vf.csi_prp_vf_mem.out_height, + vf.csi_prp_vf_mem.out_width, + IPU_ROTATE_NONE, cam->vf_bufs[0], + cam->vf_bufs[1], 0, 0); + if (err != 0) { + goto out_3; + } + + if (cam->rot_vf_bufs[0]) { + dma_free_coherent(0, cam->rot_vf_buf_size[0], + cam->rot_vf_bufs_vaddr[0], + (dma_addr_t) cam->rot_vf_bufs[0]); + cam->rot_vf_bufs_vaddr[0] = NULL; + cam->rot_vf_bufs[0] = 0; + } + if (cam->rot_vf_bufs[1]) { + dma_free_coherent(0, cam->rot_vf_buf_size[1], + cam->rot_vf_bufs_vaddr[1], + (dma_addr_t) cam->rot_vf_bufs[1]); + cam->rot_vf_bufs_vaddr[1] = NULL; + cam->rot_vf_bufs[1] = 0; + } + cam->rot_vf_buf_size[0] = PAGE_ALIGN(size); + cam->rot_vf_bufs_vaddr[0] = (void *)dma_alloc_coherent(0, + cam-> + rot_vf_buf_size + [0], + &cam-> + rot_vf_bufs + [0], + GFP_DMA | + GFP_KERNEL); + if (cam->rot_vf_bufs_vaddr[0] == NULL) { + printk(KERN_ERR "alloc rot_vf_bufs.\n"); + err = -ENOMEM; + goto out_3; + } + cam->rot_vf_buf_size[1] = PAGE_ALIGN(size); + cam->rot_vf_bufs_vaddr[1] = (void *)dma_alloc_coherent(0, + cam-> + rot_vf_buf_size + [0], + &cam-> + rot_vf_bufs + [1], + GFP_DMA | + GFP_KERNEL); + if (cam->rot_vf_bufs_vaddr[1] == NULL) { + printk(KERN_ERR "alloc rot_vf_bufs.\n"); + err = -ENOMEM; + goto out_3; + } + pr_debug("rot_vf_bufs %x %x\n", cam->rot_vf_bufs[0], + cam->rot_vf_bufs[1]); + + err = ipu_init_channel(MEM_ROT_VF_MEM, NULL); + if (err != 0) { + printk(KERN_ERR "Error MEM_ROT_VF_MEM channel\n"); + goto out_3; + } + + err = ipu_init_channel_buffer(MEM_ROT_VF_MEM, IPU_INPUT_BUFFER, + format, + vf.csi_prp_vf_mem.out_width, + vf.csi_prp_vf_mem.out_height, + vf.csi_prp_vf_mem.out_width, + cam->rotation, cam->vf_bufs[0], + cam->vf_bufs[1], 0, 0); + if (err != 0) { + printk(KERN_ERR "Error MEM_ROT_VF_MEM input buffer\n"); + goto out_3; + } + + err = ipu_init_channel_buffer(MEM_ROT_VF_MEM, IPU_OUTPUT_BUFFER, + format, + vf.csi_prp_vf_mem.out_height, + vf.csi_prp_vf_mem.out_width, + vf.csi_prp_vf_mem.out_height, + IPU_ROTATE_NONE, + cam->rot_vf_bufs[0], + cam->rot_vf_bufs[1], 0, 0); + if (err != 0) { + printk(KERN_ERR "Error MEM_ROT_VF_MEM output buffer\n"); + goto out_2; + } + + err = ipu_link_channels(CSI_PRP_VF_MEM, MEM_ROT_VF_MEM); + if (err < 0) { + printk(KERN_ERR + "Error link CSI_PRP_VF_MEM-MEM_ROT_VF_MEM\n"); + goto out_2; + } + err = ipu_init_channel(MEM_SDC_FG, NULL); + if (err != 0) + goto out_2; + + ipu_sdc_set_window_pos(MEM_SDC_FG, cam->win.w.left, + cam->win.w.top); + + err = ipu_init_channel_buffer(MEM_SDC_FG, IPU_INPUT_BUFFER, + format, + vf.csi_prp_vf_mem.out_height, + vf.csi_prp_vf_mem.out_width, + vf.csi_prp_vf_mem.out_height, + IPU_ROTATE_NONE, + cam->rot_vf_bufs[0], + cam->rot_vf_bufs[1], 0, 0); + if (err != 0) { + printk(KERN_ERR "Error initializing SDC FG buffer\n"); + goto out_2; + } + + err = ipu_link_channels(MEM_ROT_VF_MEM, MEM_SDC_FG); + if (err < 0) { + printk(KERN_ERR + "Error link MEM_ROT_VF_MEM-MEM_SDC_FG\n"); + goto out_1; + } + + ipu_enable_channel(CSI_PRP_VF_MEM); + ipu_enable_channel(MEM_ROT_VF_MEM); + ipu_enable_channel(MEM_SDC_FG); + + ipu_select_buffer(CSI_PRP_VF_MEM, IPU_OUTPUT_BUFFER, 0); + ipu_select_buffer(CSI_PRP_VF_MEM, IPU_OUTPUT_BUFFER, 1); + ipu_select_buffer(MEM_ROT_VF_MEM, IPU_OUTPUT_BUFFER, 0); + ipu_select_buffer(MEM_ROT_VF_MEM, IPU_OUTPUT_BUFFER, 1); + } else { + err = ipu_init_channel_buffer(CSI_PRP_VF_MEM, IPU_OUTPUT_BUFFER, + format, cam->win.w.width, + cam->win.w.height, + cam->win.w.width, cam->rotation, + cam->vf_bufs[0], cam->vf_bufs[1], + 0, 0); + if (err != 0) { + printk(KERN_ERR "Error initializing CSI_PRP_VF_MEM\n"); + goto out_4; + } + err = ipu_init_channel(MEM_SDC_FG, NULL); + if (err != 0) + goto out_3; + + ipu_sdc_set_window_pos(MEM_SDC_FG, cam->win.w.left, + cam->win.w.top); + err = ipu_init_channel_buffer(MEM_SDC_FG, + IPU_INPUT_BUFFER, format, + cam->win.w.width, + cam->win.w.height, + cam->win.w.width, IPU_ROTATE_NONE, + cam->vf_bufs[0], cam->vf_bufs[1], + 0, 0); + if (err != 0) { + printk(KERN_ERR "Error initializing SDC FG buffer\n"); + goto out_1; + } + + err = ipu_link_channels(CSI_PRP_VF_MEM, MEM_SDC_FG); + if (err < 0) { + printk(KERN_ERR "Error linking ipu channels\n"); + goto out_1; + } + + ipu_enable_channel(CSI_PRP_VF_MEM); + ipu_enable_channel(MEM_SDC_FG); + + ipu_select_buffer(CSI_PRP_VF_MEM, IPU_OUTPUT_BUFFER, 0); + ipu_select_buffer(CSI_PRP_VF_MEM, IPU_OUTPUT_BUFFER, 1); + } + + cam->overlay_active = true; + return err; + + out_1: + ipu_uninit_channel(MEM_SDC_FG); + out_2: + if (cam->rotation >= IPU_ROTATE_90_RIGHT) { + ipu_uninit_channel(MEM_ROT_VF_MEM); + } + out_3: + ipu_uninit_channel(CSI_PRP_VF_MEM); + out_4: + if (cam->vf_bufs_vaddr[0]) { + dma_free_coherent(0, cam->vf_bufs_size[0], + cam->vf_bufs_vaddr[0], + (dma_addr_t) cam->vf_bufs[0]); + cam->vf_bufs_vaddr[0] = NULL; + cam->vf_bufs[0] = 0; + } + if (cam->vf_bufs_vaddr[1]) { + dma_free_coherent(0, cam->vf_bufs_size[1], + cam->vf_bufs_vaddr[1], + (dma_addr_t) cam->vf_bufs[1]); + cam->vf_bufs_vaddr[1] = NULL; + cam->vf_bufs[1] = 0; + } + if (cam->rot_vf_bufs_vaddr[0]) { + dma_free_coherent(0, cam->rot_vf_buf_size[0], + cam->rot_vf_bufs_vaddr[0], + (dma_addr_t) cam->rot_vf_bufs[0]); + cam->rot_vf_bufs_vaddr[0] = NULL; + cam->rot_vf_bufs[0] = 0; + } + if (cam->rot_vf_bufs_vaddr[1]) { + dma_free_coherent(0, cam->rot_vf_buf_size[1], + cam->rot_vf_bufs_vaddr[1], + (dma_addr_t) cam->rot_vf_bufs[1]); + cam->rot_vf_bufs_vaddr[1] = NULL; + cam->rot_vf_bufs[1] = 0; + } + return err; +} + +/*! + * prpvf_stop - stop the vf task + * + * @param private cam_data * mxc v4l2 main structure + * + */ +static int prpvf_stop(void *private) +{ + cam_data *cam = (cam_data *) private; + int err = 0; + + if (cam->overlay_active == false) + return 0; + + if (cam->rotation >= IPU_ROTATE_90_RIGHT) { + ipu_unlink_channels(CSI_PRP_VF_MEM, MEM_ROT_VF_MEM); + ipu_unlink_channels(MEM_ROT_VF_MEM, MEM_SDC_FG); + } else { + ipu_unlink_channels(CSI_PRP_VF_MEM, MEM_SDC_FG); + } + + ipu_disable_channel(MEM_SDC_FG, true); + ipu_disable_channel(CSI_PRP_VF_MEM, true); + + if (cam->rotation >= IPU_ROTATE_90_RIGHT) { + ipu_disable_channel(MEM_ROT_VF_MEM, true); + ipu_uninit_channel(MEM_ROT_VF_MEM); + } + ipu_uninit_channel(MEM_SDC_FG); + ipu_uninit_channel(CSI_PRP_VF_MEM); + + ipu_csi_enable_mclk(CSI_MCLK_VF, false, false); + + if (cam->vf_bufs_vaddr[0]) { + dma_free_coherent(0, cam->vf_bufs_size[0], + cam->vf_bufs_vaddr[0], + (dma_addr_t) cam->vf_bufs[0]); + cam->vf_bufs_vaddr[0] = NULL; + cam->vf_bufs[0] = 0; + } + if (cam->vf_bufs_vaddr[1]) { + dma_free_coherent(0, cam->vf_bufs_size[1], + cam->vf_bufs_vaddr[1], + (dma_addr_t) cam->vf_bufs[1]); + cam->vf_bufs_vaddr[1] = NULL; + cam->vf_bufs[1] = 0; + } + if (cam->rot_vf_bufs_vaddr[0]) { + dma_free_coherent(0, cam->rot_vf_buf_size[0], + cam->rot_vf_bufs_vaddr[0], + (dma_addr_t) cam->rot_vf_bufs[0]); + cam->rot_vf_bufs_vaddr[0] = NULL; + cam->rot_vf_bufs[0] = 0; + } + if (cam->rot_vf_bufs_vaddr[1]) { + dma_free_coherent(0, cam->rot_vf_buf_size[1], + cam->rot_vf_bufs_vaddr[1], + (dma_addr_t) cam->rot_vf_bufs[1]); + cam->rot_vf_bufs_vaddr[1] = NULL; + cam->rot_vf_bufs[1] = 0; + } + + cam->overlay_active = false; + return err; +} + +/*! + * function to select PRP-VF as the working path + * + * @param private cam_data * mxc v4l2 main structure + * + * @return status + */ +int prp_vf_sdc_select(void *private) +{ + cam_data *cam; + int err = 0; + if (private) { + cam = (cam_data *) private; + cam->vf_start_sdc = prpvf_start; + cam->vf_stop_sdc = prpvf_stop; + cam->overlay_active = false; + } else + err = -EIO; + + return err; +} + +/*! + * function to de-select PRP-VF as the working path + * + * @param private cam_data * mxc v4l2 main structure + * + * @return int + */ +int prp_vf_sdc_deselect(void *private) +{ + cam_data *cam; + int err = 0; + err = prpvf_stop(private); + + if (private) { + cam = (cam_data *) private; + cam->vf_start_sdc = NULL; + cam->vf_stop_sdc = NULL; + } + return err; +} + +/*! + * Init viewfinder task. + * + * @return Error code indicating success or failure + */ +__init int prp_vf_sdc_init(void) +{ + return 0; +} + +/*! + * Deinit viewfinder task. + * + * @return Error code indicating success or failure + */ +void __exit prp_vf_sdc_exit(void) +{ +} + +module_init(prp_vf_sdc_init); +module_exit(prp_vf_sdc_exit); + +EXPORT_SYMBOL(prp_vf_sdc_select); +EXPORT_SYMBOL(prp_vf_sdc_deselect); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("IPU PRP VF SDC Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/mxc/capture/ipu_prp_vf_sdc_bg.c b/drivers/media/video/mxc/capture/ipu_prp_vf_sdc_bg.c new file mode 100644 index 000000000000..009cc2687d66 --- /dev/null +++ b/drivers/media/video/mxc/capture/ipu_prp_vf_sdc_bg.c @@ -0,0 +1,410 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file ipu_prp_vf_sdc_bg.c + * + * @brief IPU Use case for PRP-VF back-ground + * + * @ingroup IPU + */ +#include <linux/fb.h> +#include "mxc_v4l2_capture.h" +#include <asm/arch/ipu.h> +#include "ipu_prp_sw.h" +#include <linux/dma-mapping.h> + +static int buffer_num = 0; +static int buffer_ready = 0; + +/* + * Function definitions + */ + +/*! + * SDC V-Sync callback function. + * + * @param irq int irq line + * @param dev_id void * device id + * + * @return status IRQ_HANDLED for handled + */ +static irqreturn_t prpvf_sdc_vsync_callback(int irq, void *dev_id) +{ + pr_debug("buffer_ready %d buffer_num %d\n", buffer_ready, buffer_num); + if (buffer_ready > 0) { + ipu_select_buffer(MEM_ROT_VF_MEM, IPU_OUTPUT_BUFFER, 0); + buffer_ready--; + } + + return IRQ_HANDLED; +} + +/*! + * VF EOF callback function. + * + * @param irq int irq line + * @param dev_id void * device id + * + * @return status IRQ_HANDLED for handled + */ +static irqreturn_t prpvf_vf_eof_callback(int irq, void *dev_id) +{ + pr_debug("buffer_ready %d buffer_num %d\n", buffer_ready, buffer_num); + + ipu_select_buffer(MEM_ROT_VF_MEM, IPU_INPUT_BUFFER, buffer_num); + + buffer_num = (buffer_num == 0) ? 1 : 0; + + ipu_select_buffer(CSI_PRP_VF_MEM, IPU_OUTPUT_BUFFER, buffer_num); + buffer_ready++; + return IRQ_HANDLED; +} + +/*! + * prpvf_start - start the vf task + * + * @param private cam_data * mxc v4l2 main structure + * + */ +static int prpvf_start(void *private) +{ + cam_data *cam = (cam_data *) private; + ipu_channel_params_t vf; + u32 format; + u32 offset; + u32 size = 3; + int err = 0; + + if (!cam) { + printk(KERN_ERR "private is NULL\n"); + return -EIO; + } + + if (cam->overlay_active == true) { + pr_debug("already start.\n"); + return 0; + } + + format = cam->v4l2_fb.fmt.pixelformat; + if (cam->v4l2_fb.fmt.pixelformat == IPU_PIX_FMT_BGR24) { + size = 3; + pr_info("BGR24\n"); + } else if (cam->v4l2_fb.fmt.pixelformat == IPU_PIX_FMT_RGB565) { + size = 2; + pr_info("RGB565\n"); + } else if (cam->v4l2_fb.fmt.pixelformat == IPU_PIX_FMT_BGR32) { + size = 4; + pr_info("BGR32\n"); + } else { + printk(KERN_ERR + "unsupported fix format from the framebuffer.\n"); + return -EINVAL; + } + + offset = cam->v4l2_fb.fmt.bytesperline * cam->win.w.top + + size * cam->win.w.left; + + if (cam->v4l2_fb.base == 0) { + printk(KERN_ERR "invalid frame buffer address.\n"); + } else { + offset += (u32) cam->v4l2_fb.base; + } + + memset(&vf, 0, sizeof(ipu_channel_params_t)); + ipu_csi_get_window_size(&vf.csi_prp_vf_mem.in_width, + &vf.csi_prp_vf_mem.in_height); + vf.csi_prp_vf_mem.in_pixel_fmt = IPU_PIX_FMT_UYVY; + vf.csi_prp_vf_mem.out_width = cam->win.w.width; + vf.csi_prp_vf_mem.out_height = cam->win.w.height; + if (cam->rotation >= IPU_ROTATE_90_RIGHT) { + vf.csi_prp_vf_mem.out_width = cam->win.w.height; + vf.csi_prp_vf_mem.out_height = cam->win.w.width; + } + vf.csi_prp_vf_mem.out_pixel_fmt = format; + size = cam->win.w.width * cam->win.w.height * size; + + err = ipu_init_channel(CSI_PRP_VF_MEM, &vf); + if (err != 0) + goto out_4; + + ipu_csi_enable_mclk(CSI_MCLK_VF, true, true); + + if (cam->vf_bufs_vaddr[0]) { + dma_free_coherent(0, cam->vf_bufs_size[0], + cam->vf_bufs_vaddr[0], cam->vf_bufs[0]); + } + if (cam->vf_bufs_vaddr[1]) { + dma_free_coherent(0, cam->vf_bufs_size[1], + cam->vf_bufs_vaddr[1], cam->vf_bufs[1]); + } + cam->vf_bufs_size[0] = PAGE_ALIGN(size); + cam->vf_bufs_vaddr[0] = (void *)dma_alloc_coherent(0, + cam->vf_bufs_size[0], + &cam->vf_bufs[0], + GFP_DMA | + GFP_KERNEL); + if (cam->vf_bufs_vaddr[0] == NULL) { + printk(KERN_ERR "Error to allocate vf buffer\n"); + err = -ENOMEM; + goto out_3; + } + cam->vf_bufs_size[1] = PAGE_ALIGN(size); + cam->vf_bufs_vaddr[1] = (void *)dma_alloc_coherent(0, + cam->vf_bufs_size[1], + &cam->vf_bufs[1], + GFP_DMA | + GFP_KERNEL); + if (cam->vf_bufs_vaddr[1] == NULL) { + printk(KERN_ERR "Error to allocate vf buffer\n"); + err = -ENOMEM; + goto out_3; + } + + err = ipu_init_channel_buffer(CSI_PRP_VF_MEM, IPU_OUTPUT_BUFFER, + format, vf.csi_prp_vf_mem.out_width, + vf.csi_prp_vf_mem.out_height, + vf.csi_prp_vf_mem.out_width, + IPU_ROTATE_NONE, cam->vf_bufs[0], + cam->vf_bufs[1], 0, 0); + if (err != 0) { + printk(KERN_ERR "Error initializing CSI_PRP_VF_MEM\n"); + goto out_3; + } + err = ipu_init_channel(MEM_ROT_VF_MEM, NULL); + if (err != 0) { + printk(KERN_ERR "Error MEM_ROT_VF_MEM channel\n"); + goto out_3; + } + + err = ipu_init_channel_buffer(MEM_ROT_VF_MEM, IPU_INPUT_BUFFER, + format, vf.csi_prp_vf_mem.out_width, + vf.csi_prp_vf_mem.out_height, + vf.csi_prp_vf_mem.out_width, + cam->rotation, cam->vf_bufs[0], + cam->vf_bufs[1], 0, 0); + if (err != 0) { + printk(KERN_ERR "Error MEM_ROT_VF_MEM input buffer\n"); + goto out_2; + } + + if (cam->rotation >= IPU_ROTATE_90_RIGHT) { + err = ipu_init_channel_buffer(MEM_ROT_VF_MEM, IPU_OUTPUT_BUFFER, + format, + vf.csi_prp_vf_mem.out_height, + vf.csi_prp_vf_mem.out_width, + cam->overlay_fb->var.xres, + IPU_ROTATE_NONE, offset, 0, 0, 0); + + if (err != 0) { + printk(KERN_ERR "Error MEM_ROT_VF_MEM output buffer\n"); + goto out_2; + } + } else { + err = ipu_init_channel_buffer(MEM_ROT_VF_MEM, IPU_OUTPUT_BUFFER, + format, + vf.csi_prp_vf_mem.out_width, + vf.csi_prp_vf_mem.out_height, + cam->overlay_fb->var.xres, + IPU_ROTATE_NONE, offset, 0, 0, 0); + if (err != 0) { + printk(KERN_ERR "Error MEM_ROT_VF_MEM output buffer\n"); + goto out_2; + } + } + + err = ipu_request_irq(IPU_IRQ_PRP_VF_OUT_EOF, prpvf_vf_eof_callback, + 0, "Mxc Camera", cam); + if (err != 0) { + printk(KERN_ERR + "Error registering IPU_IRQ_PRP_VF_OUT_EOF irq.\n"); + goto out_2; + } + + err = ipu_request_irq(IPU_IRQ_SDC_BG_OUT_EOF, prpvf_sdc_vsync_callback, + 0, "Mxc Camera", NULL); + if (err != 0) { + printk(KERN_ERR + "Error registering IPU_IRQ_SDC_BG_OUT_EOF irq.\n"); + goto out_1; + } + + ipu_enable_channel(CSI_PRP_VF_MEM); + ipu_enable_channel(MEM_ROT_VF_MEM); + + buffer_num = 0; + buffer_ready = 0; + ipu_select_buffer(CSI_PRP_VF_MEM, IPU_OUTPUT_BUFFER, 0); + + cam->overlay_active = true; + return err; + + out_1: + ipu_free_irq(IPU_IRQ_PRP_VF_OUT_EOF, NULL); + out_2: + ipu_uninit_channel(MEM_ROT_VF_MEM); + out_3: + ipu_uninit_channel(CSI_PRP_VF_MEM); + out_4: + if (cam->vf_bufs_vaddr[0]) { + dma_free_coherent(0, cam->vf_bufs_size[0], + cam->vf_bufs_vaddr[0], cam->vf_bufs[0]); + cam->vf_bufs_vaddr[0] = NULL; + cam->vf_bufs[0] = 0; + } + if (cam->vf_bufs_vaddr[1]) { + dma_free_coherent(0, cam->vf_bufs_size[1], + cam->vf_bufs_vaddr[1], cam->vf_bufs[1]); + cam->vf_bufs_vaddr[1] = NULL; + cam->vf_bufs[1] = 0; + } + if (cam->rot_vf_bufs_vaddr[0]) { + dma_free_coherent(0, cam->rot_vf_buf_size[0], + cam->rot_vf_bufs_vaddr[0], + cam->rot_vf_bufs[0]); + cam->rot_vf_bufs_vaddr[0] = NULL; + cam->rot_vf_bufs[0] = 0; + } + if (cam->rot_vf_bufs_vaddr[1]) { + dma_free_coherent(0, cam->rot_vf_buf_size[1], + cam->rot_vf_bufs_vaddr[1], + cam->rot_vf_bufs[1]); + cam->rot_vf_bufs_vaddr[1] = NULL; + cam->rot_vf_bufs[1] = 0; + } + return err; +} + +/*! + * prpvf_stop - stop the vf task + * + * @param private cam_data * mxc v4l2 main structure + * + */ +static int prpvf_stop(void *private) +{ + cam_data *cam = (cam_data *) private; + + if (cam->overlay_active == false) + return 0; + + ipu_free_irq(IPU_IRQ_SDC_BG_OUT_EOF, NULL); + ipu_free_irq(IPU_IRQ_PRP_VF_OUT_EOF, cam); + + ipu_disable_channel(CSI_PRP_VF_MEM, true); + ipu_disable_channel(MEM_ROT_VF_MEM, true); + ipu_uninit_channel(CSI_PRP_VF_MEM); + ipu_uninit_channel(MEM_ROT_VF_MEM); + ipu_csi_enable_mclk(CSI_MCLK_VF, false, false); + + if (cam->vf_bufs_vaddr[0]) { + dma_free_coherent(0, cam->vf_bufs_size[0], + cam->vf_bufs_vaddr[0], cam->vf_bufs[0]); + cam->vf_bufs_vaddr[0] = NULL; + cam->vf_bufs[0] = 0; + } + if (cam->vf_bufs_vaddr[1]) { + dma_free_coherent(0, cam->vf_bufs_size[1], + cam->vf_bufs_vaddr[1], cam->vf_bufs[1]); + cam->vf_bufs_vaddr[1] = NULL; + cam->vf_bufs[1] = 0; + } + if (cam->rot_vf_bufs_vaddr[0]) { + dma_free_coherent(0, cam->rot_vf_buf_size[0], + cam->rot_vf_bufs_vaddr[0], + cam->rot_vf_bufs[0]); + cam->rot_vf_bufs_vaddr[0] = NULL; + cam->rot_vf_bufs[0] = 0; + } + if (cam->rot_vf_bufs_vaddr[1]) { + dma_free_coherent(0, cam->rot_vf_buf_size[1], + cam->rot_vf_bufs_vaddr[1], + cam->rot_vf_bufs[1]); + cam->rot_vf_bufs_vaddr[1] = NULL; + cam->rot_vf_bufs[1] = 0; + } + + buffer_num = 0; + buffer_ready = 0; + cam->overlay_active = false; + return 0; +} + +/*! + * function to select PRP-VF as the working path + * + * @param private cam_data * mxc v4l2 main structure + * + * @return status + */ +int prp_vf_sdc_select_bg(void *private) +{ + cam_data *cam = (cam_data *) private; + + if (cam) { + cam->vf_start_sdc = prpvf_start; + cam->vf_stop_sdc = prpvf_stop; + cam->overlay_active = false; + } + + return 0; +} + +/*! + * function to de-select PRP-VF as the working path + * + * @param private cam_data * mxc v4l2 main structure + * + * @return status + */ +int prp_vf_sdc_deselect_bg(void *private) +{ + cam_data *cam = (cam_data *) private; + int err = 0; + err = prpvf_stop(private); + + if (cam) { + cam->vf_start_sdc = NULL; + cam->vf_stop_sdc = NULL; + } + return err; +} + +/*! + * Init viewfinder task. + * + * @return Error code indicating success or failure + */ +__init int prp_vf_sdc_init_bg(void) +{ + return 0; +} + +/*! + * Deinit viewfinder task. + * + * @return Error code indicating success or failure + */ +void __exit prp_vf_sdc_exit_bg(void) +{ +} + +module_init(prp_vf_sdc_init_bg); +module_exit(prp_vf_sdc_exit_bg); + +EXPORT_SYMBOL(prp_vf_sdc_select_bg); +EXPORT_SYMBOL(prp_vf_sdc_deselect_bg); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("IPU PRP VF SDC Backgroud Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/mxc/capture/ipu_still.c b/drivers/media/video/mxc/capture/ipu_still.c new file mode 100644 index 000000000000..ccbe6712f1db --- /dev/null +++ b/drivers/media/video/mxc/capture/ipu_still.c @@ -0,0 +1,220 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file ipu_still.c + * + * @brief IPU Use case for still image capture + * + * @ingroup IPU + */ + +#include <asm/semaphore.h> +#include "mxc_v4l2_capture.h" +#include <asm/arch/ipu.h> +#include "ipu_prp_sw.h" + +static int callback_flag; +/* + * Function definitions + */ + +/*! + * CSI EOF callback function. + * + * @param irq int irq line + * @param dev_id void * device id + * + * @return status IRQ_HANDLED for handled + */ +static irqreturn_t prp_csi_eof_callback(int irq, void *dev_id) +{ + if (callback_flag == 2) { + ipu_select_buffer(CSI_MEM, IPU_OUTPUT_BUFFER, 0); + ipu_enable_channel(CSI_MEM); + } + + callback_flag++; + return IRQ_HANDLED; +} + +/*! + * CSI callback function. + * + * @param irq int irq line + * @param dev_id void * device id + * + * @return status IRQ_HANDLED for handled + */ +static irqreturn_t prp_still_callback(int irq, void *dev_id) +{ + cam_data *cam = (cam_data *) dev_id; + + cam->still_counter++; + wake_up_interruptible(&cam->still_queue); + + return IRQ_HANDLED; +} + +/*! + * start csi->mem task + * @param private struct cam_data * mxc capture instance + * + * @return status + */ +static int prp_still_start(void *private) +{ + cam_data *cam = (cam_data *) private; + u32 pixel_fmt; + int err; + + if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_YUV420) + pixel_fmt = IPU_PIX_FMT_YUV420P; + else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_YUV422P) + pixel_fmt = IPU_PIX_FMT_YUV422P; + else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_UYVY) + pixel_fmt = IPU_PIX_FMT_UYVY; + else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_BGR24) + pixel_fmt = IPU_PIX_FMT_BGR24; + else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_RGB24) + pixel_fmt = IPU_PIX_FMT_RGB24; + else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_RGB565) + pixel_fmt = IPU_PIX_FMT_RGB565; + else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_BGR32) + pixel_fmt = IPU_PIX_FMT_BGR32; + else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_RGB32) + pixel_fmt = IPU_PIX_FMT_RGB32; + else { + printk(KERN_ERR "format not supported\n"); + return -EINVAL; + } + + err = ipu_init_channel(CSI_MEM, NULL); + if (err != 0) + return err; + ipu_csi_enable_mclk(CSI_MCLK_RAW, true, true); + + err = ipu_init_channel_buffer(CSI_MEM, IPU_OUTPUT_BUFFER, + pixel_fmt, cam->v2f.fmt.pix.width, + cam->v2f.fmt.pix.height, + cam->v2f.fmt.pix.width, IPU_ROTATE_NONE, + cam->still_buf, 0, 0, 0); + if (err != 0) + return err; + + err = ipu_request_irq(IPU_IRQ_SENSOR_OUT_EOF, prp_still_callback, + 0, "Mxc Camera", cam); + if (err != 0) { + printk(KERN_ERR "Error registering irq.\n"); + return err; + } + callback_flag = 0; + err = ipu_request_irq(IPU_IRQ_SENSOR_EOF, prp_csi_eof_callback, + 0, "Mxc Camera", NULL); + if (err != 0) { + printk(KERN_ERR "Error IPU_IRQ_SENSOR_EOF \n"); + return err; + } + + return err; +} + +/*! + * stop csi->mem encoder task + * @param private struct cam_data * mxc capture instance + * + * @return status + */ +static int prp_still_stop(void *private) +{ + cam_data *cam = (cam_data *) private; + int err = 0; + + ipu_free_irq(IPU_IRQ_SENSOR_EOF, NULL); + ipu_free_irq(IPU_IRQ_SENSOR_OUT_EOF, cam); + + ipu_disable_channel(CSI_MEM, true); + ipu_uninit_channel(CSI_MEM); + ipu_csi_enable_mclk(CSI_MCLK_RAW, false, false); + + return err; +} + +/*! + * function to select CSI_MEM as the working path + * + * @param private struct cam_data * mxc capture instance + * + * @return status + */ +int prp_still_select(void *private) +{ + cam_data *cam = (cam_data *) private; + + if (cam) { + cam->csi_start = prp_still_start; + cam->csi_stop = prp_still_stop; + } + + return 0; +} + +/*! + * function to de-select CSI_MEM as the working path + * + * @param private struct cam_data * mxc capture instance + * + * @return status + */ +int prp_still_deselect(void *private) +{ + cam_data *cam = (cam_data *) private; + int err = 0; + + err = prp_still_stop(cam); + + if (cam) { + cam->csi_start = NULL; + cam->csi_stop = NULL; + } + + return err; +} + +/*! + * Init the Encorder channels + * + * @return Error code indicating success or failure + */ +__init int prp_still_init(void) +{ + return 0; +} + +/*! + * Deinit the Encorder channels + * + */ +void __exit prp_still_exit(void) +{ +} + +module_init(prp_still_init); +module_exit(prp_still_exit); + +EXPORT_SYMBOL(prp_still_select); +EXPORT_SYMBOL(prp_still_deselect); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("IPU PRP STILL IMAGE Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/mxc/capture/mc521da.c b/drivers/media/video/mxc/capture/mc521da.c new file mode 100644 index 000000000000..a3bcb83a1bff --- /dev/null +++ b/drivers/media/video/mxc/capture/mc521da.c @@ -0,0 +1,702 @@ +/* + * Copyright 2006-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mc521da.c + * + * @brief MC521DA camera driver functions + * + * @ingroup Camera + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/ctype.h> +#include <linux/types.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/i2c.h> +#include <linux/clk.h> +#include <asm/arch/mxc_i2c.h> +#include "mxc_v4l2_capture.h" + +#define MC521DA_I2C_ADDRESS 0x22 +#define MC521DA_TERM 0xFF + +typedef struct { + u16 width; + u16 height; +} mc521da_image_format; + +struct mc521da_reg { + u8 reg; + u8 val; +}; + +static sensor_interface *interface_param = NULL; + +static mc521da_image_format format[2] = { + { + .width = 1600, + .height = 1200, + }, + { + .width = 640, + .height = 480, + }, +}; + +const static struct mc521da_reg mc521da_initial[] = { + /*---------------------------------------------------------- + * Sensor Setting Start + *---------------------------------------------------------- + */ + {0xff, 0x01}, /* Sensor setting start */ + {0x01, 0x10}, /* Wavetable script, generated by waveman */ + {0x10, 0x64}, + {0x03, 0x00}, {0x04, 0x06}, {0x05, 0x30}, {0x06, 0x02}, {0x08, 0x00}, + {0x03, 0x01}, {0x04, 0x41}, {0x05, 0x70}, {0x06, 0x03}, {0x08, 0x00}, + {0x03, 0x02}, {0x04, 0x55}, {0x05, 0x30}, {0x06, 0x03}, {0x08, 0x00}, + {0x03, 0x03}, {0x04, 0x5A}, {0x05, 0x30}, {0x06, 0x02}, {0x08, 0x00}, + {0x03, 0x04}, {0x04, 0x7A}, {0x05, 0x30}, {0x06, 0x06}, {0x08, 0x00}, + {0x03, 0x05}, {0x04, 0x9C}, {0x05, 0x30}, {0x06, 0x0F}, {0x08, 0x00}, + {0x03, 0x06}, {0x04, 0x73}, {0x05, 0x31}, {0x06, 0x06}, {0x08, 0x00}, + {0x03, 0x07}, {0x04, 0x2D}, {0x05, 0x3B}, {0x06, 0x06}, {0x08, 0x00}, + {0x03, 0x08}, {0x04, 0x32}, {0x05, 0x33}, {0x06, 0x06}, {0x08, 0x00}, + {0x03, 0x09}, {0x04, 0x67}, {0x05, 0x63}, {0x06, 0x06}, {0x08, 0x00}, + {0x03, 0x0a}, {0x04, 0x6C}, {0x05, 0x23}, {0x06, 0x0E}, {0x08, 0x00}, + {0x03, 0x0b}, {0x04, 0x71}, {0x05, 0x23}, {0x06, 0x06}, {0x08, 0x00}, + {0x03, 0x0c}, {0x04, 0x30}, {0x05, 0x2F}, {0x06, 0x06}, {0x08, 0x00}, + {0x03, 0x0d}, {0x04, 0x00}, {0x05, 0x00}, {0x06, 0x06}, {0x08, 0x00}, + {0x07, 0x0e}, + + /* Start Address */ + {0x10, 0x64}, {0x14, 0x10}, {0x15, 0x00}, + + /* SYNC */ + {0x18, 0x40}, {0x19, 0x00}, {0x1A, 0x03}, {0x1B, 0x00}, + + /* X-Y Mirror */ + {0x11, 0x00}, {0xda, 0x00}, /* X mirror OFF, Y Mirror OFF */ + + /* Frame height */ + {0x1c, 0x13}, {0x1d, 0x04}, {0x0e, 0x4b}, {0x0f, 0x05}, + {0x9e, 0x04}, {0x9d, 0xc6}, {0xcc, 0x14}, {0xcd, 0x05}, + + /* Frame width */ + {0x0c, 0x35}, {0x0d, 0x07}, {0x9b, 0x10}, {0x9c, 0x07}, + {0x93, 0x21}, + + {0x01, 0x01}, {0x40, 0x00}, {0x41, 0x00}, {0x42, 0xf0}, + {0x43, 0x03}, {0x44, 0x0a}, {0x45, 0x00}, {0x3b, 0x40}, + {0x38, 0x18}, {0x3c, 0x00}, {0x20, 0x00}, {0x21, 0x01}, + {0x22, 0x00}, {0x23, 0x01}, {0x24, 0x00}, {0x25, 0x01}, + {0x26, 0x00}, {0x27, 0x01}, {0xb9, 0x04}, {0xb8, 0xc3}, + {0xbb, 0x04}, {0xba, 0xc3}, {0xbf, 0x04}, {0xbe, 0xc3}, + + /* Ramp */ + {0x57, 0x07}, {0x56, 0xd6}, {0x55, 0x03}, {0x54, 0x74}, + {0x9f, 0x99}, {0x94, 0x80}, {0x91, 0x78}, {0x92, 0x8b}, + + /* Output Mode */ + {0x52, 0x10}, {0x51, 0x00}, + + /* Analog Gain and Output driver */ + {0x28, 0x00}, {0xdd, 0x82}, {0xdb, 0x00}, {0xdc, 0x00}, + + /* Update */ + {0x00, 0x84}, + + /* PLL ADC clock = 75 MHz */ + {0xb5, 0x60}, {0xb4, 0x02}, {0xb5, 0x20}, + + /*----------------------------------------------*/ + /* ISP Setting Start */ + /*----------------------------------------------*/ + {0xff, 0x02}, + {0x01, 0xbd}, {0x02, 0xf8}, {0x03, 0x3a}, {0x04, 0x00}, {0x0e, 0x00}, + + /* Output mode */ + {0x88, 0x00}, {0x87, 0x11}, + + /* Threshold */ + {0xb6, 0x1b}, {0x0d, 0xc0}, {0x24, 0x00}, {0x25, 0x00}, {0x26, 0x00}, + + /* Image Effect */ + {0x3f, 0x80}, {0x40, 0x00}, {0x41, 0x00}, {0x42, 0x80}, {0x43, 0x00}, + {0x44, 0x00}, {0x45, 0x00}, {0x46, 0x00}, {0x56, 0x80}, {0x57, 0x20}, + {0x58, 0x20}, {0x59, 0x02}, {0x5a, 0x00}, {0x5b, 0x78}, {0x5c, 0x7c}, + {0x5d, 0x84}, {0x5e, 0x85}, {0x5f, 0x78}, {0x60, 0x7e}, {0x61, 0x82}, + {0x62, 0x85}, {0x63, 0x00}, {0x64, 0x80}, {0x65, 0x00}, {0x66, 0x80}, + {0x67, 0x80}, {0x68, 0x80}, + + /* Auto Focus */ + {0x6e, 0x02}, {0x6f, 0xe5}, {0x70, 0x08}, {0x71, 0x01}, {0x72, 0x00}, + + /* Decimator */ + {0x78, 0xff}, {0x79, 0xff}, {0x7a, 0x70}, {0x7b, 0x00}, {0x7c, 0x00}, + {0x7d, 0x00}, {0x7e, 0xc8}, {0x7f, 0xc8}, {0x80, 0x96}, {0x81, 0x96}, + {0x82, 0x00}, {0x83, 0x00}, {0x84, 0x00}, {0x85, 0x00}, {0x86, 0x00}, + + /* Luminance Info */ + {0xf9, 0x20}, {0xb7, 0x7f}, {0xb8, 0x28}, {0xb9, 0x08}, + {0xf9, 0xa0}, {0xb7, 0x10}, {0xb9, 0x00}, + {0xf9, 0x40}, {0xb7, 0x7f}, {0xb8, 0x28}, {0xb9, 0x08}, + {0xf9, 0xc0}, {0xb7, 0x08}, {0xb9, 0x00}, + {0xf9, 0x60}, {0xb7, 0x7f}, {0xb8, 0x28}, {0xb9, 0x08}, + {0xf9, 0xe0}, {0xb7, 0x05}, {0xb9, 0x00}, + {0xf9, 0x00}, {0xb7, 0x03}, {0xb8, 0x2d}, {0xb9, 0xcd}, + {0xf9, 0x80}, {0xb7, 0x02}, {0xb9, 0x00}, + + /* AE */ + {0x8a, 0x00}, {0x89, 0xc0}, {0x8c, 0x32}, {0x8d, 0x96}, {0x8e, 0x25}, + {0x8f, 0x70}, {0x90, 0x12}, {0x91, 0x41}, {0x9e, 0x2e}, {0x9f, 0x2e}, + {0xa0, 0x0b}, {0xa1, 0x71}, {0xa2, 0xb0}, {0xa3, 0x09}, {0xa4, 0x89}, + {0xa5, 0x68}, {0xa6, 0x1a}, {0xa7, 0xb3}, {0xa8, 0xf0}, {0xa9, 0x19}, + {0xaa, 0x6a}, {0xab, 0x6b}, {0xac, 0x01}, {0xad, 0xe8}, {0xae, 0x48}, + {0xaf, 0x01}, {0xb0, 0x96}, {0xb1, 0xe6}, {0xb2, 0x03}, {0xb3, 0x00}, + {0xb4, 0x10}, {0xb5, 0x00}, {0xb6, 0x04}, {0xba, 0x44}, {0xbb, 0x3a}, + {0xbc, 0x01}, {0xbd, 0x08}, {0xbe, 0xa0}, {0xbf, 0x01}, {0xc0, 0x82}, + {0x8a, 0xe1}, {0x8b, 0x8c}, + + /* AWB */ + {0xc8, 0x00}, {0xc9, 0x00}, {0xca, 0x40}, {0xcb, 0xB0}, {0xcc, 0x40}, + {0xcd, 0xff}, {0xce, 0x19}, {0xcf, 0x40}, {0xd0, 0x01}, {0xd1, 0x43}, + {0xd2, 0x80}, {0xd3, 0x80}, {0xd4, 0xf1}, {0xdf, 0x00}, {0xe0, 0x8f}, + {0xe1, 0x8f}, {0xe2, 0x53}, {0xe3, 0x97}, {0xe4, 0x1f}, {0xe5, 0x3b}, + {0xe6, 0x9c}, {0xe7, 0x2e}, {0xe8, 0x03}, {0xe9, 0x02}, + + /* Neutral CCM */ + {0xfa, 0x00}, {0xd5, 0x3f}, {0xd6, 0x8c}, {0xd7, 0x43}, {0xd8, 0x08}, + {0xd9, 0x27}, {0xda, 0x7e}, {0xdb, 0x17}, {0xdc, 0x1a}, {0xdd, 0x47}, + {0xde, 0xa1}, + + /* Blue CCM */ + {0xfa, 0x01}, {0xd5, 0x3f}, {0xd6, 0x77}, {0xd7, 0x34}, {0xd8, 0x03}, + {0xd9, 0x18}, {0xda, 0x6e}, {0xdb, 0x16}, {0xdc, 0x0f}, {0xdd, 0x29}, + {0xde, 0x77}, + + /* Red CCM */ + {0xfa, 0x02}, {0xd5, 0x3f}, {0xd6, 0x7d}, {0xd7, 0x2f}, {0xd8, 0x0e}, + {0xd9, 0x1e}, {0xda, 0x76}, {0xdb, 0x18}, {0xdc, 0x29}, {0xdd, 0x51}, + {0xde, 0xba}, + + /* AWB */ + {0xea, 0x00}, {0xeb, 0x1a}, {0xc8, 0x33}, {0xc9, 0xc2}, + + {0xed, 0x02}, {0xee, 0x02}, + + /* AFD */ + {0xf0, 0x11}, {0xf1, 0x03}, {0xf2, 0x05}, {0xf5, 0x05}, {0xf6, 0x32}, + {0xf7, 0x32}, + + /* Lens Shading */ + {0xf9, 0x00}, {0x05, 0x04}, {0x06, 0xff}, {0x07, 0xf2}, {0x08, 0x00}, + {0x09, 0x00}, {0x0a, 0xf2}, {0x0b, 0xff}, {0x0c, 0xff}, + {0xf9, 0x01}, {0x05, 0x04}, {0x06, 0xff}, {0x07, 0x8b}, {0x08, 0x16}, + {0x09, 0x16}, {0x0a, 0x8b}, {0x0b, 0xff}, {0x0c, 0xe0}, + {0xf9, 0x02}, {0x05, 0x04}, {0x06, 0xff}, {0x07, 0x8b}, {0x08, 0x16}, + {0x09, 0x16}, {0x0a, 0x8b}, {0x0b, 0xff}, {0x0c, 0xe0}, + {0xf9, 0x03}, {0x05, 0x04}, {0x06, 0xff}, {0x07, 0x7c}, {0x08, 0x26}, + {0x09, 0x26}, {0x0a, 0x7c}, {0x0b, 0xd0}, {0x0c, 0xe0}, + {0xf9, 0x04}, {0x05, 0x0d}, {0x06, 0x40}, {0x07, 0xa0}, {0x08, 0x00}, + {0x09, 0x00}, {0x0a, 0xa0}, {0x0b, 0x40}, {0x0c, 0xe0}, + {0xf9, 0x05}, {0x05, 0x0d}, {0x06, 0x40}, {0x07, 0xa0}, {0x08, 0x00}, + {0x09, 0x00}, {0x0a, 0xa0}, {0x0b, 0x40}, {0x0c, 0xa0}, + {0xf9, 0x06}, {0x05, 0x0d}, {0x06, 0x40}, {0x07, 0xa0}, {0x08, 0x00}, + {0x09, 0x00}, {0x0a, 0xa0}, {0x0b, 0x40}, {0x0c, 0xa0}, + {0xf9, 0x07}, {0x05, 0x0d}, {0x06, 0x40}, {0x07, 0xa0}, {0x08, 0x00}, + {0x09, 0x00}, {0x0a, 0xa0}, {0x0b, 0x40}, {0x0c, 0xa0}, + + /* Edge setting */ + {0x73, 0x68}, {0x74, 0x40}, {0x75, 0x00}, {0x76, 0xff}, {0x77, 0x80}, + {0x4f, 0x80}, {0x50, 0x82}, {0x51, 0x82}, {0x52, 0x08}, + + /* Interpolation Setting */ + {0x23, 0x7f}, {0x22, 0x08}, {0x18, 0xff}, {0x19, 0x00}, + {0x40, 0x00}, {0x53, 0xff}, {0x54, 0x0a}, {0x55, 0xc2}, + {0x1b, 0x18}, + + {0xfa, 0x00}, {0x15, 0x0c}, {0x22, 0x00}, {0x0e, 0xef}, {0x1f, 0x1d}, + {0x20, 0x2d}, {0x1c, 0x01}, {0x1d, 0x02}, {0x1e, 0x03}, {0x0e, 0xee}, + {0x12, 0x10}, {0x16, 0x10}, {0x17, 0x02}, {0x1a, 0x01}, + {0xfa, 0x04}, {0x0e, 0xef}, {0x1c, 0x01}, {0x1d, 0x02}, {0x1e, 0x03}, + {0x1f, 0x11}, {0x20, 0x11}, {0x0e, 0xee}, {0x12, 0x03}, {0x16, 0x10}, + {0x17, 0x02}, {0x1a, 0xee}, + {0xfa, 0x08}, {0x0e, 0xef}, {0x1c, 0x01}, {0x1d, 0x02}, {0x1e, 0x03}, + {0x1f, 0x00}, {0x20, 0x00}, {0x0e, 0xee}, {0x12, 0x03}, {0x16, 0x10}, + {0x17, 0x02}, {0x1a, 0x22}, + + /* Gamma Correction */ + {0x27, 0x62}, {0x28, 0x00}, {0x27, 0x62}, {0x28, 0x00}, {0x29, 0x00}, + {0x2a, 0x00}, {0x2f, 0x03}, {0x30, 0x10}, {0x31, 0x2b}, {0x32, 0x50}, + {0x33, 0x70}, {0x34, 0x90}, {0x35, 0xB0}, {0x36, 0xD0}, {0x37, 0x00}, + {0x38, 0x18}, {0x39, 0x57}, {0x3a, 0x89}, {0x3b, 0xac}, {0x3c, 0xc9}, + {0x3d, 0xde}, {0x3e, 0xef}, {0x2b, 0x00}, {0x2c, 0x00}, {0x2d, 0x40}, + {0x2e, 0xab}, + + /* Contrast */ + {0x47, 0x10}, {0x48, 0x1f}, {0x49, 0xe3}, {0x4a, 0xf0}, {0x4b, 0x08}, + {0x4c, 0x14}, {0x4d, 0xe9}, {0x4e, 0xf5}, {0x98, 0x8a}, + + {0xfa, 0x00}, + {MC521DA_TERM, MC521DA_TERM} +}; + +static int mc521da_attach(struct i2c_adapter *adapter); +static int mc521da_detach(struct i2c_client *client); + +static struct i2c_driver mc521da_i2c_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "MC521DA Client", + }, + .attach_adapter = mc521da_attach, + .detach_client = mc521da_detach, +}; + +static struct i2c_client mc521da_i2c_client = { + .name = "MC521DA I2C dev", + .addr = MC521DA_I2C_ADDRESS, + .driver = &mc521da_i2c_driver, +}; + +/* + * Function definitions + */ +static int mc521da_i2c_client_xfer(unsigned int addr, char *reg, + int reg_len, char *buf, int num, + int tran_flag) +{ + struct i2c_msg msg[2]; + int ret; + + msg[0].addr = addr; + msg[0].len = reg_len; + msg[0].buf = reg; + msg[0].flags = tran_flag; + msg[0].flags &= ~I2C_M_RD; + + msg[1].addr = addr; + msg[1].len = num; + msg[1].buf = buf; + msg[1].flags = tran_flag; + + if (tran_flag & MXC_I2C_FLAG_READ) { + msg[1].flags |= I2C_M_RD; + } else { + msg[1].flags &= ~I2C_M_RD; + } + + ret = i2c_transfer(mc521da_i2c_client.adapter, msg, 2); + if (ret >= 0) + return 0; + + return ret; +} + +static int mc521da_read_reg(u8 * reg, u8 * val) +{ + return mc521da_i2c_client_xfer(MC521DA_I2C_ADDRESS, reg, 1, val, 1, + MXC_I2C_FLAG_READ); +} + +static int mc521da_write_reg(u8 reg, u8 val) +{ + u8 temp1, temp2; + temp1 = reg; + temp2 = val; + return mc521da_i2c_client_xfer(MC521DA_I2C_ADDRESS, &temp1, 1, &temp2, + 1, 0); +} + +static int mc521da_write_regs(const struct mc521da_reg reglist[]) +{ + int err; + const struct mc521da_reg *next = reglist; + + while (!((next->reg == MC521DA_TERM) && (next->val == MC521DA_TERM))) { + err = mc521da_write_reg(next->reg, next->val); + if (err) { + return err; + } + next++; + } + return 0; +} + +/*! + * mc521da sensor downscale function + * @param downscale bool + * @return Error code indicating success or failure + */ +static u8 mc521da_sensor_downscale(bool downscale) +{ + u8 reg[1], data; + u32 i = 0; + + if (downscale == true) { + // VGA + mc521da_write_reg(0xff, 0x01); + + mc521da_write_reg(0x52, 0x30); + mc521da_write_reg(0x51, 0x00); + + mc521da_write_reg(0xda, 0x01); + mc521da_write_reg(0x00, 0x8C); + + /* Wait for changes to take effect */ + reg[0] = 0x00; + while (i < 256) { + i++; + mc521da_read_reg(reg, &data); + if ((data & 0x80) == 0) + break; + msleep(5); + } + + /* ISP */ + mc521da_write_reg(0xff, 0x02); + + mc521da_write_reg(0x03, 0x3b); /* Enable Decimator */ + + mc521da_write_reg(0x7a, 0x74); + mc521da_write_reg(0x7b, 0x01); + mc521da_write_reg(0x7e, 0x50); + mc521da_write_reg(0x7f, 0x50); + mc521da_write_reg(0x80, 0x3c); + mc521da_write_reg(0x81, 0x3c); + } else { + //UXGA + mc521da_write_reg(0xff, 0x01); + mc521da_write_reg(0x52, 0x10); + mc521da_write_reg(0x51, 0x00); + mc521da_write_reg(0xda, 0x00); + + /* update */ + mc521da_write_reg(0x00, 0x84); + + /* Wait for changes to take effect */ + reg[0] = 0x00; + while (i < 256) { + i++; + mc521da_read_reg(reg, &data); + if ((data & 0x80) == 0) + break; + msleep(5); + } + + /* ISP */ + mc521da_write_reg(0xff, 0x02); + + mc521da_write_reg(0x03, 0x3a); + + mc521da_write_reg(0x7a, 0x70); + mc521da_write_reg(0x7b, 0x00); + mc521da_write_reg(0x7e, 0xc8); + mc521da_write_reg(0x7f, 0xc8); + mc521da_write_reg(0x80, 0x96); + mc521da_write_reg(0x81, 0x96); + } + + return 0; +} + +/*! + * mc521da sensor interface Initialization + * @param param sensor_interface * + * @param width u32 + * @param height u32 + * @return None + */ +static void mc521da_interface(sensor_interface * param, u32 width, u32 height) +{ + param->clk_mode = 0x0; //gated + param->pixclk_pol = 0x0; + param->data_width = 0x1; + param->data_pol = 0x0; + param->ext_vsync = 0x0; + param->Vsync_pol = 0x0; + param->Hsync_pol = 0x0; + param->width = width - 1; + param->height = height - 1; + param->pixel_fmt = IPU_PIX_FMT_UYVY; +} + +extern void gpio_sensor_reset(bool flag); + +/*! + * mc521da Reset function + * + * @return None + */ +static sensor_interface *mc521da_reset(void) +{ + if (interface_param == NULL) + return NULL; + + mc521da_interface(interface_param, format[1].width, format[1].height); + set_mclk_rate(&interface_param->mclk); + + gpio_sensor_reset(true); + msleep(10); + gpio_sensor_reset(false); + msleep(50); + + return interface_param; +} + +/*! + * mc521da sensor configuration + * + * @param frame_rate int * + * @param high_quality int + * @return sensor_interface * + */ +static sensor_interface *mc521da_config(int *frame_rate, int high_quality) +{ + int num_clock_per_row, err; + int max_rate = 0; + int index = 1; + u16 frame_height; + + if (high_quality == 1) + index = 0; + + err = mc521da_write_regs(mc521da_initial); + if (err) { + /* Reduce the MCLK */ + interface_param->mclk = 20000000; + mc521da_reset(); + + printk(KERN_INFO "mc521da: mclk reduced\n"); + mc521da_write_regs(mc521da_initial); + } + + mc521da_interface(interface_param, format[index].width, + format[index].height); + + if (index == 0) { + mc521da_sensor_downscale(false); + } else { + mc521da_sensor_downscale(true); + } + + num_clock_per_row = 1845; + max_rate = interface_param->mclk * 3 * (index + 1) + / (2 * num_clock_per_row * 1300); + + if ((*frame_rate > max_rate) || (*frame_rate == 0)) { + *frame_rate = max_rate; + } + + frame_height = 1300 * max_rate / (*frame_rate); + + *frame_rate = interface_param->mclk * 3 * (index + 1) + / (2 * num_clock_per_row * frame_height); + + mc521da_write_reg(0xff, 0x01); + mc521da_write_reg(0xE, frame_height & 0xFF); + mc521da_write_reg(0xF, (frame_height & 0xFF00) >> 8); + mc521da_write_reg(0xCC, frame_height & 0xFF); + mc521da_write_reg(0xCD, (frame_height & 0xFF00) >> 8); + + return interface_param; +} + +/*! + * mc521da sensor set color configuration + * + * @param bright int + * @param saturation int + * @param red int + * @param green int + * @param blue int + * @return None + */ +static void +mc521da_set_color(int bright, int saturation, int red, int green, int blue) +{ + /* Select ISP */ + mc521da_write_reg(0xff, 0x02); + + mc521da_write_reg(0x41, bright); + mc521da_write_reg(0xca, red); + mc521da_write_reg(0xcb, green); + mc521da_write_reg(0xcc, blue); +} + +/*! + * mc521da sensor get color configuration + * + * @param bright int * + * @param saturation int * + * @param red int * + * @param green int * + * @param blue int * + * @return None + */ +static void +mc521da_get_color(int *bright, int *saturation, int *red, int *green, int *blue) +{ + u8 reg[1]; + u8 *pdata; + + *saturation = 0; + + /* Select ISP */ + mc521da_write_reg(0xff, 0x02); + + reg[0] = 0x41; + pdata = (u8 *) bright; + mc521da_read_reg(reg, pdata); + + reg[0] = 0xCA; + pdata = (u8 *) red; + mc521da_read_reg(reg, pdata); + + reg[0] = 0xCB; + pdata = (u8 *) green; + mc521da_read_reg(reg, pdata); + + reg[0] = 0xCC; + pdata = (u8 *) blue; + mc521da_read_reg(reg, pdata); +} + +struct camera_sensor camera_sensor_if = { + set_color:mc521da_set_color, + get_color:mc521da_get_color, + config:mc521da_config, + reset:mc521da_reset, +}; + +/*! + * mc521da I2C detect_client function + * + * @param adapter struct i2c_adapter * + * @param address int + * @param kind int + * + * @return Error code indicating success or failure + */ +static int mc521da_detect_client(struct i2c_adapter *adapter, int address, + int kind) +{ + mc521da_i2c_client.adapter = adapter; + if (i2c_attach_client(&mc521da_i2c_client)) { + mc521da_i2c_client.adapter = NULL; + printk(KERN_ERR "mc521da_attach: i2c_attach_client failed\n"); + return -1; + } + + interface_param = (sensor_interface *) + kmalloc(sizeof(sensor_interface), GFP_KERNEL); + if (!interface_param) { + printk(KERN_ERR "mc521da_attach: kmalloc failed \n"); + return -1; + } + + interface_param->mclk = 25000000; + + printk(KERN_INFO "mc521da Detected\n"); + + return 0; +} + +static unsigned short normal_i2c[] = { MC521DA_I2C_ADDRESS, I2C_CLIENT_END }; + +/* Magic definition of all other variables and things */ +I2C_CLIENT_INSMOD; + +static int mc521da_attach(struct i2c_adapter *adap) +{ + uint32_t mclk = 25000000; + struct clk *clk; + int err; + + clk = clk_get(NULL, "csi_clk"); + clk_enable(clk); + set_mclk_rate(&mclk); + + gpio_sensor_reset(true); + msleep(10); + gpio_sensor_reset(false); + msleep(100); + + err = i2c_probe(adap, &addr_data, &mc521da_detect_client); + + clk_disable(clk); + clk_put(clk); + + return err; +} + +/*! + * mc521da I2C detach function + * + * @param client struct i2c_client * + * @return Error code indicating success or failure + */ +static int mc521da_detach(struct i2c_client *client) +{ + int err; + + if (!mc521da_i2c_client.adapter) + return -1; + + err = i2c_detach_client(&mc521da_i2c_client); + mc521da_i2c_client.adapter = NULL; + + if (interface_param) + kfree(interface_param); + interface_param = NULL; + + return err; +} + +extern void gpio_sensor_active(void); +extern void gpio_sensor_inactive(void); + +/*! + * mc521da init function + * + * @return Error code indicating success or failure + */ +static __init int mc521da_init(void) +{ + gpio_sensor_active(); + return i2c_add_driver(&mc521da_i2c_driver); +} + +/*! + * mc521da cleanup function + * + * @return Error code indicating success or failure + */ +static void __exit mc521da_clean(void) +{ + i2c_del_driver(&mc521da_i2c_driver); + gpio_sensor_inactive(); +} + +module_init(mc521da_init); +module_exit(mc521da_clean); + +/* Exported symbols for modules. */ +EXPORT_SYMBOL(camera_sensor_if); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MC521DA Camera Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/mxc/capture/mt9v111.c b/drivers/media/video/mxc/capture/mt9v111.c new file mode 100644 index 000000000000..c34edf046b53 --- /dev/null +++ b/drivers/media/video/mxc/capture/mt9v111.c @@ -0,0 +1,731 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mt9v111.c + * + * @brief mt9v111 camera driver functions + * + * @ingroup Camera + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/ctype.h> +#include <linux/types.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/i2c.h> +#include <linux/clk.h> +#include <asm/arch/mxc_i2c.h> +#include "mxc_v4l2_capture.h" +#include "mt9v111.h" + +#ifdef MT9V111_DEBUG +static u16 testpattern = 0; +#endif + +static sensor_interface *interface_param = NULL; +static mt9v111_conf mt9v111_device; +static int reset_frame_rate = 30; + +#define MT9V111_FRAME_RATE_NUM 20 + +static mt9v111_image_format format[2] = { + { + .index = 0, + .width = 640, + .height = 480, + }, + { + .index = 1, + .width = 352, + .height = 288, + }, +}; + +static int mt9v111_attach(struct i2c_adapter *adapter); +static int mt9v111_detach(struct i2c_client *client); + +static struct i2c_driver mt9v111_i2c_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "MT9V111 Client", + }, + .attach_adapter = mt9v111_attach, + .detach_client = mt9v111_detach, +}; + +static struct i2c_client mt9v111_i2c_client = { + .name = "mt9v111 I2C dev", + .addr = MT9V111_I2C_ADDRESS, + .driver = &mt9v111_i2c_driver, +}; + +/* + * Function definitions + */ + +static u16 mt9v111_endian_swap16(u16 data) +{ + u16 temp; + + temp = data; + temp = ((data >> 8) & 0xff) | ((data << 8) & 0xff00); + + return temp; +} + +static int mt9v111_i2c_client_xfer(unsigned int addr, char *reg, int reg_len, + char *buf, int num, int tran_flag) +{ + struct i2c_msg msg[2]; + int ret; + + msg[0].addr = addr; + msg[0].len = reg_len; + msg[0].buf = reg; + msg[0].flags = tran_flag; + msg[0].flags &= ~I2C_M_RD; + + msg[1].addr = addr; + msg[1].len = num; + msg[1].buf = buf; + msg[1].flags = tran_flag; + + if (tran_flag & MXC_I2C_FLAG_READ) { + msg[1].flags |= I2C_M_RD; + } else { + msg[1].flags &= ~I2C_M_RD; + } + + ret = i2c_transfer(mt9v111_i2c_client.adapter, msg, 2); + if (ret >= 0) + return 0; + + return ret; +} + +#ifdef MT9V111_DEBUG +static int mt9v111_read_reg(u8 * reg, u16 * val) +{ + return mt9v111_i2c_client_xfer(MT9V111_I2C_ADDRESS, reg, 1, + (u8 *) val, 2, MXC_I2C_FLAG_READ); +} +#endif + +static int mt9v111_write_reg(u8 reg, u16 val) +{ + u8 temp1; + u16 temp2; + temp1 = reg; + temp2 = mt9v111_endian_swap16(val); + pr_debug("write reg %x val %x.\n", reg, val); + return mt9v111_i2c_client_xfer(MT9V111_I2C_ADDRESS, &temp1, 1, + (u8 *) & temp2, 2, 0); +} + +/*! + * Initialize mt9v111_sensor_lib + * Libarary for Sensor configuration through I2C + * + * @param coreReg Core Registers + * @param ifpReg IFP Register + * + * @return status + */ +static u8 mt9v111_sensor_lib(mt9v111_coreReg * coreReg, mt9v111_IFPReg * ifpReg) +{ + u8 reg; + u16 data; + u8 error = 0; + + /* + * setup to IFP registers + */ + reg = MT9V111I_ADDR_SPACE_SEL; + data = ifpReg->addrSpaceSel; + mt9v111_write_reg(reg, data); + + // Operation Mode Control + reg = MT9V111I_MODE_CONTROL; + data = ifpReg->modeControl; + mt9v111_write_reg(reg, data); + + // Output format + reg = MT9V111I_FORMAT_CONTROL; + data = ifpReg->formatControl; // Set bit 12 + mt9v111_write_reg(reg, data); + + // AE limit 4 + reg = MT9V111I_SHUTTER_WIDTH_LIMIT_AE; + data = ifpReg->gainLimitAE; + mt9v111_write_reg(reg, data); + + reg = MT9V111I_OUTPUT_FORMAT_CTRL2; + data = ifpReg->outputFormatCtrl2; + mt9v111_write_reg(reg, data); + + reg = MT9V111I_AE_SPEED; + data = ifpReg->AESpeed; + mt9v111_write_reg(reg, data); + + /* output image size */ + reg = MT9V111i_H_PAN; + data = 0x8000 | ifpReg->HPan; + mt9v111_write_reg(reg, data); + + reg = MT9V111i_H_ZOOM; + data = 0x8000 | ifpReg->HZoom; + mt9v111_write_reg(reg, data); + + reg = MT9V111i_H_SIZE; + data = 0x8000 | ifpReg->HSize; + mt9v111_write_reg(reg, data); + + reg = MT9V111i_V_PAN; + data = 0x8000 | ifpReg->VPan; + mt9v111_write_reg(reg, data); + + reg = MT9V111i_V_ZOOM; + data = 0x8000 | ifpReg->VZoom; + mt9v111_write_reg(reg, data); + + reg = MT9V111i_V_SIZE; + data = 0x8000 | ifpReg->VSize; + mt9v111_write_reg(reg, data); + + reg = MT9V111i_H_PAN; + data = ~0x8000 & ifpReg->HPan; + mt9v111_write_reg(reg, data); +#if 0 + reg = MT9V111I_UPPER_SHUTTER_DELAY_LIM; + data = ifpReg->upperShutterDelayLi; + mt9v111_write_reg(reg, data); + + reg = MT9V111I_SHUTTER_60; + data = ifpReg->shutter_width_60; + mt9v111_write_reg(reg, data); + + reg = MT9V111I_SEARCH_FLICK_60; + data = ifpReg->search_flicker_60; + mt9v111_write_reg(reg, data); +#endif + + /* + * setup to sensor core registers + */ + reg = MT9V111I_ADDR_SPACE_SEL; + data = coreReg->addressSelect; + mt9v111_write_reg(reg, data); + + // enable changes and put the Sync bit on + reg = MT9V111S_OUTPUT_CTRL; + data = MT9V111S_OUTCTRL_SYNC | MT9V111S_OUTCTRL_CHIP_ENABLE | 0x3000; + mt9v111_write_reg(reg, data); + + // min PIXCLK - Default + reg = MT9V111S_PIXEL_CLOCK_SPEED; + data = coreReg->pixelClockSpeed; + mt9v111_write_reg(reg, data); + + //Setup image flipping / Dark rows / row/column skip + reg = MT9V111S_READ_MODE; + data = coreReg->readMode; + mt9v111_write_reg(reg, data); + + //zoom 0 + reg = MT9V111S_DIGITAL_ZOOM; + data = coreReg->digitalZoom; + mt9v111_write_reg(reg, data); + + // min H-blank + reg = MT9V111S_HOR_BLANKING; + data = coreReg->horizontalBlanking; + mt9v111_write_reg(reg, data); + + // min V-blank + reg = MT9V111S_VER_BLANKING; + data = coreReg->verticalBlanking; + mt9v111_write_reg(reg, data); + + reg = MT9V111S_SHUTTER_WIDTH; + data = coreReg->shutterWidth; + mt9v111_write_reg(reg, data); + + reg = MT9V111S_SHUTTER_DELAY; + data = ifpReg->upperShutterDelayLi; + mt9v111_write_reg(reg, data); + + // changes become effective + reg = MT9V111S_OUTPUT_CTRL; + data = MT9V111S_OUTCTRL_CHIP_ENABLE | 0x3000; + mt9v111_write_reg(reg, data); + + return error; +} + +/*! + * mt9v111 sensor interface Initialization + * @param param sensor_interface * + * @param width u32 + * @param height u32 + * @return None + */ +static void mt9v111_interface(sensor_interface * param, u32 width, u32 height) +{ + param->Vsync_pol = 0x0; + param->clk_mode = 0x0; //gated + param->pixclk_pol = 0x0; + param->data_width = 0x1; + param->data_pol = 0x0; + param->ext_vsync = 0x0; + param->Vsync_pol = 0x0; + param->Hsync_pol = 0x0; + param->width = width - 1; + param->height = height - 1; + param->pixel_fmt = IPU_PIX_FMT_UYVY; + param->mclk = 27000000; +} + +/*! + * MT9V111 frame rate calculate + * + * @param frame_rate int * + * @param mclk int + * @return None + */ +static void mt9v111_rate_cal(int *frame_rate, int mclk) +{ + int num_clock_per_row; + int max_rate = 0; + + mt9v111_device.coreReg->horizontalBlanking = MT9V111_HORZBLANK_MIN; + + num_clock_per_row = (format[0].width + 114 + MT9V111_HORZBLANK_MIN) * 2; + max_rate = mclk / (num_clock_per_row * + (format[0].height + MT9V111_VERTBLANK_DEFAULT)); + + if ((*frame_rate > max_rate) || (*frame_rate == 0)) { + *frame_rate = max_rate; + } + + mt9v111_device.coreReg->verticalBlanking + = mclk / (*frame_rate * num_clock_per_row) - format[0].height; + + reset_frame_rate = *frame_rate; +} + +/*! + * MT9V111 sensor configuration + * + * @param frame_rate int * + * @param high_quality int + * @return sensor_interface * + */ +sensor_interface *mt9v111_config(int *frame_rate, int high_quality) +{ + u32 out_width, out_height; + + if (interface_param == NULL) + return NULL; + + mt9v111_device.coreReg->addressSelect = MT9V111I_SEL_SCA; + mt9v111_device.ifpReg->addrSpaceSel = MT9V111I_SEL_IFP; + + mt9v111_device.coreReg->windowHeight = MT9V111_WINHEIGHT; + mt9v111_device.coreReg->windowWidth = MT9V111_WINWIDTH; + mt9v111_device.coreReg->zoomColStart = 0; + mt9v111_device.coreReg->zomRowStart = 0; + mt9v111_device.coreReg->digitalZoom = 0x0; + + mt9v111_device.coreReg->verticalBlanking = MT9V111_VERTBLANK_DEFAULT; + mt9v111_device.coreReg->horizontalBlanking = MT9V111_HORZBLANK_MIN; + mt9v111_device.coreReg->pixelClockSpeed = 0; + mt9v111_device.coreReg->readMode = 0xd0a1; + + mt9v111_device.ifpReg->outputFormatCtrl2 = 0; + mt9v111_device.ifpReg->gainLimitAE = 0x300; + mt9v111_device.ifpReg->AESpeed = 0x80; + + // here is the default value + mt9v111_device.ifpReg->formatControl = 0xc800; + mt9v111_device.ifpReg->modeControl = 0x708e; + mt9v111_device.ifpReg->awbSpeed = 0x4514; + mt9v111_device.coreReg->shutterWidth = 0xf8; + + out_width = 640; + out_height = 480; + + /*output size */ + mt9v111_device.ifpReg->HPan = 0; + mt9v111_device.ifpReg->HZoom = 640; + mt9v111_device.ifpReg->HSize = out_width; + mt9v111_device.ifpReg->VPan = 0; + mt9v111_device.ifpReg->VZoom = 480; + mt9v111_device.ifpReg->VSize = out_height; + + mt9v111_interface(interface_param, out_width, out_height); + set_mclk_rate(&interface_param->mclk); + mt9v111_rate_cal(frame_rate, interface_param->mclk); + mt9v111_sensor_lib(mt9v111_device.coreReg, mt9v111_device.ifpReg); + + return interface_param; +} + +/*! + * mt9v111 sensor set color configuration + * + * @param bright int + * @param saturation int + * @param red int + * @param green int + * @param blue int + * @return None + */ +static void +mt9v111_set_color(int bright, int saturation, int red, int green, int blue) +{ + u8 reg; + u16 data; + + switch (saturation) { + case 100: + mt9v111_device.ifpReg->awbSpeed = 0x4514; + break; + case 150: + mt9v111_device.ifpReg->awbSpeed = 0x6D14; + break; + case 75: + mt9v111_device.ifpReg->awbSpeed = 0x4D14; + break; + case 50: + mt9v111_device.ifpReg->awbSpeed = 0x5514; + break; + case 37: + mt9v111_device.ifpReg->awbSpeed = 0x5D14; + break; + case 25: + mt9v111_device.ifpReg->awbSpeed = 0x6514; + break; + default: + mt9v111_device.ifpReg->awbSpeed = 0x4514; + break; + } + + reg = MT9V111I_ADDR_SPACE_SEL; + data = mt9v111_device.ifpReg->addrSpaceSel; + mt9v111_write_reg(reg, data); + + // Operation Mode Control + reg = MT9V111I_AWB_SPEED; + data = mt9v111_device.ifpReg->awbSpeed; + mt9v111_write_reg(reg, data); +} + +/*! + * mt9v111 sensor get color configuration + * + * @param bright int * + * @param saturation int * + * @param red int * + * @param green int * + * @param blue int * + * @return None + */ +static void +mt9v111_get_color(int *bright, int *saturation, int *red, int *green, int *blue) +{ + *saturation = (mt9v111_device.ifpReg->awbSpeed & 0x3800) >> 11; + switch (*saturation) { + case 0: + *saturation = 100; + break; + case 1: + *saturation = 75; + break; + case 2: + *saturation = 50; + break; + case 3: + *saturation = 37; + break; + case 4: + *saturation = 25; + break; + case 5: + *saturation = 150; + break; + case 6: + *saturation = 0; + break; + default: + *saturation = 0; + break; + } +} + +/*! + * mt9v111 sensor set AE measurement window mode configuration + * + * @param ae_mode int + * @return None + */ +static void mt9v111_set_ae_mode(int ae_mode) +{ + u8 reg; + u16 data; + + mt9v111_device.ifpReg->modeControl &= 0xfff3; + mt9v111_device.ifpReg->modeControl |= (ae_mode & 0x03) << 2; + + reg = MT9V111I_ADDR_SPACE_SEL; + data = mt9v111_device.ifpReg->addrSpaceSel; + mt9v111_write_reg(reg, data); + + reg = MT9V111I_MODE_CONTROL; + data = mt9v111_device.ifpReg->modeControl; + mt9v111_write_reg(reg, data); +} + +/*! + * mt9v111 sensor get AE measurement window mode configuration + * + * @param ae_mode int * + * @return None + */ +static void mt9v111_get_ae_mode(int *ae_mode) +{ + if (ae_mode != NULL) { + *ae_mode = (mt9v111_device.ifpReg->modeControl & 0xc) >> 2; + } +} + +/*! + * mt9v111 Reset function + * + * @return None + */ +static sensor_interface *mt9v111_reset(void) +{ + return mt9v111_config(&reset_frame_rate, 0); +} + +struct camera_sensor camera_sensor_if = { + .set_color = mt9v111_set_color, + .get_color = mt9v111_get_color, + .set_ae_mode = mt9v111_set_ae_mode, + .get_ae_mode = mt9v111_get_ae_mode, + .config = mt9v111_config, + .reset = mt9v111_reset, +}; + +#ifdef MT9V111_DEBUG +/*! + * Set sensor to test mode, which will generate test pattern. + * + * @return none + */ +static void mt9v111_test_pattern(bool flag) +{ + u8 reg; + u16 data; + + // switch to sensor registers + reg = MT9V111I_ADDR_SPACE_SEL; + data = MT9V111I_SEL_SCA; + mt9v111_write_reg(reg, data); + + if (flag == true) { + testpattern = MT9V111S_OUTCTRL_TEST_MODE; + + reg = MT9V111S_ROW_NOISE_CTRL; + data = mt9v111_read_reg(®, &data) & 0xBF; + data = mt9v111_endian_swap16(data); + mt9v111_write_reg(reg, data); + + reg = MT9V111S_TEST_DATA; + data = 0; + mt9v111_write_reg(reg, data); + + reg = MT9V111S_OUTPUT_CTRL; + // changes take effect + data = MT9V111S_OUTCTRL_CHIP_ENABLE | testpattern | 0x3000; + mt9v111_write_reg(reg, data); + } else { + testpattern = 0; + + reg = MT9V111S_ROW_NOISE_CTRL; + data = mt9v111_read_reg(®, &data) | 0x40; + data = mt9v111_endian_swap16(data); + mt9v111_write_reg(reg, data); + + reg = MT9V111S_OUTPUT_CTRL; + // changes take effect + data = MT9V111S_OUTCTRL_CHIP_ENABLE | testpattern | 0x3000; + mt9v111_write_reg(reg, data); + } +} +#endif + +/*! + * mt9v111 I2C detect_client function + * + * @param adapter struct i2c_adapter * + * @param address int + * @param kind int + * + * @return Error code indicating success or failure + */ +static int mt9v111_detect_client(struct i2c_adapter *adapter, int address, + int kind) +{ + mt9v111_i2c_client.adapter = adapter; + if (i2c_attach_client(&mt9v111_i2c_client)) { + mt9v111_i2c_client.adapter = NULL; + printk(KERN_ERR "mt9v111_attach: i2c_attach_client failed\n"); + return -1; + } + + interface_param = (sensor_interface *) + kmalloc(sizeof(sensor_interface), GFP_KERNEL); + if (!interface_param) { + printk(KERN_ERR "mt9v111_attach: kmalloc failed \n"); + return -1; + } + + printk(KERN_INFO "MT9V111 Detected\n"); + + return 0; +} + +static unsigned short normal_i2c[] = { MT9V111_I2C_ADDRESS, I2C_CLIENT_END }; + +/* Magic definition of all other variables and things */ +I2C_CLIENT_INSMOD; + +/*! + * mt9v111 I2C attach function + * + * @param adapter struct i2c_adapter * + * @return Error code indicating success or failure + */ +static int mt9v111_attach(struct i2c_adapter *adap) +{ + uint32_t mclk = 27000000; + struct clk *clk; + int err; + + clk = clk_get(NULL, "csi_clk"); + clk_enable(clk); + set_mclk_rate(&mclk); + + err = i2c_probe(adap, &addr_data, &mt9v111_detect_client); + + clk_disable(clk); + clk_put(clk); + + return err; +} + +/*! + * mt9v111 I2C detach function + * + * @param client struct i2c_client * + * @return Error code indicating success or failure + */ +static int mt9v111_detach(struct i2c_client *client) +{ + int err; + + if (!mt9v111_i2c_client.adapter) + return -1; + + err = i2c_detach_client(&mt9v111_i2c_client); + mt9v111_i2c_client.adapter = NULL; + + if (interface_param) + kfree(interface_param); + interface_param = NULL; + + return err; +} + +extern void gpio_sensor_active(void); + +/*! + * MT9V111 init function + * + * @return Error code indicating success or failure + */ +static __init int mt9v111_init(void) +{ + u8 err; + + gpio_sensor_active(); + + mt9v111_device.coreReg = (mt9v111_coreReg *) + kmalloc(sizeof(mt9v111_coreReg), GFP_KERNEL); + if (!mt9v111_device.coreReg) + return -1; + + memset(mt9v111_device.coreReg, 0, sizeof(mt9v111_coreReg)); + + mt9v111_device.ifpReg = (mt9v111_IFPReg *) + kmalloc(sizeof(mt9v111_IFPReg), GFP_KERNEL); + if (!mt9v111_device.ifpReg) { + kfree(mt9v111_device.coreReg); + mt9v111_device.coreReg = NULL; + return -1; + } + + memset(mt9v111_device.ifpReg, 0, sizeof(mt9v111_IFPReg)); + + err = i2c_add_driver(&mt9v111_i2c_driver); + + return err; +} + +extern void gpio_sensor_inactive(void); +/*! + * MT9V111 cleanup function + * + * @return Error code indicating success or failure + */ +static void __exit mt9v111_clean(void) +{ + if (mt9v111_device.coreReg) { + kfree(mt9v111_device.coreReg); + mt9v111_device.coreReg = NULL; + } + + if (mt9v111_device.ifpReg) { + kfree(mt9v111_device.ifpReg); + mt9v111_device.ifpReg = NULL; + } + + i2c_del_driver(&mt9v111_i2c_driver); + + gpio_sensor_inactive(); +} + +module_init(mt9v111_init); +module_exit(mt9v111_clean); + +/* Exported symbols for modules. */ +EXPORT_SYMBOL(camera_sensor_if); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("Mt9v111 Camera Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/mxc/capture/mt9v111.h b/drivers/media/video/mxc/capture/mt9v111.h new file mode 100644 index 000000000000..21a16f1e214d --- /dev/null +++ b/drivers/media/video/mxc/capture/mt9v111.h @@ -0,0 +1,421 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @defgroup Camera Sensor Drivers + */ + +/*! + * @file mt9v111.h + * + * @brief MT9V111 Camera Header file + * + * It include all the defines for bitmaps operations, also two main structure + * one for IFP interface structure, other for sensor core registers. + * + * @ingroup Camera + */ + +#ifndef MT9V111_H_ +#define MT9V111_H_ + +/*! + * mt9v111 IFP REGISTER BANK MAP + */ +#define MT9V111I_ADDR_SPACE_SEL 0x1 +#define MT9V111I_BASE_MAXTRIX_SIGN 0x2 +#define MT9V111I_BASE_MAXTRIX_SCALE15 0x3 +#define MT9V111I_BASE_MAXTRIX_SCALE69 0x4 +#define MT9V111I_APERTURE_GAIN 0x5 +#define MT9V111I_MODE_CONTROL 0x6 +#define MT9V111I_SOFT_RESET 0x7 +#define MT9V111I_FORMAT_CONTROL 0x8 +#define MT9V111I_BASE_MATRIX_CFK1 0x9 +#define MT9V111I_BASE_MATRIX_CFK2 0xa +#define MT9V111I_BASE_MATRIX_CFK3 0xb +#define MT9V111I_BASE_MATRIX_CFK4 0xc +#define MT9V111I_BASE_MATRIX_CFK5 0xd +#define MT9V111I_BASE_MATRIX_CFK6 0xe +#define MT9V111I_BASE_MATRIX_CFK7 0xf +#define MT9V111I_BASE_MATRIX_CFK8 0x10 +#define MT9V111I_BASE_MATRIX_CFK9 0x11 +#define MT9V111I_AWB_POSITION 0x12 +#define MT9V111I_AWB_RED_GAIN 0x13 +#define MT9V111I_AWB_BLUE_GAIN 0x14 +#define MT9V111I_DELTA_MATRIX_CF_SIGN 0x15 +#define MT9V111I_DELTA_MATRIX_CF_D1 0x16 +#define MT9V111I_DELTA_MATRIX_CF_D2 0x17 +#define MT9V111I_DELTA_MATRIX_CF_D3 0x18 +#define MT9V111I_DELTA_MATRIX_CF_D4 0x19 +#define MT9V111I_DELTA_MATRIX_CF_D5 0x1a +#define MT9V111I_DELTA_MATRIX_CF_D6 0x1b +#define MT9V111I_DELTA_MATRIX_CF_D7 0x1c +#define MT9V111I_DELTA_MATRIX_CF_D8 0x1d +#define MT9V111I_DELTA_MATRIX_CF_D9 0x1e +#define MT9V111I_LUMINANCE_LIMIT_WB 0x20 +#define MT9V111I_RBG_MANUUAL_WB 0x21 +#define MT9V111I_AWB_RED_LIMIT 0x22 +#define MT9V111I_AWB_BLUE_LIMIT 0x23 +#define MT9V111I_MATRIX_ADJUST_LIMIT 0x24 +#define MT9V111I_AWB_SPEED 0x25 +#define MT9V111I_H_BOUND_AE 0x26 +#define MT9V111I_V_BOUND_AE 0x27 +#define MT9V111I_H_BOUND_AE_CEN_WIN 0x2b +#define MT9V111I_V_BOUND_AE_CEN_WIN 0x2c +#define MT9V111I_BOUND_AWB_WIN 0x2d +#define MT9V111I_AE_PRECISION_TARGET 0x2e +#define MT9V111I_AE_SPEED 0x2f +#define MT9V111I_RED_AWB_MEASURE 0x30 +#define MT9V111I_LUMA_AWB_MEASURE 0x31 +#define MT9V111I_BLUE_AWB_MEASURE 0x32 +#define MT9V111I_LIMIT_SHARP_SATU_CTRL 0x33 +#define MT9V111I_LUMA_OFFSET 0x34 +#define MT9V111I_CLIP_LIMIT_OUTPUT_LUMI 0x35 +#define MT9V111I_GAIN_LIMIT_AE 0x36 +#define MT9V111I_SHUTTER_WIDTH_LIMIT_AE 0x37 +#define MT9V111I_UPPER_SHUTTER_DELAY_LIM 0x39 +#define MT9V111I_OUTPUT_FORMAT_CTRL2 0x3a +#define MT9V111I_IPF_BLACK_LEVEL_SUB 0x3b +#define MT9V111I_IPF_BLACK_LEVEL_ADD 0x3c +#define MT9V111I_ADC_LIMIT_AE_ADJ 0x3d +#define MT9V111I_GAIN_THRE_CCAM_ADJ 0x3e +#define MT9V111I_LINEAR_AE 0x3f +#define MT9V111I_THRESHOLD_EDGE_DEFECT 0x47 +#define MT9V111I_LUMA_SUM_MEASURE 0x4c +#define MT9V111I_TIME_ADV_SUM_LUMA 0x4d +#define MT9V111I_MOTION 0x52 +#define MT9V111I_GAMMA_KNEE_Y12 0x53 +#define MT9V111I_GAMMA_KNEE_Y34 0x54 +#define MT9V111I_GAMMA_KNEE_Y56 0x55 +#define MT9V111I_GAMMA_KNEE_Y78 0x56 +#define MT9V111I_GAMMA_KNEE_Y90 0x57 +#define MT9V111I_GAMMA_VALUE_Y0 0x58 +#define MT9V111I_SHUTTER_60 0x59 +#define MT9V111I_SEARCH_FLICK_60 0x5c +#define MT9V111I_RATIO_IMAGE_GAIN_BASE 0x5e +#define MT9V111I_RATIO_IMAGE_GAIN_DELTA 0x5f +#define MT9V111I_SIGN_VALUE_REG5F 0x60 +#define MT9V111I_AE_GAIN 0x62 +#define MT9V111I_MAX_GAIN_AE 0x67 +#define MT9V111I_LENS_CORRECT_CTRL 0x80 +#define MT9V111I_SHADING_PARAMETER1 0x81 +#define MT9V111I_SHADING_PARAMETER2 0x82 +#define MT9V111I_SHADING_PARAMETER3 0x83 +#define MT9V111I_SHADING_PARAMETER4 0x84 +#define MT9V111I_SHADING_PARAMETER5 0x85 +#define MT9V111I_SHADING_PARAMETER6 0x86 +#define MT9V111I_SHADING_PARAMETER7 0x87 +#define MT9V111I_SHADING_PARAMETER8 0x88 +#define MT9V111I_SHADING_PARAMETER9 0x89 +#define MT9V111I_SHADING_PARAMETER10 0x8A +#define MT9V111I_SHADING_PARAMETER11 0x8B +#define MT9V111I_SHADING_PARAMETER12 0x8C +#define MT9V111I_SHADING_PARAMETER13 0x8D +#define MT9V111I_SHADING_PARAMETER14 0x8E +#define MT9V111I_SHADING_PARAMETER15 0x8F +#define MT9V111I_SHADING_PARAMETER16 0x90 +#define MT9V111I_SHADING_PARAMETER17 0x91 +#define MT9V111I_SHADING_PARAMETER18 0x92 +#define MT9V111I_SHADING_PARAMETER19 0x93 +#define MT9V111I_SHADING_PARAMETER20 0x94 +#define MT9V111I_SHADING_PARAMETER21 0x95 +#define MT9V111i_FLASH_CTRL 0x98 +#define MT9V111i_LINE_COUNTER 0x99 +#define MT9V111i_FRAME_COUNTER 0x9A +#define MT9V111i_H_PAN 0xA5 +#define MT9V111i_H_ZOOM 0xA6 +#define MT9V111i_H_SIZE 0xA7 +#define MT9V111i_V_PAN 0xA8 +#define MT9V111i_V_ZOOM 0xA9 +#define MT9V111i_V_SIZE 0xAA + +#define MT9V111I_SEL_IFP 0x1 +#define MT9V111I_SEL_SCA 0x4 +#define MT9V111I_FC_RGB_OR_YUV 0x1000 + +/*! + * Mt9v111 SENSOR CORE REGISTER BANK MAP + */ +#define MT9V111S_ADDR_SPACE_SEL 0x1 +#define MT9V111S_COLUMN_START 0x2 +#define MT9V111S_WIN_HEIGHT 0x3 +#define MT9V111S_WIN_WIDTH 0x4 +#define MT9V111S_HOR_BLANKING 0x5 +#define MT9V111S_VER_BLANKING 0x6 +#define MT9V111S_OUTPUT_CTRL 0x7 +#define MT9V111S_ROW_START 0x8 +#define MT9V111S_SHUTTER_WIDTH 0x9 +#define MT9V111S_PIXEL_CLOCK_SPEED 0xa +#define MT9V111S_RESTART 0xb +#define MT9V111S_SHUTTER_DELAY 0xc +#define MT9V111S_RESET 0xd +#define MT9V111S_COLUMN_START_IN_ZOOM 0x12 +#define MT9V111S_ROW_START_IN_ZOOM 0x13 +#define MT9V111S_DIGITAL_ZOOM 0x1e +#define MT9V111S_READ_MODE 0x20 +#define MT9V111S_DAC_CTRL 0x27 +#define MT9V111S_GREEN1_GAIN 0x2b +#define MT9V111S_BLUE_GAIN 0x2c +#define MT9V111S_READ_GAIN 0x2d +#define MT9V111S_GREEN2_GAIN 0x2e +#define MT9V111S_ROW_NOISE_CTRL 0x30 +#define MT9V111S_DARK_TARGET_W 0x31 +#define MT9V111S_TEST_DATA 0x32 +#define MT9V111S_GLOBAL_GAIN 0x35 +#define MT9V111S_SENSOR_CORE_VERSION 0x36 +#define MT9V111S_DARK_TARGET_WO 0x37 +#define MT9V111S_VERF_DAC 0x41 +#define MT9V111S_VCM_VCL 0x42 +#define MT9V111S_DISABLE_BYPASS 0x58 +#define MT9V111S_CALIB_MEAN_TEST 0x59 +#define MT9V111S_DARK_G1_AVE 0x5B +#define MT9V111S_DARK_G2_AVE 0x5C +#define MT9V111S_DARK_R_AVE 0x5D +#define MT9V111S_DARK_B_AVE 0x5E +#define MT9V111S_CAL_THRESHOLD 0x5f +#define MT9V111S_CAL_G1 0x60 +#define MT9V111S_CAL_G2 0x61 +#define MT9V111S_CAL_CTRL 0x62 +#define MT9V111S_CAL_R 0x63 +#define MT9V111S_CAL_B 0x64 +#define MT9V111S_CHIP_ENABLE 0xF1 +#define MT9V111S_CHIP_VERSION 0xFF + +// OUTPUT_CTRL +#define MT9V111S_OUTCTRL_SYNC 0x1 +#define MT9V111S_OUTCTRL_CHIP_ENABLE 0x2 +#define MT9V111S_OUTCTRL_TEST_MODE 0x40 + +// READ_MODE +#define MT9V111S_RM_NOBADFRAME 0x1 +#define MT9V111S_RM_NODESTRUCT 0x2 +#define MT9V111S_RM_COLUMNSKIP 0x4 +#define MT9V111S_RM_ROWSKIP 0x8 +#define MT9V111S_RM_BOOSTEDRESET 0x1000 +#define MT9V111S_RM_COLUMN_LATE 0x10 +#define MT9V111S_RM_ROW_LATE 0x80 +#define MT9V111S_RM_RIGTH_TO_LEFT 0x4000 +#define MT9V111S_RM_BOTTOM_TO_TOP 0x8000 + +/*! I2C Slave Address */ +#define MT9V111_I2C_ADDRESS 0x48 + +/*! + * The image resolution enum for the mt9v111 sensor + */ +typedef enum { + MT9V111_OutputResolution_VGA = 0, /*!< VGA size */ + MT9V111_OutputResolution_QVGA, /*!< QVGA size */ + MT9V111_OutputResolution_CIF, /*!< CIF size */ + MT9V111_OutputResolution_QCIF, /*!< QCIF size */ + MT9V111_OutputResolution_QQVGA, /*!< QQVGA size */ + MT9V111_OutputResolution_SXGA /*!< SXGA size */ +} MT9V111_OutputResolution; + +enum { + MT9V111_WINWIDTH = 0x287, + MT9V111_WINWIDTH_DEFAULT = 0x287, + MT9V111_WINWIDTH_MIN = 0x9, + + MT9V111_WINHEIGHT = 0x1E7, + MT9V111_WINHEIGHT_DEFAULT = 0x1E7, + + MT9V111_HORZBLANK_DEFAULT = 0x26, + MT9V111_HORZBLANK_MIN = 0x9, + MT9V111_HORZBLANK_MAX = 0x3FF, + + MT9V111_VERTBLANK_DEFAULT = 0x4, + MT9V111_VERTBLANK_MIN = 0x3, + MT9V111_VERTBLANK_MAX = 0xFFF, +}; + +/*! + * Mt9v111 Core Register structure. + */ +typedef struct { + u32 addressSelect; /*!< select address bank for Core Register 0x4 */ + u32 columnStart; /*!< Starting Column */ + u32 windowHeight; /*!< Window Height */ + u32 windowWidth; /*!< Window Width */ + u32 horizontalBlanking; /*!< Horizontal Blank time, in pixels */ + u32 verticalBlanking; /*!< Vertical Blank time, in pixels */ + u32 outputControl; /*!< Register to control sensor output */ + u32 rowStart; /*!< Starting Row */ + u32 shutterWidth; + u32 pixelClockSpeed; /*!< pixel date rate */ + u32 restart; /*!< Abandon the readout of current frame */ + u32 shutterDelay; + u32 reset; /*!< reset the sensor to the default mode */ + u32 zoomColStart; /*!< Column start in the Zoom mode */ + u32 zomRowStart; /*!< Row start in the Zoom mode */ + u32 digitalZoom; /*!< 1 means zoom by 2 */ + u32 readMode; /*!< Readmode: aspects of the readout of the sensor */ + u32 dACStandbyControl; + u32 green1Gain; /*!< Gain Settings */ + u32 blueGain; + u32 redGain; + u32 green2Gain; + u32 rowNoiseControl; + u32 darkTargetwNC; + u32 testData; /*!< test mode */ + u32 globalGain; + u32 chipVersion; + u32 darkTargetwoNC; + u32 vREFDACs; + u32 vCMandVCL; + u32 disableBypass; + u32 calibMeanTest; + u32 darkG1average; + u32 darkG2average; + u32 darkRaverage; + u32 darkBaverage; + u32 calibThreshold; + u32 calibGreen1; + u32 calibGreen2; + u32 calibControl; + u32 calibRed; + u32 calibBlue; + u32 chipEnable; /*!< Image core Registers written by image flow processor */ +} mt9v111_coreReg; + +/*! + * Mt9v111 IFP Register structure. + */ +typedef struct { + u32 addrSpaceSel; /*!< select address bank for Core Register 0x1 */ + u32 baseMaxtrixSign; /*!< sign of coefficient for base color correction matrix */ + u32 baseMaxtrixScale15; /*!< scaling of color correction coefficient K1-5 */ + u32 baseMaxtrixScale69; /*!< scaling of color correction coefficient K6-9 */ + u32 apertureGain; /*!< sharpening */ + u32 modeControl; /*!< bit 7 CCIR656 sync codes are embedded in the image */ + u32 softReset; /*!< Image processing mode: 1 reset mode, 0 operational mode */ + u32 formatControl; /*!< bit12 1 for RGB565, 0 for YcrCb */ + u32 baseMatrixCfk1; /*!< K1 Color correction coefficient */ + u32 baseMatrixCfk2; /*!< K2 Color correction coefficient */ + u32 baseMatrixCfk3; /*!< K3 Color correction coefficient */ + u32 baseMatrixCfk4; /*!< K4 Color correction coefficient */ + u32 baseMatrixCfk5; /*!< K5 Color correction coefficient */ + u32 baseMatrixCfk6; /*!< K6 Color correction coefficient */ + u32 baseMatrixCfk7; /*!< K7 Color correction coefficient */ + u32 baseMatrixCfk8; /*!< K8 Color correction coefficient */ + u32 baseMatrixCfk9; /*!< K9 Color correction coefficient */ + u32 awbPosition; /*!< Current position of AWB color correction matrix */ + u32 awbRedGain; /*!< Current value of AWB red channel gain */ + u32 awbBlueGain; /*!< Current value of AWB blue channel gain */ + u32 deltaMatrixCFSign; /*!< Sign of coefficients of delta color correction matrix register */ + u32 deltaMatrixCFD1; /*!< D1 Delta coefficient */ + u32 deltaMatrixCFD2; /*!< D2 Delta coefficient */ + u32 deltaMatrixCFD3; /*!< D3 Delta coefficient */ + u32 deltaMatrixCFD4; /*!< D4 Delta coefficient */ + u32 deltaMatrixCFD5; /*!< D5 Delta coefficient */ + u32 deltaMatrixCFD6; /*!< D6 Delta coefficient */ + u32 deltaMatrixCFD7; /*!< D7 Delta coefficient */ + u32 deltaMatrixCFD8; /*!< D8 Delta coefficient */ + u32 deltaMatrixCFD9; /*!< D9 Delta coefficient */ + u32 lumLimitWB; /*!< Luminance range of pixels considered in WB statistics */ + u32 RBGManualWB; /*!< Red and Blue color channel gains for manual white balance */ + u32 awbRedLimit; /*!< Limits on Red channel gain adjustment through AWB */ + u32 awbBlueLimit; /*!< Limits on Blue channel gain adjustment through AWB */ + u32 matrixAdjLimit; /*!< Limits on color correction matrix adjustment through AWB */ + u32 awbSpeed; /*!< AWB speed and color saturation control */ + u32 HBoundAE; /*!< Horizontal boundaries of AWB measurement window */ + u32 VBoundAE; /*!< Vertical boundaries of AWB measurement window */ + u32 HBoundAECenWin; /*!< Horizontal boundaries of AE measurement window for backlight compensation */ + u32 VBoundAECenWin; /*!< Vertical boundaries of AE measurement window for backlight compensation */ + u32 boundAwbWin; /*!< Boundaries of AWB measurement window */ + u32 AEPrecisionTarget; /*!< Auto exposure target and precision control */ + u32 AESpeed; /*!< AE speed and sensitivity control register */ + u32 redAWBMeasure; /*!< Measure of the red channel value used by AWB */ + u32 lumaAWBMeasure; /*!< Measure of the luminance channel value used by AWB */ + u32 blueAWBMeasure; /*!< Measure of the blue channel value used by AWB */ + u32 limitSharpSatuCtrl; /*!< Automatic control of sharpness and color saturation */ + u32 lumaOffset; /*!< Luminance offset control (brightness control) */ + u32 clipLimitOutputLumi; /*!< Clipping limits for output luminance */ + u32 gainLimitAE; /*!< Imager gain limits for AE adjustment */ + u32 shutterWidthLimitAE; /*!< Shutter width (exposure time) limits for AE adjustment */ + u32 upperShutterDelayLi; /*!< Upper Shutter Delay Limit */ + u32 outputFormatCtrl2; /*!< Output Format Control 2 + 00 = 16-bit RGB565. + 01 = 15-bit RGB555. + 10 = 12-bit RGB444x. + 11 = 12-bit RGBx444. */ + u32 ipfBlackLevelSub; /*!< IFP black level subtraction */ + u32 ipfBlackLevelAdd; /*!< IFP black level addition */ + u32 adcLimitAEAdj; /*!< ADC limits for AE adjustment */ + u32 agimnThreCamAdj; /*!< Gain threshold for CCM adjustment */ + u32 linearAE; + u32 thresholdEdgeDefect; /*!< Edge threshold for interpolation and defect correction */ + u32 lumaSumMeasure; /*!< Luma measured by AE engine */ + u32 timeAdvSumLuma; /*!< Time-averaged luminance value tracked by auto exposure */ + u32 motion; /*!< 1 when motion is detected */ + u32 gammaKneeY12; /*!< Gamma knee points Y1 and Y2 */ + u32 gammaKneeY34; /*!< Gamma knee points Y3 and Y4 */ + u32 gammaKneeY56; /*!< Gamma knee points Y5 and Y6 */ + u32 gammaKneeY78; /*!< Gamma knee points Y7 and Y8 */ + u32 gammaKneeY90; /*!< Gamma knee points Y9 and Y10 */ + u32 gammaKneeY0; /*!< Gamma knee point Y0 */ + u32 shutter_width_60; + u32 search_flicker_60; + u32 ratioImageGainBase; + u32 ratioImageGainDelta; + u32 signValueReg5F; + u32 aeGain; + u32 maxGainAE; + u32 lensCorrectCtrl; + u32 shadingParameter1; /*!< Shade Parameters */ + u32 shadingParameter2; + u32 shadingParameter3; + u32 shadingParameter4; + u32 shadingParameter5; + u32 shadingParameter6; + u32 shadingParameter7; + u32 shadingParameter8; + u32 shadingParameter9; + u32 shadingParameter10; + u32 shadingParameter11; + u32 shadingParameter12; + u32 shadingParameter13; + u32 shadingParameter14; + u32 shadingParameter15; + u32 shadingParameter16; + u32 shadingParameter17; + u32 shadingParameter18; + u32 shadingParameter19; + u32 shadingParameter20; + u32 shadingParameter21; + u32 flashCtrl; /*!< Flash control */ + u32 lineCounter; /*!< Line counter */ + u32 frameCounter; /*!< Frame counter */ + u32 HPan; /*!< Horizontal pan in decimation */ + u32 HZoom; /*!< Horizontal zoom in decimation */ + u32 HSize; /*!< Horizontal output size iIn decimation */ + u32 VPan; /*!< Vertical pan in decimation */ + u32 VZoom; /*!< Vertical zoom in decimation */ + u32 VSize; /*!< Vertical output size in decimation */ +} mt9v111_IFPReg; + +/*! + * mt9v111 Config structure + */ +typedef struct { + mt9v111_coreReg *coreReg; /*!< Sensor Core Register Bank */ + mt9v111_IFPReg *ifpReg; /*!< IFP Register Bank */ +} mt9v111_conf; + +typedef struct { + u8 index; + u16 width; + u16 height; +} mt9v111_image_format; + +#endif // MT9V111_H_ diff --git a/drivers/media/video/mxc/capture/mx27_csi.c b/drivers/media/video/mxc/capture/mx27_csi.c new file mode 100644 index 000000000000..0ff86510d83c --- /dev/null +++ b/drivers/media/video/mxc/capture/mx27_csi.c @@ -0,0 +1,332 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mx27_csi.c + * + * @brief CMOS Sensor interface functions + * + * @ingroup CSI + */ +#include <linux/types.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/interrupt.h> +#include <linux/spinlock.h> +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/clk.h> +#include <asm/arch/clock.h> +#include <asm/arch/hardware.h> + +#include "mx27_csi.h" + +static csi_config_t g_csi_cfg; /* csi hardware configuration */ +static bool gcsi_mclk_on = false; +static csi_irq_callback_t g_callback = 0; +static void *g_callback_data = 0; +static struct clk csi_mclk; + +static irqreturn_t csi_irq_handler(int irq, void *data) +{ + unsigned long status = __raw_readl(CSI_CSISR); + + __raw_writel(status, CSI_CSISR); + if (g_callback) + g_callback(g_callback_data, status); + + pr_debug("CSI status = 0x%08lX\n", status); + + return IRQ_HANDLED; +} + +static void csihw_set_config(csi_config_t * cfg) +{ + unsigned val = 0; + + /* control reg 1 */ + val |= cfg->swap16_en ? BIT_SWAP16_EN : 0; + val |= cfg->ext_vsync ? BIT_EXT_VSYNC : 0; + val |= cfg->eof_int_en ? BIT_EOF_INT_EN : 0; + val |= cfg->prp_if_en ? BIT_PRP_IF_EN : 0; + val |= cfg->ccir_mode ? BIT_CCIR_MODE : 0; + val |= cfg->cof_int_en ? BIT_COF_INT_EN : 0; + val |= cfg->sf_or_inten ? BIT_SF_OR_INTEN : 0; + val |= cfg->rf_or_inten ? BIT_RF_OR_INTEN : 0; + val |= cfg->statff_level << SHIFT_STATFF_LEVEL; + val |= cfg->staff_inten ? BIT_STATFF_INTEN : 0; + val |= cfg->rxff_level << SHIFT_RXFF_LEVEL; + val |= cfg->rxff_inten ? BIT_RXFF_INTEN : 0; + val |= cfg->sof_pol ? BIT_SOF_POL : 0; + val |= cfg->sof_inten ? BIT_SOF_INTEN : 0; + val |= cfg->mclkdiv << SHIFT_MCLKDIV; + val |= cfg->hsync_pol ? BIT_HSYNC_POL : 0; + val |= cfg->ccir_en ? BIT_CCIR_EN : 0; + val |= cfg->mclken ? BIT_MCLKEN : 0; + val |= cfg->fcc ? BIT_FCC : 0; + val |= cfg->pack_dir ? BIT_PACK_DIR : 0; + val |= cfg->gclk_mode ? BIT_GCLK_MODE : 0; + val |= cfg->inv_data ? BIT_INV_DATA : 0; + val |= cfg->inv_pclk ? BIT_INV_PCLK : 0; + val |= cfg->redge ? BIT_REDGE : 0; + + __raw_writel(val, CSI_CSICR1); + + /* control reg 3 */ + val = 0x0; + val |= cfg->csi_sup ? BIT_CSI_SUP : 0; + val |= cfg->zero_pack_en ? BIT_ZERO_PACK_EN : 0; + val |= cfg->ecc_int_en ? BIT_ECC_INT_EN : 0; + val |= cfg->ecc_auto_en ? BIT_ECC_AUTO_EN : 0; + + __raw_writel(val, CSI_CSICR3); + + /* rxfifo counter */ + __raw_writel(cfg->rxcnt, CSI_CSIRXCNT); + + /* update global config */ + memcpy(&g_csi_cfg, cfg, sizeof(csi_config_t)); +} + +static void csihw_reset_frame_count(void) +{ + __raw_writel(__raw_readl(CSI_CSICR3) | BIT_FRMCNT_RST, CSI_CSICR3); +} + +static void csihw_reset(void) +{ + csihw_reset_frame_count(); + __raw_writel(CSICR1_RESET_VAL, CSI_CSICR1); + __raw_writel(CSICR2_RESET_VAL, CSI_CSICR2); + __raw_writel(CSICR3_RESET_VAL, CSI_CSICR3); +} + +/*! + * csi_init_interface + * Sets initial values for the CSI registers. + * The width and height of the sensor and the actual frame size will be + * set to the same values. + * @param width Sensor width + * @param height Sensor height + * @param pixel_fmt pixel format + * @param sig csi_signal_cfg_t + * + * @return 0 for success, -EINVAL for error + */ +int32_t csi_init_interface(uint16_t width, uint16_t height, + uint32_t pixel_fmt, csi_signal_cfg_t sig) +{ + csi_config_t cfg; + + /* Set the CSI_SENS_CONF register remaining fields */ + cfg.swap16_en = 1; + cfg.ext_vsync = sig.ext_vsync; + cfg.eof_int_en = 0; + cfg.prp_if_en = 1; + cfg.ccir_mode = 0; + cfg.cof_int_en = 0; + cfg.sf_or_inten = 0; + cfg.rf_or_inten = 0; + cfg.statff_level = 0; + cfg.staff_inten = 0; + cfg.rxff_level = 2; + cfg.rxff_inten = 0; + cfg.sof_pol = 1; + cfg.sof_inten = 0; + cfg.mclkdiv = 0; + cfg.hsync_pol = 1; + cfg.ccir_en = 0; + cfg.mclken = gcsi_mclk_on ? 1 : 0; + cfg.fcc = 1; + cfg.pack_dir = 0; + cfg.gclk_mode = 1; + cfg.inv_data = sig.data_pol; + cfg.inv_pclk = sig.pixclk_pol; + cfg.redge = 1; + cfg.csicnt1_rsv = 0; + + /* control reg 3 */ + cfg.frmcnt = 0; + cfg.frame_reset = 0; + cfg.csi_sup = 0; + cfg.zero_pack_en = 0; + cfg.ecc_int_en = 0; + cfg.ecc_auto_en = 0; + + csihw_set_config(&cfg); + + return 0; +} + +/*! + * csi_enable_prpif + * Enable or disable CSI-PrP interface + * @param enable Non-zero to enable, zero to disable + */ +void csi_enable_prpif(uint32_t enable) +{ + if (enable) { + g_csi_cfg.prp_if_en = 1; + g_csi_cfg.sof_inten = 0; + g_csi_cfg.pack_dir = 0; + } else { + g_csi_cfg.prp_if_en = 0; + g_csi_cfg.sof_inten = 1; + g_csi_cfg.pack_dir = 1; + } + + csihw_set_config(&g_csi_cfg); +} + +/*! + * csi_enable_mclk + * + * @param src enum define which source to control the clk + * CSI_MCLK_VF CSI_MCLK_ENC CSI_MCLK_RAW CSI_MCLK_I2C + * @param flag true to enable mclk, false to disable mclk + * @param wait true to wait 100ms make clock stable, false not wait + * + * @return 0 for success + */ +int32_t csi_enable_mclk(int src, bool flag, bool wait) +{ + if (flag == true) { + clk_enable(&csi_mclk); + if (wait == true) + msleep(10); + pr_debug("Enable csi clock from source %d\n", src); + gcsi_mclk_on = true; + } else { + clk_disable(&csi_mclk); + pr_debug("Disable csi clock from source %d\n", src); + gcsi_mclk_on = false; + } + + return 0; +} + +/*! + * csi_read_mclk_flag + * + * @return gcsi_mclk_source + */ +int csi_read_mclk_flag(void) +{ + return 0; +} + +void csi_set_callback(csi_irq_callback_t callback, void *data) +{ + g_callback = callback; + g_callback_data = data; +} + +static void _mclk_recalc(struct clk *clk) +{ + u32 div; + + div = (__raw_readl(CSI_CSICR1) & BIT_MCLKDIV) >> SHIFT_MCLKDIV; + div = (div + 1) * 2; + + clk->rate = clk->parent->rate / div; +} + +static unsigned long _mclk_round_rate(struct clk *clk, unsigned long rate) +{ + /* Keep CSI divider and change parent clock */ + if (clk->parent->round_rate) { + return clk->parent->round_rate(clk->parent, rate * 2); + } + return 0; +} + +static int _mclk_set_rate(struct clk *clk, unsigned long rate) +{ + int ret = -EINVAL; + + /* Keep CSI divider and change parent clock */ + if (clk->parent->set_rate) { + ret = clk->parent->set_rate(clk->parent, rate * 2); + if (ret == 0) { + clk->rate = clk->parent->rate / 2; + } + } + + return ret; +} + +static int _mclk_enable(struct clk *clk) +{ + __raw_writel(__raw_readl(CSI_CSICR1) | BIT_MCLKEN, CSI_CSICR1); + return 0; +} + +static void _mclk_disable(struct clk *clk) +{ + __raw_writel(__raw_readl(CSI_CSICR1) & ~BIT_MCLKEN, CSI_CSICR1); +} + +static struct clk csi_mclk = { + .name = "csi_clk", + .recalc = _mclk_recalc, + .round_rate = _mclk_round_rate, + .set_rate = _mclk_set_rate, + .enable = _mclk_enable, + .disable = _mclk_disable, +}; + +int32_t __init csi_init_module(void) +{ + int ret = 0; + struct clk *per_clk; + + per_clk = clk_get(NULL, "csi_perclk"); + if (IS_ERR(per_clk)) + return PTR_ERR(per_clk); + clk_put(per_clk); + csi_mclk.parent = per_clk; + clk_register(&csi_mclk); + clk_enable(per_clk); + csi_mclk.recalc(&csi_mclk); + + csihw_reset(); + + /* interrupt enable */ + ret = request_irq(INT_CSI, csi_irq_handler, 0, "csi", 0); + if (ret) + pr_debug("CSI error: irq request fail\n"); + + return ret; +} + +void __exit csi_cleanup_module(void) +{ + /* free irq */ + free_irq(INT_CSI, 0); + + clk_disable(&csi_mclk); +} + +module_init(csi_init_module); +module_exit(csi_cleanup_module); + +EXPORT_SYMBOL(csi_init_interface); +EXPORT_SYMBOL(csi_enable_mclk); +EXPORT_SYMBOL(csi_read_mclk_flag); +EXPORT_SYMBOL(csi_set_callback); +EXPORT_SYMBOL(csi_enable_prpif); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MX27 CSI driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/mxc/capture/mx27_csi.h b/drivers/media/video/mxc/capture/mx27_csi.h new file mode 100644 index 000000000000..0d5fadc40122 --- /dev/null +++ b/drivers/media/video/mxc/capture/mx27_csi.h @@ -0,0 +1,165 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mx27_csi.h + * + * @brief CMOS Sensor interface functions + * + * @ingroup CSI + */ + +#ifndef MX27_CSI_H +#define MX27_CSI_H + +/* reset values */ +#define CSICR1_RESET_VAL 0x40000800 +#define CSICR2_RESET_VAL 0x0 +#define CSICR3_RESET_VAL 0x0 + +/* csi control reg 1 */ +#define BIT_SWAP16_EN (0x1 << 31) +#define BIT_EXT_VSYNC (0x1 << 30) +#define BIT_EOF_INT_EN (0x1 << 29) +#define BIT_PRP_IF_EN (0x1 << 28) +#define BIT_CCIR_MODE (0x1 << 27) +#define BIT_COF_INT_EN (0x1 << 26) +#define BIT_SF_OR_INTEN (0x1 << 25) +#define BIT_RF_OR_INTEN (0x1 << 24) +#define BIT_STATFF_LEVEL (0x3 << 22) +#define BIT_STATFF_INTEN (0x1 << 21) +#define BIT_RXFF_LEVEL (0x3 << 19) +#define BIT_RXFF_INTEN (0x1 << 18) +#define BIT_SOF_POL (0x1 << 17) +#define BIT_SOF_INTEN (0x1 << 16) +#define BIT_MCLKDIV (0xF << 12) +#define BIT_HSYNC_POL (0x1 << 11) +#define BIT_CCIR_EN (0x1 << 10) +#define BIT_MCLKEN (0x1 << 9) +#define BIT_FCC (0x1 << 8) +#define BIT_PACK_DIR (0x1 << 7) +#define BIT_CLR_STATFIFO (0x1 << 6) +#define BIT_CLR_RXFIFO (0x1 << 5) +#define BIT_GCLK_MODE (0x1 << 4) +#define BIT_INV_DATA (0x1 << 3) +#define BIT_INV_PCLK (0x1 << 2) +#define BIT_REDGE (0x1 << 1) + +#define SHIFT_STATFF_LEVEL 22 +#define SHIFT_RXFF_LEVEL 19 +#define SHIFT_MCLKDIV 12 + +/* control reg 3 */ +#define BIT_FRMCNT (0xFFFF << 16) +#define BIT_FRMCNT_RST (0x1 << 15) +#define BIT_CSI_SUP (0x1 << 3) +#define BIT_ZERO_PACK_EN (0x1 << 2) +#define BIT_ECC_INT_EN (0x1 << 1) +#define BIT_ECC_AUTO_EN (0x1) + +#define SHIFT_FRMCNT 16 + +/* csi status reg */ +#define BIT_SFF_OR_INT (0x1 << 25) +#define BIT_RFF_OR_INT (0x1 << 24) +#define BIT_STATFF_INT (0x1 << 21) +#define BIT_RXFF_INT (0x1 << 18) +#define BIT_EOF_INT (0x1 << 17) +#define BIT_SOF_INT (0x1 << 16) +#define BIT_F2_INT (0x1 << 15) +#define BIT_F1_INT (0x1 << 14) +#define BIT_COF_INT (0x1 << 13) +#define BIT_ECC_INT (0x1 << 1) +#define BIT_DRDY (0x1 << 0) + +#define CSI_MCLK_VF 1 +#define CSI_MCLK_ENC 2 +#define CSI_MCLK_RAW 4 +#define CSI_MCLK_I2C 8 + +#define CSI_CSICR1 (IO_ADDRESS(CSI_BASE_ADDR)) +#define CSI_CSICR2 (IO_ADDRESS(CSI_BASE_ADDR + 0x4)) +#define CSI_CSISR (IO_ADDRESS(CSI_BASE_ADDR + 0x8)) +#define CSI_STATFIFO (IO_ADDRESS(CSI_BASE_ADDR + 0xC)) +#define CSI_CSIRXFIFO (IO_ADDRESS(CSI_BASE_ADDR + 0x10)) +#define CSI_CSIRXCNT (IO_ADDRESS(CSI_BASE_ADDR + 0x14)) +#define CSI_CSICR3 (IO_ADDRESS(CSI_BASE_ADDR + 0x1C)) + +#define CSI_CSIRXFIFO_PHYADDR (CSI_BASE_ADDR + 0x10) + +static __inline void csi_clear_status(unsigned long status) +{ + __raw_writel(status, CSI_CSISR); +} + +typedef struct { + unsigned data_width:3; + unsigned clk_mode:2; + unsigned ext_vsync:1; + unsigned Vsync_pol:1; + unsigned Hsync_pol:1; + unsigned pixclk_pol:1; + unsigned data_pol:1; + unsigned sens_clksrc:1; +} csi_signal_cfg_t; + +typedef struct { + /* control reg 1 */ + unsigned int swap16_en:1; + unsigned int ext_vsync:1; + unsigned int eof_int_en:1; + unsigned int prp_if_en:1; + unsigned int ccir_mode:1; + unsigned int cof_int_en:1; + unsigned int sf_or_inten:1; + unsigned int rf_or_inten:1; + unsigned int statff_level:2; + unsigned int staff_inten:1; + unsigned int rxff_level:2; + unsigned int rxff_inten:1; + unsigned int sof_pol:1; + unsigned int sof_inten:1; + unsigned int mclkdiv:4; + unsigned int hsync_pol:1; + unsigned int ccir_en:1; + unsigned int mclken:1; + unsigned int fcc:1; + unsigned int pack_dir:1; + unsigned int gclk_mode:1; + unsigned int inv_data:1; + unsigned int inv_pclk:1; + unsigned int redge:1; + unsigned int csicnt1_rsv:1; + + /* control reg 3 */ + unsigned int frmcnt:16; + unsigned int frame_reset:1; + unsigned int csi_sup:1; + unsigned int zero_pack_en:1; + unsigned int ecc_int_en:1; + unsigned int ecc_auto_en:1; + + /* fifo counter */ + unsigned int rxcnt; +} csi_config_t; + +typedef void (*csi_irq_callback_t) (void *data, unsigned long status); + +int32_t csi_enable_mclk(int src, bool flag, bool wait); +int32_t csi_init_interface(uint16_t width, uint16_t height, + uint32_t pixel_fmt, csi_signal_cfg_t sig); +int csi_read_mclk_flag(void); +void csi_set_callback(csi_irq_callback_t callback, void *data); +void csi_enable_prpif(uint32_t enable); + +#endif diff --git a/drivers/media/video/mxc/capture/mx27_prp.h b/drivers/media/video/mxc/capture/mx27_prp.h new file mode 100644 index 000000000000..e32e9029daff --- /dev/null +++ b/drivers/media/video/mxc/capture/mx27_prp.h @@ -0,0 +1,310 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mx27_prp.h + * + * @brief Header file for MX27 V4L2 capture driver + * + * @ingroup MXC_V4L2_CAPTURE + */ +#ifndef __MX27_PRP_H__ +#define __MX27_PRP_H__ + +#define PRP_REG(ofs) (IO_ADDRESS(EMMA_BASE_ADDR) + ofs) + +/* Register definitions of PrP */ +#define PRP_CNTL PRP_REG(0x00) +#define PRP_INTRCNTL PRP_REG(0x04) +#define PRP_INTRSTATUS PRP_REG(0x08) +#define PRP_SOURCE_Y_PTR PRP_REG(0x0C) +#define PRP_SOURCE_CB_PTR PRP_REG(0x10) +#define PRP_SOURCE_CR_PTR PRP_REG(0x14) +#define PRP_DEST_RGB1_PTR PRP_REG(0x18) +#define PRP_DEST_RGB2_PTR PRP_REG(0x1C) +#define PRP_DEST_Y_PTR PRP_REG(0x20) +#define PRP_DEST_CB_PTR PRP_REG(0x24) +#define PRP_DEST_CR_PTR PRP_REG(0x28) +#define PRP_SOURCE_FRAME_SIZE PRP_REG(0x2C) +#define PRP_CH1_LINE_STRIDE PRP_REG(0x30) +#define PRP_SRC_PIXEL_FORMAT_CNTL PRP_REG(0x34) +#define PRP_CH1_PIXEL_FORMAT_CNTL PRP_REG(0x38) +#define PRP_CH1_OUT_IMAGE_SIZE PRP_REG(0x3C) +#define PRP_CH2_OUT_IMAGE_SIZE PRP_REG(0x40) +#define PRP_SOURCE_LINE_STRIDE PRP_REG(0x44) +#define PRP_CSC_COEF_012 PRP_REG(0x48) +#define PRP_CSC_COEF_345 PRP_REG(0x4C) +#define PRP_CSC_COEF_678 PRP_REG(0x50) +#define PRP_CH1_RZ_HORI_COEF1 PRP_REG(0x54) +#define PRP_CH1_RZ_HORI_COEF2 PRP_REG(0x58) +#define PRP_CH1_RZ_HORI_VALID PRP_REG(0x5C) +#define PRP_CH1_RZ_VERT_COEF1 PRP_REG(0x60) +#define PRP_CH1_RZ_VERT_COEF2 PRP_REG(0x64) +#define PRP_CH1_RZ_VERT_VALID PRP_REG(0x68) +#define PRP_CH2_RZ_HORI_COEF1 PRP_REG(0x6C) +#define PRP_CH2_RZ_HORI_COEF2 PRP_REG(0x70) +#define PRP_CH2_RZ_HORI_VALID PRP_REG(0x74) +#define PRP_CH2_RZ_VERT_COEF1 PRP_REG(0x78) +#define PRP_CH2_RZ_VERT_COEF2 PRP_REG(0x7C) +#define PRP_CH2_RZ_VERT_VALID PRP_REG(0x80) + +#define B_SET(b) (1 << (b)) + +/* Bit definitions for PrP control register */ +#define PRP_CNTL_RSTVAL 0x28 +#define PRP_CNTL_CH1EN B_SET(0) +#define PRP_CNTL_CH2EN B_SET(1) +#define PRP_CNTL_CSI B_SET(2) +#define PRP_CNTL_IN_32 B_SET(3) +#define PRP_CNTL_IN_RGB B_SET(4) +#define PRP_CNTL_IN_YUV420 0 +#define PRP_CNTL_IN_YUV422 PRP_CNTL_IN_32 +#define PRP_CNTL_IN_RGB16 PRP_CNTL_IN_RGB +#define PRP_CNTL_IN_RGB32 (PRP_CNTL_IN_RGB | PRP_CNTL_IN_32) +#define PRP_CNTL_CH1_RGB8 0 +#define PRP_CNTL_CH1_RGB16 B_SET(5) +#define PRP_CNTL_CH1_RGB32 B_SET(6) +#define PRP_CNTL_CH1_YUV422 (B_SET(5) | B_SET(6)) +#define PRP_CNTL_CH2_YUV420 0 +#define PRP_CNTL_CH2_YUV422 B_SET(7) +#define PRP_CNTL_CH2_YUV444 B_SET(8) +#define PRP_CNTL_CH1_LOOP B_SET(9) +#define PRP_CNTL_CH2_LOOP B_SET(10) +#define PRP_CNTL_AUTODROP B_SET(11) +#define PRP_CNTL_RST B_SET(12) +#define PRP_CNTL_CNTREN B_SET(13) +#define PRP_CNTL_WINEN B_SET(14) +#define PRP_CNTL_UNCHAIN B_SET(15) +#define PRP_CNTL_IN_SKIP_NONE 0 +#define PRP_CNTL_IN_SKIP_1_2 B_SET(16) +#define PRP_CNTL_IN_SKIP_1_3 B_SET(17) +#define PRP_CNTL_IN_SKIP_2_3 (B_SET(16) | B_SET(17)) +#define PRP_CNTL_IN_SKIP_1_4 B_SET(18) +#define PRP_CNTL_IN_SKIP_3_4 (B_SET(16) | B_SET(18)) +#define PRP_CNTL_IN_SKIP_2_5 (B_SET(17) | B_SET(18)) +#define PRP_CNTL_IN_SKIP_3_5 (B_SET(16) | B_SET(17) | B_SET(18)) +#define PRP_CNTL_CH1_SKIP_NONE 0 +#define PRP_CNTL_CH1_SKIP_1_2 B_SET(19) +#define PRP_CNTL_CH1_SKIP_1_3 B_SET(20) +#define PRP_CNTL_CH1_SKIP_2_3 (B_SET(19) | B_SET(20)) +#define PRP_CNTL_CH1_SKIP_1_4 B_SET(21) +#define PRP_CNTL_CH1_SKIP_3_4 (B_SET(19) | B_SET(21)) +#define PRP_CNTL_CH1_SKIP_2_5 (B_SET(20) | B_SET(21)) +#define PRP_CNTL_CH1_SKIP_3_5 (B_SET(19) | B_SET(20) | B_SET(21)) +#define PRP_CNTL_CH2_SKIP_NONE 0 +#define PRP_CNTL_CH2_SKIP_1_2 B_SET(22) +#define PRP_CNTL_CH2_SKIP_1_3 B_SET(23) +#define PRP_CNTL_CH2_SKIP_2_3 (B_SET(22) | B_SET(23)) +#define PRP_CNTL_CH2_SKIP_1_4 B_SET(24) +#define PRP_CNTL_CH2_SKIP_3_4 (B_SET(22) | B_SET(24)) +#define PRP_CNTL_CH2_SKIP_2_5 (B_SET(23) | B_SET(24)) +#define PRP_CNTL_CH2_SKIP_3_5 (B_SET(22) | B_SET(23) | B_SET(24)) +#define PRP_CNTL_FIFO_I128 0 +#define PRP_CNTL_FIFO_I96 B_SET(25) +#define PRP_CNTL_FIFO_I64 B_SET(26) +#define PRP_CNTL_FIFO_I32 (B_SET(25) | B_SET(26)) +#define PRP_CNTL_FIFO_O64 0 +#define PRP_CNTL_FIFO_O48 B_SET(27) +#define PRP_CNTL_FIFO_O32 B_SET(28) +#define PRP_CNTL_FIFO_O16 (B_SET(27) | B_SET(28)) +#define PRP_CNTL_CH2B1 B_SET(29) +#define PRP_CNTL_CH2B2 B_SET(30) +#define PRP_CNTL_CH2_FLOWEN B_SET(31) + +/* Bit definitions for PrP interrupt control register */ +#define PRP_INTRCNTL_RDERR B_SET(0) +#define PRP_INTRCNTL_CH1WERR B_SET(1) +#define PRP_INTRCNTL_CH2WERR B_SET(2) +#define PRP_INTRCNTL_CH1FC B_SET(3) +#define PRP_INTRCNTL_CH2FC B_SET(5) +#define PRP_INTRCNTL_LBOVF B_SET(7) +#define PRP_INTRCNTL_CH2OVF B_SET(8) + +/* Bit definitions for PrP interrupt status register */ +#define PRP_INTRSTAT_RDERR B_SET(0) +#define PRP_INTRSTAT_CH1WERR B_SET(1) +#define PRP_INTRSTAT_CH2WERR B_SET(2) +#define PRP_INTRSTAT_CH2BUF2 B_SET(3) +#define PRP_INTRSTAT_CH2BUF1 B_SET(4) +#define PRP_INTRSTAT_CH1BUF2 B_SET(5) +#define PRP_INTRSTAT_CH1BUF1 B_SET(6) +#define PRP_INTRSTAT_LBOVF B_SET(7) +#define PRP_INTRSTAT_CH2OVF B_SET(8) + +#define PRP_CHANNEL_1 0x1 +#define PRP_CHANNEL_2 0x2 + +/* PRP-CSI config */ +#define PRP_CSI_EN 0x80 +#define PRP_CSI_LOOP (0x40 | PRP_CSI_EN) +#define PRP_CSI_IRQ_FRM (0x08 | PRP_CSI_LOOP) +#define PRP_CSI_IRQ_CH1ERR (0x10 | PRP_CSI_LOOP) +#define PRP_CSI_IRQ_CH2ERR (0x20 | PRP_CSI_LOOP) +#define PRP_CSI_IRQ_ALL (0x38 | PRP_CSI_LOOP) +#define PRP_CSI_SKIP_NONE 0 +#define PRP_CSI_SKIP_1OF2 1 +#define PRP_CSI_SKIP_1OF3 2 +#define PRP_CSI_SKIP_2OF3 3 +#define PRP_CSI_SKIP_1OF4 4 +#define PRP_CSI_SKIP_3OF4 5 +#define PRP_CSI_SKIP_2OF5 6 +#define PRP_CSI_SKIP_4OF5 7 + +#define PRP_PIXIN_RGB565 0x2CA00565 +#define PRP_PIXIN_RGB888 0x41000888 +#define PRP_PIXIN_YUV420 0 +#define PRP_PIXIN_YUYV 0x22000888 +#define PRP_PIXIN_YVYU 0x20100888 +#define PRP_PIXIN_UYVY 0x03080888 +#define PRP_PIXIN_VYUY 0x01180888 +#define PRP_PIXIN_YUV422 0x62080888 + +#define PRP_PIX1_RGB332 0x14400322 +#define PRP_PIX1_RGB565 0x2CA00565 +#define PRP_PIX1_RGB888 0x41000888 +#define PRP_PIX1_YUYV 0x62000888 +#define PRP_PIX1_YVYU 0x60100888 +#define PRP_PIX1_UYVY 0x43080888 +#define PRP_PIX1_VYUY 0x41180888 +#define PRP_PIX1_UNUSED 0 + +#define PRP_PIX2_YUV420 0 +#define PRP_PIX2_YUV422 1 +#define PRP_PIX2_YUV444 4 +#define PRP_PIX2_UNUSED 8 + +#define PRP_ALGO_WIDTH_ANY 0 +#define PRP_ALGO_HEIGHT_ANY 0 +#define PRP_ALGO_WIDTH_BIL 1 +#define PRP_ALGO_WIDTH_AVG 2 +#define PRP_ALGO_HEIGHT_BIL 4 +#define PRP_ALGO_HEIGHT_AVG 8 +#define PRP_ALGO_BYPASS 0x10 + +typedef struct _emma_prp_ratio { + unsigned short num; + unsigned short den; +} emma_prp_ratio; + +/* + * The following definitions are for resizing. Definition values must not + * be changed otherwise decision logic will be wrong. + */ +#define SCALE_RETRY 16 /* retry times if ratio is not supported */ + +#define BC_COEF 3 +#define MAX_TBL 20 +#define SZ_COEF (1 << BC_COEF) + +#define ALGO_AUTO 0 +#define ALGO_BIL 1 +#define ALGO_AVG 2 + +typedef struct { + char tbl[20]; /* table entries */ + char len; /* table length used */ + char algo; /* ALGO_xxx */ + char ratio[20]; /* ratios used */ +} scale_t; + +/* + * structure for prp scaling. + * algorithm - bilinear or averaging for each axis + * PRP_ALGO_WIDTH_x | PRP_ALGO_HEIGHT_x | PRP_ALGO_BYPASS + * PRP_ALGO_BYPASS - Ch1 will not use Ch2 scaling with this flag + */ +typedef struct _emma_prp_scale { + unsigned char algo; + emma_prp_ratio width; + emma_prp_ratio height; +} emma_prp_scale; + +typedef struct emma_prp_cfg { + unsigned int in_pix; /* PRP_PIXIN_xxx */ + unsigned short in_width; /* image width, 32 - 2044 */ + unsigned short in_height; /* image height, 32 - 2044 */ + unsigned char in_csi; /* PRP_CSI_SKIP_x | PRP_CSI_LOOP */ + unsigned short in_line_stride; /* in_line_stride and in_line_skip */ + unsigned short in_line_skip; /* allow cropping from CSI */ + unsigned int in_ptr; /* bus address */ + /* + * in_csc[9] = 1 -> Y-16 + * if in_csc[1..9] == 0 + * in_csc[0] represents YUV range 0-3 = A0,A1,B0,B1; + * else + * in_csc[0..9] represents either format + */ + unsigned short in_csc[10]; + + unsigned char ch2_pix; /* PRP_PIX2_xxx */ + emma_prp_scale ch2_scale; /* resizing paramters */ + unsigned short ch2_width; /* 4-2044, 0 = scaled */ + unsigned short ch2_height; /* 4-2044, 0 = scaled */ + unsigned int ch2_ptr; /* bus addr */ + unsigned int ch2_ptr2; /* bus addr for 2nd buf (loop mode) */ + unsigned char ch2_csi; /* PRP_CSI_SKIP_x | PRP_CSI_LOOP */ + + unsigned int ch1_pix; /* PRP_PIX1_xxx */ + emma_prp_scale ch1_scale; /* resizing parameters */ + unsigned short ch1_width; /* 4-2044, 0 = scaled */ + unsigned short ch1_height; /* 4-2044, 0 = scaled */ + unsigned short ch1_stride; /* 4-4088, 0 = ch1_width */ + unsigned int ch1_ptr; /* bus addr */ + unsigned int ch1_ptr2; /* bus addr for 2nd buf (loop mode) */ + unsigned char ch1_csi; /* PRP_CSI_SKIP_x | PRP_CSI_LOOP */ + + /* + * channel resizing coefficients + * scale[0] for channel 1 width + * scale[1] for channel 1 height + * scale[2] for channel 2 width + * scale[3] for channel 2 height + */ + scale_t scale[4]; +} emma_prp_cfg; + +int prphw_reset(void); +int prphw_enable(int channel); +int prphw_disable(int channel); +int prphw_inptr(emma_prp_cfg *); +int prphw_ch1ptr(emma_prp_cfg *); +int prphw_ch1ptr2(emma_prp_cfg *); +int prphw_ch2ptr(emma_prp_cfg *); +int prphw_ch2ptr2(emma_prp_cfg *); +int prphw_cfg(emma_prp_cfg *); +int prphw_isr(void); +void prphw_init(void); +void prphw_exit(void); + +/* + * scale out coefficient table + * din in scale numerator + * dout in scale denominator + * inv in pre-scale dimension + * vout in/out post-scale output dimension + * pout out post-scale internal dimension [opt] + * retry in retry times (round the output length) when need + */ +int prp_scale(scale_t * pscale, int din, int dout, int inv, + unsigned short *vout, unsigned short *pout, int retry); + +int prp_init(void *dev_id); +void prp_exit(void *dev_id); +int prp_enc_select(void *data); +int prp_enc_deselect(void *data); +int prp_vf_select(void *data); +int prp_vf_deselect(void *data); +int prp_still_select(void *data); +int prp_still_deselect(void *data); + +#endif /* __MX27_PRP_H__ */ diff --git a/drivers/media/video/mxc/capture/mx27_prphw.c b/drivers/media/video/mxc/capture/mx27_prphw.c new file mode 100644 index 000000000000..c56a6df1716e --- /dev/null +++ b/drivers/media/video/mxc/capture/mx27_prphw.c @@ -0,0 +1,1099 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mx27_prphw.c + * + * @brief MX27 Video For Linux 2 capture driver + * + * @ingroup MXC_V4L2_CAPTURE + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/string.h> +#include <linux/clk.h> +#include <asm/io.h> +#include <linux/delay.h> + +#include "mx27_prp.h" + +#define PRP_MIN_IN_WIDTH 32 +#define PRP_MAX_IN_WIDTH 2044 +#define PRP_MIN_IN_HEIGHT 32 +#define PRP_MAX_IN_HEIGHT 2044 + +typedef struct _coeff_t { + unsigned long coeff[2]; + unsigned long cntl; +} coeff_t[2][2]; + +static coeff_t *PRP_RSZ_COEFF = (coeff_t *) PRP_CH1_RZ_HORI_COEF1; + +static unsigned char scale_get(scale_t * t, + unsigned char *i, unsigned char *out); +static int gcd(int x, int y); +static int ratio(int x, int y, int *den); +static int prp_scale_bilinear(scale_t * t, int coeff, int base, int nxt); +static int prp_scale_ave(scale_t * t, unsigned char base); +static int ave_scale(scale_t * t, int inv, int outv); +static int scale(scale_t * t, int inv, int outv); + +/*! + * @param t table + * @param i table index + * @param out bilinear # input pixels to advance + * average whether result is ready for output + * @return coefficient +*/ +static unsigned char scale_get(scale_t * t, unsigned char *i, + unsigned char *out) +{ + unsigned char c; + + c = t->tbl[*i]; + (*i)++; + *i %= t->len; + + if (out) { + if (t->algo == ALGO_BIL) { + for ((*out) = 1; + (*i) && ((*i) < t->len) && !t->tbl[(*i)]; (*i)++) { + (*out)++; + } + if ((*i) == t->len) + (*i) = 0; + } else + *out = c >> BC_COEF; + } + + c &= SZ_COEF - 1; + + if (c == SZ_COEF - 1) + c = SZ_COEF; + + return c; +} + +/*! + * @brief Get maximum common divisor. + * @param x First input value + * @param y Second input value + * @return Maximum common divisor of x and y + */ +static int gcd(int x, int y) +{ + int k; + + if (x < y) { + k = x; + x = y; + y = k; + } + + while ((k = x % y)) { + x = y; + y = k; + } + + return y; +} + +/*! + * @brief Get ratio. + * @param x First input value + * @param y Second input value + * @param den Denominator of the ratio (corresponding to y) + * @return Numerator of the ratio (corresponding to x) + */ +static int ratio(int x, int y, int *den) +{ + int g; + + if (!x || !y) + return 0; + + g = gcd(x, y); + *den = y / g; + + return x / g; +} + +/*! + * @brief Build PrP coefficient entry based on bilinear algorithm + * + * @param t The pointer to scale_t structure + * @param coeff The weighting coefficient + * @param base The base of the coefficient + * @param nxt Number of pixels to be read + * + * @return The length of current coefficient table on success + * -1 on failure + */ +static int prp_scale_bilinear(scale_t * t, int coeff, int base, int nxt) +{ + int i; + + if (t->len >= sizeof(t->tbl)) + return -1; + + coeff = ((coeff << BC_COEF) + (base >> 1)) / base; + if (coeff >= SZ_COEF - 1) + coeff--; + + coeff |= SZ_COEF; + t->tbl[(int)t->len++] = (unsigned char)coeff; + + for (i = 1; i < nxt; i++) { + if (t->len >= MAX_TBL) + return -1; + + t->tbl[(int)t->len++] = 0; + } + + return t->len; +} + +#define _bary(name) static const unsigned char name[] + +_bary(c1) = { +7}; + +_bary(c2) = { +4, 4}; + +_bary(c3) = { +2, 4, 2}; + +_bary(c4) = { +2, 2, 2, 2}; + +_bary(c5) = { +1, 2, 2, 2, 1}; + +_bary(c6) = { +1, 1, 2, 2, 1, 1}; + +_bary(c7) = { +1, 1, 1, 2, 1, 1, 1}; + +_bary(c8) = { +1, 1, 1, 1, 1, 1, 1, 1}; + +_bary(c9) = { +1, 1, 1, 1, 1, 1, 1, 1, 0}; + +_bary(c10) = { +0, 1, 1, 1, 1, 1, 1, 1, 1, 0}; + +_bary(c11) = { +0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0}; + +_bary(c12) = { +0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0}; + +_bary(c13) = { +0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0}; + +_bary(c14) = { +0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0}; + +_bary(c15) = { +0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0}; + +_bary(c16) = { +1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0}; + +_bary(c17) = { +0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0}; + +_bary(c18) = { +0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0}; + +_bary(c19) = { +0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0}; + +_bary(c20) = { +0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0}; + +static const unsigned char *ave_coeff[] = { + c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, + c11, c12, c13, c14, c15, c16, c17, c18, c19, c20 +}; + +/*! + * @brief Build PrP coefficient table based on average algorithm + * + * @param t The pointer to scale_t structure + * @param base The base of the coefficient + * + * @return The length of current coefficient table on success + * -1 on failure + */ +static int prp_scale_ave(scale_t * t, unsigned char base) +{ + if (t->len + base > sizeof(t->tbl)) + return -1; + + memcpy(&t->tbl[(int)t->len], ave_coeff[(int)base - 1], base); + t->len = (unsigned char)(t->len + base); + t->tbl[t->len - 1] |= SZ_COEF; + + return t->len; +} + +/*! + * @brief Build PrP coefficient table based on average algorithm + * + * @param t The pointer to scale_t structure + * @param inv Input resolution + * @param outv Output resolution + * + * @return The length of current coefficient table on success + * -1 on failure + */ +static int ave_scale(scale_t * t, int inv, int outv) +{ + int ratio_count; + + ratio_count = 0; + if (outv != 1) { + unsigned char a[20]; + int v; + + /* split n:m into multiple n[i]:1 */ + for (v = 0; v < outv; v++) + a[v] = (unsigned char)(inv / outv); + + inv %= outv; + if (inv) { + /* find start of next layer */ + v = (outv - inv) >> 1; + inv += v; + for (; v < inv; v++) + a[v]++; + } + + for (v = 0; v < outv; v++) { + if (prp_scale_ave(t, a[v]) < 0) + return -1; + + t->ratio[ratio_count] = a[v]; + ratio_count++; + } + } else if (prp_scale_ave(t, inv) < 0) { + return -1; + } else { + t->ratio[ratio_count++] = (char)inv; + ratio_count++; + } + + return t->len; +} + +/*! + * @brief Build PrP coefficient table + * + * @param t The pointer to scale_t structure + * @param inv input resolution reduced ratio + * @param outv output resolution reduced ratio + * + * @return The length of current coefficient table on success + * -1 on failure + */ +static int scale(scale_t * t, int inv, int outv) +{ + int v; /* overflow counter */ + int coeff, nxt; /* table output */ + + t->len = 0; + if (t->algo == ALGO_AUTO) { + /* automatic choice - bilinear for shrinking less than 2:1 */ + t->algo = ((outv != inv) && ((2 * outv) > inv)) ? + ALGO_BIL : ALGO_AVG; + } + + /* 1:1 resize must use averaging, bilinear will hang */ + if ((inv == outv) && (t->algo == ALGO_BIL)) { + pr_debug("Warning: 1:1 resize must use averaging algo\n"); + t->algo = ALGO_AVG; + } + + memset(t->tbl, 0, sizeof(t->tbl)); + if (t->algo == ALGO_BIL) { + t->ratio[0] = (char)inv; + t->ratio[1] = (char)outv; + } else + memset(t->ratio, 0, sizeof(t->ratio)); + + if (inv == outv) { + /* force scaling */ + t->ratio[0] = 1; + if (t->algo == ALGO_BIL) + t->ratio[1] = 1; + + return prp_scale_ave(t, 1); + } + + if (inv < outv) { + pr_debug("Upscaling not supported %d:%d\n", inv, outv); + return -1; + } + + if (t->algo != ALGO_BIL) + return ave_scale(t, inv, outv); + + v = 0; + if (inv >= 2 * outv) { + /* downscale: >=2:1 bilinear approximation */ + coeff = inv - 2 * outv; + v = 0; + nxt = 0; + do { + v += coeff; + nxt = 2; + while (v >= outv) { + v -= outv; + nxt++; + } + + if (prp_scale_bilinear(t, 1, 2, nxt) < 0) + return -1; + } while (v); + } else { + /* downscale: bilinear */ + int in_pos_inc = 2 * outv; + int out_pos = inv; + int out_pos_inc = 2 * inv; + int init_carry = inv - outv; + int carry = init_carry; + + v = outv + in_pos_inc; + do { + coeff = v - out_pos; + out_pos += out_pos_inc; + carry += out_pos_inc; + for (nxt = 0; v < out_pos; nxt++) { + v += in_pos_inc; + carry -= in_pos_inc; + } + if (prp_scale_bilinear(t, coeff, in_pos_inc, nxt) < 0) + return -1; + } while (carry != init_carry); + } + return t->len; +} + +/*! + * @brief Build PrP coefficient table + * + * @param pscale The pointer to scale_t structure which holdes + * coefficient tables + * @param din Scale ratio numerator + * @param dout Scale ratio denominator + * @param inv Input resolution + * @param vout Output resolution + * @param pout Internal output resolution + * @param retry Retry times (round the output length) when need + * + * @return Zero on success, others on failure + */ +int prp_scale(scale_t * pscale, int din, int dout, int inv, + unsigned short *vout, unsigned short *pout, int retry) +{ + int num; + int den; + unsigned short outv; + + /* auto-generation of values */ + if (!(dout && din)) { + if (!*vout) + dout = din = 1; + else { + din = inv; + dout = *vout; + } + } + + if (din < dout) { + pr_debug("Scale err, unsupported ratio %d : %d\n", din, dout); + return -1; + } + + lp_retry: + num = ratio(din, dout, &den); + if (!num) { + pr_debug("Scale err, unsupported ratio %d : %d\n", din, dout); + return -1; + } + + if (num > MAX_TBL || scale(pscale, num, den) < 0) { + dout++; + if (retry--) + goto lp_retry; + + pr_debug("Scale err, unsupported ratio %d : %d\n", num, den); + return -1; + } + + if (pscale->algo == ALGO_BIL) { + unsigned char i, j, k; + + outv = + (unsigned short)(inv / pscale->ratio[0] * pscale->ratio[1]); + inv %= pscale->ratio[0]; + for (i = j = 0; inv > 0; j++) { + unsigned char nxt; + + k = scale_get(pscale, &i, &nxt); + if (inv == 1 && k < SZ_COEF) { + /* needs 2 pixels for this output */ + break; + } + inv -= nxt; + } + outv = outv + j; + } else { + unsigned char i, tot; + + for (tot = i = 0; pscale->ratio[i]; i++) + tot = tot + pscale->ratio[i]; + + outv = (unsigned short)(inv / tot) * i; + inv %= tot; + for (i = 0; inv > 0; i++, outv++) + inv -= pscale->ratio[i]; + } + + if (!(*vout) || ((*vout) > outv)) + *vout = outv; + + if (pout) + *pout = outv; + + return 0; +} + +/*! + * @brief Reset PrP block + */ +int prphw_reset(void) +{ + unsigned long val; + unsigned long flag; + int i; + + flag = PRP_CNTL_RST; + val = PRP_CNTL_RSTVAL; + + __raw_writel(flag, PRP_CNTL); + + /* timeout */ + for (i = 0; i < 1000; i++) { + if (!(__raw_readl(PRP_CNTL) & flag)) { + pr_debug("PrP reset over\n"); + break; + } + msleep(1); + } + + /* verify reset value */ + if (__raw_readl(PRP_CNTL) != val) { + pr_info("PrP reset err, val = 0x%08X\n", __raw_readl(PRP_CNTL)); + return -1; + } + + return 0; +} + +/*! + * @brief Enable PrP channel. + * @param channel Channel number to be enabled + * @return Zero on success, others on failure + */ +int prphw_enable(int channel) +{ + unsigned long val; + + val = __raw_readl(PRP_CNTL); + if (channel & PRP_CHANNEL_1) + val |= PRP_CNTL_CH1EN; + if (channel & PRP_CHANNEL_2) + val |= (PRP_CNTL_CH2EN | PRP_CNTL_CH2_FLOWEN); + + __raw_writel(val, PRP_CNTL); + + return 0; +} + +/*! + * @brief Disable PrP channel. + * @param channel Channel number to be disable + * @return Zero on success, others on failure + */ +int prphw_disable(int channel) +{ + unsigned long val; + + val = __raw_readl(PRP_CNTL); + if (channel & PRP_CHANNEL_1) + val &= ~PRP_CNTL_CH1EN; + if (channel & PRP_CHANNEL_2) + val &= ~(PRP_CNTL_CH2EN | PRP_CNTL_CH2_FLOWEN); + + __raw_writel(val, PRP_CNTL); + + return 0; +} + +/*! + * @brief Set PrP input buffer address. + * @param cfg Pointer to PrP configuration parameter + * @return Zero on success, others on failure + */ +int prphw_inptr(emma_prp_cfg * cfg) +{ + if (cfg->in_csi & PRP_CSI_EN) + return -1; + + __raw_writel(cfg->in_ptr, PRP_SOURCE_Y_PTR); + if (cfg->in_pix == PRP_PIXIN_YUV420) { + u32 size; + + size = cfg->in_line_stride * cfg->in_height; + __raw_writel(cfg->in_ptr + size, PRP_SOURCE_CB_PTR); + __raw_writel(cfg->in_ptr + size + (size >> 2), + PRP_SOURCE_CR_PTR); + } + return 0; +} + +/*! + * @brief Set PrP channel 1 output buffer 1 address. + * @param cfg Pointer to PrP configuration parameter + * @return Zero on success, others on failure + */ +int prphw_ch1ptr(emma_prp_cfg * cfg) +{ + if (cfg->ch1_pix == PRP_PIX1_UNUSED) + return -1; + + __raw_writel(cfg->ch1_ptr, PRP_DEST_RGB1_PTR); + + /* support double buffer in loop mode only */ + if ((cfg->in_csi & PRP_CSI_LOOP) == PRP_CSI_LOOP) { + if (cfg->ch1_ptr2) + __raw_writel(cfg->ch1_ptr2, PRP_DEST_RGB2_PTR); + else + __raw_writel(cfg->ch1_ptr, PRP_DEST_RGB2_PTR); + } + + return 0; +} + +/*! + * @brief Set PrP channel 1 output buffer 2 address. + * @param cfg Pointer to PrP configuration parameter + * @return Zero on success, others on failure + */ +int prphw_ch1ptr2(emma_prp_cfg * cfg) +{ + if (cfg->ch1_pix == PRP_PIX1_UNUSED || + (cfg->in_csi & PRP_CSI_LOOP) != PRP_CSI_LOOP) + return -1; + + if (cfg->ch1_ptr2) + __raw_writel(cfg->ch1_ptr2, PRP_DEST_RGB2_PTR); + else + return -1; + + return 0; +} + +/*! + * @brief Set PrP channel 2 output buffer 1 address. + * @param cfg Pointer to PrP configuration parameter + * @return Zero on success, others on failure + */ +int prphw_ch2ptr(emma_prp_cfg * cfg) +{ + u32 size; + + if (cfg->ch2_pix == PRP_PIX2_UNUSED) + return -1; + + __raw_writel(cfg->ch2_ptr, PRP_DEST_Y_PTR); + + if (cfg->ch2_pix == PRP_PIX2_YUV420) { + size = cfg->ch2_width * cfg->ch2_height; + __raw_writel(cfg->ch2_ptr + size, PRP_DEST_CB_PTR); + __raw_writel(cfg->ch2_ptr + size + (size >> 2), + PRP_DEST_CR_PTR); + } + + __raw_writel(__raw_readl(PRP_CNTL) | PRP_CNTL_CH2B1, PRP_CNTL); + return 0; +} + +/*! + * @brief Set PrP channel 2 output buffer 2 address. + * @param cfg Pointer to PrP configuration parameter + * @return Zero on success, others on failure + */ +int prphw_ch2ptr2(emma_prp_cfg * cfg) +{ + u32 size; + + if (cfg->ch2_pix == PRP_PIX2_UNUSED || + (cfg->in_csi & PRP_CSI_LOOP) != PRP_CSI_LOOP) + return -1; + + __raw_writel(cfg->ch2_ptr2, PRP_SOURCE_Y_PTR); + if (cfg->ch2_pix == PRP_PIX2_YUV420) { + size = cfg->ch2_width * cfg->ch2_height; + __raw_writel(cfg->ch2_ptr2 + size, PRP_SOURCE_CB_PTR); + __raw_writel(cfg->ch2_ptr2 + size + (size >> 2), + PRP_SOURCE_CR_PTR); + } + + __raw_writel(__raw_readl(PRP_CNTL) | PRP_CNTL_CH2B2, PRP_CNTL); + return 0; +} + +/*! + * @brief Build CSC table + * @param csc CSC table + * in csc[0]=index 0..3 : A.1 A.0 B.1 B.0 + * csc[1]=direction 0 : YUV2RGB 1 : RGB2YUV + * out csc[0..4] are coefficients c[9] is offset + * csc[0..8] are coefficients c[9] is offset + */ +void csc_tbl(short csc[10]) +{ + static const unsigned short _r2y[][9] = { + {0x4D, 0x4B, 0x3A, 0x57, 0x55, 0x40, 0x40, 0x6B, 0x29}, + {0x42, 0x41, 0x32, 0x4C, 0x4A, 0x38, 0x38, 0x5E, 0x24}, + {0x36, 0x5C, 0x25, 0x3B, 0x63, 0x40, 0x40, 0x74, 0x18}, + {0x2F, 0x4F, 0x20, 0x34, 0x57, 0x38, 0x38, 0x66, 0x15}, + }; + static const unsigned short _y2r[][5] = { + {0x80, 0xb4, 0x2c, 0x5b, 0x0e4}, + {0x95, 0xcc, 0x32, 0x68, 0x104}, + {0x80, 0xca, 0x18, 0x3c, 0x0ec}, + {0x95, 0xe5, 0x1b, 0x44, 0x1e0}, + }; + unsigned short *_csc; + int _csclen; + + csc[9] = csc[0] & 1; + _csclen = csc[0] & 3; + + if (csc[1]) { + _csc = (unsigned short *)_r2y[_csclen]; + _csclen = sizeof(_r2y[0]); + } else { + _csc = (unsigned short *)_y2r[_csclen]; + _csclen = sizeof(_y2r[0]); + memset(csc + 5, 0, sizeof(short) * 4); + } + memcpy(csc, _csc, _csclen); +} + +/*! + * @brief Setup PrP resize coefficient registers + * + * @param ch PrP channel number + * @param dir Direction, 0 - horizontal, 1 - vertical + * @param scale The pointer to scale_t structure + */ +static void prp_set_scaler(int ch, int dir, scale_t * scale) +{ + int i; + unsigned int coeff[2]; + unsigned int valid; + + for (coeff[0] = coeff[1] = valid = 0, i = 19; i >= 0; i--) { + int j; + + j = i > 9 ? 1 : 0; + coeff[j] = (coeff[j] << BC_COEF) | + (scale->tbl[i] & (SZ_COEF - 1)); + + if (i == 5 || i == 15) + coeff[j] <<= 1; + + valid = (valid << 1) | (scale->tbl[i] >> BC_COEF); + } + + valid |= (scale->len << 24) | ((2 - scale->algo) << 31); + + for (i = 0; i < 2; i++) + (*PRP_RSZ_COEFF)[1 - ch][dir].coeff[i] = coeff[i]; + + (*PRP_RSZ_COEFF)[1 - ch][dir].cntl = valid; +} + +/*! + * @brief Setup PrP registers relevant to input. + * @param cfg Pointer to PrP configuration parameter + * @param prp_cntl Holds the value for PrP control register + * @return Zero on success, others on failure + */ +static int prphw_input_cfg(emma_prp_cfg * cfg, unsigned long *prp_cntl) +{ + unsigned long mask; + + switch (cfg->in_pix) { + case PRP_PIXIN_YUV420: + *prp_cntl |= PRP_CNTL_IN_YUV420; + mask = 0x7; + break; + case PRP_PIXIN_YUYV: + case PRP_PIXIN_YVYU: + case PRP_PIXIN_UYVY: + case PRP_PIXIN_VYUY: + *prp_cntl |= PRP_CNTL_IN_YUV422; + mask = 0x1; + break; + case PRP_PIXIN_RGB565: + *prp_cntl |= PRP_CNTL_IN_RGB16; + mask = 0x1; + break; + case PRP_PIXIN_RGB888: + *prp_cntl |= PRP_CNTL_IN_RGB32; + mask = 0; + break; + default: + pr_debug("Unsupported input pix format 0x%08X\n", cfg->in_pix); + return -1; + } + + /* align the input image width */ + if (cfg->in_width & mask) { + pr_debug("in_width misaligned. in_width=%d\n", cfg->in_width); + return -1; + } + + if ((cfg->in_width < PRP_MIN_IN_WIDTH) + || (cfg->in_width > PRP_MAX_IN_WIDTH)) { + pr_debug("Unsupported input width %d\n", cfg->in_width); + return -1; + } + + cfg->in_height &= ~1; /* truncate to make even */ + + if ((cfg->in_height < PRP_MIN_IN_HEIGHT) + || (cfg->in_height > PRP_MAX_IN_HEIGHT)) { + pr_debug("Unsupported input height %d\n", cfg->in_height); + return -1; + } + + if (!(cfg->in_csi & PRP_CSI_EN)) + if (!cfg->in_line_stride) + cfg->in_line_stride = cfg->in_width; + + __raw_writel(cfg->in_pix, PRP_SRC_PIXEL_FORMAT_CNTL); + __raw_writel((cfg->in_width << 16) | cfg->in_height, + PRP_SOURCE_FRAME_SIZE); + __raw_writel((cfg->in_line_skip << 16) | cfg->in_line_stride, + PRP_SOURCE_LINE_STRIDE); + + if (!(cfg->in_csi & PRP_CSI_EN)) { + __raw_writel(cfg->in_ptr, PRP_SOURCE_Y_PTR); + if (cfg->in_pix == PRP_PIXIN_YUV420) { + unsigned int size; + + size = cfg->in_line_stride * cfg->in_height; + __raw_writel(cfg->in_ptr + size, PRP_SOURCE_CB_PTR); + __raw_writel(cfg->in_ptr + size + (size >> 2), + PRP_SOURCE_CR_PTR); + } + } + + /* always cropping */ + *prp_cntl |= PRP_CNTL_WINEN; + + /* color space conversion */ + if (!cfg->in_csc[1]) { + if (cfg->in_csc[0] > 3) { + pr_debug("in_csc invalid 0x%X\n", cfg->in_csc[0]); + return -1; + } + if ((cfg->in_pix == PRP_PIXIN_RGB565) + || (cfg->in_pix == PRP_PIXIN_RGB888)) + cfg->in_csc[1] = 1; + else + cfg->in_csc[0] = 0; + csc_tbl(cfg->in_csc); + } + + __raw_writel((cfg->in_csc[0] << 21) | (cfg->in_csc[1] << 11) + | cfg->in_csc[2], PRP_CSC_COEF_012); + __raw_writel((cfg->in_csc[3] << 21) | (cfg->in_csc[4] << 11) + | cfg->in_csc[5], PRP_CSC_COEF_345); + __raw_writel((cfg->in_csc[6] << 21) | (cfg->in_csc[7] << 11) + | cfg->in_csc[8] | (cfg->in_csc[9] << 31), + PRP_CSC_COEF_678); + + if (cfg->in_csi & PRP_CSI_EN) { + *prp_cntl |= PRP_CNTL_CSI; + + /* loop mode enable, ch1 ch2 together */ + if ((cfg->in_csi & PRP_CSI_LOOP) == PRP_CSI_LOOP) + *prp_cntl |= (PRP_CNTL_CH1_LOOP | PRP_CNTL_CH2_LOOP); + } + + return 0; +} + +/*! + * @brief Setup PrP registers relevant to channel 2. + * @param cfg Pointer to PrP configuration parameter + * @param prp_cntl Holds the value for PrP control register + * @return Zero on success, others on failure + */ +static int prphw_ch2_cfg(emma_prp_cfg * cfg, unsigned long *prp_cntl) +{ + switch (cfg->ch2_pix) { + case PRP_PIX2_YUV420: + *prp_cntl |= PRP_CNTL_CH2_YUV420; + break; + case PRP_PIX2_YUV422: + *prp_cntl |= PRP_CNTL_CH2_YUV422; + break; + case PRP_PIX2_YUV444: + *prp_cntl |= PRP_CNTL_CH2_YUV444; + break; + case PRP_PIX2_UNUSED: + return 0; + default: + pr_debug("Unsupported channel 2 pix format 0x%08X\n", + cfg->ch2_pix); + return -1; + } + + if (cfg->ch2_pix == PRP_PIX2_YUV420) { + cfg->ch2_height &= ~1; /* ensure U/V presence */ + cfg->ch2_width &= ~7; /* ensure U/V word aligned */ + } else if (cfg->ch2_pix == PRP_PIX2_YUV422) { + cfg->ch2_width &= ~1; /* word aligned */ + } + + __raw_writel((cfg->ch2_width << 16) | cfg->ch2_height, + PRP_CH2_OUT_IMAGE_SIZE); + + if (cfg->ch2_pix == PRP_PIX2_YUV420) { + u32 size; + + /* Luminanance band start address */ + __raw_writel(cfg->ch2_ptr, PRP_DEST_Y_PTR); + + if ((cfg->in_csi & PRP_CSI_LOOP) == PRP_CSI_LOOP) { + if (!cfg->ch2_ptr2) + __raw_writel(cfg->ch2_ptr, PRP_SOURCE_Y_PTR); + else + __raw_writel(cfg->ch2_ptr2, PRP_SOURCE_Y_PTR); + } + + /* Cb and Cr band start address */ + size = cfg->ch2_width * cfg->ch2_height; + __raw_writel(cfg->ch2_ptr + size, PRP_DEST_CB_PTR); + __raw_writel(cfg->ch2_ptr + size + (size >> 2), + PRP_DEST_CR_PTR); + + if ((cfg->in_csi & PRP_CSI_LOOP) == PRP_CSI_LOOP) { + if (!cfg->ch2_ptr2) { + __raw_writel(cfg->ch2_ptr + size, + PRP_SOURCE_CB_PTR); + __raw_writel(cfg->ch2_ptr + size + (size >> 2), + PRP_SOURCE_CR_PTR); + } else { + __raw_writel(cfg->ch2_ptr2 + size, + PRP_SOURCE_CB_PTR); + __raw_writel(cfg->ch2_ptr2 + size + (size >> 2), + PRP_SOURCE_CR_PTR); + } + } + } else { /* Pixel interleaved YUV422 or YUV444 */ + __raw_writel(cfg->ch2_ptr, PRP_DEST_Y_PTR); + + if ((cfg->in_csi & PRP_CSI_LOOP) == PRP_CSI_LOOP) { + if (!cfg->ch2_ptr2) + __raw_writel(cfg->ch2_ptr, PRP_SOURCE_Y_PTR); + else + __raw_writel(cfg->ch2_ptr2, PRP_SOURCE_Y_PTR); + } + } + *prp_cntl |= PRP_CNTL_CH2B1 | PRP_CNTL_CH2B2; + + return 0; +} + +/*! + * @brief Setup PrP registers relevant to channel 1. + * @param cfg Pointer to PrP configuration parameter + * @param prp_cntl Holds the value for PrP control register + * @return Zero on success, others on failure + */ +static int prphw_ch1_cfg(emma_prp_cfg * cfg, unsigned long *prp_cntl) +{ + int ch1_bpp = 0; + + switch (cfg->ch1_pix) { + case PRP_PIX1_RGB332: + *prp_cntl |= PRP_CNTL_CH1_RGB8; + ch1_bpp = 1; + break; + case PRP_PIX1_RGB565: + *prp_cntl |= PRP_CNTL_CH1_RGB16; + ch1_bpp = 2; + break; + case PRP_PIX1_RGB888: + *prp_cntl |= PRP_CNTL_CH1_RGB32; + ch1_bpp = 4; + break; + case PRP_PIX1_YUYV: + case PRP_PIX1_YVYU: + case PRP_PIX1_UYVY: + case PRP_PIX1_VYUY: + *prp_cntl |= PRP_CNTL_CH1_YUV422; + ch1_bpp = 2; + break; + case PRP_PIX1_UNUSED: + return 0; + default: + pr_debug("Unsupported channel 1 pix format 0x%08X\n", + cfg->ch1_pix); + return -1; + } + + /* parallel or cascade resize */ + if (cfg->ch1_scale.algo & PRP_ALGO_BYPASS) + *prp_cntl |= PRP_CNTL_UNCHAIN; + + /* word align */ + if (ch1_bpp == 2) + cfg->ch1_width &= ~1; + else if (ch1_bpp == 1) + cfg->ch1_width &= ~3; + + if (!cfg->ch1_stride) + cfg->ch1_stride = cfg->ch1_width; + + __raw_writel(cfg->ch1_pix, PRP_CH1_PIXEL_FORMAT_CNTL); + __raw_writel((cfg->ch1_width << 16) | cfg->ch1_height, + PRP_CH1_OUT_IMAGE_SIZE); + __raw_writel(cfg->ch1_stride * ch1_bpp, PRP_CH1_LINE_STRIDE); + __raw_writel(cfg->ch1_ptr, PRP_DEST_RGB1_PTR); + + /* double buffer for loop mode */ + if ((cfg->in_csi & PRP_CSI_LOOP) == PRP_CSI_LOOP) { + if (cfg->ch1_ptr2) + __raw_writel(cfg->ch1_ptr2, PRP_DEST_RGB2_PTR); + else + __raw_writel(cfg->ch1_ptr, PRP_DEST_RGB2_PTR); + } + + return 0; +} + +/*! + * @brief Setup PrP registers. + * @param cfg Pointer to PrP configuration parameter + * @return Zero on success, others on failure + */ +int prphw_cfg(emma_prp_cfg * cfg) +{ + unsigned long prp_cntl = 0; + unsigned long val; + + /* input pixel format checking */ + if (prphw_input_cfg(cfg, &prp_cntl)) + return -1; + + if (prphw_ch2_cfg(cfg, &prp_cntl)) + return -1; + + if (prphw_ch1_cfg(cfg, &prp_cntl)) + return -1; + + /* register setting */ + __raw_writel(prp_cntl, PRP_CNTL); + + /* interrupt configuration */ + val = PRP_INTRCNTL_RDERR | PRP_INTRCNTL_LBOVF; + if (cfg->ch1_pix != PRP_PIX1_UNUSED) + val |= PRP_INTRCNTL_CH1FC | PRP_INTRCNTL_CH1WERR; + if (cfg->ch2_pix != PRP_PIX2_UNUSED) + val |= + PRP_INTRCNTL_CH2FC | PRP_INTRCNTL_CH2WERR | + PRP_INTRCNTL_CH2OVF; + __raw_writel(val, PRP_INTRCNTL); + + prp_set_scaler(1, 0, &cfg->scale[0]); /* Channel 1 width */ + prp_set_scaler(1, 1, &cfg->scale[1]); /* Channel 1 height */ + prp_set_scaler(0, 0, &cfg->scale[2]); /* Channel 2 width */ + prp_set_scaler(0, 1, &cfg->scale[3]); /* Channel 2 height */ + + return 0; +} + +/*! + * @brief Check PrP interrupt status. + * @return PrP interrupt status + */ +int prphw_isr(void) +{ + int status; + + status = __raw_readl(PRP_INTRSTATUS) & 0x1FF; + + if (status & (PRP_INTRSTAT_RDERR | PRP_INTRSTAT_CH1WERR | + PRP_INTRSTAT_CH2WERR)) + pr_debug("isr bus error. status= 0x%08X\n", status); + else if (status & PRP_INTRSTAT_CH2OVF) + pr_debug("isr ch 2 buffer overflow. status= 0x%08X\n", status); + else if (status & PRP_INTRSTAT_LBOVF) + pr_debug("isr line buffer overflow. status= 0x%08X\n", status); + + /* silicon bug?? enable bit does not self clear? */ + if (!(__raw_readl(PRP_CNTL) & PRP_CNTL_CH1_LOOP)) + __raw_writel(__raw_readl(PRP_CNTL) & (~PRP_CNTL_CH1EN), + PRP_CNTL); + if (!(__raw_readl(PRP_CNTL) & PRP_CNTL_CH2_LOOP)) + __raw_writel(__raw_readl(PRP_CNTL) & (~PRP_CNTL_CH2EN), + PRP_CNTL); + + __raw_writel(status, PRP_INTRSTATUS); /* clr irq */ + + return status; +} + +static struct clk *emma_clk; + +/*! + * @brief PrP module clock enable + */ +void prphw_init(void) +{ + emma_clk = clk_get(NULL, "emma_clk"); + clk_enable(emma_clk); +} + +/*! + * @brief PrP module clock disable + */ +void prphw_exit(void) +{ + clk_disable(emma_clk); + clk_put(emma_clk); +} diff --git a/drivers/media/video/mxc/capture/mx27_prpsw.c b/drivers/media/video/mxc/capture/mx27_prpsw.c new file mode 100644 index 000000000000..1f93e32d36c1 --- /dev/null +++ b/drivers/media/video/mxc/capture/mx27_prpsw.c @@ -0,0 +1,1042 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mx27_prpsw.c + * + * @brief MX27 Video For Linux 2 capture driver + * + * @ingroup MXC_V4L2_CAPTURE + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/string.h> +#include <linux/fb.h> +#include <linux/pci.h> +#include <asm/cacheflush.h> +#include <asm/io.h> +#include <asm/irq.h> + +#include "mxc_v4l2_capture.h" +#include "mx27_prp.h" +#include "mx27_csi.h" +#include "../drivers/video/mxc/mx2fb.h" +#include "../opl/opl.h" + +#define MEAN_COEF (SZ_COEF >> 1) + +static char prp_dev[] = "emma_prp"; +static int g_still_on = 0; +static emma_prp_cfg g_prp_cfg; +static int g_vfbuf, g_rotbuf; +static struct tasklet_struct prp_vf_tasklet; + +/* + * The following variables represents the virtual address for the cacheable + * buffers accessed by SW rotation/mirroring. The rotation/mirroring in + * cacheable buffers has significant performance improvement than it in + * non-cacheable buffers. + */ +static char *g_vaddr_vfbuf[2] = { 0, 0 }; +static char *g_vaddr_rotbuf[2] = { 0, 0 }; +static char *g_vaddr_fb = 0; + +static int set_ch1_addr(emma_prp_cfg * cfg, cam_data * cam); +static int prp_v4l2_cfg(emma_prp_cfg * cfg, cam_data * cam); +static int prp_vf_mem_alloc(cam_data * cam); +static void prp_vf_mem_free(cam_data * cam); +static int prp_rot_mem_alloc(cam_data * cam); +static void prp_rot_mem_free(cam_data * cam); +static int prp_enc_update_eba(u32 eba, int *buffer_num); +static int prp_enc_enable(void *private); +static int prp_enc_disable(void *private); +static int prp_vf_start(void *private); +static int prp_vf_stop(void *private); +static int prp_still_start(void *private); +static int prp_still_stop(void *private); +static irqreturn_t prp_isr(int irq, void *dev_id); +static void rotation(unsigned long private); +static int prp_resize_check_ch1(emma_prp_cfg * cfg); +static int prp_resize_check_ch2(emma_prp_cfg * cfg); + +#define PRP_DUMP(val) pr_debug("%s\t = 0x%08X\t%d\n", #val, val, val) + +/*! + * @brief Dump PrP configuration parameters. + * @param cfg The pointer to PrP configuration parameter + */ +static void prp_cfg_dump(emma_prp_cfg * cfg) +{ + PRP_DUMP(cfg->in_pix); + PRP_DUMP(cfg->in_width); + PRP_DUMP(cfg->in_height); + PRP_DUMP(cfg->in_csi); + PRP_DUMP(cfg->in_line_stride); + PRP_DUMP(cfg->in_line_skip); + PRP_DUMP(cfg->in_ptr); + + PRP_DUMP(cfg->ch1_pix); + PRP_DUMP(cfg->ch1_width); + PRP_DUMP(cfg->ch1_height); + PRP_DUMP(cfg->ch1_scale.algo); + PRP_DUMP(cfg->ch1_scale.width.num); + PRP_DUMP(cfg->ch1_scale.width.den); + PRP_DUMP(cfg->ch1_scale.height.num); + PRP_DUMP(cfg->ch1_scale.height.den); + PRP_DUMP(cfg->ch1_stride); + PRP_DUMP(cfg->ch1_ptr); + PRP_DUMP(cfg->ch1_ptr2); + PRP_DUMP(cfg->ch1_csi); + + PRP_DUMP(cfg->ch2_pix); + PRP_DUMP(cfg->ch2_width); + PRP_DUMP(cfg->ch2_height); + PRP_DUMP(cfg->ch2_scale.algo); + PRP_DUMP(cfg->ch2_scale.width.num); + PRP_DUMP(cfg->ch2_scale.width.den); + PRP_DUMP(cfg->ch2_scale.height.num); + PRP_DUMP(cfg->ch2_scale.height.den); + PRP_DUMP(cfg->ch2_ptr); + PRP_DUMP(cfg->ch2_ptr2); + PRP_DUMP(cfg->ch2_csi); +} + +/*! + * @brief Set PrP channel 1 output address. + * @param cfg Pointer to emma_prp_cfg structure + * @param cam Pointer to cam_data structure + * @return Zero on success, others on failure + */ +static int set_ch1_addr(emma_prp_cfg * cfg, cam_data * cam) +{ + if (cam->rotation != V4L2_MXC_ROTATE_NONE) { + cfg->ch1_ptr = (unsigned int)cam->rot_vf_bufs[0]; + cfg->ch1_ptr2 = (unsigned int)cam->rot_vf_bufs[1]; + if ((cam->rotation == V4L2_MXC_ROTATE_90_RIGHT) + || (cam->rotation == V4L2_MXC_ROTATE_90_RIGHT_VFLIP) + || (cam->rotation == V4L2_MXC_ROTATE_90_RIGHT_HFLIP) + || (cam->rotation == V4L2_MXC_ROTATE_90_LEFT)) + cfg->ch1_stride = cam->win.w.height; + else + cfg->ch1_stride = cam->win.w.width; + + if (cam->v4l2_fb.flags != V4L2_FBUF_FLAG_OVERLAY) { + struct fb_info *fb = cam->overlay_fb; + if (!fb) + return -1; + if (g_vaddr_fb) + iounmap(g_vaddr_fb); + g_vaddr_fb = ioremap_cached(fb->fix.smem_start, + fb->fix.smem_len); + if (!g_vaddr_fb) + return -1; + } + } else if (cam->v4l2_fb.flags == V4L2_FBUF_FLAG_OVERLAY) { + cfg->ch1_ptr = (unsigned int)cam->vf_bufs[0]; + cfg->ch1_ptr2 = (unsigned int)cam->vf_bufs[1]; + cfg->ch1_stride = cam->win.w.width; + } else { + struct fb_info *fb = cam->overlay_fb; + + if (!fb) + return -1; + + cfg->ch1_ptr = fb->fix.smem_start; + cfg->ch1_ptr += cam->win.w.top * fb->var.xres_virtual + * (fb->var.bits_per_pixel >> 3) + + cam->win.w.left * (fb->var.bits_per_pixel >> 3); + cfg->ch1_ptr2 = cfg->ch1_ptr; + cfg->ch1_stride = fb->var.xres_virtual; + } + + return 0; +} + +/*! + * @brief Setup PrP configuration parameters. + * @param cfg Pointer to emma_prp_cfg structure + * @param cam Pointer to cam_data structure + * @return Zero on success, others on failure + */ +static int prp_v4l2_cfg(emma_prp_cfg * cfg, cam_data * cam) +{ + cfg->in_pix = PRP_PIXIN_YUYV; + cfg->in_width = cam->crop_current.width; + cfg->in_height = cam->crop_current.height; + cfg->in_line_stride = cam->crop_current.left; + cfg->in_line_skip = cam->crop_current.top; + cfg->in_ptr = 0; + cfg->in_csi = PRP_CSI_LOOP; + memset(cfg->in_csc, 0, sizeof(cfg->in_csc)); + + if (cam->overlay_on) { + /* Convert V4L2 pixel format to PrP pixel format */ + switch (cam->v4l2_fb.fmt.pixelformat) { + case V4L2_PIX_FMT_RGB332: + cfg->ch1_pix = PRP_PIX1_RGB332; + break; + case V4L2_PIX_FMT_RGB32: + case V4L2_PIX_FMT_BGR32: + cfg->ch1_pix = PRP_PIX1_RGB888; + break; + case V4L2_PIX_FMT_YUYV: + cfg->ch1_pix = PRP_PIX1_YUYV; + break; + case V4L2_PIX_FMT_UYVY: + cfg->ch1_pix = PRP_PIX1_UYVY; + break; + case V4L2_PIX_FMT_RGB565: + default: + cfg->ch1_pix = PRP_PIX1_RGB565; + break; + } + if ((cam->rotation == V4L2_MXC_ROTATE_90_RIGHT) + || (cam->rotation == V4L2_MXC_ROTATE_90_RIGHT_VFLIP) + || (cam->rotation == V4L2_MXC_ROTATE_90_RIGHT_HFLIP) + || (cam->rotation == V4L2_MXC_ROTATE_90_LEFT)) { + cfg->ch1_width = cam->win.w.height; + cfg->ch1_height = cam->win.w.width; + } else { + cfg->ch1_width = cam->win.w.width; + cfg->ch1_height = cam->win.w.height; + } + + if (set_ch1_addr(cfg, cam)) + return -1; + } else { + cfg->ch1_pix = PRP_PIX1_UNUSED; + cfg->ch1_width = cfg->in_width; + cfg->ch1_height = cfg->in_height; + } + cfg->ch1_scale.algo = 0; + cfg->ch1_scale.width.num = cfg->in_width; + cfg->ch1_scale.width.den = cfg->ch1_width; + cfg->ch1_scale.height.num = cfg->in_height; + cfg->ch1_scale.height.den = cfg->ch1_height; + cfg->ch1_csi = PRP_CSI_EN; + + if (cam->capture_on || g_still_on) { + switch (cam->v2f.fmt.pix.pixelformat) { + case V4L2_PIX_FMT_YUYV: + cfg->ch2_pix = PRP_PIX2_YUV422; + break; + case V4L2_PIX_FMT_YUV420: + cfg->ch2_pix = PRP_PIX2_YUV420; + break; + /* + * YUV444 is not defined by V4L2. + * We support it in default case. + */ + default: + cfg->ch2_pix = PRP_PIX2_YUV444; + break; + } + cfg->ch2_width = cam->v2f.fmt.pix.width; + cfg->ch2_height = cam->v2f.fmt.pix.height; + } else { + cfg->ch2_pix = PRP_PIX2_UNUSED; + cfg->ch2_width = cfg->in_width; + cfg->ch2_height = cfg->in_height; + } + cfg->ch2_scale.algo = 0; + cfg->ch2_scale.width.num = cfg->in_width; + cfg->ch2_scale.width.den = cfg->ch2_width; + cfg->ch2_scale.height.num = cfg->in_height; + cfg->ch2_scale.height.den = cfg->ch2_height; + cfg->ch2_csi = PRP_CSI_EN; + + memset(cfg->scale, 0, sizeof(cfg->scale)); + cfg->scale[0].algo = cfg->ch1_scale.algo & 3; + cfg->scale[1].algo = (cfg->ch1_scale.algo >> 2) & 3; + cfg->scale[2].algo = cfg->ch2_scale.algo & 3; + cfg->scale[3].algo = (cfg->ch2_scale.algo >> 2) & 3; + + prp_cfg_dump(cfg); + + if (prp_resize_check_ch2(cfg)) + return -1; + + if (prp_resize_check_ch1(cfg)) + return -1; + + return 0; +} + +/*! + * @brief PrP interrupt handler + */ +static irqreturn_t prp_isr(int irq, void *dev_id) +{ + int status; + cam_data *cam = (cam_data *) dev_id; + + status = prphw_isr(); + + if (g_still_on && (status & PRP_INTRSTAT_CH2BUF1)) { + prp_still_stop(cam); + cam->still_counter++; + wake_up_interruptible(&cam->still_queue); + /* + * Still & video capture use the same PrP channel 2. + * They are execlusive. + */ + } else if (cam->capture_on) { + if (status & (PRP_INTRSTAT_CH2BUF1 | PRP_INTRSTAT_CH2BUF2)) { + cam->enc_callback(0, cam); + } + } + if (cam->overlay_on + && (status & (PRP_INTRSTAT_CH1BUF1 | PRP_INTRSTAT_CH1BUF2))) { + if (cam->rotation != V4L2_MXC_ROTATE_NONE) { + g_rotbuf = (status & PRP_INTRSTAT_CH1BUF1) ? 0 : 1; + tasklet_schedule(&prp_vf_tasklet); + } else if (cam->v4l2_fb.flags == V4L2_FBUF_FLAG_OVERLAY) { + struct fb_gwinfo gwinfo; + + gwinfo.enabled = 1; + gwinfo.alpha_value = 255; + gwinfo.ck_enabled = 0; + gwinfo.xpos = cam->win.w.left; + gwinfo.ypos = cam->win.w.top; + gwinfo.xres = cam->win.w.width; + gwinfo.yres = cam->win.w.height; + gwinfo.xres_virtual = cam->win.w.width; + gwinfo.vs_reversed = 0; + if (status & PRP_INTRSTAT_CH1BUF1) + gwinfo.base = (unsigned long)cam->vf_bufs[0]; + else + gwinfo.base = (unsigned long)cam->vf_bufs[1]; + + mx2_gw_set(&gwinfo); + } + } + + return IRQ_HANDLED; +} + +/*! + * @brief PrP initialization. + * @param dev_id Pointer to cam_data structure + * @return Zero on success, others on failure + */ +int prp_init(void *dev_id) +{ + enable_irq(INT_EMMAPRP); + if (request_irq(INT_EMMAPRP, prp_isr, 0, prp_dev, dev_id)) + return -1; + prphw_init(); + + return 0; +} + +/*! + * @brief PrP initialization. + * @param dev_id Pointer to cam_data structure + */ +void prp_exit(void *dev_id) +{ + prphw_exit(); + disable_irq(INT_EMMAPRP); + free_irq(INT_EMMAPRP, dev_id); +} + +/*! + * @brief Update PrP channel 2 output buffer address. + * @param eba Physical address for PrP output buffer + * @param buffer_num The PrP channel 2 buffer number to be updated + * @return Zero on success, others on failure + */ +static int prp_enc_update_eba(u32 eba, int *buffer_num) +{ + if (*buffer_num) { + g_prp_cfg.ch2_ptr2 = eba; + prphw_ch2ptr2(&g_prp_cfg); + *buffer_num = 0; + } else { + g_prp_cfg.ch2_ptr = eba; + prphw_ch2ptr(&g_prp_cfg); + *buffer_num = 1; + } + + return 0; +} + +/*! + * @brief Enable PrP for encoding. + * @param private Pointer to cam_data structure + * @return Zero on success, others on failure + */ +static int prp_enc_enable(void *private) +{ + cam_data *cam = (cam_data *) private; + + if (prp_v4l2_cfg(&g_prp_cfg, cam)) + return -1; + + csi_enable_mclk(CSI_MCLK_ENC, true, true); + prphw_reset(); + + if (prphw_cfg(&g_prp_cfg)) + return -1; + + prphw_enable(cam->overlay_on ? (PRP_CHANNEL_1 | PRP_CHANNEL_2) + : PRP_CHANNEL_2); + + return 0; +} + +/*! + * @brief Disable PrP for encoding. + * @param private Pointer to cam_data structure + * @return Zero on success, others on failure + */ +static int prp_enc_disable(void *private) +{ + prphw_disable(PRP_CHANNEL_2); + csi_enable_mclk(CSI_MCLK_ENC, false, false); + + return 0; +} + +/*! + * @brief Setup encoding functions. + * @param private Pointer to cam_data structure + * @return Zero on success, others on failure + */ +int prp_enc_select(void *private) +{ + int ret = 0; + cam_data *cam = (cam_data *) private; + + if (cam) { + cam->enc_update_eba = prp_enc_update_eba; + cam->enc_enable = prp_enc_enable; + cam->enc_disable = prp_enc_disable; + } else + ret = -EIO; + + return ret; +} + +/*! + * @brief Uninstall encoding functions. + * @param private Pointer to cam_data structure + * @return Zero on success, others on failure + */ +int prp_enc_deselect(void *private) +{ + int ret = 0; + cam_data *cam = (cam_data *) private; + + ret = prp_enc_disable(private); + + if (cam) { + cam->enc_update_eba = NULL; + cam->enc_enable = NULL; + cam->enc_disable = NULL; + } + + return ret; +} + +/*! + * @brief Allocate memory for overlay. + * @param cam Pointer to cam_data structure + * @return Zero on success, others on failure + */ +static int prp_vf_mem_alloc(cam_data * cam) +{ + int i; + + for (i = 0; i < 2; i++) { + cam->vf_bufs_size[i] = cam->win.w.width * cam->win.w.height * 2; + cam->vf_bufs_vaddr[i] = dma_alloc_coherent(0, + cam->vf_bufs_size[i], + &cam->vf_bufs[i], + GFP_DMA | + GFP_KERNEL); + if (!cam->vf_bufs_vaddr[i]) { + pr_debug("Failed to alloc memory for vf.\n"); + prp_vf_mem_free(cam); + return -1; + } + + g_vaddr_vfbuf[i] = + ioremap_cached(cam->vf_bufs[i], cam->vf_bufs_size[i]); + if (!g_vaddr_vfbuf[i]) { + pr_debug("Failed to ioremap_cached() for vf.\n"); + prp_vf_mem_free(cam); + return -1; + } + } + + return 0; +} + +/*! + * @brief Free memory for overlay. + * @param cam Pointer to cam_data structure + * @return Zero on success, others on failure + */ +static void prp_vf_mem_free(cam_data * cam) +{ + int i; + + for (i = 0; i < 2; i++) { + if (cam->vf_bufs_vaddr[i]) { + dma_free_coherent(0, + cam->vf_bufs_size[i], + cam->vf_bufs_vaddr[i], + cam->vf_bufs[i]); + } + cam->vf_bufs[i] = 0; + cam->vf_bufs_vaddr[i] = 0; + cam->vf_bufs_size[i] = 0; + if (g_vaddr_vfbuf[i]) { + iounmap(g_vaddr_vfbuf[i]); + g_vaddr_vfbuf[i] = 0; + } + } +} + +/*! + * @brief Allocate intermediate memory for overlay rotation/mirroring. + * @param cam Pointer to cam_data structure + * @return Zero on success, others on failure + */ +static int prp_rot_mem_alloc(cam_data * cam) +{ + int i; + + for (i = 0; i < 2; i++) { + cam->rot_vf_buf_size[i] = + cam->win.w.width * cam->win.w.height * 2; + cam->rot_vf_bufs_vaddr[i] = + dma_alloc_coherent(0, cam->rot_vf_buf_size[i], + &cam->rot_vf_bufs[i], + GFP_DMA | GFP_KERNEL); + if (!cam->rot_vf_bufs_vaddr[i]) { + pr_debug("Failed to alloc memory for vf rotation.\n"); + prp_rot_mem_free(cam); + return -1; + } + + g_vaddr_rotbuf[i] = + ioremap_cached(cam->rot_vf_bufs[i], + cam->rot_vf_buf_size[i]); + if (!g_vaddr_rotbuf[i]) { + pr_debug + ("Failed to ioremap_cached() for rotation buffer.\n"); + prp_rot_mem_free(cam); + return -1; + } + } + + return 0; +} + +/*! + * @brief Free intermedaite memory for overlay rotation/mirroring. + * @param cam Pointer to cam_data structure + * @return Zero on success, others on failure + */ +static void prp_rot_mem_free(cam_data * cam) +{ + int i; + + for (i = 0; i < 2; i++) { + if (cam->rot_vf_bufs_vaddr[i]) { + dma_free_coherent(0, + cam->rot_vf_buf_size[i], + cam->rot_vf_bufs_vaddr[i], + cam->rot_vf_bufs[i]); + } + cam->rot_vf_bufs[i] = 0; + cam->rot_vf_bufs_vaddr[i] = 0; + cam->rot_vf_buf_size[i] = 0; + if (g_vaddr_rotbuf[i]) { + iounmap(g_vaddr_rotbuf[i]); + g_vaddr_rotbuf[i] = 0; + } + } +} + +/*! + * @brief Start overlay (view finder). + * @param private Pointer to cam_data structure + * @return Zero on success, others on failure + */ +static int prp_vf_start(void *private) +{ + cam_data *cam = (cam_data *) private; + + if (cam->v4l2_fb.flags == V4L2_FBUF_FLAG_OVERLAY) { + prp_vf_mem_free(cam); + if (prp_vf_mem_alloc(cam)) { + pr_info("Error to allocate vf buffer\n"); + return -ENOMEM; + } + } + + if (cam->rotation != V4L2_MXC_ROTATE_NONE) { + prp_rot_mem_free(cam); + if (prp_rot_mem_alloc(cam)) { + pr_info("Error to allocate rotation buffer\n"); + prp_vf_mem_free(cam); + return -ENOMEM; + } + } + + if (prp_v4l2_cfg(&g_prp_cfg, cam)) { + prp_vf_mem_free(cam); + prp_rot_mem_free(cam); + return -1; + } + + csi_enable_mclk(CSI_MCLK_VF, true, true); + prphw_reset(); + + if (prphw_cfg(&g_prp_cfg)) { + prp_vf_mem_free(cam); + prp_rot_mem_free(cam); + return -1; + } + g_vfbuf = g_rotbuf = 0; + tasklet_init(&prp_vf_tasklet, rotation, (unsigned long)private); + + prphw_enable(cam->capture_on ? (PRP_CHANNEL_1 | PRP_CHANNEL_2) + : PRP_CHANNEL_1); + + return 0; +} + +/*! + * @brief Stop overlay (view finder). + * @param private Pointer to cam_data structure + * @return Zero on success, others on failure + */ +static int prp_vf_stop(void *private) +{ + cam_data *cam = (cam_data *) private; + + prphw_disable(PRP_CHANNEL_1); + + csi_enable_mclk(CSI_MCLK_VF, false, false); + tasklet_kill(&prp_vf_tasklet); + + if (cam->v4l2_fb.flags == V4L2_FBUF_FLAG_OVERLAY) { + struct fb_gwinfo gwinfo; + + /* Disable graphic window */ + gwinfo.enabled = 0; + mx2_gw_set(&gwinfo); + + prp_vf_mem_free(cam); + } + prp_rot_mem_free(cam); + if (g_vaddr_fb) { + iounmap(g_vaddr_fb); + g_vaddr_fb = 0; + } + + return 0; +} + +/*! + * @brief Setup overlay functions. + * @param private Pointer to cam_data structure + * @return Zero on success, others on failure + */ +int prp_vf_select(void *private) +{ + int ret = 0; + cam_data *cam = (cam_data *) private; + + if (cam) { + cam->vf_start_sdc = prp_vf_start; + cam->vf_stop_sdc = prp_vf_stop; + cam->overlay_active = false; + } else + ret = -EIO; + + return ret; +} + +/*! + * @brief Uninstall overlay functions. + * @param private Pointer to cam_data structure + * @return Zero on success, others on failure + */ +int prp_vf_deselect(void *private) +{ + int ret = 0; + cam_data *cam = (cam_data *) private; + + ret = prp_vf_stop(private); + + if (cam) { + cam->vf_start_sdc = NULL; + cam->vf_stop_sdc = NULL; + } + + return ret; +} + +/*! + * @brief Start still picture capture. + * @param private Pointer to cam_data structure + * @return Zero on success, others on failure + */ +static int prp_still_start(void *private) +{ + cam_data *cam = (cam_data *) private; + + g_still_on = 1; + g_prp_cfg.ch2_ptr = (unsigned int)cam->still_buf; + g_prp_cfg.ch2_ptr2 = 0; + + if (prp_v4l2_cfg(&g_prp_cfg, cam)) + return -1; + + csi_enable_mclk(CSI_MCLK_RAW, true, true); + prphw_reset(); + + if (prphw_cfg(&g_prp_cfg)) { + g_still_on = 0; + return -1; + } + + prphw_enable(cam->overlay_on ? (PRP_CHANNEL_1 | PRP_CHANNEL_2) + : PRP_CHANNEL_2); + + return 0; +} + +/*! + * @brief Stop still picture capture. + * @param private Pointer to cam_data structure + * @return Zero on success, others on failure + */ +static int prp_still_stop(void *private) +{ + prphw_disable(PRP_CHANNEL_2); + + csi_enable_mclk(CSI_MCLK_RAW, false, false); + + g_still_on = 0; + + return 0; +} + +/*! + * @brief Setup functions for still picture capture. + * @param private Pointer to cam_data structure + * @return Zero on success, others on failure + */ +int prp_still_select(void *private) +{ + cam_data *cam = (cam_data *) private; + + if (cam) { + cam->csi_start = prp_still_start; + cam->csi_stop = prp_still_stop; + } + + return 0; +} + +/*! + * @brief Uninstall functions for still picture capture. + * @param private Pointer to cam_data structure + * @return Zero on success, others on failure + */ +int prp_still_deselect(void *private) +{ + cam_data *cam = (cam_data *) private; + int err = 0; + + err = prp_still_stop(cam); + + if (cam) { + cam->csi_start = NULL; + cam->csi_stop = NULL; + } + + return err; +} + +/*! + * @brief Perform software rotation or mirroring + * @param private Argument passed to the tasklet + */ +static void rotation(unsigned long private) +{ + char *src, *dst; + int width, height, s_stride, d_stride; + int size; + cam_data *cam = (cam_data *) private; + + src = g_vaddr_rotbuf[g_rotbuf]; + size = cam->rot_vf_buf_size[g_rotbuf]; + + if ((cam->rotation == V4L2_MXC_ROTATE_90_RIGHT) + || (cam->rotation == V4L2_MXC_ROTATE_90_RIGHT_VFLIP) + || (cam->rotation == V4L2_MXC_ROTATE_90_RIGHT_HFLIP) + || (cam->rotation == V4L2_MXC_ROTATE_90_LEFT)) { + width = cam->win.w.height; + height = cam->win.w.width; + s_stride = cam->win.w.height << 1; + } else { + width = cam->win.w.width; + height = cam->win.w.height; + s_stride = cam->win.w.width << 1; + } + + if (cam->v4l2_fb.flags == V4L2_FBUF_FLAG_OVERLAY) { + dst = g_vaddr_vfbuf[g_vfbuf]; + d_stride = cam->win.w.width << 1; + } else { /* The destination is the framebuffer */ + struct fb_info *fb = cam->overlay_fb; + if (!fb) + return; + dst = g_vaddr_fb; + dst += cam->win.w.top * fb->var.xres_virtual + * (fb->var.bits_per_pixel >> 3) + + cam->win.w.left * (fb->var.bits_per_pixel >> 3); + d_stride = fb->var.xres_virtual << 1; + } + + /* + * Invalidate the data in cache before performing the SW rotaion + * or mirroring in case the image size is less than QVGA. For image + * larger than QVGA it is not invalidated becase the invalidation + * will consume much time while we don't see any artifacts on the + * output if we don't perform invalidation for them. + * Similarly we don't flush the data after SW rotation/mirroring. + */ + if (size < 320 * 240 * 2) + dmac_inv_range(src, src + size); + switch (cam->rotation) { + case V4L2_MXC_ROTATE_VERT_FLIP: + opl_vmirror_u16(src, s_stride, width, height, dst, d_stride); + break; + case V4L2_MXC_ROTATE_HORIZ_FLIP: + opl_hmirror_u16(src, s_stride, width, height, dst, d_stride); + break; + case V4L2_MXC_ROTATE_180: + opl_rotate180_u16(src, s_stride, width, height, dst, d_stride); + break; + case V4L2_MXC_ROTATE_90_RIGHT: + opl_rotate90_u16(src, s_stride, width, height, dst, d_stride); + break; + case V4L2_MXC_ROTATE_90_RIGHT_VFLIP: + opl_rotate90_vmirror_u16(src, s_stride, width, height, dst, + d_stride); + break; + case V4L2_MXC_ROTATE_90_RIGHT_HFLIP: + /* ROTATE_90_RIGHT_HFLIP = ROTATE_270_RIGHT_VFLIP */ + opl_rotate270_vmirror_u16(src, s_stride, width, height, dst, + d_stride); + break; + case V4L2_MXC_ROTATE_90_LEFT: + opl_rotate270_u16(src, s_stride, width, height, dst, d_stride); + break; + default: + return; + } + + /* Config and display the graphic window */ + if (cam->v4l2_fb.flags == V4L2_FBUF_FLAG_OVERLAY) { + struct fb_gwinfo gwinfo; + + gwinfo.enabled = 1; + gwinfo.alpha_value = 255; + gwinfo.ck_enabled = 0; + gwinfo.xpos = cam->win.w.left; + gwinfo.ypos = cam->win.w.top; + gwinfo.xres = cam->win.w.width; + gwinfo.yres = cam->win.w.height; + gwinfo.xres_virtual = cam->win.w.width; + gwinfo.vs_reversed = 0; + gwinfo.base = (unsigned long)cam->vf_bufs[g_vfbuf]; + mx2_gw_set(&gwinfo); + + g_vfbuf = g_vfbuf ? 0 : 1; + } +} + +/* + * @brief Check if the resize ratio is supported based on the input and output + * dimension + * @param input input dimension + * @param output output dimension + * @return output dimension (should equal the parameter *output*) + * -1 on failure + */ +static int check_simple(scale_t * scale, int input, int output) +{ + unsigned short int_out; /* PrP internel width or height */ + unsigned short orig_out = output; + + if (prp_scale(scale, input, output, input, &orig_out, &int_out, 0)) + return -1; /* resize failed */ + else + return int_out; +} + +/* + * @brief Check if the resize ratio is supported based on the input and output + * dimension + * @param input input dimension + * @param output output dimension + * @return output dimension, may be rounded. + * -1 on failure + */ +static int check_simple_retry(scale_t * scale, int input, int output) +{ + unsigned short int_out; /* PrP internel width or height */ + unsigned short orig_out = output; + + if (prp_scale(scale, input, output, input, &orig_out, &int_out, + SCALE_RETRY)) + return -1; /* resize failed */ + else + return int_out; +} + +/*! + * @brief Check if the resize ratio is supported by PrP channel 1 + * @param cfg Pointer to emma_prp_cfg structure + * @return Zero on success, others on failure + */ +static int prp_resize_check_ch1(emma_prp_cfg * cfg) +{ + int in_w, in_h, ch1_w, ch1_h, ch2_w, ch2_h, w, h; + scale_t *pscale = &cfg->scale[0]; /* Ch1 width resize coeff */ + + if (cfg->ch1_pix == PRP_PIX1_UNUSED) + return 0; + + in_w = cfg->in_width; + in_h = cfg->in_height; + ch1_w = cfg->ch1_width; + ch1_h = cfg->ch1_height; + ch2_w = cfg->ch2_width; + ch2_h = cfg->ch2_height; + + /* + * For channel 1, try parallel resize first. If the resize + * ratio is not exactly supported, try cascade resize. If it + * still fails, use parallel resize but with rounded value. + */ + w = check_simple(pscale, in_w, ch1_w); + h = check_simple(pscale + 1, in_h, ch1_h); + if ((w == ch1_w) && (h == ch1_h)) + goto exit_parallel; + + if (cfg->ch2_pix != PRP_PIX2_UNUSED) { + /* + * Channel 2 is already used. The pscale is still pointing + * to ch1 resize coeff for temporary use. + */ + w = check_simple(pscale, in_w, ch2_w); + h = check_simple(pscale + 1, in_h, ch2_h); + if ((w == ch2_w) && (h == ch2_h)) { + /* Try cascade resize now */ + w = check_simple(pscale, ch2_w, ch1_w); + h = check_simple(pscale + 1, ch2_h, ch1_h); + if ((w == ch1_w) && (h == ch1_h)) + goto exit_cascade; + } + } else { + /* + * Try cascade resize for width, width is multiple of 2. + * Channel 2 is not used. So we have more values to pick + * for channel 2 resize. + */ + for (w = in_w - 2; w > ch1_w; w -= 2) { + /* Ch2 width resize */ + if (check_simple(pscale + 2, in_w, w) != w) + continue; + /* Ch1 width resize */ + if (check_simple(pscale, w, ch1_w) != ch1_w) + continue; + break; + } + if ((ch2_w = w) > ch1_w) { + /* try cascade resize for height */ + for (h = in_h - 1; h > ch1_h; h--) { + /* Ch2 height resize */ + if (check_simple(pscale + 3, in_h, h) != h) + continue; + /* Ch1 height resize */ + if (check_simple(pscale + 1, h, ch1_h) != ch1_h) + continue; + break; + } + if ((ch2_h = h) > ch1_h) + goto exit_cascade; + } + } + + /* Have to try parallel resize again and round the dimensions */ + w = check_simple_retry(pscale, in_w, ch1_w); + h = check_simple_retry(pscale + 1, in_h, ch1_h); + if ((w != -1) && (h != -1)) + goto exit_parallel; + + pr_debug("Ch1 resize error.\n"); + return -1; + + exit_parallel: + cfg->ch1_scale.algo |= PRP_ALGO_BYPASS; + pr_debug("ch1 parallel resize.\n"); + pr_debug("original width = %d internel width = %d\n", ch1_w, w); + pr_debug("original height = %d internel height = %d\n", ch1_h, h); + return 0; + + exit_cascade: + cfg->ch1_scale.algo &= ~PRP_ALGO_BYPASS; + pr_debug("ch1 cascade resize.\n"); + pr_debug("[width] in : ch2 : ch1=%d : %d : %d\n", in_w, ch2_w, ch1_w); + pr_debug("[height] in : ch2 : ch1=%d : %d : %d\n", in_h, ch2_h, ch1_h); + return 0; +} + +/*! + * @brief Check if the resize ratio is supported by PrP channel 2 + * @param cfg Pointer to emma_prp_cfg structure + * @return Zero on success, others on failure + */ +static int prp_resize_check_ch2(emma_prp_cfg * cfg) +{ + int w, h; + scale_t *pscale = &cfg->scale[2]; /* Ch2 width resize coeff */ + + if (cfg->ch2_pix == PRP_PIX2_UNUSED) + return 0; + + w = check_simple_retry(pscale, cfg->in_width, cfg->ch2_width); + h = check_simple_retry(pscale + 1, cfg->in_height, cfg->ch2_height); + if ((w != -1) && (h != -1)) { + pr_debug("Ch2 resize.\n"); + pr_debug("Original width = %d internel width = %d\n", + cfg->ch2_width, w); + pr_debug("Original height = %d internel height = %d\n", + cfg->ch2_height, h); + return 0; + } else { + pr_debug("Ch2 resize error.\n"); + return -1; + } +} diff --git a/drivers/media/video/mxc/capture/mx27_v4l2_capture.c b/drivers/media/video/mxc/capture/mx27_v4l2_capture.c new file mode 100644 index 000000000000..e6045f609b50 --- /dev/null +++ b/drivers/media/video/mxc/capture/mx27_v4l2_capture.c @@ -0,0 +1,2077 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mx27_v4l2_capture.c + * + * @brief MX27 Video For Linux 2 driver + * + * @ingroup MXC_V4L2_CAPTURE + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/fs.h> +#include <linux/slab.h> +#include <linux/ctype.h> +#include <linux/pagemap.h> +#include <linux/vmalloc.h> +#include <linux/types.h> +#include <linux/fb.h> +#include <linux/pci.h> +#include <linux/platform_device.h> +#include <linux/version.h> +#include <media/v4l2-dev.h> +#include <asm/io.h> +#include <asm/semaphore.h> + +#include "mxc_v4l2_capture.h" +#include "mx27_prp.h" +#include "mx27_csi.h" + +static int csi_mclk_flag_backup; +static int video_nr = -1; +static cam_data *g_cam; + +/*! + * Free frame buffers + * + * @param cam Structure cam_data * + * + * @return status 0 success. + */ +static int mxc_free_frame_buf(cam_data * cam) +{ + int i; + + for (i = 0; i < FRAME_NUM; i++) { + if (cam->frame[i].vaddress != 0) { + dma_free_coherent(0, + cam->frame[i].buffer.length, + cam->frame[i].vaddress, + cam->frame[i].paddress); + cam->frame[i].vaddress = 0; + } + } + + return 0; +} + +/*! + * Allocate frame buffers + * + * @param cam Structure cam_data * + * + * @param count int number of buffer need to allocated + * + * @return status -0 Successfully allocated a buffer, -ENOBUFS failed. + */ +static int mxc_allocate_frame_buf(cam_data * cam, int count) +{ + int i; + + for (i = 0; i < count; i++) { + cam->frame[i].vaddress = dma_alloc_coherent(0, + PAGE_ALIGN(cam->v2f. + fmt.pix. + sizeimage), + &cam->frame[i]. + paddress, + GFP_DMA | + GFP_KERNEL); + if (cam->frame[i].vaddress == 0) { + pr_debug("mxc_allocate_frame_buf failed.\n"); + mxc_free_frame_buf(cam); + return -ENOBUFS; + } + cam->frame[i].buffer.index = i; + cam->frame[i].buffer.flags = V4L2_BUF_FLAG_MAPPED; + cam->frame[i].buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + cam->frame[i].buffer.length = + PAGE_ALIGN(cam->v2f.fmt.pix.sizeimage); + cam->frame[i].buffer.memory = V4L2_MEMORY_MMAP; + cam->frame[i].buffer.m.offset = cam->frame[i].paddress; + cam->frame[i].index = i; + } + + return 0; +} + +/*! + * Free frame buffers status + * + * @param cam Structure cam_data * + * + * @return none + */ +static void mxc_free_frames(cam_data * cam) +{ + int i; + + for (i = 0; i < FRAME_NUM; i++) { + cam->frame[i].buffer.flags = V4L2_BUF_FLAG_MAPPED; + } + + cam->enc_counter = 0; + cam->skip_frame = 0; + INIT_LIST_HEAD(&cam->ready_q); + INIT_LIST_HEAD(&cam->working_q); + INIT_LIST_HEAD(&cam->done_q); +} + +/*! + * Return the buffer status + * + * @param cam Structure cam_data * + * @param buf Structure v4l2_buffer * + * + * @return status 0 success, EINVAL failed. + */ +static int mxc_v4l2_buffer_status(cam_data * cam, struct v4l2_buffer *buf) +{ + /* check range */ + if (buf->index < 0 || buf->index >= FRAME_NUM) { + pr_debug("mxc_v4l2_buffer_status buffers not allocated\n"); + return -EINVAL; + } + + memcpy(buf, &(cam->frame[buf->index].buffer), sizeof(*buf)); + return 0; +} + +/*! + * start the encoder job + * + * @param cam structure cam_data * + * + * @return status 0 Success + */ +static int mxc_streamon(cam_data * cam) +{ + struct mxc_v4l_frame *frame; + int err = 0; + + if (!cam) + return -EIO; + + if (list_empty(&cam->ready_q)) { + printk(KERN_ERR "mxc_streamon buffer not been queued yet\n"); + return -EINVAL; + } + + cam->capture_pid = current->pid; + + if (cam->enc_enable) { + err = cam->enc_enable(cam); + if (err != 0) { + return err; + } + } + + cam->ping_pong_csi = 0; + if (cam->enc_update_eba) { + frame = + list_entry(cam->ready_q.next, struct mxc_v4l_frame, queue); + list_del(cam->ready_q.next); + list_add_tail(&frame->queue, &cam->working_q); + err = cam->enc_update_eba(frame->paddress, &cam->ping_pong_csi); + + frame = + list_entry(cam->ready_q.next, struct mxc_v4l_frame, queue); + list_del(cam->ready_q.next); + list_add_tail(&frame->queue, &cam->working_q); + err |= + cam->enc_update_eba(frame->paddress, &cam->ping_pong_csi); + } else { + return -EINVAL; + } + + return err; +} + +/*! + * Shut down the encoder job + * + * @param cam structure cam_data * + * + * @return status 0 Success + */ +static int mxc_streamoff(cam_data * cam) +{ + int err = 0; + + if (!cam) + return -EIO; + + if (cam->enc_disable) { + err = cam->enc_disable(cam); + } + mxc_free_frames(cam); + return err; +} + +/*! + * Valid whether the palette is supported + * + * @param palette pixel format + * + * @return 0 if failed + */ +static inline int valid_mode(u32 palette) +{ + /* + * MX27 PrP channel 2 supports YUV444, but YUV444 is not + * defined by V4L2 :( + */ + return ((palette == V4L2_PIX_FMT_YUYV) || + (palette == V4L2_PIX_FMT_YUV420)); +} + +/*! + * Valid and adjust the overlay window size, position + * + * @param cam structure cam_data * + * @param win struct v4l2_window * + * + * @return 0 + */ +static int verify_preview(cam_data * cam, struct v4l2_window *win) +{ + if (cam->output >= num_registered_fb) { + pr_debug("verify_preview No matched.\n"); + return -1; + } + cam->overlay_fb = (struct fb_info *)registered_fb[cam->output]; + + /* TODO: suppose 16bpp, 4 bytes alignment */ + win->w.left &= ~0x1; + + if (win->w.width + win->w.left > cam->overlay_fb->var.xres) + win->w.width = cam->overlay_fb->var.xres - win->w.left; + if (win->w.height + win->w.top > cam->overlay_fb->var.yres) + win->w.height = cam->overlay_fb->var.yres - win->w.top; + + /* + * TODO: suppose 16bpp. Rounded down to a multiple of 2 pixels for + * width according to PrP limitations. + */ + if ((cam->rotation == V4L2_MXC_ROTATE_90_RIGHT) + || (cam->rotation == V4L2_MXC_ROTATE_90_RIGHT_VFLIP) + || (cam->rotation == V4L2_MXC_ROTATE_90_RIGHT_HFLIP) + || (cam->rotation == V4L2_MXC_ROTATE_90_LEFT)) + win->w.height &= ~0x1; + else + win->w.width &= ~0x1; + + return 0; +} + +/*! + * start the viewfinder job + * + * @param cam structure cam_data * + * + * @return status 0 Success + */ +static int start_preview(cam_data * cam) +{ + int err = 0; + + err = prp_vf_select(cam); + if (err != 0) + return err; + + cam->overlay_pid = current->pid; + err = cam->vf_start_sdc(cam); + + return err; +} + +/*! + * shut down the viewfinder job + * + * @param cam structure cam_data * + * + * @return status 0 Success + */ +static int stop_preview(cam_data * cam) +{ + int err = 0; + + err = prp_vf_deselect(cam); + return err; +} + +/*! + * V4L2 - mxc_v4l2_g_fmt function + * + * @param cam structure cam_data * + * + * @param f structure v4l2_format * + * + * @return status 0 success, EINVAL failed + */ +static int mxc_v4l2_g_fmt(cam_data * cam, struct v4l2_format *f) +{ + int retval = 0; + + switch (f->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + f->fmt.pix.width = cam->v2f.fmt.pix.width; + f->fmt.pix.height = cam->v2f.fmt.pix.height; + f->fmt.pix.sizeimage = cam->v2f.fmt.pix.sizeimage; + f->fmt.pix.pixelformat = cam->v2f.fmt.pix.pixelformat; + f->fmt.pix.bytesperline = cam->v2f.fmt.pix.bytesperline; + f->fmt.pix.colorspace = V4L2_COLORSPACE_JPEG; + retval = 0; + break; + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + f->fmt.win = cam->win; + break; + default: + retval = -EINVAL; + } + return retval; +} + +/*! + * V4L2 - mxc_v4l2_s_fmt function + * + * @param cam structure cam_data * + * + * @param f structure v4l2_format * + * + * @return status 0 success, EINVAL failed + */ +static int mxc_v4l2_s_fmt(cam_data * cam, struct v4l2_format *f) +{ + int retval = 0; + int size = 0; + int bytesperline = 0; + + switch (f->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + if (!valid_mode(f->fmt.pix.pixelformat)) { + pr_debug("mxc_v4l2_s_fmt: format not supported\n"); + retval = -EINVAL; + } + + if (cam->rotation != V4L2_MXC_ROTATE_NONE) + pr_debug("mxc_v4l2_s_fmt: capture rotation ignored\n"); + + switch (f->fmt.pix.pixelformat) { + case V4L2_PIX_FMT_YUYV: + f->fmt.pix.width &= ~0x1; /* Multiple of 2 */ + size = f->fmt.pix.width * f->fmt.pix.height * 2; + bytesperline = f->fmt.pix.width * 2; + break; + case V4L2_PIX_FMT_YUV420: + f->fmt.pix.width &= ~0x7; /* Multiple of 8 */ + f->fmt.pix.height &= ~0x1; /* Multiple of 2 */ + size = f->fmt.pix.width * f->fmt.pix.height * 3 / 2; + bytesperline = f->fmt.pix.width * 3 / 2; + break; + default: + /* Suppose it's YUV444 or 32bpp */ + size = f->fmt.pix.width * f->fmt.pix.height * 4; + bytesperline = f->fmt.pix.width * 4; + pr_info("mxc_v4l2_s_fmt: default assume" + " to be YUV444 interleaved.\n"); + break; + } + + if (f->fmt.pix.bytesperline < bytesperline) { + f->fmt.pix.bytesperline = bytesperline; + } else { + bytesperline = f->fmt.pix.bytesperline; + } + + if (f->fmt.pix.sizeimage > size) { + pr_debug("mxc_v4l2_s_fmt: sizeimage bigger than" + " needed.\n"); + size = f->fmt.pix.sizeimage; + } + f->fmt.pix.sizeimage = size; + + cam->v2f.fmt.pix.sizeimage = size; + cam->v2f.fmt.pix.bytesperline = bytesperline; + cam->v2f.fmt.pix.width = f->fmt.pix.width; + cam->v2f.fmt.pix.height = f->fmt.pix.height; + cam->v2f.fmt.pix.pixelformat = f->fmt.pix.pixelformat; + retval = 0; + break; + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + retval = verify_preview(cam, &f->fmt.win); + cam->win = f->fmt.win; + break; + default: + retval = -EINVAL; + } + return retval; +} + +/*! + * get control param + * + * @param cam structure cam_data * + * + * @param c structure v4l2_control * + * + * @return status 0 success, EINVAL failed + */ +static int mxc_get_v42l_control(cam_data * cam, struct v4l2_control *c) +{ + int status = 0; + + switch (c->id) { + case V4L2_CID_HFLIP: + c->value = cam->rotation; + break; + case V4L2_CID_VFLIP: + c->value = cam->rotation; + break; + case V4L2_CID_MXC_ROT: + c->value = cam->rotation; + break; + case V4L2_CID_BRIGHTNESS: + c->value = cam->bright; + break; + case V4L2_CID_HUE: + c->value = cam->hue; + break; + case V4L2_CID_CONTRAST: + c->value = cam->contrast; + break; + case V4L2_CID_SATURATION: + c->value = cam->saturation; + break; + case V4L2_CID_RED_BALANCE: + c->value = cam->red; + break; + case V4L2_CID_BLUE_BALANCE: + c->value = cam->blue; + break; + case V4L2_CID_BLACK_LEVEL: + c->value = cam->ae_mode; + break; + default: + status = -EINVAL; + } + return status; +} + +/*! + * V4L2 - set_control function + * V4L2_CID_MXC_ROT is the extention for rotation/mirroring. + * + * @param cam structure cam_data * + * + * @param c structure v4l2_control * + * + * @return status 0 success, EINVAL failed + */ +static int mxc_set_v42l_control(cam_data * cam, struct v4l2_control *c) +{ + switch (c->id) { + case V4L2_CID_HFLIP: + if (c->value == 1) { + if ((cam->rotation != V4L2_MXC_ROTATE_VERT_FLIP) && + (cam->rotation != V4L2_MXC_ROTATE_180)) + cam->rotation = V4L2_MXC_ROTATE_HORIZ_FLIP; + else + cam->rotation = V4L2_MXC_ROTATE_180; + } else { + if (cam->rotation == V4L2_MXC_ROTATE_HORIZ_FLIP) + cam->rotation = V4L2_MXC_ROTATE_NONE; + else if (cam->rotation == V4L2_MXC_ROTATE_180) + cam->rotation = V4L2_MXC_ROTATE_VERT_FLIP; + } + break; + case V4L2_CID_VFLIP: + if (c->value == 1) { + if ((cam->rotation != V4L2_MXC_ROTATE_HORIZ_FLIP) && + (cam->rotation != V4L2_MXC_ROTATE_180)) + cam->rotation = V4L2_MXC_ROTATE_VERT_FLIP; + else + cam->rotation = V4L2_MXC_ROTATE_180; + } else { + if (cam->rotation == V4L2_MXC_ROTATE_VERT_FLIP) + cam->rotation = V4L2_MXC_ROTATE_NONE; + if (cam->rotation == V4L2_MXC_ROTATE_180) + cam->rotation = V4L2_MXC_ROTATE_HORIZ_FLIP; + } + break; + case V4L2_CID_MXC_ROT: + switch (c->value) { + case V4L2_MXC_ROTATE_NONE: + case V4L2_MXC_ROTATE_VERT_FLIP: + case V4L2_MXC_ROTATE_HORIZ_FLIP: + case V4L2_MXC_ROTATE_180: + case V4L2_MXC_ROTATE_90_RIGHT: + case V4L2_MXC_ROTATE_90_RIGHT_VFLIP: + case V4L2_MXC_ROTATE_90_RIGHT_HFLIP: + case V4L2_MXC_ROTATE_90_LEFT: + cam->rotation = c->value; + break; + default: + return -EINVAL; + } + break; + case V4L2_CID_HUE: + cam->hue = c->value; + break; + case V4L2_CID_CONTRAST: + cam->contrast = c->value; + break; + case V4L2_CID_BRIGHTNESS: + cam->bright = c->value; + case V4L2_CID_SATURATION: + cam->saturation = c->value; + case V4L2_CID_RED_BALANCE: + cam->red = c->value; + case V4L2_CID_BLUE_BALANCE: + cam->blue = c->value; + csi_enable_mclk(CSI_MCLK_I2C, true, true); + cam->cam_sensor->set_color(cam->bright, cam->saturation, + cam->red, cam->green, cam->blue); + csi_enable_mclk(CSI_MCLK_I2C, false, false); + break; + case V4L2_CID_BLACK_LEVEL: + cam->ae_mode = c->value & 0x03; + csi_enable_mclk(CSI_MCLK_I2C, true, true); + if (cam->cam_sensor->set_ae_mode) + cam->cam_sensor->set_ae_mode(cam->ae_mode); + csi_enable_mclk(CSI_MCLK_I2C, false, false); + break; + case V4L2_CID_MXC_FLASH: + break; + default: + return -EINVAL; + } + return 0; +} + +/*! + * V4L2 - mxc_v4l2_s_param function + * + * @param cam structure cam_data * + * + * @param parm structure v4l2_streamparm * + * + * @return status 0 success, EINVAL failed + */ +static int mxc_v4l2_s_param(cam_data * cam, struct v4l2_streamparm *parm) +{ + sensor_interface *param; + csi_signal_cfg_t csi_param; + + if (parm->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) { + pr_debug("mxc_v4l2_s_param invalid type\n"); + return -EINVAL; + } + + if (parm->parm.capture.timeperframe.denominator > + cam->standard.frameperiod.denominator) { + pr_debug("mxc_v4l2_s_param frame rate %d larger " + "than standard supported %d\n", + parm->parm.capture.timeperframe.denominator, + cam->standard.frameperiod.denominator); + return -EINVAL; + } + + cam->streamparm.parm.capture.capability = V4L2_CAP_TIMEPERFRAME; + + csi_enable_mclk(CSI_MCLK_I2C, true, true); + param = cam->cam_sensor->config + (&parm->parm.capture.timeperframe.denominator, + parm->parm.capture.capturemode); + csi_enable_mclk(CSI_MCLK_I2C, false, false); + + cam->streamparm.parm.capture.timeperframe = + parm->parm.capture.timeperframe; + + if ((parm->parm.capture.capturemode != 0) && + (parm->parm.capture.capturemode != V4L2_MODE_HIGHQUALITY)) { + pr_debug("mxc_v4l2_s_param frame un-supported capture mode\n"); + return -EINVAL; + } + + if (parm->parm.capture.capturemode == + cam->streamparm.parm.capture.capturemode) { + return 0; + } + + /* resolution changed, so need to re-program the CSI */ + csi_param.sens_clksrc = 0; + csi_param.clk_mode = param->clk_mode; + csi_param.pixclk_pol = param->pixclk_pol; + csi_param.data_width = param->data_width; + csi_param.data_pol = param->data_pol; + csi_param.ext_vsync = param->ext_vsync; + csi_param.Vsync_pol = param->Vsync_pol; + csi_param.Hsync_pol = param->Hsync_pol; + csi_init_interface(param->width, param->height, param->pixel_fmt, + csi_param); + + if (parm->parm.capture.capturemode != V4L2_MODE_HIGHQUALITY) { + cam->streamparm.parm.capture.capturemode = 0; + } else { + cam->streamparm.parm.capture.capturemode = + V4L2_MODE_HIGHQUALITY; + cam->streamparm.parm.capture.extendedmode = + parm->parm.capture.extendedmode; + cam->streamparm.parm.capture.readbuffers = 1; + } + return 0; +} + +/*! + * Dequeue one V4L capture buffer + * + * @param cam structure cam_data * + * @param buf structure v4l2_buffer * + * + * @return status 0 success, EINVAL invalid frame number, + * ETIME timeout, ERESTARTSYS interrupted by user + */ +static int mxc_v4l_dqueue(cam_data * cam, struct v4l2_buffer *buf) +{ + int retval = 0; + struct mxc_v4l_frame *frame; + + if (!wait_event_interruptible_timeout(cam->enc_queue, + cam->enc_counter != 0, 10 * HZ)) { + printk(KERN_ERR "mxc_v4l_dqueue timeout enc_counter %x\n", + cam->enc_counter); + return -ETIME; + } else if (signal_pending(current)) { + printk(KERN_ERR "mxc_v4l_dqueue() interrupt received\n"); + return -ERESTARTSYS; + } + + cam->enc_counter--; + + frame = list_entry(cam->done_q.next, struct mxc_v4l_frame, queue); + list_del(cam->done_q.next); + if (frame->buffer.flags & V4L2_BUF_FLAG_DONE) { + frame->buffer.flags &= ~V4L2_BUF_FLAG_DONE; + } else if (frame->buffer.flags & V4L2_BUF_FLAG_QUEUED) { + printk(KERN_ERR "VIDIOC_DQBUF: Buffer not filled.\n"); + frame->buffer.flags &= ~V4L2_BUF_FLAG_QUEUED; + retval = -EINVAL; + } else if ((frame->buffer.flags & 0x7) == V4L2_BUF_FLAG_MAPPED) { + printk(KERN_ERR "VIDIOC_DQBUF: Buffer not queued.\n"); + retval = -EINVAL; + } + + buf->bytesused = cam->v2f.fmt.pix.sizeimage; + buf->index = frame->index; + buf->flags = frame->buffer.flags; + + return retval; +} + +/*! + * V4L interface - open function + * + * @param inode structure inode * + * @param file structure file * + * + * @return status 0 success, ENODEV invalid device instance, + * ENODEV timeout, ERESTARTSYS interrupted by user + */ +static int mxc_v4l_open(struct inode *inode, struct file *file) +{ + sensor_interface *param; + csi_signal_cfg_t csi_param; + struct video_device *dev = video_devdata(file); + cam_data *cam = dev->priv; + int err = 0; + + if (!cam) { + pr_info("Internal error, cam_data not found!\n"); + return -ENODEV; + } + + if (down_interruptible(&cam->busy_lock)) + return -EINTR; + + if (signal_pending(current)) + goto oops; + + if (cam->open_count++ == 0) { + wait_event_interruptible(cam->power_queue, + cam->low_power == false); + + err = prp_enc_select(cam); + + cam->enc_counter = 0; + cam->skip_frame = 0; + INIT_LIST_HEAD(&cam->ready_q); + INIT_LIST_HEAD(&cam->working_q); + INIT_LIST_HEAD(&cam->done_q); + + csi_enable_mclk(CSI_MCLK_I2C, true, true); + param = cam->cam_sensor->reset(); + if (param == NULL) { + cam->open_count--; + csi_enable_mclk(CSI_MCLK_I2C, false, false); + err = -ENODEV; + goto oops; + } + csi_param.sens_clksrc = 0; + csi_param.clk_mode = param->clk_mode; + csi_param.pixclk_pol = param->pixclk_pol; + csi_param.data_width = param->data_width; + csi_param.data_pol = param->data_pol; + csi_param.ext_vsync = param->ext_vsync; + csi_param.Vsync_pol = param->Vsync_pol; + csi_param.Hsync_pol = param->Hsync_pol; + csi_init_interface(param->width, param->height, + param->pixel_fmt, csi_param); + cam->cam_sensor->get_color(&cam->bright, &cam->saturation, + &cam->red, &cam->green, &cam->blue); + if (cam->cam_sensor->get_ae_mode) + cam->cam_sensor->get_ae_mode(&cam->ae_mode); + csi_enable_mclk(CSI_MCLK_I2C, false, false); + prp_init(cam); + + } + + file->private_data = dev; + oops: + up(&cam->busy_lock); + return err; +} + +/*! + * V4L interface - close function + * + * @param inode struct inode * + * @param file struct file * + * + * @return 0 success + */ +static int mxc_v4l_close(struct inode *inode, struct file *file) +{ + struct video_device *dev = video_devdata(file); + int err = 0; + cam_data *cam = dev->priv; + + /* for the case somebody hit the ctrl C */ + if (cam->overlay_pid == current->pid) { + err = stop_preview(cam); + cam->overlay_on = false; + } + if (cam->capture_pid == current->pid) { + err |= mxc_streamoff(cam); + cam->capture_on = false; + wake_up_interruptible(&cam->enc_queue); + } + + if (--cam->open_count == 0) { + wait_event_interruptible(cam->power_queue, + cam->low_power == false); + pr_debug("mxc_v4l_close: release resource\n"); + + err |= prp_enc_deselect(cam); + + mxc_free_frame_buf(cam); + file->private_data = NULL; + + /* capture off */ + wake_up_interruptible(&cam->enc_queue); + mxc_free_frames(cam); + cam->enc_counter++; + prp_exit(cam); + } + + return err; +} + +#ifdef CONFIG_VIDEO_MXC_CSI_DMA +#include <asm/arch/dma.h> + +#define CSI_DMA_STATUS_IDLE 0 /* DMA is not started */ +#define CSI_DMA_STATUS_WORKING 1 /* DMA is transfering the data */ +#define CSI_DMA_STATUS_DONE 2 /* One frame completes successfully */ +#define CSI_DMA_STATUS_ERROR 3 /* Error occurs during the DMA */ + +/* + * Sometimes the start of the DMA is not synchronized with the CSI + * SOF (Start of Frame) interrupt which will lead to incorrect + * captured image. In this case the driver will re-try capturing + * another frame. The following macro defines the maximum re-try + * times. + */ +#define CSI_DMA_RETRY 8 + +/* + * Size of the physical contiguous memory area used to hold image data + * transfered by DMA. It can be less than the size of the image data. + */ +#define CSI_MEM_SIZE (1024 * 600) + +/* Number of bytes for one DMA transfer */ +#define CSI_DMA_LENGTH (1024 * 200) + +static int g_dma_channel = 0; +static int g_dma_status = CSI_DMA_STATUS_DONE; +static volatile int g_dma_completed; /* number of completed DMA transfers */ +static volatile int g_dma_copied; /* number of copied DMA transfers */ +static struct tasklet_struct g_dma_tasklet; +static char *g_user_buf; /* represents the buf passed by read() */ +static int g_user_count; /* represents the count passed by read() */ + +/*! + * @brief setup the DMA to transfer data + * There may be more than one DMA to transfer the whole image. Those + * DMAs work like chain. This function is used to setup the DMA in + * case there is enough space to hold the data. + * @param data pointer to the cam structure + */ +static void mxc_csi_dma_chaining(void *data) +{ + cam_data *cam = (cam_data *) data; + int count, chained = 0; + int max_dma = CSI_MEM_SIZE / CSI_DMA_LENGTH; + mxc_dma_requestbuf_t dma_request; + + while (chained * CSI_DMA_LENGTH < g_user_count) { + /* + * Calculate how many bytes the DMA should transfer. It may + * be less than CSI_DMA_LENGTH if the DMA is the last one. + */ + if ((chained + 1) * CSI_DMA_LENGTH > g_user_count) + count = g_user_count - chained * CSI_DMA_LENGTH; + else + count = CSI_DMA_LENGTH; + pr_debug("%s() DMA chained count = %d\n", __FUNCTION__, count); + + /* Config DMA */ + memset(&dma_request, 0, sizeof(mxc_dma_requestbuf_t)); + dma_request.dst_addr = cam->still_buf + + (chained % max_dma) * CSI_DMA_LENGTH; + dma_request.src_addr = (dma_addr_t) CSI_CSIRXFIFO_PHYADDR; + dma_request.num_of_bytes = count; + mxc_dma_config(g_dma_channel, &dma_request, 1, + MXC_DMA_MODE_READ); + + chained++; + } +} + +/*! + * @brief Copy image data from physical contiguous memory to user space buffer + * Once the data are copied, there will be more spare space in the + * physical contiguous memory to receive data from DMA. + * @param data pointer to the cam structure + */ +static void mxc_csi_dma_task(unsigned long data) +{ + cam_data *cam = (cam_data *) data; + int count; + int max_dma = CSI_MEM_SIZE / CSI_DMA_LENGTH; + + while (g_dma_copied < g_dma_completed) { + /* + * Calculate how many bytes the DMA has transfered. It may + * be less than CSI_DMA_LENGTH if the DMA is the last one. + */ + if ((g_dma_copied + 1) * CSI_DMA_LENGTH > g_user_count) + count = g_user_count - g_dma_copied * CSI_DMA_LENGTH; + else + count = CSI_DMA_LENGTH; + if (copy_to_user(g_user_buf + g_dma_copied * CSI_DMA_LENGTH, + cam->still_buf_vaddr + (g_dma_copied % max_dma) + * CSI_DMA_LENGTH, count)) + pr_debug("Warning: some bytes not copied\n"); + + g_dma_copied++; + } + + /* If the whole image has been captured */ + if (g_dma_copied * CSI_DMA_LENGTH >= g_user_count) { + cam->still_counter++; + wake_up_interruptible(&cam->still_queue); + } + + pr_debug("%s() DMA completed = %d copied = %d\n", + __FUNCTION__, g_dma_completed, g_dma_copied); +} + +/*! + * @brief DMA interrupt callback function + * @param data pointer to the cam structure + * @param error DMA error flag + * @param count number of bytes transfered by the DMA + */ +static void mxc_csi_dma_callback(void *data, int error, unsigned int count) +{ + cam_data *cam = (cam_data *) data; + int max_dma = CSI_MEM_SIZE / CSI_DMA_LENGTH; + unsigned long lock_flags; + + spin_lock_irqsave(&cam->int_lock, lock_flags); + + g_dma_completed++; + + if (error != MXC_DMA_DONE) { + g_dma_status = CSI_DMA_STATUS_ERROR; + pr_debug("%s() DMA error\n", __FUNCTION__); + } + + /* If the whole image has been captured */ + if ((g_dma_status != CSI_DMA_STATUS_ERROR) + && (g_dma_completed * CSI_DMA_LENGTH >= g_user_count)) + g_dma_status = CSI_DMA_STATUS_DONE; + + if ((g_dma_status == CSI_DMA_STATUS_WORKING) && + (g_dma_completed >= g_dma_copied + max_dma)) { + g_dma_status = CSI_DMA_STATUS_ERROR; + pr_debug("%s() Previous buffer over written\n", __FUNCTION__); + } + + /* Schedule the tasklet */ + tasklet_schedule(&g_dma_tasklet); + + spin_unlock_irqrestore(&cam->int_lock, lock_flags); + + pr_debug("%s() count = %d bytes\n", __FUNCTION__, count); +} + +/*! + * @brief CSI interrupt callback function + * @param data pointer to the cam structure + * @param status CSI interrupt status + */ +static void mxc_csi_irq_callback(void *data, unsigned long status) +{ + cam_data *cam = (cam_data *) data; + unsigned long lock_flags; + + spin_lock_irqsave(&cam->int_lock, lock_flags); + + /* Wait for SOF (Start of Frame) interrupt to sync the image */ + if (status & BIT_SOF_INT) { + if (g_dma_status == CSI_DMA_STATUS_IDLE) { + /* Start DMA transfer to capture image */ + mxc_dma_enable(g_dma_channel); + g_dma_status = CSI_DMA_STATUS_WORKING; + pr_debug("%s() DMA started.\n", __FUNCTION__); + } else if (g_dma_status == CSI_DMA_STATUS_WORKING) { + /* + * Another SOF occurs during DMA transfer. In this + * case the image is not synchronized so need to + * report error and probably try again. + */ + g_dma_status = CSI_DMA_STATUS_ERROR; + pr_debug("%s() Image is not synchronized with DMA - " + "SOF before DMA completes\n", __FUNCTION__); + } + } + + spin_unlock_irqrestore(&cam->int_lock, lock_flags); + + pr_debug("%s() g_dma_status = %d\n", __FUNCTION__, g_dma_status); +} + +/*! + * V4L interface - read function + * + * @param file struct file * + * @param read buf char * + * @param count size_t + * @param ppos structure loff_t * + * + * @return bytes read + */ +static ssize_t +mxc_v4l_read(struct file *file, char *buf, size_t count, loff_t * ppos) +{ + int err = 0; + struct video_device *dev = video_devdata(file); + cam_data *cam = dev->priv; + int retry = CSI_DMA_RETRY; + + g_user_buf = buf; + + if (down_interruptible(&cam->busy_lock)) + return -EINTR; + + /* Video capture and still image capture are exclusive */ + if (cam->capture_on == true) { + err = -EBUSY; + goto exit0; + } + + /* The CSI-DMA can not do CSC */ + if (cam->v2f.fmt.pix.pixelformat != V4L2_PIX_FMT_YUYV) { + pr_info("mxc_v4l_read support YUYV pixel format only\n"); + err = -EINVAL; + goto exit0; + } + + /* The CSI-DMA can not do resize or crop */ + if ((cam->v2f.fmt.pix.width != cam->crop_bounds.width) + || (cam->v2f.fmt.pix.height != cam->crop_bounds.height)) { + pr_info("mxc_v4l_read resize is not supported\n"); + pr_info("supported image size width = %d height = %d\n", + cam->crop_bounds.width, cam->crop_bounds.height); + err = -EINVAL; + goto exit0; + } + if ((cam->crop_current.left != cam->crop_bounds.left) + || (cam->crop_current.width != cam->crop_bounds.width) + || (cam->crop_current.top != cam->crop_bounds.top) + || (cam->crop_current.height != cam->crop_bounds.height)) { + pr_info("mxc_v4l_read cropping is not supported\n"); + err = -EINVAL; + goto exit0; + } + + cam->still_buf_vaddr = dma_alloc_coherent(0, + PAGE_ALIGN(CSI_MEM_SIZE), + &cam->still_buf, + GFP_DMA | GFP_KERNEL); + + if (!cam->still_buf_vaddr) { + pr_info("mxc_v4l_read failed at allocate still_buf\n"); + err = -ENOBUFS; + goto exit0; + } + + /* Initialize DMA */ + g_dma_channel = mxc_dma_request(MXC_DMA_CSI_RX, "CSI RX DMA"); + if (g_dma_channel < 0) { + pr_debug("mxc_v4l_read failed to request DMA channel\n"); + err = -EIO; + goto exit1; + } + + err = mxc_dma_callback_set(g_dma_channel, + (mxc_dma_callback_t) mxc_csi_dma_callback, + (void *)cam); + if (err != 0) { + pr_debug("mxc_v4l_read failed to set DMA callback\n"); + err = -EIO; + goto exit2; + } + + g_user_buf = buf; + if (cam->v2f.fmt.pix.sizeimage < count) + g_user_count = cam->v2f.fmt.pix.sizeimage; + else + g_user_count = count & ~0x3; + + tasklet_init(&g_dma_tasklet, mxc_csi_dma_task, (unsigned long)cam); + g_dma_status = CSI_DMA_STATUS_DONE; + csi_set_callback(mxc_csi_irq_callback, cam); + csi_enable_prpif(0); + + /* clear current SOF first */ + csi_clear_status(BIT_SOF_INT); + csi_enable_mclk(CSI_MCLK_RAW, true, true); + + do { + g_dma_completed = g_dma_copied = 0; + mxc_csi_dma_chaining(cam); + cam->still_counter = 0; + g_dma_status = CSI_DMA_STATUS_IDLE; + + if (!wait_event_interruptible_timeout(cam->still_queue, + cam->still_counter != 0, + 10 * HZ)) { + pr_info("mxc_v4l_read timeout counter %x\n", + cam->still_counter); + err = -ETIME; + goto exit3; + } + + if (g_dma_status == CSI_DMA_STATUS_DONE) + break; + + if (retry-- == 0) + break; + + pr_debug("Now retry image capture\n"); + } while (1); + + if (g_dma_status != CSI_DMA_STATUS_DONE) + err = -EIO; + + exit3: + csi_enable_prpif(1); + g_dma_status = CSI_DMA_STATUS_DONE; + csi_set_callback(0, 0); + csi_enable_mclk(CSI_MCLK_RAW, false, false); + tasklet_kill(&g_dma_tasklet); + + exit2: + mxc_dma_free(g_dma_channel); + + exit1: + dma_free_coherent(0, PAGE_ALIGN(CSI_MEM_SIZE), + cam->still_buf_vaddr, cam->still_buf); + cam->still_buf = 0; + + exit0: + up(&cam->busy_lock); + if (err < 0) + return err; + else + return g_user_count; +} +#else +/*! + * V4L interface - read function + * + * @param file struct file * + * @param read buf char * + * @param count size_t + * @param ppos structure loff_t * + * + * @return bytes read + */ +static ssize_t +mxc_v4l_read(struct file *file, char *buf, size_t count, loff_t * ppos) +{ + int err = 0; + u8 *v_address; + struct video_device *dev = video_devdata(file); + cam_data *cam = dev->priv; + + if (down_interruptible(&cam->busy_lock)) + return -EINTR; + + /* Video capture and still image capture are exclusive */ + if (cam->capture_on == true) { + err = -EBUSY; + goto exit0; + } + + v_address = dma_alloc_coherent(0, + PAGE_ALIGN(cam->v2f.fmt.pix.sizeimage), + &cam->still_buf, GFP_DMA | GFP_KERNEL); + + if (!v_address) { + pr_info("mxc_v4l_read failed at allocate still_buf\n"); + err = -ENOBUFS; + goto exit0; + } + + if (prp_still_select(cam)) { + err = -EIO; + goto exit1; + } + + cam->still_counter = 0; + if (cam->csi_start(cam)) { + err = -EIO; + goto exit2; + } + + if (!wait_event_interruptible_timeout(cam->still_queue, + cam->still_counter != 0, + 10 * HZ)) { + pr_info("mxc_v4l_read timeout counter %x\n", + cam->still_counter); + err = -ETIME; + goto exit2; + } + err = copy_to_user(buf, v_address, cam->v2f.fmt.pix.sizeimage); + + exit2: + prp_still_deselect(cam); + + exit1: + dma_free_coherent(0, cam->v2f.fmt.pix.sizeimage, v_address, + cam->still_buf); + cam->still_buf = 0; + + exit0: + up(&cam->busy_lock); + if (err < 0) + return err; + else + return (cam->v2f.fmt.pix.sizeimage - err); +} +#endif /* CONFIG_VIDEO_MXC_CSI_DMA */ + +/*! + * V4L interface - ioctl function + * + * @param inode struct inode * + * + * @param file struct file * + * + * @param ioctlnr unsigned int + * + * @param arg void * + * + * @return 0 success, ENODEV for invalid device instance, + * -1 for other errors. + */ +static int +mxc_v4l_do_ioctl(struct inode *inode, struct file *file, + unsigned int ioctlnr, void *arg) +{ + struct video_device *dev = video_devdata(file); + cam_data *cam = dev->priv; + int retval = 0; + unsigned long lock_flags; + + if (!cam) + return -EBADF; + + wait_event_interruptible(cam->power_queue, cam->low_power == false); + /* make this _really_ smp-safe */ + if (down_interruptible(&cam->busy_lock)) + return -EBUSY; + + switch (ioctlnr) { + /*! + * V4l2 VIDIOC_QUERYCAP ioctl + */ + case VIDIOC_QUERYCAP:{ + struct v4l2_capability *cap = arg; + strcpy(cap->driver, "mxc_v4l2"); + cap->version = KERNEL_VERSION(0, 1, 11); + cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | + V4L2_CAP_VIDEO_OVERLAY | V4L2_CAP_STREAMING + | V4L2_CAP_READWRITE; + cap->card[0] = '\0'; + cap->bus_info[0] = '\0'; + retval = 0; + break; + } + + /*! + * V4l2 VIDIOC_G_FMT ioctl + */ + case VIDIOC_G_FMT:{ + struct v4l2_format *gf = arg; + retval = mxc_v4l2_g_fmt(cam, gf); + break; + } + + /*! + * V4l2 VIDIOC_S_FMT ioctl + */ + case VIDIOC_S_FMT:{ + struct v4l2_format *sf = arg; + retval = mxc_v4l2_s_fmt(cam, sf); + break; + } + + /*! + * V4l2 VIDIOC_REQBUFS ioctl + */ + case VIDIOC_REQBUFS:{ + struct v4l2_requestbuffers *req = arg; + if (req->count > FRAME_NUM) { + pr_info("VIDIOC_REQBUFS: not enough buffer\n"); + req->count = FRAME_NUM; + } + + if ((req->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) || + (req->memory != V4L2_MEMORY_MMAP)) { + pr_debug("VIDIOC_REQBUFS: wrong buffer type\n"); + retval = -EINVAL; + break; + } + + mxc_streamoff(cam); + mxc_free_frame_buf(cam); + + retval = mxc_allocate_frame_buf(cam, req->count); + break; + } + + /*! + * V4l2 VIDIOC_QUERYBUF ioctl + */ + case VIDIOC_QUERYBUF:{ + struct v4l2_buffer *buf = arg; + int index = buf->index; + + if (buf->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) { + pr_debug + ("VIDIOC_QUERYBUFS: wrong buffer type\n"); + retval = -EINVAL; + break; + } + + memset(buf, 0, sizeof(buf)); + buf->index = index; + + down(&cam->param_lock); + retval = mxc_v4l2_buffer_status(cam, buf); + up(&cam->param_lock); + break; + } + + /*! + * V4l2 VIDIOC_QBUF ioctl + */ + case VIDIOC_QBUF:{ + struct v4l2_buffer *buf = arg; + int index = buf->index; + + pr_debug("VIDIOC_QBUF: %d\n", buf->index); + + spin_lock_irqsave(&cam->int_lock, lock_flags); + if ((cam->frame[index].buffer.flags & 0x7) == + V4L2_BUF_FLAG_MAPPED) { + cam->frame[index].buffer.flags |= + V4L2_BUF_FLAG_QUEUED; + if (cam->skip_frame > 0) { + list_add_tail(&cam->frame[index].queue, + &cam->working_q); + retval = + cam->enc_update_eba(cam-> + frame[index]. + paddress, + &cam-> + ping_pong_csi); + cam->skip_frame = 0; + } else { + list_add_tail(&cam->frame[index].queue, + &cam->ready_q); + } + } else if (cam->frame[index].buffer.flags & + V4L2_BUF_FLAG_QUEUED) { + pr_debug + ("VIDIOC_QBUF: buffer already queued\n"); + } else if (cam->frame[index].buffer. + flags & V4L2_BUF_FLAG_DONE) { + pr_debug + ("VIDIOC_QBUF: overwrite done buffer.\n"); + cam->frame[index].buffer.flags &= + ~V4L2_BUF_FLAG_DONE; + cam->frame[index].buffer.flags |= + V4L2_BUF_FLAG_QUEUED; + } + buf->flags = cam->frame[index].buffer.flags; + spin_unlock_irqrestore(&cam->int_lock, lock_flags); + break; + } + + /*! + * V4l2 VIDIOC_DQBUF ioctl + */ + case VIDIOC_DQBUF:{ + struct v4l2_buffer *buf = arg; + + retval = mxc_v4l_dqueue(cam, buf); + + break; + } + + /*! + * V4l2 VIDIOC_STREAMON ioctl + */ + case VIDIOC_STREAMON:{ + cam->capture_on = true; + retval = mxc_streamon(cam); + break; + } + + /*! + * V4l2 VIDIOC_STREAMOFF ioctl + */ + case VIDIOC_STREAMOFF:{ + retval = mxc_streamoff(cam); + cam->capture_on = false; + break; + } + + /*! + * V4l2 VIDIOC_G_CTRL ioctl + */ + case VIDIOC_G_CTRL:{ + retval = mxc_get_v42l_control(cam, arg); + break; + } + + /*! + * V4l2 VIDIOC_S_CTRL ioctl + */ + case VIDIOC_S_CTRL:{ + retval = mxc_set_v42l_control(cam, arg); + break; + } + + /*! + * V4l2 VIDIOC_CROPCAP ioctl + */ + case VIDIOC_CROPCAP:{ + struct v4l2_cropcap *cap = arg; + + if (cap->type != V4L2_BUF_TYPE_VIDEO_CAPTURE && + cap->type != V4L2_BUF_TYPE_VIDEO_OVERLAY) { + retval = -EINVAL; + break; + } + cap->bounds = cam->crop_bounds; + cap->defrect = cam->crop_defrect; + break; + } + + /*! + * V4l2 VIDIOC_G_CROP ioctl + */ + case VIDIOC_G_CROP:{ + struct v4l2_crop *crop = arg; + + if (crop->type != V4L2_BUF_TYPE_VIDEO_CAPTURE && + crop->type != V4L2_BUF_TYPE_VIDEO_OVERLAY) { + retval = -EINVAL; + break; + } + crop->c = cam->crop_current; + break; + } + + /*! + * V4l2 VIDIOC_S_CROP ioctl + */ + case VIDIOC_S_CROP:{ + struct v4l2_crop *crop = arg; + struct v4l2_rect *b = &cam->crop_bounds; + int i; + + if (crop->type != V4L2_BUF_TYPE_VIDEO_CAPTURE && + crop->type != V4L2_BUF_TYPE_VIDEO_OVERLAY) { + retval = -EINVAL; + break; + } + + crop->c.top = (crop->c.top < b->top) ? b->top + : crop->c.top; + if (crop->c.top > b->top + b->height) + crop->c.top = b->top + b->height - 1; + if (crop->c.height > b->top + b->height - crop->c.top) + crop->c.height = + b->top + b->height - crop->c.top; + + crop->c.left = (crop->c.left < b->left) ? b->left + : crop->c.left; + if (crop->c.left > b->left + b->width) + crop->c.left = b->left + b->width - 1; + if (crop->c.width > b->left - crop->c.left + b->width) + crop->c.width = + b->left - crop->c.left + b->width; + + crop->c.width &= ~0x1; + + /* + * MX27 PrP limitation: + * The right spare space (CSI_FRAME_X_SIZE + * - SOURCE_LINE_STRIDE - PICTURE_X_SIZE)) must be + * multiple of 32. + * So we tune the crop->c.left value to the closest + * desired cropping value and meet the PrP requirement. + */ + i = ((b->left + b->width) + - (crop->c.left + crop->c.width)) % 32; + if (i <= 16) { + if (crop->c.left + crop->c.width + i + <= b->left + b->width) + crop->c.left += i; + else if (crop->c.left - (32 - i) >= b->left) + crop->c.left -= 32 - i; + else { + retval = -EINVAL; + break; + } + } else { + if (crop->c.left - (32 - i) >= b->left) + crop->c.left -= 32 - i; + else if (crop->c.left + crop->c.width + i + <= b->left + b->width) + crop->c.left += i; + else { + retval = -EINVAL; + break; + } + } + + cam->crop_current = crop->c; + + break; + } + + /*! + * V4l2 VIDIOC_OVERLAY ioctl + */ + case VIDIOC_OVERLAY:{ + int *on = arg; + if (*on) { + cam->overlay_on = true; + retval = start_preview(cam); + } + if (!*on) { + retval = stop_preview(cam); + cam->overlay_on = false; + } + break; + } + + /*! + * V4l2 VIDIOC_G_FBUF ioctl + */ + case VIDIOC_G_FBUF:{ + struct v4l2_framebuffer *fb = arg; + struct fb_var_screeninfo *var; + + if (cam->output >= num_registered_fb) { + retval = -EINVAL; + break; + } + + var = ®istered_fb[cam->output]->var; + cam->v4l2_fb.fmt.width = var->xres; + cam->v4l2_fb.fmt.height = var->yres; + cam->v4l2_fb.fmt.bytesperline = + var->xres_virtual * var->bits_per_pixel; + cam->v4l2_fb.fmt.colorspace = V4L2_COLORSPACE_SRGB; + *fb = cam->v4l2_fb; + break; + } + + /*! + * V4l2 VIDIOC_S_FBUF ioctl + */ + case VIDIOC_S_FBUF:{ + struct v4l2_framebuffer *fb = arg; + cam->v4l2_fb.flags = fb->flags; + cam->v4l2_fb.fmt.pixelformat = fb->fmt.pixelformat; + break; + } + + case VIDIOC_G_PARM:{ + struct v4l2_streamparm *parm = arg; + if (parm->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) { + pr_debug("VIDIOC_G_PARM invalid type\n"); + retval = -EINVAL; + break; + } + parm->parm.capture = cam->streamparm.parm.capture; + break; + } + case VIDIOC_S_PARM:{ + struct v4l2_streamparm *parm = arg; + retval = mxc_v4l2_s_param(cam, parm); + break; + } + + /* linux v4l2 bug, kernel c0485619 user c0405619 */ + case VIDIOC_ENUMSTD:{ + struct v4l2_standard *e = arg; + *e = cam->standard; + pr_debug("VIDIOC_ENUMSTD call\n"); + retval = 0; + break; + } + + case VIDIOC_G_STD:{ + v4l2_std_id *e = arg; + *e = cam->standard.id; + break; + } + + case VIDIOC_S_STD:{ + break; + } + + case VIDIOC_ENUMOUTPUT: + { + struct v4l2_output *output = arg; + + if (output->index >= num_registered_fb) { + retval = -EINVAL; + break; + } + + strncpy(output->name, + registered_fb[output->index]->fix.id, 31); + output->type = V4L2_OUTPUT_TYPE_ANALOG; + output->audioset = 0; + output->modulator = 0; + output->std = V4L2_STD_UNKNOWN; + + break; + } + case VIDIOC_G_OUTPUT: + { + int *p_output_num = arg; + + *p_output_num = cam->output; + break; + } + case VIDIOC_S_OUTPUT: + { + int *p_output_num = arg; + + if (*p_output_num >= num_registered_fb) { + retval = -EINVAL; + break; + } + + cam->output = *p_output_num; + break; + } + + case VIDIOC_ENUM_FMT: + case VIDIOC_TRY_FMT: + case VIDIOC_QUERYCTRL: + case VIDIOC_ENUMINPUT: + case VIDIOC_G_INPUT: + case VIDIOC_S_INPUT: + case VIDIOC_G_TUNER: + case VIDIOC_S_TUNER: + case VIDIOC_G_FREQUENCY: + case VIDIOC_S_FREQUENCY: + default: + retval = -EINVAL; + break; + } + + up(&cam->busy_lock); + return retval; +} + +/* + * V4L interface - ioctl function + * + * @return None + */ +static int +mxc_v4l_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return video_usercopy(inode, file, cmd, arg, mxc_v4l_do_ioctl); +} + +/*! + * V4L interface - mmap function + * + * @param file structure file * + * + * @param vma structure vm_area_struct * + * + * @return status 0 Success, EINTR busy lock error, ENOBUFS remap_page error + */ +static int mxc_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct video_device *dev = video_devdata(file); + unsigned long size; + int res = 0; + cam_data *cam = dev->priv; + + pr_debug("pgoff=0x%lx, start=0x%lx, end=0x%lx\n", + vma->vm_pgoff, vma->vm_start, vma->vm_end); + + /* make this _really_ smp-safe */ + if (down_interruptible(&cam->busy_lock)) + return -EINTR; + + size = vma->vm_end - vma->vm_start; + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + + if (remap_pfn_range(vma, vma->vm_start, + vma->vm_pgoff, size, vma->vm_page_prot)) { + pr_debug("mxc_mmap: remap_pfn_range failed\n"); + res = -ENOBUFS; + goto mxc_mmap_exit; + } + + vma->vm_flags &= ~VM_IO; /* using shared anonymous pages */ + + mxc_mmap_exit: + up(&cam->busy_lock); + return res; +} + +/*! + * V4L interface - poll function + * + * @param file structure file * + * + * @param wait structure poll_table * + * + * @return status POLLIN | POLLRDNORM + */ +static unsigned int mxc_poll(struct file *file, poll_table * wait) +{ + struct video_device *dev = video_devdata(file); + cam_data *cam = dev->priv; + wait_queue_head_t *queue = NULL; + int res = POLLIN | POLLRDNORM; + + if (down_interruptible(&cam->busy_lock)) + return -EINTR; + + queue = &cam->enc_queue; + poll_wait(file, queue, wait); + + up(&cam->busy_lock); + return res; +} + +static struct +file_operations mxc_v4l_fops = { + .owner = THIS_MODULE, + .open = mxc_v4l_open, + .release = mxc_v4l_close, + .read = mxc_v4l_read, + .ioctl = mxc_v4l_ioctl, + .mmap = mxc_mmap, + .poll = mxc_poll, +}; + +static struct video_device mxc_v4l_template = { + .owner = THIS_MODULE, + .name = "Mxc Camera", + .type = 0, + .type2 = VID_TYPE_CAPTURE, + .hardware = 0, + .fops = &mxc_v4l_fops, + .release = video_device_release, +}; + +static void camera_platform_release(struct device *device) +{ +} + +/*! Device Definition for Mt9v111 devices */ +static struct platform_device mxc_v4l2_devices = { + .name = "mxc_v4l2", + .dev = { + .release = camera_platform_release, + }, + .id = 0, +}; + +extern struct camera_sensor camera_sensor_if; + +/*! +* Camera V4l2 callback function. +* +* @return status +*/ +static void camera_callback(u32 mask, void *dev) +{ + struct mxc_v4l_frame *done_frame; + struct mxc_v4l_frame *ready_frame; + + cam_data *cam = (cam_data *) dev; + if (cam == NULL) + return; + + if (list_empty(&cam->working_q)) { + printk(KERN_ERR "camera_callback: working queue empty\n"); + return; + } + + done_frame = + list_entry(cam->working_q.next, struct mxc_v4l_frame, queue); + if (done_frame->buffer.flags & V4L2_BUF_FLAG_QUEUED) { + done_frame->buffer.flags |= V4L2_BUF_FLAG_DONE; + done_frame->buffer.flags &= ~V4L2_BUF_FLAG_QUEUED; + + if (list_empty(&cam->ready_q)) { + cam->skip_frame++; + } else { + ready_frame = + list_entry(cam->ready_q.next, struct mxc_v4l_frame, + queue); + list_del(cam->ready_q.next); + list_add_tail(&ready_frame->queue, &cam->working_q); + cam->enc_update_eba(ready_frame->paddress, + &cam->ping_pong_csi); + } + + /* Added to the done queue */ + list_del(cam->working_q.next); + list_add_tail(&done_frame->queue, &cam->done_q); + + /* Wake up the queue */ + cam->enc_counter++; + wake_up_interruptible(&cam->enc_queue); + } else { + printk(KERN_ERR "camera_callback :buffer not queued\n"); + } +} + +/*! + * initialize cam_data structure + * + * @param cam structure cam_data * + * + * @return status 0 Success + */ +static void init_camera_struct(cam_data * cam) +{ + int i; + + /* Default everything to 0 */ + memset(cam, 0, sizeof(cam_data)); + + init_MUTEX(&cam->param_lock); + init_MUTEX(&cam->busy_lock); + + cam->video_dev = video_device_alloc(); + if (cam->video_dev == NULL) + return; + + *(cam->video_dev) = mxc_v4l_template; + + video_set_drvdata(cam->video_dev, cam); + dev_set_drvdata(&mxc_v4l2_devices.dev, (void *)cam); + cam->video_dev->minor = -1; + + for (i = 0; i < FRAME_NUM; i++) { + cam->frame[i].width = 0; + cam->frame[i].height = 0; + cam->frame[i].paddress = 0; + } + + init_waitqueue_head(&cam->enc_queue); + init_waitqueue_head(&cam->still_queue); + + /* setup cropping */ + cam->crop_bounds.left = 0; + cam->crop_bounds.width = 640; + cam->crop_bounds.top = 0; + cam->crop_bounds.height = 480; + cam->crop_current = cam->crop_defrect = cam->crop_bounds; + cam->streamparm.parm.capture.capturemode = 0; + + cam->standard.index = 0; + cam->standard.id = V4L2_STD_UNKNOWN; + cam->standard.frameperiod.denominator = 30; + cam->standard.frameperiod.numerator = 1; + cam->standard.framelines = 480; + cam->streamparm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + cam->streamparm.parm.capture.timeperframe = cam->standard.frameperiod; + cam->streamparm.parm.capture.capability = V4L2_CAP_TIMEPERFRAME; + cam->overlay_on = false; + cam->capture_on = false; + cam->skip_frame = 0; + cam->v4l2_fb.capability = V4L2_FBUF_CAP_EXTERNOVERLAY; + cam->v4l2_fb.flags = V4L2_FBUF_FLAG_PRIMARY; + + cam->v2f.fmt.pix.sizeimage = 352 * 288 * 3 / 2; + cam->v2f.fmt.pix.bytesperline = 288 * 3 / 2; + cam->v2f.fmt.pix.width = 288; + cam->v2f.fmt.pix.height = 352; + cam->v2f.fmt.pix.pixelformat = V4L2_PIX_FMT_YUV420; + cam->win.w.width = 160; + cam->win.w.height = 160; + cam->win.w.left = 0; + cam->win.w.top = 0; + + cam->cam_sensor = &camera_sensor_if; + cam->enc_callback = camera_callback; + + init_waitqueue_head(&cam->power_queue); + cam->int_lock = SPIN_LOCK_UNLOCKED; + spin_lock_init(&cam->int_lock); +} + +extern void gpio_sensor_active(void); +extern void gpio_sensor_inactive(void); + +/*! + * camera_power function + * Turn Sensor power On/Off + * + * @param cameraOn true to turn camera on, otherwise shut down + * + * @return status + */ +static u8 camera_power(bool cameraOn) +{ + if (cameraOn == true) { + gpio_sensor_active(); + csi_enable_mclk(csi_mclk_flag_backup, true, true); + } else { + csi_mclk_flag_backup = csi_read_mclk_flag(); + csi_enable_mclk(csi_mclk_flag_backup, false, false); + gpio_sensor_inactive(); + } + return 0; +} + +/*! + * This function is called to put the sensor in a low power state. Refer to the + * document driver-model/driver.txt in the kernel source tree for more + * information. + * + * @param pdev the device structure used to give information on which I2C + * to suspend + * @param state the power state the device is entering + * + * @return The function returns 0 on success and -1 on failure. + */ +static int mxc_v4l2_suspend(struct platform_device *pdev, pm_message_t state) +{ + cam_data *cam = platform_get_drvdata(pdev); + + if (cam == NULL) { + return -1; + } + + cam->low_power = true; + + if (cam->overlay_on == true) + stop_preview(cam); + if ((cam->capture_on == true) && cam->enc_disable) { + cam->enc_disable(cam); + } + camera_power(false); + + return 0; +} + +/*! + * This function is called to bring the sensor back from a low power state.Refer + * to the document driver-model/driver.txt in the kernel source tree for more + * information. + * + * @param pdev the device structure + * + * @return The function returns 0 on success and -1 on failure + */ +static int mxc_v4l2_resume(struct platform_device *pdev) +{ + cam_data *cam = platform_get_drvdata(pdev); + + if (cam == NULL) { + return -1; + } + + cam->low_power = false; + wake_up_interruptible(&cam->power_queue); + + if (cam->overlay_on == true) + start_preview(cam); + if (cam->capture_on == true) + mxc_streamon(cam); + camera_power(true); + + return 0; +} + +/*! + * This structure contains pointers to the power management callback functions. + */ +static struct platform_driver mxc_v4l2_driver = { + .driver = { + .name = "mxc_v4l2", + .owner = THIS_MODULE, + .bus = &platform_bus_type, + }, + .probe = NULL, + .remove = NULL, + .suspend = mxc_v4l2_suspend, + .resume = mxc_v4l2_resume, + .shutdown = NULL, +}; + +/*! + * Entry point for the V4L2 + * + * @return Error code indicating success or failure + */ +static __init int camera_init(void) +{ + u8 err = 0; + cam_data *cam; + + if ((g_cam = cam = kmalloc(sizeof(cam_data), GFP_KERNEL)) == NULL) { + pr_debug("failed to mxc_v4l_register_camera\n"); + return -1; + } + + init_camera_struct(cam); + + /* Register the I2C device */ + err = platform_device_register(&mxc_v4l2_devices); + if (err != 0) { + pr_debug("camera_init: platform_device_register failed.\n"); + video_device_release(cam->video_dev); + kfree(cam); + g_cam = NULL; + } + + /* Register the device driver structure. */ + err = platform_driver_register(&mxc_v4l2_driver); + if (err != 0) { + platform_device_unregister(&mxc_v4l2_devices); + pr_debug("camera_init: driver_register failed.\n"); + video_device_release(cam->video_dev); + kfree(cam); + g_cam = NULL; + return err; + } + + /* register v4l device */ + if (video_register_device(cam->video_dev, VFL_TYPE_GRABBER, video_nr) + == -1) { + platform_driver_unregister(&mxc_v4l2_driver); + platform_device_unregister(&mxc_v4l2_devices); + video_device_release(cam->video_dev); + kfree(cam); + g_cam = NULL; + pr_debug("video_register_device failed\n"); + return -1; + } + + return err; +} + +/*! + * Exit and cleanup for the V4L2 + * + */ +static void __exit camera_exit(void) +{ + pr_debug("unregistering video\n"); + + video_unregister_device(g_cam->video_dev); + + platform_driver_unregister(&mxc_v4l2_driver); + platform_device_unregister(&mxc_v4l2_devices); + + if (g_cam->open_count) { + pr_debug("camera open -- setting ops to NULL\n"); + } else { + pr_debug("freeing camera\n"); + mxc_free_frame_buf(g_cam); + kfree(g_cam); + g_cam = NULL; + } +} + +module_init(camera_init); +module_exit(camera_exit); + +module_param(video_nr, int, 0444); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("V4L2 capture driver for Mxc based cameras"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("video"); diff --git a/drivers/media/video/mxc/capture/mxc_v4l2_capture.c b/drivers/media/video/mxc/capture/mxc_v4l2_capture.c new file mode 100644 index 000000000000..04c52e0a9bea --- /dev/null +++ b/drivers/media/video/mxc/capture/mxc_v4l2_capture.c @@ -0,0 +1,1865 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file drivers/media/video/mxc/capture/mxc_v4l2_capture.c + * + * @brief Mxc Video For Linux 2 driver + * + * @ingroup MXC_V4L2_CAPTURE + */ + +#include <linux/version.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/fs.h> +#include <linux/slab.h> +#include <linux/ctype.h> +#include <asm/io.h> +#include <asm/semaphore.h> +#include <linux/pagemap.h> +#include <linux/vmalloc.h> +#include <linux/types.h> +#include <linux/fb.h> +#include <linux/dma-mapping.h> + +#include <asm/arch/mxcfb.h> +#include "mxc_v4l2_capture.h" +#include "ipu_prp_sw.h" + +static int csi_mclk_flag_backup; +static int video_nr = -1; +static cam_data *g_cam; + +#define MXC_V4L2_CAPTURE_NUM_OUTPUTS 2 +static struct v4l2_output mxc_capture_outputs[MXC_V4L2_CAPTURE_NUM_OUTPUTS] = { + { + .index = 0, + .name = "DISP3", + .type = V4L2_OUTPUT_TYPE_ANALOG, + .audioset = 0, + .modulator = 0, + .std = V4L2_STD_UNKNOWN, + }, + { + .index = 1, + .name = "DISP0", + .type = V4L2_OUTPUT_TYPE_ANALOG, + .audioset = 0, + .modulator = 0, + .std = V4L2_STD_UNKNOWN, + } +}; + +/*! + * Free frame buffers + * + * @param cam Structure cam_data * + * + * @return status 0 success. + */ +static int mxc_free_frame_buf(cam_data * cam) +{ + int i; + + for (i = 0; i < FRAME_NUM; i++) { + if (cam->frame[i].vaddress != 0) { + dma_free_coherent(0, cam->frame[i].buffer.length, + cam->frame[i].vaddress, + cam->frame[i].paddress); + cam->frame[i].vaddress = 0; + } + } + + return 0; +} + +/*! + * Allocate frame buffers + * + * @param cam Structure cam_data * + * + * @param count int number of buffer need to allocated + * + * @return status -0 Successfully allocated a buffer, -ENOBUFS failed. + */ +static int mxc_allocate_frame_buf(cam_data * cam, int count) +{ + int i; + + for (i = 0; i < count; i++) { + cam->frame[i].vaddress = + dma_alloc_coherent(0, + PAGE_ALIGN(cam->v2f.fmt.pix.sizeimage), + &cam->frame[i].paddress, + GFP_DMA | GFP_KERNEL); + if (cam->frame[i].vaddress == 0) { + printk(KERN_ERR "mxc_allocate_frame_buf failed.\n"); + mxc_free_frame_buf(cam); + return -ENOBUFS; + } + cam->frame[i].buffer.index = i; + cam->frame[i].buffer.flags = V4L2_BUF_FLAG_MAPPED; + cam->frame[i].buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + cam->frame[i].buffer.length = + PAGE_ALIGN(cam->v2f.fmt.pix.sizeimage); + cam->frame[i].buffer.memory = V4L2_MEMORY_MMAP; + cam->frame[i].buffer.m.offset = cam->frame[i].paddress; + cam->frame[i].index = i; + } + + return 0; +} + +/*! + * Free frame buffers status + * + * @param cam Structure cam_data * + * + * @return none + */ +static void mxc_free_frames(cam_data * cam) +{ + int i; + + for (i = 0; i < FRAME_NUM; i++) { + cam->frame[i].buffer.flags = V4L2_BUF_FLAG_MAPPED; + } + + cam->enc_counter = 0; + cam->skip_frame = 0; + INIT_LIST_HEAD(&cam->ready_q); + INIT_LIST_HEAD(&cam->working_q); + INIT_LIST_HEAD(&cam->done_q); +} + +/*! + * Return the buffer status + * + * @param cam Structure cam_data * + * @param buf Structure v4l2_buffer * + * + * @return status 0 success, EINVAL failed. + */ +static int mxc_v4l2_buffer_status(cam_data * cam, struct v4l2_buffer *buf) +{ + if (buf->index < 0 || buf->index >= FRAME_NUM) { + printk(KERN_ERR + "mxc_v4l2_buffer_status buffers not allocated\n"); + return -EINVAL; + } + + memcpy(buf, &(cam->frame[buf->index].buffer), sizeof(*buf)); + return 0; +} + +/*! + * start the encoder job + * + * @param cam structure cam_data * + * + * @return status 0 Success + */ +static int mxc_streamon(cam_data * cam) +{ + struct mxc_v4l_frame *frame; + int err = 0; + + if (list_empty(&cam->ready_q)) { + printk(KERN_ERR "mxc_streamon buffer not been queued yet\n"); + return -EINVAL; + } + + cam->capture_pid = current->pid; + + if (cam->enc_enable) { + err = cam->enc_enable(cam); + if (err != 0) { + return err; + } + } + + cam->ping_pong_csi = 0; + if (cam->enc_update_eba) { + frame = + list_entry(cam->ready_q.next, struct mxc_v4l_frame, queue); + list_del(cam->ready_q.next); + list_add_tail(&frame->queue, &cam->working_q); + err = + cam->enc_update_eba(frame->buffer.m.offset, + &cam->ping_pong_csi); + + frame = + list_entry(cam->ready_q.next, struct mxc_v4l_frame, queue); + list_del(cam->ready_q.next); + list_add_tail(&frame->queue, &cam->working_q); + err |= + cam->enc_update_eba(frame->buffer.m.offset, + &cam->ping_pong_csi); + } else { + return -EINVAL; + } + + cam->capture_on = true; + return err; +} + +/*! + * Shut down the encoder job + * + * @param cam structure cam_data * + * + * @return status 0 Success + */ +static int mxc_streamoff(cam_data * cam) +{ + int err = 0; + + if (cam->capture_on == false) + return 0; + + if (cam->enc_disable) { + err = cam->enc_disable(cam); + } + mxc_free_frames(cam); + cam->capture_on = false; + return err; +} + +/*! + * Valid whether the palette is supported + * + * @param palette V4L2_PIX_FMT_RGB565, V4L2_PIX_FMT_BGR24 or V4L2_PIX_FMT_BGR32 + * + * @return 0 if failed + */ +static inline int valid_mode(u32 palette) +{ + return ((palette == V4L2_PIX_FMT_RGB565) || + (palette == V4L2_PIX_FMT_BGR24) || + (palette == V4L2_PIX_FMT_RGB24) || + (palette == V4L2_PIX_FMT_BGR32) || + (palette == V4L2_PIX_FMT_RGB32) || + (palette == V4L2_PIX_FMT_YUV422P) || + (palette == V4L2_PIX_FMT_UYVY) || + (palette == V4L2_PIX_FMT_YUV420)); +} + +/*! + * Valid and adjust the overlay window size, position + * + * @param cam structure cam_data * + * @param win struct v4l2_window * + * + * @return 0 + */ +static int verify_preview(cam_data * cam, struct v4l2_window *win) +{ + int i = 0; + int *width, *height; + + do { + cam->overlay_fb = (struct fb_info *)registered_fb[i]; + if (cam->overlay_fb == NULL) { + printk(KERN_ERR "verify_preview No matched.\n"); + return -1; + } + if (strncmp(cam->overlay_fb->fix.id, + mxc_capture_outputs[cam->output].name, 5) == 0) { + break; + } + } while (++i < FB_MAX); + + /* 4 bytes alignment for both FG and BG */ + if (cam->overlay_fb->var.bits_per_pixel == 24) { + win->w.left -= win->w.left % 4; + } else if (cam->overlay_fb->var.bits_per_pixel == 16) { + win->w.left -= win->w.left % 2; + } + + if (win->w.width + win->w.left > cam->overlay_fb->var.xres) + win->w.width = cam->overlay_fb->var.xres - win->w.left; + if (win->w.height + win->w.top > cam->overlay_fb->var.yres) + win->w.height = cam->overlay_fb->var.yres - win->w.top; + + /* stride line limitation */ + win->w.height -= win->w.height % 8; + win->w.width -= win->w.width % 8; + + if (cam->rotation >= IPU_ROTATE_90_RIGHT) { + height = &win->w.width; + width = &win->w.height; + } else { + width = &win->w.width; + height = &win->w.height; + } + + if ((cam->crop_bounds.width / *width > 8) || + ((cam->crop_bounds.width / *width == 8) && + (cam->crop_bounds.width % *width))) { + *width = cam->crop_bounds.width / 8; + if (*width % 8) + *width += 8 - *width % 8; + if (*width + win->w.left > cam->overlay_fb->var.xres) { + printk(KERN_ERR "width exceed resize limit.\n"); + return -1; + } + printk(KERN_ERR "width exceed limit resize to %d.\n", *width); + } + + if ((cam->crop_bounds.height / *height > 8) || + ((cam->crop_bounds.height / *height == 8) && + (cam->crop_bounds.height % *height))) { + *height = cam->crop_bounds.height / 8; + if (*height % 8) + *height += 8 - *height % 8; + if (*height + win->w.top > cam->overlay_fb->var.yres) { + printk(KERN_ERR "height exceed resize limit.\n"); + return -1; + } + printk(KERN_ERR "height exceed limit resize to %d.\n", *height); + } + + return 0; +} + +/*! + * start the viewfinder job + * + * @param cam structure cam_data * + * + * @return status 0 Success + */ +static int start_preview(cam_data * cam) +{ + int err = 0; +#if defined(CONFIG_MXC_IPU_PRP_VF_SDC) || defined(CONFIG_MXC_IPU_PRP_VF_SDC_MODULE) + if (cam->output == 0) { + if (cam->v4l2_fb.flags == V4L2_FBUF_FLAG_OVERLAY) + err = prp_vf_sdc_select(cam); + else if (cam->v4l2_fb.flags == V4L2_FBUF_FLAG_PRIMARY) + err = prp_vf_sdc_select_bg(cam); + if (err != 0) + return err; + + err = cam->vf_start_sdc(cam); + } +#endif + +#if defined(CONFIG_MXC_IPU_PRP_VF_ADC) || defined(CONFIG_MXC_IPU_PRP_VF_ADC_MODULE) + if (cam->output == 1) { + err = prp_vf_adc_select(cam); + if (err != 0) + return err; + + err = cam->vf_start_adc(cam); + } +#endif + + return err; +} + +/*! + * shut down the viewfinder job + * + * @param cam structure cam_data * + * + * @return status 0 Success + */ +static int stop_preview(cam_data * cam) +{ + int err = 0; + +#if defined(CONFIG_MXC_IPU_PRP_VF_ADC) || defined(CONFIG_MXC_IPU_PRP_VF_ADC_MODULE) + if (cam->output == 1) { + err = prp_vf_adc_deselect(cam); + } +#endif + +#if defined(CONFIG_MXC_IPU_PRP_VF_SDC) || defined(CONFIG_MXC_IPU_PRP_VF_SDC_MODULE) + if (cam->output == 0) { + if (cam->v4l2_fb.flags == V4L2_FBUF_FLAG_OVERLAY) + err = prp_vf_sdc_deselect(cam); + else if (cam->v4l2_fb.flags == V4L2_FBUF_FLAG_PRIMARY) + err = prp_vf_sdc_deselect_bg(cam); + } +#endif + + return err; +} + +/*! + * V4L2 - mxc_v4l2_g_fmt function + * + * @param cam structure cam_data * + * + * @param f structure v4l2_format * + * + * @return status 0 success, EINVAL failed + */ +static int mxc_v4l2_g_fmt(cam_data * cam, struct v4l2_format *f) +{ + int retval = 0; + + switch (f->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + f->fmt.pix = cam->v2f.fmt.pix; + retval = 0; + break; + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + f->fmt.win = cam->win; + break; + default: + retval = -EINVAL; + } + return retval; +} + +/*! + * V4L2 - mxc_v4l2_s_fmt function + * + * @param cam structure cam_data * + * + * @param f structure v4l2_format * + * + * @return status 0 success, EINVAL failed + */ +static int mxc_v4l2_s_fmt(cam_data * cam, struct v4l2_format *f) +{ + int retval = 0; + int size = 0; + int bytesperline = 0; + int *width, *height; + + switch (f->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + if (!valid_mode(f->fmt.pix.pixelformat)) { + printk(KERN_ERR + "mxc_v4l2_s_fmt: format not supported\n"); + return -EINVAL; + } + + if (cam->rotation >= IPU_ROTATE_90_RIGHT) { + height = &f->fmt.pix.width; + width = &f->fmt.pix.height; + } else { + width = &f->fmt.pix.width; + height = &f->fmt.pix.height; + } + + /* stride line limitation */ + *width -= *width % 8; + *height -= *height % 8; + + if ((cam->crop_bounds.width / *width > 8) || + ((cam->crop_bounds.width / *width == 8) && + (cam->crop_bounds.width % *width))) { + *width = cam->crop_bounds.width / 8; + if (*width % 8) + *width += 8 - *width % 8; + printk(KERN_ERR "width exceed limit resize to %d.\n", + *width); + } + + if ((cam->crop_bounds.height / *height > 8) || + ((cam->crop_bounds.height / *height == 8) && + (cam->crop_bounds.height % *height))) { + *height = cam->crop_bounds.height / 8; + if (*height % 8) + *height += 8 - *height % 8; + printk(KERN_ERR "height exceed limit resize to %d.\n", + *height); + } + + switch (f->fmt.pix.pixelformat) { + case V4L2_PIX_FMT_RGB565: + size = f->fmt.pix.width * f->fmt.pix.height * 2; + bytesperline = f->fmt.pix.width * 2; + break; + case V4L2_PIX_FMT_BGR24: + size = f->fmt.pix.width * f->fmt.pix.height * 3; + bytesperline = f->fmt.pix.width * 3; + break; + case V4L2_PIX_FMT_RGB24: + size = f->fmt.pix.width * f->fmt.pix.height * 3; + bytesperline = f->fmt.pix.width * 3; + break; + case V4L2_PIX_FMT_BGR32: + size = f->fmt.pix.width * f->fmt.pix.height * 4; + bytesperline = f->fmt.pix.width * 4; + break; + case V4L2_PIX_FMT_RGB32: + size = f->fmt.pix.width * f->fmt.pix.height * 4; + bytesperline = f->fmt.pix.width * 4; + break; + case V4L2_PIX_FMT_YUV422P: + size = f->fmt.pix.width * f->fmt.pix.height * 2; + bytesperline = f->fmt.pix.width; + break; + case V4L2_PIX_FMT_UYVY: + size = f->fmt.pix.width * f->fmt.pix.height * 2; + bytesperline = f->fmt.pix.width * 2; + break; + case V4L2_PIX_FMT_YUV420: + size = f->fmt.pix.width * f->fmt.pix.height * 3 / 2; + bytesperline = f->fmt.pix.width; + break; + default: + break; + } + + if (f->fmt.pix.bytesperline < bytesperline) { + f->fmt.pix.bytesperline = bytesperline; + } else { + bytesperline = f->fmt.pix.bytesperline; + } + + if (f->fmt.pix.sizeimage < size) { + f->fmt.pix.sizeimage = size; + } else { + size = f->fmt.pix.sizeimage; + } + + cam->v2f.fmt.pix = f->fmt.pix; + + if (cam->v2f.fmt.pix.priv != 0) { + if (copy_from_user(&cam->offset, + (void *)cam->v2f.fmt.pix.priv, + sizeof(cam->offset))) { + retval = -EFAULT; + break; + } + } + retval = 0; + break; + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + retval = verify_preview(cam, &f->fmt.win); + cam->win = f->fmt.win; + break; + default: + retval = -EINVAL; + } + return retval; +} + +/*! + * get control param + * + * @param cam structure cam_data * + * + * @param c structure v4l2_control * + * + * @return status 0 success, EINVAL failed + */ +static int mxc_get_v42l_control(cam_data * cam, struct v4l2_control *c) +{ + int status = 0; + + switch (c->id) { + case V4L2_CID_HFLIP: + if (cam->rotation == IPU_ROTATE_HORIZ_FLIP) + c->value = 1; + break; + case V4L2_CID_VFLIP: + if (cam->rotation == IPU_ROTATE_VERT_FLIP) + c->value = 1; + break; + case V4L2_CID_MXC_ROT: + c->value = cam->rotation; + break; + case V4L2_CID_BRIGHTNESS: + c->value = cam->bright; + break; + case V4L2_CID_HUE: + c->value = cam->hue; + break; + case V4L2_CID_CONTRAST: + c->value = cam->contrast; + break; + case V4L2_CID_SATURATION: + c->value = cam->saturation; + break; + case V4L2_CID_RED_BALANCE: + c->value = cam->red; + break; + case V4L2_CID_BLUE_BALANCE: + c->value = cam->blue; + break; + case V4L2_CID_BLACK_LEVEL: + c->value = cam->ae_mode; + break; + default: + status = -EINVAL; + } + return status; +} + +/*! + * V4L2 - set_control function + * V4L2_CID_PRIVATE_BASE is the extention for IPU preprocessing. + * 0 for normal operation + * 1 for vertical flip + * 2 for horizontal flip + * 3 for horizontal and vertical flip + * 4 for 90 degree rotation + * @param cam structure cam_data * + * + * @param c structure v4l2_control * + * + * @return status 0 success, EINVAL failed + */ +static int mxc_set_v42l_control(cam_data * cam, struct v4l2_control *c) +{ + switch (c->id) { + case V4L2_CID_HFLIP: + if (c->value == 1) { + if ((cam->rotation != IPU_ROTATE_VERT_FLIP) && + (cam->rotation != IPU_ROTATE_180)) + cam->rotation = IPU_ROTATE_HORIZ_FLIP; + else + cam->rotation = IPU_ROTATE_180; + } else { + if (cam->rotation == IPU_ROTATE_HORIZ_FLIP) + cam->rotation = IPU_ROTATE_NONE; + if (cam->rotation == IPU_ROTATE_180) + cam->rotation = IPU_ROTATE_VERT_FLIP; + } + break; + case V4L2_CID_VFLIP: + if (c->value == 1) { + if ((cam->rotation != IPU_ROTATE_HORIZ_FLIP) && + (cam->rotation != IPU_ROTATE_180)) + cam->rotation = IPU_ROTATE_VERT_FLIP; + else + cam->rotation = IPU_ROTATE_180; + } else { + if (cam->rotation == IPU_ROTATE_VERT_FLIP) + cam->rotation = IPU_ROTATE_NONE; + if (cam->rotation == IPU_ROTATE_180) + cam->rotation = IPU_ROTATE_HORIZ_FLIP; + } + break; + case V4L2_CID_MXC_ROT: + switch (c->value) { + case V4L2_MXC_ROTATE_NONE: + cam->rotation = IPU_ROTATE_NONE; + break; + case V4L2_MXC_ROTATE_VERT_FLIP: + cam->rotation = IPU_ROTATE_VERT_FLIP; + break; + case V4L2_MXC_ROTATE_HORIZ_FLIP: + cam->rotation = IPU_ROTATE_HORIZ_FLIP; + break; + case V4L2_MXC_ROTATE_180: + cam->rotation = IPU_ROTATE_180; + break; + case V4L2_MXC_ROTATE_90_RIGHT: + cam->rotation = IPU_ROTATE_90_RIGHT; + break; + case V4L2_MXC_ROTATE_90_RIGHT_VFLIP: + cam->rotation = IPU_ROTATE_90_RIGHT_VFLIP; + break; + case V4L2_MXC_ROTATE_90_RIGHT_HFLIP: + cam->rotation = IPU_ROTATE_90_RIGHT_HFLIP; + break; + case V4L2_MXC_ROTATE_90_LEFT: + cam->rotation = IPU_ROTATE_90_LEFT; + break; + default: + return -EINVAL; + } + break; + case V4L2_CID_HUE: + cam->hue = c->value; + break; + case V4L2_CID_CONTRAST: + cam->contrast = c->value; + break; + case V4L2_CID_BRIGHTNESS: + cam->bright = c->value; + case V4L2_CID_SATURATION: + cam->saturation = c->value; + case V4L2_CID_RED_BALANCE: + cam->red = c->value; + case V4L2_CID_BLUE_BALANCE: + cam->blue = c->value; + ipu_csi_enable_mclk(CSI_MCLK_I2C, true, true); + cam->cam_sensor->set_color(cam->bright, cam->saturation, + cam->red, cam->green, cam->blue); + ipu_csi_enable_mclk(CSI_MCLK_I2C, false, false); + break; + case V4L2_CID_BLACK_LEVEL: + cam->ae_mode = c->value & 0x03; + ipu_csi_enable_mclk(CSI_MCLK_I2C, true, true); + if (cam->cam_sensor->set_ae_mode) + cam->cam_sensor->set_ae_mode(cam->ae_mode); + ipu_csi_enable_mclk(CSI_MCLK_I2C, false, false); + break; + case V4L2_CID_MXC_FLASH: + ipu_csi_flash_strobe(true); + break; + default: + return -EINVAL; + } + return 0; +} + +/*! + * V4L2 - mxc_v4l2_s_param function + * + * @param cam structure cam_data * + * + * @param parm structure v4l2_streamparm * + * + * @return status 0 success, EINVAL failed + */ +static int mxc_v4l2_s_param(cam_data * cam, struct v4l2_streamparm *parm) +{ + sensor_interface *param; + ipu_csi_signal_cfg_t csi_param; + int err = 0; + + if (parm->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) { + printk(KERN_ERR "mxc_v4l2_s_param invalid type\n"); + return -EINVAL; + } + + if (parm->parm.capture.timeperframe.denominator > + cam->standard.frameperiod.denominator) { + printk(KERN_ERR "mxc_v4l2_s_param frame rate %d larger " + "than standard supported %d\n", + parm->parm.capture.timeperframe.denominator, + cam->standard.frameperiod.denominator); + return -EINVAL; + } + + /* Stop the viewfinder */ + if (cam->overlay_on == true) { + stop_preview(cam); + } + + cam->streamparm.parm.capture.capability = V4L2_CAP_TIMEPERFRAME; + + ipu_csi_enable_mclk(CSI_MCLK_I2C, true, true); + param = cam->cam_sensor->config + (&parm->parm.capture.timeperframe.denominator, + parm->parm.capture.capturemode); + ipu_csi_enable_mclk(CSI_MCLK_I2C, false, false); + cam->streamparm.parm.capture.timeperframe = + parm->parm.capture.timeperframe; + + if ((parm->parm.capture.capturemode != 0) && + (parm->parm.capture.capturemode != V4L2_MODE_HIGHQUALITY)) { + printk(KERN_ERR + "mxc_v4l2_s_param frame un-supported capture mode\n"); + err = -EINVAL; + goto exit; + } + + if (parm->parm.capture.capturemode == + cam->streamparm.parm.capture.capturemode) { + goto exit; + } + + /* resolution changed, so need to re-program the CSI */ + csi_param.sens_clksrc = 0; + csi_param.clk_mode = param->clk_mode; + csi_param.pixclk_pol = param->pixclk_pol; + csi_param.data_width = param->data_width; + csi_param.data_pol = param->data_pol; + csi_param.ext_vsync = param->ext_vsync; + csi_param.Vsync_pol = param->Vsync_pol; + csi_param.Hsync_pol = param->Hsync_pol; + ipu_csi_init_interface(param->width, param->height, + param->pixel_fmt, csi_param); + ipu_csi_set_window_size(param->width + 1, param->height + 1); + + if (parm->parm.capture.capturemode != V4L2_MODE_HIGHQUALITY) { + cam->streamparm.parm.capture.capturemode = 0; + } else { + cam->streamparm.parm.capture.capturemode = + V4L2_MODE_HIGHQUALITY; + cam->streamparm.parm.capture.extendedmode = + parm->parm.capture.extendedmode; + cam->streamparm.parm.capture.readbuffers = 1; + } + + exit: + if (cam->overlay_on == true) { + start_preview(cam); + } + + return err; +} + +/*! + * Dequeue one V4L capture buffer + * + * @param cam structure cam_data * + * @param buf structure v4l2_buffer * + * + * @return status 0 success, EINVAL invalid frame number, + * ETIME timeout, ERESTARTSYS interrupted by user + */ +static int mxc_v4l_dqueue(cam_data * cam, struct v4l2_buffer *buf) +{ + int retval = 0; + struct mxc_v4l_frame *frame; + + if (!wait_event_interruptible_timeout(cam->enc_queue, + cam->enc_counter != 0, 10 * HZ)) { + printk(KERN_ERR "mxc_v4l_dqueue timeout enc_counter %x\n", + cam->enc_counter); + return -ETIME; + } else if (signal_pending(current)) { + printk(KERN_ERR "mxc_v4l_dqueue() interrupt received\n"); + return -ERESTARTSYS; + } + + cam->enc_counter--; + + frame = list_entry(cam->done_q.next, struct mxc_v4l_frame, queue); + list_del(cam->done_q.next); + if (frame->buffer.flags & V4L2_BUF_FLAG_DONE) { + frame->buffer.flags &= ~V4L2_BUF_FLAG_DONE; + } else if (frame->buffer.flags & V4L2_BUF_FLAG_QUEUED) { + printk(KERN_ERR "VIDIOC_DQBUF: Buffer not filled.\n"); + frame->buffer.flags &= ~V4L2_BUF_FLAG_QUEUED; + retval = -EINVAL; + } else if ((frame->buffer.flags & 0x7) == V4L2_BUF_FLAG_MAPPED) { + printk(KERN_ERR "VIDIOC_DQBUF: Buffer not queued.\n"); + retval = -EINVAL; + } + + buf->bytesused = cam->v2f.fmt.pix.sizeimage; + buf->index = frame->index; + buf->flags = frame->buffer.flags; + buf->m = cam->frame[frame->index].buffer.m; + + return retval; +} + +/*! + * V4L interface - open function + * + * @param inode structure inode * + * @param file structure file * + * + * @return status 0 success, ENODEV invalid device instance, + * ENODEV timeout, ERESTARTSYS interrupted by user + */ +static int mxc_v4l_open(struct inode *inode, struct file *file) +{ + sensor_interface *param; + ipu_csi_signal_cfg_t csi_param; + struct video_device *dev = video_devdata(file); + cam_data *cam = dev->priv; + int err = 0; + + if (!cam) { + printk(KERN_ERR "Internal error, cam_data not found!\n"); + return -EBADF; + } + + down(&cam->busy_lock); + + err = 0; + if (signal_pending(current)) + goto oops; + + if (cam->open_count++ == 0) { + wait_event_interruptible(cam->power_queue, + cam->low_power == false); + +#if defined(CONFIG_MXC_IPU_PRP_ENC) || defined(CONFIG_MXC_IPU_PRP_ENC_MODULE) + err = prp_enc_select(cam); +#endif + + cam->enc_counter = 0; + cam->skip_frame = 0; + INIT_LIST_HEAD(&cam->ready_q); + INIT_LIST_HEAD(&cam->working_q); + INIT_LIST_HEAD(&cam->done_q); + + ipu_csi_enable_mclk(CSI_MCLK_I2C, true, true); + param = cam->cam_sensor->reset(); + if (param == NULL) { + cam->open_count--; + ipu_csi_enable_mclk(CSI_MCLK_I2C, false, false); + err = -ENODEV; + goto oops; + } + + csi_param.sens_clksrc = 0; + csi_param.clk_mode = param->clk_mode; + csi_param.pixclk_pol = param->pixclk_pol; + csi_param.data_width = param->data_width; + csi_param.data_pol = param->data_pol; + csi_param.ext_vsync = param->ext_vsync; + csi_param.Vsync_pol = param->Vsync_pol; + csi_param.Hsync_pol = param->Hsync_pol; + ipu_csi_init_interface(param->width, param->height, + param->pixel_fmt, csi_param); + + cam->cam_sensor->get_color(&cam->bright, &cam->saturation, + &cam->red, &cam->green, &cam->blue); + if (cam->cam_sensor->get_ae_mode) + cam->cam_sensor->get_ae_mode(&cam->ae_mode); + + /* pr_info("mxc_v4l_open saturation %x ae_mode %x\n", + cam->saturation, cam->ae_mode); */ + + ipu_csi_enable_mclk(CSI_MCLK_I2C, false, false); + } + + file->private_data = dev; + oops: + up(&cam->busy_lock); + return err; +} + +/*! + * V4L interface - close function + * + * @param inode struct inode * + * @param file struct file * + * + * @return 0 success + */ +static int mxc_v4l_close(struct inode *inode, struct file *file) +{ + struct video_device *dev = video_devdata(file); + int err = 0; + cam_data *cam = dev->priv; + + if (!cam) { + printk(KERN_ERR "Internal error, cam_data not found!\n"); + return -EBADF; + } + + /* for the case somebody hit the ctrl C */ + if (cam->overlay_pid == current->pid) { + err = stop_preview(cam); + cam->overlay_on = false; + } + if (cam->capture_pid == current->pid) { + err |= mxc_streamoff(cam); + wake_up_interruptible(&cam->enc_queue); + } + + if (--cam->open_count == 0) { + wait_event_interruptible(cam->power_queue, + cam->low_power == false); + pr_info("mxc_v4l_close: release resource\n"); + +#if defined(CONFIG_MXC_IPU_PRP_ENC) || defined(CONFIG_MXC_IPU_PRP_ENC_MODULE) + err |= prp_enc_deselect(cam); +#endif + mxc_free_frame_buf(cam); + file->private_data = NULL; + + /* capture off */ + wake_up_interruptible(&cam->enc_queue); + mxc_free_frames(cam); + cam->enc_counter++; + } + return err; +} + +#if defined(CONFIG_MXC_IPU_PRP_ENC) || defined(CONFIG_MXC_IPU_PRP_ENC_MODULE) +/* + * V4L interface - read function + * + * @param file struct file * + * @param read buf char * + * @param count size_t + * @param ppos structure loff_t * + * + * @return bytes read + */ +static ssize_t +mxc_v4l_read(struct file *file, char *buf, size_t count, loff_t * ppos) +{ + int err = 0; + u8 *v_address; + struct video_device *dev = video_devdata(file); + cam_data *cam = dev->priv; + + if (down_interruptible(&cam->busy_lock)) + return -EINTR; + + /* Stop the viewfinder */ + if (cam->overlay_on == true) + stop_preview(cam); + + v_address = dma_alloc_coherent(0, + PAGE_ALIGN(cam->v2f.fmt.pix.sizeimage), + &cam->still_buf, GFP_DMA | GFP_KERNEL); + + if (!v_address) { + err = -ENOBUFS; + goto exit0; + } + + err = prp_still_select(cam); + if (err != 0) { + err = -EIO; + goto exit1; + } + + cam->still_counter = 0; + err = cam->csi_start(cam); + if (err != 0) { + err = -EIO; + goto exit2; + } + + if (!wait_event_interruptible_timeout(cam->still_queue, + cam->still_counter != 0, + 10 * HZ)) { + printk(KERN_ERR "mxc_v4l_read timeout counter %x\n", + cam->still_counter); + err = -ETIME; + goto exit2; + } + err = copy_to_user(buf, v_address, cam->v2f.fmt.pix.sizeimage); + + exit2: + prp_still_deselect(cam); + + exit1: + dma_free_coherent(0, cam->v2f.fmt.pix.sizeimage, v_address, + cam->still_buf); + cam->still_buf = 0; + + exit0: + if (cam->overlay_on == true) { + start_preview(cam); + } + + up(&cam->busy_lock); + if (err < 0) + return err; + + return (cam->v2f.fmt.pix.sizeimage - err); +} +#endif + +/*! + * V4L interface - ioctl function + * + * @param inode struct inode * + * + * @param file struct file * + * + * @param ioctlnr unsigned int + * + * @param arg void * + * + * @return 0 success, ENODEV for invalid device instance, + * -1 for other errors. + */ +static int +mxc_v4l_do_ioctl(struct inode *inode, struct file *file, + unsigned int ioctlnr, void *arg) +{ + struct video_device *dev = video_devdata(file); + cam_data *cam = dev->priv; + int retval = 0; + unsigned long lock_flags; + + wait_event_interruptible(cam->power_queue, cam->low_power == false); + /* make this _really_ smp-safe */ + if (down_interruptible(&cam->busy_lock)) + return -EBUSY; + + switch (ioctlnr) { + /*! + * V4l2 VIDIOC_QUERYCAP ioctl + */ + case VIDIOC_QUERYCAP:{ + struct v4l2_capability *cap = arg; + strcpy(cap->driver, "mxc_v4l2"); + cap->version = KERNEL_VERSION(0, 1, 11); + cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | + V4L2_CAP_VIDEO_OVERLAY | V4L2_CAP_STREAMING + | V4L2_CAP_READWRITE; + cap->card[0] = '\0'; + cap->bus_info[0] = '\0'; + retval = 0; + break; + } + + /*! + * V4l2 VIDIOC_G_FMT ioctl + */ + case VIDIOC_G_FMT:{ + struct v4l2_format *gf = arg; + retval = mxc_v4l2_g_fmt(cam, gf); + break; + } + + /*! + * V4l2 VIDIOC_S_FMT ioctl + */ + case VIDIOC_S_FMT:{ + struct v4l2_format *sf = arg; + retval = mxc_v4l2_s_fmt(cam, sf); + break; + } + + /*! + * V4l2 VIDIOC_REQBUFS ioctl + */ + case VIDIOC_REQBUFS:{ + struct v4l2_requestbuffers *req = arg; + if (req->count > FRAME_NUM) { + printk(KERN_ERR + "VIDIOC_REQBUFS: not enough buffer\n"); + req->count = FRAME_NUM; + } + + if ((req->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) || + (req->memory != V4L2_MEMORY_MMAP)) { + printk(KERN_ERR + "VIDIOC_REQBUFS: wrong buffer type\n"); + retval = -EINVAL; + break; + } + + mxc_streamoff(cam); + mxc_free_frame_buf(cam); + + retval = mxc_allocate_frame_buf(cam, req->count); + break; + } + + /*! + * V4l2 VIDIOC_QUERYBUF ioctl + */ + case VIDIOC_QUERYBUF:{ + struct v4l2_buffer *buf = arg; + int index = buf->index; + + if (buf->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) { + printk(KERN_ERR + "VIDIOC_QUERYBUFS: wrong buffer type\n"); + retval = -EINVAL; + break; + } + + memset(buf, 0, sizeof(buf)); + buf->index = index; + + down(&cam->param_lock); + retval = mxc_v4l2_buffer_status(cam, buf); + up(&cam->param_lock); + break; + } + + /*! + * V4l2 VIDIOC_QBUF ioctl + */ + case VIDIOC_QBUF:{ + struct v4l2_buffer *buf = arg; + int index = buf->index; + + spin_lock_irqsave(&cam->int_lock, lock_flags); + cam->frame[index].buffer.m.offset = buf->m.offset; + if ((cam->frame[index].buffer.flags & 0x7) == + V4L2_BUF_FLAG_MAPPED) { + cam->frame[index].buffer.flags |= + V4L2_BUF_FLAG_QUEUED; + if (cam->skip_frame > 0) { + list_add_tail(&cam->frame[index].queue, + &cam->working_q); + retval = + cam->enc_update_eba(cam-> + frame[index]. + buffer.m.offset, + &cam-> + ping_pong_csi); + cam->skip_frame = 0; + } else { + list_add_tail(&cam->frame[index].queue, + &cam->ready_q); + } + } else if (cam->frame[index].buffer. + flags & V4L2_BUF_FLAG_QUEUED) { + printk(KERN_ERR + "VIDIOC_QBUF: buffer already queued\n"); + } else if (cam->frame[index].buffer. + flags & V4L2_BUF_FLAG_DONE) { + printk(KERN_ERR + "VIDIOC_QBUF: overwrite done buffer.\n"); + cam->frame[index].buffer.flags &= + ~V4L2_BUF_FLAG_DONE; + cam->frame[index].buffer.flags |= + V4L2_BUF_FLAG_QUEUED; + } + + buf->flags = cam->frame[index].buffer.flags; + spin_unlock_irqrestore(&cam->int_lock, lock_flags); + break; + } + + /*! + * V4l2 VIDIOC_DQBUF ioctl + */ + case VIDIOC_DQBUF:{ + struct v4l2_buffer *buf = arg; + + retval = mxc_v4l_dqueue(cam, buf); + + break; + } + + /*! + * V4l2 VIDIOC_STREAMON ioctl + */ + case VIDIOC_STREAMON:{ + retval = mxc_streamon(cam); + break; + } + + /*! + * V4l2 VIDIOC_STREAMOFF ioctl + */ + case VIDIOC_STREAMOFF:{ + retval = mxc_streamoff(cam); + break; + } + + /*! + * V4l2 VIDIOC_G_CTRL ioctl + */ + case VIDIOC_G_CTRL:{ + retval = mxc_get_v42l_control(cam, arg); + break; + } + + /*! + * V4l2 VIDIOC_S_CTRL ioctl + */ + case VIDIOC_S_CTRL:{ + retval = mxc_set_v42l_control(cam, arg); + break; + } + + /*! + * V4l2 VIDIOC_CROPCAP ioctl + */ + case VIDIOC_CROPCAP:{ + struct v4l2_cropcap *cap = arg; + + if (cap->type != V4L2_BUF_TYPE_VIDEO_CAPTURE && + cap->type != V4L2_BUF_TYPE_VIDEO_OVERLAY) { + retval = -EINVAL; + break; + } + cap->bounds = cam->crop_bounds; + cap->defrect = cam->crop_defrect; + break; + } + + /*! + * V4l2 VIDIOC_G_CROP ioctl + */ + case VIDIOC_G_CROP:{ + struct v4l2_crop *crop = arg; + + if (crop->type != V4L2_BUF_TYPE_VIDEO_CAPTURE && + crop->type != V4L2_BUF_TYPE_VIDEO_OVERLAY) { + retval = -EINVAL; + break; + } + crop->c = cam->crop_current; + break; + } + + /*! + * V4l2 VIDIOC_S_CROP ioctl + */ + case VIDIOC_S_CROP:{ + struct v4l2_crop *crop = arg; + struct v4l2_rect *b = &cam->crop_bounds; + + if (crop->type != V4L2_BUF_TYPE_VIDEO_CAPTURE && + crop->type != V4L2_BUF_TYPE_VIDEO_OVERLAY) { + retval = -EINVAL; + break; + } + + crop->c.top = (crop->c.top < b->top) ? b->top + : crop->c.top; + if (crop->c.top > b->top + b->height) + crop->c.top = b->top + b->height - 1; + if (crop->c.height > b->top + b->height - crop->c.top) + crop->c.height = + b->top + b->height - crop->c.top; + + crop->c.left = (crop->c.left < b->left) ? b->left + : crop->c.left; + if (crop->c.left > b->left + b->width) + crop->c.left = b->left + b->width - 1; + if (crop->c.width > b->left - crop->c.left + b->width) + crop->c.width = + b->left - crop->c.left + b->width; + + crop->c.width -= crop->c.width % 8; + crop->c.left -= crop->c.left % 4; + cam->crop_current = crop->c; + + ipu_csi_set_window_size(cam->crop_current.width, + cam->crop_current.height); + ipu_csi_set_window_pos(cam->crop_current.left, + cam->crop_current.top); + break; + } + + /*! + * V4l2 VIDIOC_OVERLAY ioctl + */ + case VIDIOC_OVERLAY:{ + int *on = arg; + if (*on) { + cam->overlay_on = true; + cam->overlay_pid = current->pid; + retval = start_preview(cam); + } + if (!*on) { + retval = stop_preview(cam); + cam->overlay_on = false; + } + break; + } + + /*! + * V4l2 VIDIOC_G_FBUF ioctl + */ + case VIDIOC_G_FBUF:{ + struct v4l2_framebuffer *fb = arg; + *fb = cam->v4l2_fb; + fb->capability = V4L2_FBUF_CAP_EXTERNOVERLAY; + break; + } + + /*! + * V4l2 VIDIOC_S_FBUF ioctl + */ + case VIDIOC_S_FBUF:{ + struct v4l2_framebuffer *fb = arg; + cam->v4l2_fb = *fb; + break; + } + + case VIDIOC_G_PARM:{ + struct v4l2_streamparm *parm = arg; + if (parm->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) { + printk(KERN_ERR "VIDIOC_G_PARM invalid type\n"); + retval = -EINVAL; + break; + } + parm->parm.capture = cam->streamparm.parm.capture; + break; + } + case VIDIOC_S_PARM:{ + struct v4l2_streamparm *parm = arg; + retval = mxc_v4l2_s_param(cam, parm); + break; + } + + /* linux v4l2 bug, kernel c0485619 user c0405619 */ + case VIDIOC_ENUMSTD:{ + struct v4l2_standard *e = arg; + *e = cam->standard; + printk(KERN_ERR "VIDIOC_ENUMSTD call\n"); + retval = 0; + break; + } + + case VIDIOC_G_STD:{ + v4l2_std_id *e = arg; + *e = cam->standard.id; + break; + } + + case VIDIOC_S_STD:{ + break; + } + + case VIDIOC_ENUMOUTPUT: + { + struct v4l2_output *output = arg; + + if (output->index >= MXC_V4L2_CAPTURE_NUM_OUTPUTS) { + retval = -EINVAL; + break; + } + + *output = mxc_capture_outputs[output->index]; + + break; + } + case VIDIOC_G_OUTPUT: + { + int *p_output_num = arg; + + *p_output_num = cam->output; + break; + } + case VIDIOC_S_OUTPUT: + { + int *p_output_num = arg; + + if (*p_output_num >= MXC_V4L2_CAPTURE_NUM_OUTPUTS) { + retval = -EINVAL; + break; + } + + cam->output = *p_output_num; + break; + } + + case VIDIOC_ENUM_FMT: + case VIDIOC_TRY_FMT: + case VIDIOC_QUERYCTRL: + case VIDIOC_ENUMINPUT: + case VIDIOC_G_INPUT: + case VIDIOC_S_INPUT: + case VIDIOC_G_TUNER: + case VIDIOC_S_TUNER: + case VIDIOC_G_FREQUENCY: + case VIDIOC_S_FREQUENCY: + default: + retval = -EINVAL; + break; + } + + up(&cam->busy_lock); + return retval; +} + +/* + * V4L interface - ioctl function + * + * @return None + */ +static int +mxc_v4l_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return video_usercopy(inode, file, cmd, arg, mxc_v4l_do_ioctl); +} + +/*! + * V4L interface - mmap function + * + * @param file structure file * + * + * @param vma structure vm_area_struct * + * + * @return status 0 Success, EINTR busy lock error, ENOBUFS remap_page error + */ +static int mxc_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct video_device *dev = video_devdata(file); + unsigned long size; + int res = 0; + cam_data *cam = dev->priv; + + pr_debug("pgoff=0x%lx, start=0x%lx, end=0x%lx\n", + vma->vm_pgoff, vma->vm_start, vma->vm_end); + + /* make this _really_ smp-safe */ + if (down_interruptible(&cam->busy_lock)) + return -EINTR; + + size = vma->vm_end - vma->vm_start; + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + + if (remap_pfn_range(vma, vma->vm_start, + vma->vm_pgoff, size, vma->vm_page_prot)) { + printk(KERN_ERR "mxc_mmap: remap_pfn_range failed\n"); + res = -ENOBUFS; + goto mxc_mmap_exit; + } + + vma->vm_flags &= ~VM_IO; /* using shared anonymous pages */ + + mxc_mmap_exit: + up(&cam->busy_lock); + return res; +} + +/*! + * V4L interface - poll function + * + * @param file structure file * + * + * @param wait structure poll_table * + * + * @return status POLLIN | POLLRDNORM + */ +static unsigned int mxc_poll(struct file *file, poll_table * wait) +{ + struct video_device *dev = video_devdata(file); + cam_data *cam = dev->priv; + wait_queue_head_t *queue = NULL; + int res = POLLIN | POLLRDNORM; + + if (down_interruptible(&cam->busy_lock)) + return -EINTR; + + queue = &cam->enc_queue; + poll_wait(file, queue, wait); + + up(&cam->busy_lock); + return res; +} + +static struct +file_operations mxc_v4l_fops = { + .owner = THIS_MODULE, + .open = mxc_v4l_open, + .release = mxc_v4l_close, + .read = mxc_v4l_read, + .ioctl = mxc_v4l_ioctl, + .mmap = mxc_mmap, + .poll = mxc_poll, +}; + +static struct video_device mxc_v4l_template = { + .owner = THIS_MODULE, + .name = "Mxc Camera", + .type = 0, + .type2 = VID_TYPE_CAPTURE, + .hardware = 0, + .fops = &mxc_v4l_fops, + .release = video_device_release, +}; + +static void camera_platform_release(struct device *device) +{ +} + +/*! Device Definition for Mt9v111 devices */ +static struct platform_device mxc_v4l2_devices = { + .name = "mxc_v4l2", + .dev = { + .release = camera_platform_release, + }, + .id = 0, +}; + +extern struct camera_sensor camera_sensor_if; + +/*! +* Camera V4l2 callback function. +* +* @param mask u32 +* +* @param dev void device structure +* +* @return status +*/ +static void camera_callback(u32 mask, void *dev) +{ + struct mxc_v4l_frame *done_frame; + struct mxc_v4l_frame *ready_frame; + + cam_data *cam = (cam_data *) dev; + if (cam == NULL) + return; + + if (list_empty(&cam->working_q)) { + printk(KERN_ERR "camera_callback: working queue empty\n"); + return; + } + + done_frame = + list_entry(cam->working_q.next, struct mxc_v4l_frame, queue); + if (done_frame->buffer.flags & V4L2_BUF_FLAG_QUEUED) { + done_frame->buffer.flags |= V4L2_BUF_FLAG_DONE; + done_frame->buffer.flags &= ~V4L2_BUF_FLAG_QUEUED; + + if (list_empty(&cam->ready_q)) { + cam->skip_frame++; + } else { + ready_frame = + list_entry(cam->ready_q.next, struct mxc_v4l_frame, + queue); + list_del(cam->ready_q.next); + list_add_tail(&ready_frame->queue, &cam->working_q); + cam->enc_update_eba(ready_frame->buffer.m.offset, + &cam->ping_pong_csi); + } + + /* Added to the done queue */ + list_del(cam->working_q.next); + list_add_tail(&done_frame->queue, &cam->done_q); + + /* Wake up the queue */ + cam->enc_counter++; + wake_up_interruptible(&cam->enc_queue); + } else { + printk(KERN_ERR "camera_callback :buffer not queued\n"); + } +} + +/*! + * initialize cam_data structure + * + * @param cam structure cam_data * + * + * @return status 0 Success + */ +static void init_camera_struct(cam_data * cam) +{ + /* Default everything to 0 */ + memset(cam, 0, sizeof(cam_data)); + + init_MUTEX(&cam->param_lock); + init_MUTEX(&cam->busy_lock); + + cam->video_dev = video_device_alloc(); + if (cam->video_dev == NULL) + return; + + *(cam->video_dev) = mxc_v4l_template; + + video_set_drvdata(cam->video_dev, cam); + dev_set_drvdata(&mxc_v4l2_devices.dev, (void *)cam); + cam->video_dev->minor = -1; + + init_waitqueue_head(&cam->enc_queue); + init_waitqueue_head(&cam->still_queue); + + /* setup cropping */ + cam->crop_bounds.left = 0; + cam->crop_bounds.width = 640; + cam->crop_bounds.top = 0; + cam->crop_bounds.height = 480; + cam->crop_current = cam->crop_defrect = cam->crop_bounds; + ipu_csi_set_window_size(cam->crop_current.width, + cam->crop_current.height); + ipu_csi_set_window_pos(cam->crop_current.left, cam->crop_current.top); + cam->streamparm.parm.capture.capturemode = 0; + + cam->standard.index = 0; + cam->standard.id = V4L2_STD_UNKNOWN; + cam->standard.frameperiod.denominator = 30; + cam->standard.frameperiod.numerator = 1; + cam->standard.framelines = 480; + cam->streamparm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + cam->streamparm.parm.capture.timeperframe = cam->standard.frameperiod; + cam->streamparm.parm.capture.capability = V4L2_CAP_TIMEPERFRAME; + cam->overlay_on = false; + cam->capture_on = false; + cam->skip_frame = 0; + cam->v4l2_fb.flags = V4L2_FBUF_FLAG_OVERLAY; + + cam->v2f.fmt.pix.sizeimage = 352 * 288 * 3 / 2; + cam->v2f.fmt.pix.bytesperline = 288 * 3 / 2; + cam->v2f.fmt.pix.width = 288; + cam->v2f.fmt.pix.height = 352; + cam->v2f.fmt.pix.pixelformat = V4L2_PIX_FMT_YUV420; + cam->win.w.width = 160; + cam->win.w.height = 160; + cam->win.w.left = 0; + cam->win.w.top = 0; + + cam->cam_sensor = &camera_sensor_if; + cam->enc_callback = camera_callback; + init_waitqueue_head(&cam->power_queue); + cam->int_lock = SPIN_LOCK_UNLOCKED; + spin_lock_init(&cam->int_lock); +} + +extern void gpio_sensor_active(void); +extern void gpio_sensor_inactive(void); + +/*! + * camera_power function + * Turn Sensor power On/Off + * + * @param cameraOn true to turn camera on, otherwise shut down + * + * @return status + */ +static u8 camera_power(bool cameraOn) +{ + if (cameraOn == true) { + gpio_sensor_active(); + ipu_csi_enable_mclk(csi_mclk_flag_backup, true, true); + } else { + csi_mclk_flag_backup = ipu_csi_read_mclk_flag(); + ipu_csi_enable_mclk(csi_mclk_flag_backup, false, false); + gpio_sensor_inactive(); + } + return 0; +} + +/*! + * This function is called to put the sensor in a low power state. Refer to the + * document driver-model/driver.txt in the kernel source tree for more + * information. + * + * @param pdev the device structure used to give information on which I2C + * to suspend + * @param state the power state the device is entering + * + * @return The function returns 0 on success and -1 on failure. + */ +static int mxc_v4l2_suspend(struct platform_device *pdev, pm_message_t state) +{ + cam_data *cam = platform_get_drvdata(pdev); + + if (cam == NULL) { + return -1; + } + + cam->low_power = true; + + if (cam->overlay_on == true) + stop_preview(cam); + if ((cam->capture_on == true) && cam->enc_disable) { + cam->enc_disable(cam); + } + camera_power(false); + + return 0; +} + +/*! + * This function is called to bring the sensor back from a low power state.Refer + * to the document driver-model/driver.txt in the kernel source tree for more + * information. + * + * @param pdev the device structure + * + * @return The function returns 0 on success and -1 on failure + */ +static int mxc_v4l2_resume(struct platform_device *pdev) +{ + cam_data *cam = platform_get_drvdata(pdev); + + if (cam == NULL) { + return -1; + } + + cam->low_power = false; + wake_up_interruptible(&cam->power_queue); + + if (cam->overlay_on == true) + start_preview(cam); + if (cam->capture_on == true) + mxc_streamon(cam); + camera_power(true); + + return 0; +} + +/*! + * This structure contains pointers to the power management callback functions. + */ +static struct platform_driver mxc_v4l2_driver = { + .driver = { + .name = "mxc_v4l2", + }, + .probe = NULL, + .remove = NULL, + .suspend = mxc_v4l2_suspend, + .resume = mxc_v4l2_resume, + .shutdown = NULL, +}; + +/*! + * Entry point for the V4L2 + * + * @return Error code indicating success or failure + */ +static __init int camera_init(void) +{ + u8 err = 0; + + /* Register the device driver structure. */ + err = platform_driver_register(&mxc_v4l2_driver); + if (err != 0) { + printk("camera_init: platform_driver_register failed.\n"); + return err; + } + + if ((g_cam = kmalloc(sizeof(cam_data), GFP_KERNEL)) == NULL) { + printk(KERN_ERR "failed to mxc_v4l_register_camera\n"); + return -1; + } + + init_camera_struct(g_cam); + + /* Register the I2C device */ + err = platform_device_register(&mxc_v4l2_devices); + if (err != 0) { + printk(KERN_ERR + "camera_init: platform_device_register failed.\n"); + video_device_release(g_cam->video_dev); + kfree(g_cam); + g_cam = NULL; + } + + /* register v4l device */ + if (video_register_device(g_cam->video_dev, VFL_TYPE_GRABBER, video_nr) + == -1) { + platform_device_unregister(&mxc_v4l2_devices); + platform_driver_unregister(&mxc_v4l2_driver); + video_device_release(g_cam->video_dev); + kfree(g_cam); + g_cam = NULL; + printk(KERN_ERR "video_register_device failed\n"); + return -1; + } + + return err; +} + +/*! + * Exit and cleanup for the V4L2 + * + */ +static void __exit camera_exit(void) +{ + pr_info("unregistering video\n"); + video_unregister_device(g_cam->video_dev); + + platform_driver_unregister(&mxc_v4l2_driver); + platform_device_unregister(&mxc_v4l2_devices); + + if (g_cam->open_count) { + printk(KERN_ERR "camera open -- setting ops to NULL\n"); + } else { + pr_info("freeing camera\n"); + mxc_free_frame_buf(g_cam); + kfree(g_cam); + g_cam = NULL; + } +} + +module_init(camera_init); +module_exit(camera_exit); + +module_param(video_nr, int, 0444); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("V4L2 capture driver for Mxc based cameras"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("video"); diff --git a/drivers/media/video/mxc/capture/mxc_v4l2_capture.h b/drivers/media/video/mxc/capture/mxc_v4l2_capture.h new file mode 100644 index 000000000000..b76908a62c71 --- /dev/null +++ b/drivers/media/video/mxc/capture/mxc_v4l2_capture.h @@ -0,0 +1,177 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @defgroup MXC_V4L2_CAPTURE MXC V4L2 Video Capture Driver + */ +/*! + * @file mxc_v4l2_capture.h + * + * @brief mxc V4L2 capture device API Header file + * + * It include all the defines for frame operations, also three structure defines + * use case ops structure, common v4l2 driver structure and frame structure. + * + * @ingroup MXC_V4L2_CAPTURE + */ +#ifndef __MXC_V4L2_CAPTURE_H__ +#define __MXC_V4L2_CAPTURE_H__ + +#include <asm/uaccess.h> +#include <linux/list.h> +#include <linux/smp_lock.h> + +#include <media/v4l2-dev.h> +#include <asm/arch/ipu.h> +#include <asm/arch/mxc_v4l2.h> + +#define FRAME_NUM 3 + +/*! + * v4l2 frame structure. + */ +struct mxc_v4l_frame { + u32 paddress; + void *vaddress; + int count; + int width; + int height; + + struct v4l2_buffer buffer; + struct list_head queue; + int index; +}; + +typedef struct { + u8 clk_mode; + u8 ext_vsync; + u8 Vsync_pol; + u8 Hsync_pol; + u8 pixclk_pol; + u8 data_pol; + u8 data_width; + u16 width; + u16 height; + u32 pixel_fmt; + u32 mclk; +} sensor_interface; + +/* Sensor control function */ +struct camera_sensor { + void (*set_color) (int bright, int saturation, int red, int green, + int blue); + void (*get_color) (int *bright, int *saturation, int *red, int *green, + int *blue); + void (*set_ae_mode) (int ae_mode); + void (*get_ae_mode) (int *ae_mode); + sensor_interface *(*config) (int *frame_rate, int high_quality); + sensor_interface *(*reset) (void); +}; + +/*! + * common v4l2 driver structure. + */ +typedef struct _cam_data { + struct video_device *video_dev; + + /* semaphore guard against SMP multithreading */ + struct semaphore busy_lock; + + int open_count; + + /* params lock for this camera */ + struct semaphore param_lock; + + /* Encorder */ + struct list_head ready_q; + struct list_head done_q; + struct list_head working_q; + int ping_pong_csi; + spinlock_t int_lock; + struct mxc_v4l_frame frame[FRAME_NUM]; + int skip_frame; + wait_queue_head_t enc_queue; + int enc_counter; + dma_addr_t rot_enc_bufs[2]; + void *rot_enc_bufs_vaddr[2]; + int rot_enc_buf_size[2]; + enum v4l2_buf_type type; + + /* still image capture */ + wait_queue_head_t still_queue; + int still_counter; + dma_addr_t still_buf; + void *still_buf_vaddr; + + /* overlay */ + struct v4l2_window win; + struct v4l2_framebuffer v4l2_fb; + dma_addr_t vf_bufs[2]; + void *vf_bufs_vaddr[2]; + int vf_bufs_size[2]; + dma_addr_t rot_vf_bufs[2]; + void *rot_vf_bufs_vaddr[2]; + int rot_vf_buf_size[2]; + bool overlay_active; + int output; + struct fb_info *overlay_fb; + + /* v4l2 format */ + struct v4l2_format v2f; + int rotation; + struct v4l2_mxc_offset offset; + + /* V4l2 control bit */ + int bright; + int hue; + int contrast; + int saturation; + int red; + int green; + int blue; + int ae_mode; + + /* standart */ + struct v4l2_streamparm streamparm; + struct v4l2_standard standard; + + /* crop */ + struct v4l2_rect crop_bounds; + struct v4l2_rect crop_defrect; + struct v4l2_rect crop_current; + + int (*enc_update_eba) (dma_addr_t eba, int *bufferNum); + int (*enc_enable) (void *private); + int (*enc_disable) (void *private); + void (*enc_callback) (u32 mask, void *dev); + int (*vf_start_adc) (void *private); + int (*vf_stop_adc) (void *private); + int (*vf_start_sdc) (void *private); + int (*vf_stop_sdc) (void *private); + int (*csi_start) (void *private); + int (*csi_stop) (void *private); + + /* misc status flag */ + bool overlay_on; + bool capture_on; + int overlay_pid; + int capture_pid; + bool low_power; + wait_queue_head_t power_queue; + + /* camera sensor interface */ + struct camera_sensor *cam_sensor; +} cam_data; + +void set_mclk_rate(uint32_t * p_mclk_freq); +#endif /* __MXC_V4L2_CAPTURE_H__ */ diff --git a/drivers/media/video/mxc/capture/s5k3aaex.c b/drivers/media/video/mxc/capture/s5k3aaex.c new file mode 100644 index 000000000000..455d55e4aa55 --- /dev/null +++ b/drivers/media/video/mxc/capture/s5k3aaex.c @@ -0,0 +1,572 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file s5k3aaex.c + * + * @brief s5k3aaex camera driver functions + * + * @ingroup Camera + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/ctype.h> +#include <linux/types.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/i2c.h> +#include "asm/arch/mxc_i2c.h" +#include "s5k3aaex.h" +#include "mxc_v4l2_capture.h" + +static s5k3aaex_conf s5k3aaex_device; +static sensor_interface *interface_param = NULL; + +static int s5k3aaex_attach(struct i2c_adapter *adapter); +static int s5k3aaex_detach(struct i2c_client *client); + +static struct i2c_driver s5k3aaex_i2c_driver = { + .owner = THIS_MODULE, + .name = "S5K3AAEX Client", + .flags = I2C_DF_NOTIFY, + .attach_adapter = s5k3aaex_attach, + .detach_client = s5k3aaex_detach, +}; + +static struct i2c_client s5k3aaex_i2c_client = { + .name = "s5k3aaex I2C dev", + .id = 1, + .addr = S5K3AAEX_I2C_ADDRESS, + .driver = &s5k3aaex_i2c_driver, +}; + +static s5k3aaex_image_format format[2] = { + { + .index = 0, + .imageFormat = S5K3AAEX_OutputResolution_SXGA, + .width = S5K3AAEX_WINWIDTH, + .height = S5K3AAEX_WINHEIGHT, + }, + { + .index = 1, + .imageFormat = S5K3AAEX_OutputResolution_VGA, + .width = 640, + .height = 512, + }, +}; + +extern void gpio_sensor_setup(void); +extern void gpio_sensor_reset(bool flag); +extern void gpio_sensor_suspend(bool flag); + +/* + * Function definitions + */ +static int s5k3aaex_i2c_client_xfer(unsigned int addr, char *reg, + int reg_len, char *buf, int num, + int tran_flag) +{ + struct i2c_msg msg[2]; + int ret; + + msg[0].addr = addr; + msg[0].len = reg_len; + msg[0].buf = reg; + msg[0].flags = tran_flag; + msg[0].flags &= ~I2C_M_RD; + + msg[1].addr = addr; + msg[1].len = num; + msg[1].buf = buf; + msg[1].flags = tran_flag; + + if (tran_flag & MXC_I2C_FLAG_READ) { + msg[1].flags |= I2C_M_RD; + } else { + msg[1].flags &= ~I2C_M_RD; + } + + ret = i2c_transfer(s5k3aaex_i2c_client.adapter, msg, 2); + if (ret >= 0) + return 0; + return ret; +} + +static int s5k3aaex_read_reg(u8 * reg, u8 * val) +{ + msleep(100); + return s5k3aaex_i2c_client_xfer(S5K3AAEX_I2C_ADDRESS, reg, 1, val, 1, + MXC_I2C_FLAG_READ); +} + +static int s5k3aaex_write_reg(u8 reg, u8 val) +{ + u8 temp1, temp2; + temp1 = reg; + temp2 = val; + msleep(100); + return s5k3aaex_i2c_client_xfer(S5K3AAEX_I2C_ADDRESS, &temp1, 1, &temp2, + 1, 0); +} + +static u8 s5k3aaex_sensor_downscale(bool downscale) +{ + u8 error = 0; + u8 reg; + u8 data; + + if (downscale == true) { + pr_info("VGA\n"); + reg = 0xEC; + data = 0; + s5k3aaex_write_reg(reg, data); + + reg = 0x2; + data = 0x30; + s5k3aaex_write_reg(reg, data); + } else { + pr_info("SXGA\n"); + reg = 0xEC; + data = 0; + s5k3aaex_write_reg(reg, data); + + reg = 0x2; + data = 0; + s5k3aaex_write_reg(reg, data); + } + + return error; +} + +/*! + * Initialize s5k3aaex_sensor_lib + * Libarary for Sensor configuration through I2C + * + * @param page0 page0 Registers + * @param page2 page2 Registers + * + * @return status + */ +static u8 s5k3aaex_sensor_lib(s5k3aaex_page0 * page0, s5k3aaex_page2 * page2) +{ + u8 error = 0; + u8 reg; + u8 data; + + // changed to ARM command register map page 0 + reg = 0xEC; + data = page0->addressSelect; + s5k3aaex_write_reg(reg, data); + // set the main clock + reg = 0x72; + data = page0->mainClock; + s5k3aaex_write_reg(reg, data); + + // changed to CIS register map page 02 + reg = 0xEC; + data = page2->addressSelect; + s5k3aaex_write_reg(reg, data); + + // write the Hblank width + reg = 0x1e; + data = (u8) (page2->hblank & 0xFF); + s5k3aaex_write_reg(reg, data); + reg = 0x1d; + data = (u8) ((page2->hblank >> 8) & 0xFF); + s5k3aaex_write_reg(reg, data); + + // write the Vblank width + reg = 0x18; + data = (u8) (page2->vblank & 0xFF); + s5k3aaex_write_reg(reg, data); + reg = 0x17; + data = (u8) ((page2->vblank >> 8) & 0xFF); + s5k3aaex_write_reg(reg, data); + + // write the WRP + reg = 0x5; + data = (u8) (page2->wrp & 0xFF); + s5k3aaex_write_reg(reg, data); + reg = 0x4; + data = (u8) ((page2->wrp >> 8) & 0xFF); + s5k3aaex_write_reg(reg, data); + + // write the WCP + reg = 0x7; + data = (u8) (page2->wcp & 0xFF); + s5k3aaex_write_reg(reg, data); + reg = 0x6; + data = (u8) ((page2->wcp >> 8) & 0xFF); + s5k3aaex_write_reg(reg, data); + + // write DEFCOR_MOV_ADC 8 bit + reg = 0x2; + data = 0x8; + s5k3aaex_write_reg(reg, data); + + // changed to ARM command register map page 1 + reg = 0xEC; + data = 1; + s5k3aaex_write_reg(reg, data); + + // write size to itu r 601 + reg = 0x6a; + data = 0x5; + s5k3aaex_write_reg(reg, data); + + return error; +} + +/*! + * s5k3aaex sensor interface Initialization + * @param param sensor_interface * + * @param width u32 + * @param height u32 + * @return None + */ +static void s5k3aaex_interface(sensor_interface * param, u32 width, u32 height) +{ + param->clk_mode = 0x0; //gated + param->pixclk_pol = 0x0; + param->data_width = 0x1; + param->data_pol = 0x0; + param->ext_vsync = 0x0; + param->Vsync_pol = 0x0; + param->Hsync_pol = 0x1; + param->width = width - 1; + param->height = ((height == 512) ? 480 : height) - 1; + param->pixel_fmt = IPU_PIX_FMT_UYVY; +} + +static int s5k3aaex_rate_cal(int *frame_rate, int mclk) +{ + int num_clock_per_row; + int max_rate = 0; + int index = 0; + u16 width; + u16 height; + + do { + s5k3aaex_device.page0->imageFormat = format[index].imageFormat; + height = format[index].height; + width = format[index++].width; + s5k3aaex_device.page2->hblank = S5K3AAEX_HORZBLANK_DEFAULT; + s5k3aaex_device.page2->vblank = S5K3AAEX_VERTBLANK_DEFAULT; + + num_clock_per_row = (width + s5k3aaex_device.page2->hblank) * 2; + max_rate = mclk / (num_clock_per_row * + (height + s5k3aaex_device.page2->vblank)); + } while ((index < 2) && (max_rate < *frame_rate)); + + s5k3aaex_interface(interface_param, width, height); + + if (max_rate < *frame_rate) + *frame_rate = max_rate; + if (*frame_rate == 0) + *frame_rate = max_rate; + + s5k3aaex_device.page2->vblank = mclk / + (*frame_rate * num_clock_per_row) - height; + + return index; +} + +/*! + * s5k3aaex sensor configuration + * + * @param frame_rate int * + * @param high_quality int + * @return sensor_interface * + */ +static sensor_interface *s5k3aaex_config(int *frame_rate, int high_quality) +{ + int index; + + index = s5k3aaex_rate_cal(frame_rate, interface_param->mclk); + + if (index == 1) { + s5k3aaex_sensor_downscale(false); + } else { + s5k3aaex_sensor_downscale(true); + } + + s5k3aaex_device.page0->mainClock = interface_param->mclk / 1048576 * 5; + s5k3aaex_sensor_lib(s5k3aaex_device.page0, s5k3aaex_device.page2); + + return interface_param; +} + +/*! + * s5k3aaex sensor set color configuration + * + * @param bright int + * @param saturation int + * @param red int + * @param green int + * @param blue int + * @return None + */ +static void s5k3aaex_set_color(int bright, int saturation, int red, int green, + int blue) +{ + u8 reg; + u8 data; + + reg = 0xEC; + data = 0; + s5k3aaex_write_reg(reg, data); + + // set Brightness/Color Level balance + reg = 0x76; + data = (u8) bright; + s5k3aaex_write_reg(reg, data); + reg = 0x77; + data = (u8) saturation; + s5k3aaex_write_reg(reg, data); + + reg = 0xEC; + data = 1; + s5k3aaex_write_reg(reg, data); + + // set Red + reg = 0x10; + data = (u8) red; + s5k3aaex_write_reg(reg, data); + // set Blue + reg = 0x18; + data = (u8) blue; + s5k3aaex_write_reg(reg, data); +} + +/*! + * s5k3aaex sensor get color configuration + * + * @param bright int * + * @param saturation int * + * @param red int * + * @param green int * + * @param blue int * + * @return None + */ +static void s5k3aaex_get_color(int *bright, int *saturation, int *red, + int *green, int *blue) +{ + u8 reg; + u8 data; + u8 *pdata; + + reg = 0xEC; + data = 0; + s5k3aaex_write_reg(reg, data); + + // get Brightness/Color Level balance + reg = 0x76; + pdata = (u8 *) bright; + s5k3aaex_read_reg(®, pdata); + + reg = 0x77; + pdata = (u8 *) saturation; + s5k3aaex_read_reg(®, pdata); + + reg = 0xEC; + data = 1; + s5k3aaex_write_reg(reg, data); + + // get Red + reg = 0x10; + pdata = (u8 *) red; + s5k3aaex_read_reg(®, pdata); + + // get Blue + reg = 0x18; + pdata = (u8 *) blue; + s5k3aaex_read_reg(®, pdata); +} + +/*! + * s5k3aaex Reset function + * + * @return None + */ +static sensor_interface *s5k3aaex_reset(void) +{ + set_mclk_rate(&interface_param->mclk); + + /* Reset for 10 cycle */ + gpio_sensor_reset(true); + msleep(10); + gpio_sensor_reset(false); + msleep(30); + + s5k3aaex_interface(interface_param, format[0].width, format[0].height); + return interface_param; +} + +struct camera_sensor camera_sensor_if = { + set_color:s5k3aaex_set_color, + get_color:s5k3aaex_get_color, + config:s5k3aaex_config, + reset:s5k3aaex_reset, +}; + +#if 0 +static void s5k3aaex_test_pattern(bool flag) +{ + u8 reg; + u8 data; + + // changed to ARM command register map page 0 + reg = 0xEC; + data = 0; + s5k3aaex_write_reg(reg, data); + + if (flag == true) { + reg = 0xb; + data = 0x1; + s5k3aaex_write_reg(reg, data); + } else { + reg = 0xb; + data = 0x0; + s5k3aaex_write_reg(reg, data); + } +} +#endif + +/*! + * s5k3aaex I2C attach function + * + * @param adapter struct i2c_adapter * + * @return Error code indicating success or failure + */ +static int s5k3aaex_attach(struct i2c_adapter *adapter) +{ + if (strcmp(adapter->name, MXC_ADAPTER_NAME) != 0) { + printk(KERN_ERR "s5k3aaex_attach: %s\n", adapter->name); + return -1; + } + + s5k3aaex_i2c_client.adapter = adapter; + if (i2c_attach_client(&s5k3aaex_i2c_client)) { + s5k3aaex_i2c_client.adapter = NULL; + printk(KERN_ERR "s5k3aaex_attach: i2c_attach_client failed\n"); + return -1; + } + + interface_param = (sensor_interface *) + kmalloc(sizeof(sensor_interface), GFP_KERNEL); + if (!interface_param) { + printk(KERN_ERR "s5k3aaex_attach: kmalloc failed \n"); + return -1; + } + + gpio_sensor_setup(); + + gpio_sensor_suspend(false); + + interface_param->mclk = 0x2000000; + + return 0; +} + +/*! + * s5k3aaex I2C detach function + * + * @param client struct i2c_client * + * @return Error code indicating success or failure + */ +static int s5k3aaex_detach(struct i2c_client *client) +{ + int err; + + if (!s5k3aaex_i2c_client.adapter) + return -1; + + err = i2c_detach_client(&s5k3aaex_i2c_client); + s5k3aaex_i2c_client.adapter = NULL; + + if (interface_param) + kfree(interface_param); + interface_param = NULL; + + return err; +} + +/*! + * s5k3aaex init function + * + * @return Error code indicating success or failure + */ +static __init int s5k3aaex_init(void) +{ + u8 err = 0; + + s5k3aaex_device.page0 = (s5k3aaex_page0 *) + kmalloc(sizeof(s5k3aaex_page0), GFP_KERNEL); + + if (!s5k3aaex_device.page0) + return -1; + memset(s5k3aaex_device.page0, 0, sizeof(s5k3aaex_page0)); + s5k3aaex_device.page0->addressSelect = 0; + s5k3aaex_device.page0->functionOnOff = 0x48; + + s5k3aaex_device.page2 = (s5k3aaex_page2 *) + kmalloc(sizeof(s5k3aaex_page2), GFP_KERNEL); + if (!s5k3aaex_device.page2) { + kfree(s5k3aaex_device.page0); + s5k3aaex_device.page0 = NULL; + return -1; + } + memset(s5k3aaex_device.page2, 0, sizeof(s5k3aaex_page2)); + s5k3aaex_device.page2->addressSelect = 2; + s5k3aaex_device.page2->wrp = 14; + s5k3aaex_device.page2->wcp = 14; + + s5k3aaex_device.page2->hblank = S5K3AAEX_HORZBLANK_DEFAULT; + s5k3aaex_device.page2->vblank = S5K3AAEX_VERTBLANK_DEFAULT; + + err = i2c_add_driver(&s5k3aaex_i2c_driver); + + return err; +} + +/*! + * s5k3aaex cleanup function + * + * @return Error code indicating success or failure + */ +static void __exit s5k3aaex_clean(void) +{ + i2c_del_driver(&s5k3aaex_i2c_driver); + + if (s5k3aaex_device.page0) { + kfree(s5k3aaex_device.page0); + s5k3aaex_device.page0 = NULL; + } + + if (s5k3aaex_device.page2) { + kfree(s5k3aaex_device.page2); + s5k3aaex_device.page2 = NULL; + } +} + +module_init(s5k3aaex_init); +module_exit(s5k3aaex_clean); + +/* Exported symbols for modules. */ +EXPORT_SYMBOL(camera_sensor_if); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("s5k3aaex Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/mxc/capture/s5k3aaex.h b/drivers/media/video/mxc/capture/s5k3aaex.h new file mode 100644 index 000000000000..61bcb15110b5 --- /dev/null +++ b/drivers/media/video/mxc/capture/s5k3aaex.h @@ -0,0 +1,88 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file s5k3aaex.h + * + * @brief S5K3AAEX Camera Header file + * + * It include all the defines for bitmaps operations, also two main structure + * one for IFP interface structure, other for sensor core registers. + * + * @ingroup Camera + */ + +#ifndef __S5K3AAEX_H__ +#define __S5K3AAEX_H__ + +/*! I2C Slave Address */ +#define S5K3AAEX_I2C_ADDRESS 0x2d + +enum { + S5K3AAEX_OutputResolution_SXGA = 0, + S5K3AAEX_OutputResolution_VGA = 0x1c, + S5K3AAEX_OutputResolution_QVGA = 0x14, + S5K3AAEX_OutputResolution_QQVGA = 0x16, + S5K3AAEX_OutputResolution_CIF = 0x10, + S5K3AAEX_OutputResolution_QCIF = 0x12, +}; + +enum { + S5K3AAEX_WINWIDTH = 0x500, + S5K3AAEX_WINHEIGHT = 0x400, + S5K3AAEX_ROWSTART = 14, + S5K3AAEX_COLSTART = 14, + + S5K3AAEX_HORZBLANK_DEFAULT = 142, + S5K3AAEX_VERTBLANK_DEFAULT = 101, +}; + +typedef struct { + u8 index; + u8 imageFormat; + u16 width; + u16 height; +} s5k3aaex_image_format; + +/*! + * s5k3aaex ARM command Register page 0 structure. + */ +typedef struct { + u8 addressSelect; + u8 imageFormat; + u8 functionOnOff; + u8 mainClock; +} s5k3aaex_page0; + +/*! + * s5k3aaex IFP Register structure. + */ +typedef struct { + u8 addressSelect; + u16 wrp; + u16 wcp; + u16 wrd; + u16 wcw; + u16 vblank; + u16 hblank; +} s5k3aaex_page2; + +/*! + * s5k3aaex Config structure + */ +typedef struct { + s5k3aaex_page0 *page0; + s5k3aaex_page2 *page2; +} s5k3aaex_conf; + +#endif /* __S5K3AAEX_H__ */ diff --git a/drivers/media/video/mxc/capture/sensor_clock.c b/drivers/media/video/mxc/capture/sensor_clock.c new file mode 100644 index 000000000000..e2be4f44ad4f --- /dev/null +++ b/drivers/media/video/mxc/capture/sensor_clock.c @@ -0,0 +1,56 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file sensor_clock.c + * + * @brief camera clock function + * + * @ingroup Camera + */ +#include <linux/init.h> +#include <linux/ctype.h> +#include <linux/types.h> +#include <linux/device.h> +#include <linux/clk.h> + +/* + * set_mclk_rate + * + * @param p_mclk_freq mclk frequence + * + */ +void set_mclk_rate(uint32_t * p_mclk_freq) +{ + struct clk *clk; + int i; + uint32_t freq = 0; + uint32_t step = *p_mclk_freq / 8; + + clk = clk_get(NULL, "csi_clk"); + + for (i = 0; i <= 8; i++) { + freq = clk_round_rate(clk, *p_mclk_freq - (i * step)); + if (freq <= *p_mclk_freq) + break; + } + clk_set_rate(clk, freq); + + *p_mclk_freq = freq; + + clk_put(clk); + pr_debug("mclk frequency = %d\n", *p_mclk_freq); +} + +/* Exported symbols for modules. */ +EXPORT_SYMBOL(set_mclk_rate); diff --git a/drivers/media/video/mxc/opl/Makefile b/drivers/media/video/mxc/opl/Makefile new file mode 100644 index 000000000000..092a62c5ac4a --- /dev/null +++ b/drivers/media/video/mxc/opl/Makefile @@ -0,0 +1,5 @@ +opl-objs := opl_mod.o rotate90_u16.o rotate270_u16.o \ + rotate90_u16_qcif.o rotate270_u16_qcif.o \ + vmirror_u16.o hmirror_rotate180_u16.o + +obj-$(CONFIG_VIDEO_MXC_OPL) += opl.o diff --git a/drivers/media/video/mxc/opl/hmirror_rotate180_u16.c b/drivers/media/video/mxc/opl/hmirror_rotate180_u16.c new file mode 100644 index 000000000000..3119a128c1f2 --- /dev/null +++ b/drivers/media/video/mxc/opl/hmirror_rotate180_u16.c @@ -0,0 +1,259 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include "opl.h" + +static inline u32 rot_left_u16(u16 x, unsigned int n) +{ + return (x << n) | (x >> (16 - n)); +} + +static inline u32 rot_left_u32(u32 x, unsigned int n) +{ + return (x << n) | (x >> (32 - n)); +} + +static inline u32 byte_swap_u32(u32 x) +{ + u32 t1, t2, t3; + + t1 = x ^ ((x << 16) | x >> 16); + t2 = t1 & 0xff00ffff; + t3 = (x >> 8) | (x << 24); + return t3 ^ (t2 >> 8); +} + +static int opl_hmirror_u16_by1(const u8 * src, int src_line_stride, int width, + int height, u8 * dst, int dst_line_stride, + int vmirror); +static int opl_hmirror_u16_by2(const u8 * src, int src_line_stride, int width, + int height, u8 * dst, int dst_line_stride, + int vmirror); +static int opl_hmirror_u16_by4(const u8 * src, int src_line_stride, int width, + int height, u8 * dst, int dst_line_stride, + int vmirror); +static int opl_hmirror_u16_by8(const u8 * src, int src_line_stride, int width, + int height, u8 * dst, int dst_line_stride, + int vmirror); + +int opl_hmirror_u16(const u8 * src, int src_line_stride, int width, int height, + u8 * dst, int dst_line_stride) +{ + if (!src || !dst) + return OPLERR_NULL_PTR; + + if (width == 0 || height == 0 || src_line_stride == 0 + || dst_line_stride == 0) + return OPLERR_BAD_ARG; + + if (width % 8 == 0) + return opl_hmirror_u16_by8(src, src_line_stride, width, height, + dst, dst_line_stride, 0); + else if (width % 4 == 0) + return opl_hmirror_u16_by4(src, src_line_stride, width, height, + dst, dst_line_stride, 0); + else if (width % 2 == 0) + return opl_hmirror_u16_by2(src, src_line_stride, width, height, + dst, dst_line_stride, 0); + else /* (width % 1) */ + return opl_hmirror_u16_by1(src, src_line_stride, width, height, + dst, dst_line_stride, 0); +} + +int opl_rotate180_u16(const u8 * src, int src_line_stride, int width, + int height, u8 * dst, int dst_line_stride) +{ + if (!src || !dst) + return OPLERR_NULL_PTR; + + if (width == 0 || height == 0 || src_line_stride == 0 + || dst_line_stride == 0) + return OPLERR_BAD_ARG; + + if (width % 8 == 0) + return opl_hmirror_u16_by8(src, src_line_stride, width, height, + dst, dst_line_stride, 1); + else if (width % 4 == 0) + return opl_hmirror_u16_by4(src, src_line_stride, width, height, + dst, dst_line_stride, 1); + else if (width % 2 == 0) + return opl_hmirror_u16_by2(src, src_line_stride, width, height, + dst, dst_line_stride, 1); + else /* (width % 1) */ + return opl_hmirror_u16_by1(src, src_line_stride, width, height, + dst, dst_line_stride, 1); +} + +static int opl_hmirror_u16_by1(const u8 * src, int src_line_stride, int width, + int height, u8 * dst, int dst_line_stride, + int vmirror) +{ + const u8 *src_row_addr; + const u8 *psrc; + u8 *dst_row_addr, *pdst; + int i, j; + u16 pixel; + + src_row_addr = src; + if (vmirror) { + dst_row_addr = dst + dst_line_stride * (height - 1); + dst_line_stride = -dst_line_stride; + } else + dst_row_addr = dst; + + /* Loop over all rows */ + for (i = 0; i < height; i++) { + /* Loop over each pixel */ + psrc = src_row_addr; + pdst = dst_row_addr + (width - 1) * BYTES_PER_PIXEL + - (BYTES_PER_PIXEL - BYTES_PER_PIXEL); + for (j = 0; j < width; j++) { + pixel = *(u16 *) psrc; + *(u16 *) pdst = pixel; + psrc += BYTES_PER_PIXEL; + pdst -= BYTES_PER_PIXEL; + } + src_row_addr += src_line_stride; + dst_row_addr += dst_line_stride; + } + + return OPLERR_SUCCESS; +} + +static int opl_hmirror_u16_by2(const u8 * src, int src_line_stride, int width, + int height, u8 * dst, int dst_line_stride, + int vmirror) +{ + const u8 *src_row_addr; + const u8 *psrc; + u8 *dst_row_addr, *pdst; + int i, j; + u32 pixelsin, pixelsout; + + src_row_addr = src; + if (vmirror) { + dst_row_addr = dst + dst_line_stride * (height - 1); + dst_line_stride = -dst_line_stride; + } else + dst_row_addr = dst; + + /* Loop over all rows */ + for (i = 0; i < height; i++) { + /* Loop over each pixel */ + psrc = src_row_addr; + pdst = dst_row_addr + (width - 2) * BYTES_PER_PIXEL; + for (j = 0; j < (width >> 1); j++) { + pixelsin = *(u32 *) psrc; + pixelsout = rot_left_u32(pixelsin, 16); + *(u32 *) pdst = pixelsout; + psrc += BYTES_PER_2PIXEL; + pdst -= BYTES_PER_2PIXEL; + } + src_row_addr += src_line_stride; + dst_row_addr += dst_line_stride; + } + + return OPLERR_SUCCESS; +} + +static int opl_hmirror_u16_by4(const u8 * src, int src_line_stride, int width, + int height, u8 * dst, int dst_line_stride, + int vmirror) +{ + const u8 *src_row_addr; + const u8 *psrc; + u8 *dst_row_addr, *pdst; + int i, j; + + union doubleword { + u64 dw; + u32 w[2]; + }; + + union doubleword inbuf; + union doubleword outbuf; + + src_row_addr = src; + if (vmirror) { + dst_row_addr = dst + dst_line_stride * (height - 1); + dst_line_stride = -dst_line_stride; + } else + dst_row_addr = dst; + + /* Loop over all rows */ + for (i = 0; i < height; i++) { + /* Loop over each pixel */ + psrc = src_row_addr; + pdst = dst_row_addr + (width - 4) * BYTES_PER_PIXEL; + for (j = 0; j < (width >> 2); j++) { + inbuf.dw = *(u64 *) psrc; + outbuf.w[0] = rot_left_u32(inbuf.w[1], 16); + outbuf.w[1] = rot_left_u32(inbuf.w[0], 16); + *(u64 *) pdst = outbuf.dw; + psrc += BYTES_PER_4PIXEL; + pdst -= BYTES_PER_4PIXEL; + } + src_row_addr += src_line_stride; + dst_row_addr += dst_line_stride; + } + return OPLERR_SUCCESS; +} + +static int opl_hmirror_u16_by8(const u8 * src, int src_line_stride, int width, + int height, u8 * dst, int dst_line_stride, + int vmirror) +{ + const u8 *src_row_addr; + const u8 *psrc; + u8 *dst_row_addr, *pdst; + int i, j; + + src_row_addr = src; + if (vmirror) { + dst_row_addr = dst + dst_line_stride * (height - 1); + dst_line_stride = -dst_line_stride; + } else + dst_row_addr = dst; + + /* Loop over all rows */ + for (i = 0; i < height; i++) { + /* Loop over each pixel */ + psrc = src_row_addr; + pdst = dst_row_addr + (width - 1) * BYTES_PER_PIXEL - 2; + for (j = (width >> 3); j > 0; j--) { + __asm__ volatile ( + "ldmia %0!,{r2-r5}\n\t" + "mov r6, r2\n\t" + "mov r7, r3\n\t" + "mov r2, r5, ROR #16\n\t" + "mov r3, r4, ROR #16\n\t" + "mov r4, r7, ROR #16\n\t" + "mov r5, r6, ROR #16\n\t" + "stmda %1!,{r2-r5}\n\t" + + :"+r"(psrc), "+r"(pdst) + :"0"(psrc), "1"(pdst) + :"r2", "r3", "r4", "r5", "r6", "r7", + "memory" + ); + } + src_row_addr += src_line_stride; + dst_row_addr += dst_line_stride; + } + + return OPLERR_SUCCESS; +} + +EXPORT_SYMBOL(opl_hmirror_u16); +EXPORT_SYMBOL(opl_rotate180_u16); diff --git a/drivers/media/video/mxc/opl/opl.h b/drivers/media/video/mxc/opl/opl.h new file mode 100644 index 000000000000..24644c8e78fa --- /dev/null +++ b/drivers/media/video/mxc/opl/opl.h @@ -0,0 +1,162 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @defgroup OPLIP OPL Image Processing + */ +/*! + * @file opl.h + * + * @brief The OPL (Open Primitives Library) Image Processing library defines + * efficient functions for rotation and mirroring. + * + * It includes ARM9-optimized rotation and mirroring functions. It is derived + * from the original OPL project which is found at sourceforge.freescale.net. + * + * @ingroup OPLIP + */ +#ifndef __OPL_H__ +#define __OPL_H__ + +#include <linux/types.h> + +#define BYTES_PER_PIXEL 2 +#define CACHE_LINE_WORDS 8 +#define BYTES_PER_WORD 4 + +#define BYTES_PER_2PIXEL (BYTES_PER_PIXEL * 2) +#define BYTES_PER_4PIXEL (BYTES_PER_PIXEL * 4) +#define BYTES_PER_8PIXEL (BYTES_PER_PIXEL * 8) + +#define QCIF_Y_WIDTH 176 +#define QCIF_Y_HEIGHT 144 + +/*! Enumerations of opl error code */ +enum opl_error { + OPLERR_SUCCESS = 0, + OPLERR_NULL_PTR, + OPLERR_BAD_ARG, + OPLERR_DIV_BY_ZERO, + OPLERR_OVER_FLOW, + OPLERR_UNDER_FLOW, + OPLERR_MISALIGNED, +}; + +/*! + * @brief Rotate a 16bbp buffer 90 degrees clockwise. + * + * @param src Pointer to the input buffer + * @param src_line_stride Length in bytes of a raster line of the input buffer + * @param width Width in pixels of the region in the input buffer + * @param height Height in pixels of the region in the input buffer + * @param dst Pointer to the output buffer + * @param dst_line_stride Length in bytes of a raster line of the output buffer + * + * @return Standard OPL error code. See enumeration for possible result codes. + */ +int opl_rotate90_u16(const u8 * src, int src_line_stride, int width, int height, + u8 * dst, int dst_line_stride); + +/*! + * @brief Rotate a 16bbp buffer 180 degrees clockwise. + * + * @param src Pointer to the input buffer + * @param src_line_stride Length in bytes of a raster line of the input buffer + * @param width Width in pixels of the region in the input buffer + * @param height Height in pixels of the region in the input buffer + * @param dst Pointer to the output buffer + * @param dst_line_stride Length in bytes of a raster line of the output buffer + * + * @return Standard OPL error code. See enumeration for possible result codes. + */ +int opl_rotate180_u16(const u8 * src, int src_line_stride, int width, + int height, u8 * dst, int dst_line_stride); + +/*! + * @brief Rotate a 16bbp buffer 270 degrees clockwise + * + * @param src Pointer to the input buffer + * @param src_line_stride Length in bytes of a raster line of the input buffer + * @param width Width in pixels of the region in the input buffer + * @param height Height in pixels of the region in the input buffer + * @param dst Pointer to the output buffer + * @param dst_line_stride Length in bytes of a raster line of the output buffer + * + * @return Standard OPL error code. See enumeration for possible result codes. + */ +int opl_rotate270_u16(const u8 * src, int src_line_stride, int width, + int height, u8 * dst, int dst_line_stride); + +/*! + * @brief Mirror a 16bpp buffer horizontally + * + * @param src Pointer to the input buffer + * @param src_line_stride Length in bytes of a raster line of the input buffer + * @param width Width in pixels of the region in the input buffer + * @param height Height in pixels of the region in the input buffer + * @param dst Pointer to the output buffer + * @param dst_line_stride Length in bytes of a raster line of the output buffer + * + * @return Standard OPL error code. See enumeration for possible result codes. + */ +int opl_hmirror_u16(const u8 * src, int src_line_stride, int width, int height, + u8 * dst, int dst_line_stride); + +/*! + * @brief Mirror a 16bpp buffer vertically + * + * @param src Pointer to the input buffer + * @param src_line_stride Length in bytes of a raster line of the input buffer + * @param width Width in pixels of the region in the input buffer + * @param height Height in pixels of the region in the input buffer + * @param dst Pointer to the output buffer + * @param dst_line_stride Length in bytes of a raster line of the output buffer + * + * @return Standard OPL error code. See enumeration for possible result codes. + */ +int opl_vmirror_u16(const u8 * src, int src_line_stride, int width, int height, + u8 * dst, int dst_line_stride); + +/*! + * @brief Rotate a 16bbp buffer 90 degrees clockwise and mirror vertically + * It is equivalent to rotate 270 degree and mirror horizontally + * + * @param src Pointer to the input buffer + * @param src_line_stride Length in bytes of a raster line of the input buffer + * @param width Width in pixels of the region in the input buffer + * @param height Height in pixels of the region in the input buffer + * @param dst Pointer to the output buffer + * @param dst_line_stride Length in bytes of a raster line of the output buffer + * + * @return Standard OPL error code. See enumeration for possible result codes. + */ +int opl_rotate90_vmirror_u16(const u8 * src, int src_line_stride, int width, + int height, u8 * dst, int dst_line_stride); + +/*! + * @brief Rotate a 16bbp buffer 270 degrees clockwise and mirror vertically + * It is equivalent to rotate 90 degree and mirror horizontally + * + * @param src Pointer to the input buffer + * @param src_line_stride Length in bytes of a raster line of the input buffer + * @param width Width in pixels of the region in the input buffer + * @param height Height in pixels of the region in the input buffer + * @param dst Pointer to the output buffer + * @param dst_line_stride Length in bytes of a raster line of the output buffer + * + * @return Standard OPL error code. See enumeration for possible result codes. + */ +int opl_rotate270_vmirror_u16(const u8 * src, int src_line_stride, int width, + int height, u8 * dst, int dst_line_stride); + +#endif /* __OPL_H__ */ diff --git a/drivers/media/video/mxc/opl/opl_mod.c b/drivers/media/video/mxc/opl/opl_mod.c new file mode 100644 index 000000000000..a581aadda252 --- /dev/null +++ b/drivers/media/video/mxc/opl/opl_mod.c @@ -0,0 +1,30 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> + +static __init int opl_init(void) +{ + return 0; +} + +static void __exit opl_exit(void) +{ +} + +module_init(opl_init); +module_exit(opl_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("OPL Software Rotation/Mirroring"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/mxc/opl/rotate270_u16.c b/drivers/media/video/mxc/opl/rotate270_u16.c new file mode 100644 index 000000000000..add87f1a9e44 --- /dev/null +++ b/drivers/media/video/mxc/opl/rotate270_u16.c @@ -0,0 +1,285 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include "opl.h" + +static int opl_rotate270_u16_by16(const u8 * src, int src_line_stride, + int width, int height, u8 * dst, + int dst_line_stride, int vmirror); +static int opl_rotate270_u16_by4(const u8 * src, int src_line_stride, int width, + int height, u8 * dst, int dst_line_stride, + int vmirror); +static int opl_rotate270_vmirror_u16_both(const u8 * src, int src_line_stride, + int width, int height, u8 * dst, + int dst_line_stride, int vmirror); +int opl_rotate270_u16_qcif(const u8 * src, u8 * dst); + +int opl_rotate270_u16(const u8 * src, int src_line_stride, int width, + int height, u8 * dst, int dst_line_stride) +{ + return opl_rotate270_vmirror_u16_both(src, src_line_stride, width, + height, dst, dst_line_stride, 0); +} + +int opl_rotate270_vmirror_u16(const u8 * src, int src_line_stride, int width, + int height, u8 * dst, int dst_line_stride) +{ + return opl_rotate270_vmirror_u16_both(src, src_line_stride, width, + height, dst, dst_line_stride, 1); +} + +static int opl_rotate270_vmirror_u16_both(const u8 * src, int src_line_stride, + int width, int height, u8 * dst, + int dst_line_stride, int vmirror) +{ + const int BLOCK_SIZE_PIXELS = CACHE_LINE_WORDS * BYTES_PER_WORD + / BYTES_PER_PIXEL; + const int BLOCK_SIZE_PIXELS_BY4 = CACHE_LINE_WORDS * BYTES_PER_WORD + / BYTES_PER_PIXEL / 4; + + if (!src || !dst) + return OPLERR_NULL_PTR; + + if (width == 0 || height == 0 || src_line_stride == 0 + || dst_line_stride == 0) + return OPLERR_BAD_ARG; + + /* The QCIF algorithm doesn't support vertical mirroring */ + if (vmirror == 0 && width == QCIF_Y_WIDTH && height == QCIF_Y_HEIGHT + && src_line_stride == QCIF_Y_WIDTH * 2 + && src_line_stride == QCIF_Y_HEIGHT * 2) + return opl_rotate270_u16_qcif(src, dst); + else if (width % BLOCK_SIZE_PIXELS == 0 + && height % BLOCK_SIZE_PIXELS == 0) + return opl_rotate270_u16_by16(src, src_line_stride, width, + height, dst, dst_line_stride, + vmirror); + else if (width % BLOCK_SIZE_PIXELS_BY4 == 0 + && height % BLOCK_SIZE_PIXELS_BY4 == 0) + return opl_rotate270_u16_by4(src, src_line_stride, width, + height, dst, dst_line_stride, + vmirror); + else + return OPLERR_BAD_ARG; +} + +/* + * Rotate Counter Clockwise, divide RGB component into 16 row strips, read + * non sequentially and write sequentially. This is done in 16 line strips + * so that the cache is used better. Cachelines are 8 words = 32 bytes. Pixels + * are 2 bytes. The 16 reads will be cache misses, but the next 240 should + * be from cache. The writes to the output buffer will be sequential for 16 + * writes. + * + * Example: + * Input data matrix: output matrix + * + * 0 | 1 | 2 | 3 | 4 | 4 | 0 | 0 | 3 | + * 4 | 3 | 2 | 1 | 0 | 3 | 1 | 9 | 6 | + * 6 | 7 | 8 | 9 | 0 | 2 | 2 | 8 | 2 | + * 5 | 3 | 2 | 6 | 3 | 1 | 3 | 7 | 3 | + * ^ 0 | 4 | 6 | 5 | < Write the input data sequentially + * Read first column + * Start at the bottom + * Move to next column and repeat + * + * Loop over k decreasing (blocks) + * in_block_ptr = src + (((RGB_HEIGHT_PIXELS / BLOCK_SIZE_PIXELS) - k) + * * BLOCK_SIZE_PIXELS) * (RGB_WIDTH_BYTES) + * out_block_ptr = dst + (((RGB_HEIGHT_PIXELS / BLOCK_SIZE_PIXELS) - k) + * * BLOCK_SIZE_BYTES) + (RGB_WIDTH_PIXELS - 1) + * * RGB_HEIGHT_PIXELS * BYTES_PER_PIXEL + * + * Loop over i decreasing (width) + * Each pix: + * in_block_ptr += RGB_WIDTH_BYTES + * out_block_ptr += 4 + * + * Each row of block: + * in_block_ptr -= RGB_WIDTH_BYTES * BLOCK_SIZE_PIXELS - 2 + * out_block_ptr -= RGB_HEIGHT_PIXELS * BYTES_PER_PIXEL + 2 * BLOCK_SIZE_PIXELS; + * + * It may perform vertical mirroring too depending on the vmirror flag. + */ +static int opl_rotate270_u16_by16(const u8 * src, int src_line_stride, + int width, int height, u8 * dst, + int dst_line_stride, int vmirror) +{ + const int BLOCK_SIZE_PIXELS = CACHE_LINE_WORDS * BYTES_PER_WORD + / BYTES_PER_PIXEL; + const int IN_INDEX = src_line_stride * BLOCK_SIZE_PIXELS + - BYTES_PER_PIXEL; + const int OUT_INDEX = vmirror ? + -dst_line_stride + BYTES_PER_PIXEL * BLOCK_SIZE_PIXELS + : dst_line_stride + BYTES_PER_PIXEL * BLOCK_SIZE_PIXELS; + const u8 *in_block_ptr; + u8 *out_block_ptr; + int i, k; + + for (k = height / BLOCK_SIZE_PIXELS; k > 0; k--) { + in_block_ptr = src + (((height / BLOCK_SIZE_PIXELS) - k) + * BLOCK_SIZE_PIXELS) * src_line_stride; + out_block_ptr = dst + (((height / BLOCK_SIZE_PIXELS) - k) + * BLOCK_SIZE_PIXELS * BYTES_PER_PIXEL) + + (width - 1) * dst_line_stride; + + /* + * For vertical mirroring the writing starts from the + * first line + */ + if (vmirror) + out_block_ptr -= dst_line_stride * (width - 1); + + for (i = width; i > 0; i--) { + __asm__ volatile ( + "ldrh r2, [%0], %4\n\t" + "ldrh r3, [%0], %4\n\t" + "ldrh r4, [%0], %4\n\t" + "ldrh r5, [%0], %4\n\t" + "orr r2, r2, r3, lsl #16\n\t" + "orr r4, r4, r5, lsl #16\n\t" + "str r2, [%1], #4\n\t" + "str r4, [%1], #4\n\t" + + "ldrh r2, [%0], %4\n\t" + "ldrh r3, [%0], %4\n\t" + "ldrh r4, [%0], %4\n\t" + "ldrh r5, [%0], %4\n\t" + "orr r2, r2, r3, lsl #16\n\t" + "orr r4, r4, r5, lsl #16\n\t" + "str r2, [%1], #4\n\t" + "str r4, [%1], #4\n\t" + + "ldrh r2, [%0], %4\n\t" + "ldrh r3, [%0], %4\n\t" + "ldrh r4, [%0], %4\n\t" + "ldrh r5, [%0], %4\n\t" + "orr r2, r2, r3, lsl #16\n\t" + "orr r4, r4, r5, lsl #16\n\t" + "str r2, [%1], #4\n\t" + "str r4, [%1], #4\n\t" + + "ldrh r2, [%0], %4\n\t" + "ldrh r3, [%0], %4\n\t" + "ldrh r4, [%0], %4\n\t" + "ldrh r5, [%0], %4\n\t" + "orr r2, r2, r3, lsl #16\n\t" + "orr r4, r4, r5, lsl #16\n\t" + "str r2, [%1], #4\n\t" + "str r4, [%1], #4\n\t" + + :"+r" (in_block_ptr), "+r"(out_block_ptr) /* output */ + :"0"(in_block_ptr), "1"(out_block_ptr), "r"(src_line_stride) /* input */ + :"r2", "r3", "r4", "r5", "memory" /* modify */ + ); + in_block_ptr -= IN_INDEX; + out_block_ptr -= OUT_INDEX; + } + } + + return OPLERR_SUCCESS; +} + +/* + * Rotate Counter Clockwise, divide RGB component into 4 row strips, read + * non sequentially and write sequentially. This is done in 4 line strips + * so that the cache is used better. Cachelines are 8 words = 32 bytes. Pixels + * are 2 bytes. The 4 reads will be cache misses, but the next 60 should + * be from cache. The writes to the output buffer will be sequential for 4 + * writes. + * + * Example: + * Input data matrix: output matrix + * + * 0 | 1 | 2 | 3 | 4 | 4 | 0 | 0 | 3 | + * 4 | 3 | 2 | 1 | 0 | 3 | 1 | 9 | 6 | + * 6 | 7 | 8 | 9 | 0 | 2 | 2 | 8 | 2 | + * 5 | 3 | 2 | 6 | 3 | 1 | 3 | 7 | 3 | + * ^ 0 | 4 | 6 | 5 | < Write the input data sequentially + * Read first column + * Start at the bottom + * Move to next column and repeat + * + * Loop over k decreasing (blocks) + * in_block_ptr = src + (((RGB_HEIGHT_PIXELS / BLOCK_SIZE_PIXELS) - k) + * * BLOCK_SIZE_PIXELS) * (RGB_WIDTH_BYTES) + * out_block_ptr = dst + (((RGB_HEIGHT_PIXELS / BLOCK_SIZE_PIXELS) - k) + * * BLOCK_SIZE_BYTES) + (RGB_WIDTH_PIXELS - 1) + * * RGB_HEIGHT_PIXELS * BYTES_PER_PIXEL + * + * Loop over i decreasing (width) + * Each pix: + * in_block_ptr += RGB_WIDTH_BYTES + * out_block_ptr += 4 + * + * Each row of block: + * in_block_ptr -= RGB_WIDTH_BYTES * BLOCK_SIZE_PIXELS - 2 + * out_block_ptr -= RGB_HEIGHT_PIXELS * BYTES_PER_PIXEL + 2 * BLOCK_SIZE_PIXELS; + * + * It may perform vertical mirroring too depending on the vmirror flag. + */ +static int opl_rotate270_u16_by4(const u8 * src, int src_line_stride, int width, + int height, u8 * dst, int dst_line_stride, + int vmirror) +{ + const int BLOCK_SIZE_PIXELS = CACHE_LINE_WORDS * BYTES_PER_WORD + / BYTES_PER_PIXEL / 4; + const int IN_INDEX = src_line_stride * BLOCK_SIZE_PIXELS + - BYTES_PER_PIXEL; + const int OUT_INDEX = vmirror ? + -dst_line_stride + BYTES_PER_PIXEL * BLOCK_SIZE_PIXELS + : dst_line_stride + BYTES_PER_PIXEL * BLOCK_SIZE_PIXELS; + const u8 *in_block_ptr; + u8 *out_block_ptr; + int i, k; + + for (k = height / BLOCK_SIZE_PIXELS; k > 0; k--) { + in_block_ptr = src + (((height / BLOCK_SIZE_PIXELS) - k) + * BLOCK_SIZE_PIXELS) * src_line_stride; + out_block_ptr = dst + (((height / BLOCK_SIZE_PIXELS) - k) + * BLOCK_SIZE_PIXELS * BYTES_PER_PIXEL) + + (width - 1) * dst_line_stride; + + /* + * For vertical mirroring the writing starts from the + * first line + */ + if (vmirror) + out_block_ptr -= dst_line_stride * (width - 1); + + for (i = width; i > 0; i--) { + __asm__ volatile ( + "ldrh r2, [%0], %4\n\t" + "ldrh r3, [%0], %4\n\t" + "ldrh r4, [%0], %4\n\t" + "ldrh r5, [%0], %4\n\t" + "orr r2, r2, r3, lsl #16\n\t" + "orr r4, r4, r5, lsl #16\n\t" + "str r2, [%1], #4\n\t" + "str r4, [%1], #4\n\t" + + :"+r" (in_block_ptr), "+r"(out_block_ptr) /* output */ + :"0"(in_block_ptr), "1"(out_block_ptr), "r"(src_line_stride) /* input */ + :"r2", "r3", "r4", "r5", "memory" /* modify */ + ); + in_block_ptr -= IN_INDEX; + out_block_ptr -= OUT_INDEX; + } + } + + return OPLERR_SUCCESS; +} + +EXPORT_SYMBOL(opl_rotate270_u16); +EXPORT_SYMBOL(opl_rotate270_vmirror_u16); diff --git a/drivers/media/video/mxc/opl/rotate270_u16_qcif.S b/drivers/media/video/mxc/opl/rotate270_u16_qcif.S new file mode 100644 index 000000000000..4101eaac4554 --- /dev/null +++ b/drivers/media/video/mxc/opl/rotate270_u16_qcif.S @@ -0,0 +1,70 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/linkage.h> + + .text + .align 2 +ENTRY(opl_rotate270_u16_qcif) + STMFD sp!,{r4-r10} + MOV r12,#0x160 + MOV r10,#0x90 + MOV r3,r10,LSR #4 +.L1.16: + RSB r2,r3,r10,LSR #4 + MOV r5,r2,LSL #5 + MOV r4,r12,LSR #1 + SMULBB r4,r5,r4 + ADD r2,r1,r2,LSL #5 + ADD r5,r2,#0xc000 + ADD r5,r5,#0x4e0 + MOV r2,r12,LSR #1 + ADD r4,r0,r4 +.L1.52: + LDRH r6,[r4],r12 + LDRH r7,[r4],r12 + LDRH r8,[r4],r12 + LDRH r9,[r4],r12 + ORR r6,r6,r7,LSL #16 + ORR r7,r8,r9,LSL #16 + STMIA r5!,{r6,r7} + SUBS r2,r2,#1 + LDRH r6,[r4],r12 + LDRH r7,[r4],r12 + LDRH r8,[r4],r12 + LDRH r9,[r4],r12 + ORR r6,r6,r7,LSL #16 + ORR r7,r8,r9,LSL #16 + STMIA r5!,{r6,r7} + LDRH r6,[r4],r12 + LDRH r7,[r4],r12 + LDRH r8,[r4],r12 + LDRH r9,[r4],r12 + ORR r6,r6,r7,LSL #16 + ORR r7,r8,r9,LSL #16 + STMIA r5!,{r6,r7} + LDRH r6,[r4],r12 + LDRH r7,[r4],r12 + LDRH r8,[r4],r12 + LDRH r9,[r4],r12 + ORR r6,r6,r7,LSL #16 + ORR r7,r8,r9,LSL #16 + SUB r4,r4,#0x1500 + STMIA r5,{r6,r7} + SUB r5,r5,#0x138 + SUB r4,r4,#0xfe + BGT .L1.52 + SUBS r3,r3,#1 + BGT .L1.16 + LDMFD sp!,{r4-r10} + BX lr + .size opl_rotate270_u16_qcif, . - opl_rotate270_u16_qcif diff --git a/drivers/media/video/mxc/opl/rotate90_u16.c b/drivers/media/video/mxc/opl/rotate90_u16.c new file mode 100644 index 000000000000..dd7d445aa952 --- /dev/null +++ b/drivers/media/video/mxc/opl/rotate90_u16.c @@ -0,0 +1,220 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include "opl.h" + +static int opl_rotate90_u16_by16(const u8 * src, int src_line_stride, int width, + int height, u8 * dst, int dst_line_stride, + int vmirror); +static int opl_rotate90_u16_by4(const u8 * src, int src_line_stride, int width, + int height, u8 * dst, int dst_line_stride, + int vmirror); +static int opl_rotate90_vmirror_u16_both(const u8 * src, int src_line_stride, + int width, int height, u8 * dst, + int dst_line_stride, int vmirror); +int opl_rotate90_u16_qcif(const u8 * src, u8 * dst); + +int opl_rotate90_u16(const u8 * src, int src_line_stride, int width, int height, + u8 * dst, int dst_line_stride) +{ + return opl_rotate90_vmirror_u16_both(src, src_line_stride, width, + height, dst, dst_line_stride, 0); +} + +int opl_rotate90_vmirror_u16(const u8 * src, int src_line_stride, int width, + int height, u8 * dst, int dst_line_stride) +{ + return opl_rotate90_vmirror_u16_both(src, src_line_stride, width, + height, dst, dst_line_stride, 1); +} + +static int opl_rotate90_vmirror_u16_both(const u8 * src, int src_line_stride, + int width, int height, u8 * dst, + int dst_line_stride, int vmirror) +{ + const int BLOCK_SIZE_PIXELS = CACHE_LINE_WORDS * BYTES_PER_WORD + / BYTES_PER_PIXEL; + const int BLOCK_SIZE_PIXELS_BY4 = CACHE_LINE_WORDS * BYTES_PER_WORD + / BYTES_PER_PIXEL / 4; + + if (!src || !dst) + return OPLERR_NULL_PTR; + + if (width == 0 || height == 0 || src_line_stride == 0 + || dst_line_stride == 0) + return OPLERR_BAD_ARG; + + /* The QCIF algorithm doesn't support vertical mirroring */ + if (vmirror == 0 && width == QCIF_Y_WIDTH && height == QCIF_Y_HEIGHT + && src_line_stride == QCIF_Y_WIDTH * 2 + && src_line_stride == QCIF_Y_HEIGHT * 2) + return opl_rotate90_u16_qcif(src, dst); + else if (width % BLOCK_SIZE_PIXELS == 0 + && height % BLOCK_SIZE_PIXELS == 0) + return opl_rotate90_u16_by16(src, src_line_stride, width, + height, dst, dst_line_stride, + vmirror); + else if (width % BLOCK_SIZE_PIXELS_BY4 == 0 + && height % BLOCK_SIZE_PIXELS_BY4 == 0) + return opl_rotate90_u16_by4(src, src_line_stride, width, height, + dst, dst_line_stride, vmirror); + else + return OPLERR_BAD_ARG; +} + +/* + * Performs clockwise rotation (and possibly vertical mirroring depending + * on the vmirror flag) using block sizes of 16x16 + * The algorithm is similar to 270 degree clockwise rotation algorithm + */ +static int opl_rotate90_u16_by16(const u8 * src, int src_line_stride, int width, + int height, u8 * dst, int dst_line_stride, + int vmirror) +{ + const int BLOCK_SIZE_PIXELS = CACHE_LINE_WORDS * BYTES_PER_WORD + / BYTES_PER_PIXEL; + const int BLOCK_SIZE_BYTES = BYTES_PER_PIXEL * BLOCK_SIZE_PIXELS; + const int IN_INDEX = src_line_stride * BLOCK_SIZE_PIXELS + + BYTES_PER_PIXEL; + const int OUT_INDEX = vmirror ? + -dst_line_stride - BLOCK_SIZE_BYTES + : dst_line_stride - BLOCK_SIZE_BYTES; + const u8 *in_block_ptr; + u8 *out_block_ptr; + int i, k; + + for (k = height / BLOCK_SIZE_PIXELS; k > 0; k--) { + in_block_ptr = src + src_line_stride * (height - 1) + - (src_line_stride * BLOCK_SIZE_PIXELS * + (height / BLOCK_SIZE_PIXELS - k)); + out_block_ptr = dst + BYTES_PER_PIXEL * BLOCK_SIZE_PIXELS * + ((height / BLOCK_SIZE_PIXELS) - k); + + /* + * For vertical mirroring the writing starts from the + * bottom line + */ + if (vmirror) + out_block_ptr += dst_line_stride * (width - 1); + + for (i = width; i > 0; i--) { + __asm__ volatile ( + "ldrh r2, [%0], -%4\n\t" + "ldrh r3, [%0], -%4\n\t" + "ldrh r4, [%0], -%4\n\t" + "ldrh r5, [%0], -%4\n\t" + "orr r2, r2, r3, lsl #16\n\t" + "orr r4, r4, r5, lsl #16\n\t" + "str r2, [%1], #4\n\t" + "str r4, [%1], #4\n\t" + + "ldrh r2, [%0], -%4\n\t" + "ldrh r3, [%0], -%4\n\t" + "ldrh r4, [%0], -%4\n\t" + "ldrh r5, [%0], -%4\n\t" + "orr r2, r2, r3, lsl #16\n\t" + "orr r4, r4, r5, lsl #16\n\t" + "str r2, [%1], #4\n\t" + "str r4, [%1], #4\n\t" + + "ldrh r2, [%0], -%4\n\t" + "ldrh r3, [%0], -%4\n\t" + "ldrh r4, [%0], -%4\n\t" + "ldrh r5, [%0], -%4\n\t" + "orr r2, r2, r3, lsl #16\n\t" + "orr r4, r4, r5, lsl #16\n\t" + "str r2, [%1], #4\n\t" + "str r4, [%1], #4\n\t" + + "ldrh r2, [%0], -%4\n\t" + "ldrh r3, [%0], -%4\n\t" + "ldrh r4, [%0], -%4\n\t" + "ldrh r5, [%0], -%4\n\t" + "orr r2, r2, r3, lsl #16\n\t" + "orr r4, r4, r5, lsl #16\n\t" + "str r2, [%1], #4\n\t" + "str r4, [%1], #4\n\t" + + :"+r" (in_block_ptr), "+r"(out_block_ptr) /* output */ + :"0"(in_block_ptr), "1"(out_block_ptr), "r"(src_line_stride) /* input */ + :"r2", "r3", "r4", "r5", "memory" /* modify */ + ); + in_block_ptr += IN_INDEX; + out_block_ptr += OUT_INDEX; + } + } + + return OPLERR_SUCCESS; +} + +/* + * Performs clockwise rotation (and possibly vertical mirroring depending + * on the vmirror flag) using block sizes of 4x4 + * The algorithm is similar to 270 degree clockwise rotation algorithm + */ +static int opl_rotate90_u16_by4(const u8 * src, int src_line_stride, int width, + int height, u8 * dst, int dst_line_stride, + int vmirror) +{ + const int BLOCK_SIZE_PIXELS = CACHE_LINE_WORDS * BYTES_PER_WORD + / BYTES_PER_PIXEL / 4; + const int BLOCK_SIZE_BYTES = BYTES_PER_PIXEL * BLOCK_SIZE_PIXELS; + const int IN_INDEX = src_line_stride * BLOCK_SIZE_PIXELS + + BYTES_PER_PIXEL; + const int OUT_INDEX = vmirror ? + -dst_line_stride - BLOCK_SIZE_BYTES + : dst_line_stride - BLOCK_SIZE_BYTES; + const u8 *in_block_ptr; + u8 *out_block_ptr; + int i, k; + + for (k = height / BLOCK_SIZE_PIXELS; k > 0; k--) { + in_block_ptr = src + src_line_stride * (height - 1) + - (src_line_stride * BLOCK_SIZE_PIXELS * + (height / BLOCK_SIZE_PIXELS - k)); + out_block_ptr = dst + BYTES_PER_PIXEL * BLOCK_SIZE_PIXELS + * ((height / BLOCK_SIZE_PIXELS) - k); + + /* + * For horizontal mirroring the writing starts from the + * bottom line + */ + if (vmirror) + out_block_ptr += dst_line_stride * (width - 1); + + for (i = width; i > 0; i--) { + __asm__ volatile ( + "ldrh r2, [%0], -%4\n\t" + "ldrh r3, [%0], -%4\n\t" + "ldrh r4, [%0], -%4\n\t" + "ldrh r5, [%0], -%4\n\t" + "orr r2, r2, r3, lsl #16\n\t" + "orr r4, r4, r5, lsl #16\n\t" + "str r2, [%1], #4\n\t" + "str r4, [%1], #4\n\t" + + :"+r" (in_block_ptr), "+r"(out_block_ptr) /* output */ + :"0"(in_block_ptr), "1"(out_block_ptr), "r"(src_line_stride) /* input */ + :"r2", "r3", "r4", "r5", "memory" /* modify */ + ); + in_block_ptr += IN_INDEX; + out_block_ptr += OUT_INDEX; + } + } + + return OPLERR_SUCCESS; +} + +EXPORT_SYMBOL(opl_rotate90_u16); +EXPORT_SYMBOL(opl_rotate90_vmirror_u16); diff --git a/drivers/media/video/mxc/opl/rotate90_u16_qcif.S b/drivers/media/video/mxc/opl/rotate90_u16_qcif.S new file mode 100644 index 000000000000..8568a9e629e5 --- /dev/null +++ b/drivers/media/video/mxc/opl/rotate90_u16_qcif.S @@ -0,0 +1,71 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/linkage.h> + + .text + .align 2 +ENTRY(opl_rotate90_u16_qcif) + STMFD sp!,{r4-r10} + MOV r12,#0x160 + MOV r10,#0x90 + MOV r3,r10,LSR #4 +.L1.216: + RSB r2,r3,r10,LSR #4 + MOV r4,#0x20 + SMULBB r5,r4,r2 + MOV r4,#0x1600 + SMULBB r2,r4,r2 + ADD r4,r0,#0xc000 + ADD r4,r4,#0x4a0 + SUB r4,r4,r2 + MOV r2,r12,LSR #1 + ADD r5,r1,r5 +.L1.256: + LDRH r6,[r4],-r12 + LDRH r7,[r4],-r12 + LDRH r8,[r4],-r12 + LDRH r9,[r4],-r12 + ORR r6,r6,r7,LSL #16 + ORR r7,r8,r9,LSL #16 + STMIA r5!,{r6,r7} + SUBS r2,r2,#1 + LDRH r6,[r4],-r12 + LDRH r7,[r4],-r12 + LDRH r8,[r4],-r12 + LDRH r9,[r4],-r12 + ORR r6,r6,r7,LSL #16 + ORR r7,r8,r9,LSL #16 + STMIA r5!,{r6,r7} + LDRH r6,[r4],-r12 + LDRH r7,[r4],-r12 + LDRH r8,[r4],-r12 + LDRH r9,[r4],-r12 + ORR r6,r6,r7,LSL #16 + ORR r7,r8,r9,LSL #16 + STMIA r5!,{r6,r7} + LDRH r6,[r4],-r12 + LDRH r7,[r4],-r12 + LDRH r8,[r4],-r12 + LDRH r9,[r4],-r12 + ORR r6,r6,r7,LSL #16 + ORR r7,r8,r9,LSL #16 + ADD r4,r4,#0x1600 + STMIA r5!,{r6,r7} + ADD r5,r5,#0x100 + ADD r4,r4,#2 + BGT .L1.256 + SUBS r3,r3,#1 + BGT .L1.216 + LDMFD sp!,{r4-r10} + BX lr + .size opl_rotate90_u16_qcif, . - opl_rotate90_u16_qcif diff --git a/drivers/media/video/mxc/opl/vmirror_u16.c b/drivers/media/video/mxc/opl/vmirror_u16.c new file mode 100644 index 000000000000..57f805c08a81 --- /dev/null +++ b/drivers/media/video/mxc/opl/vmirror_u16.c @@ -0,0 +1,46 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include <linux/string.h> +#include "opl.h" + +int opl_vmirror_u16(const u8 * src, int src_line_stride, int width, int height, + u8 * dst, int dst_line_stride) +{ + const u8 *src_row_addr; + u8 *dst_row_addr; + int i; + + if (!src || !dst) + return OPLERR_NULL_PTR; + + if (width == 0 || height == 0 || src_line_stride == 0 + || dst_line_stride == 0) + return OPLERR_BAD_ARG; + + src_row_addr = src; + dst_row_addr = dst + (height - 1) * dst_line_stride; + + /* Loop over all rows */ + for (i = 0; i < height; i++) { + /* memcpy each row */ + memcpy(dst_row_addr, src_row_addr, BYTES_PER_PIXEL * width); + src_row_addr += src_line_stride; + dst_row_addr -= dst_line_stride; + } + + return OPLERR_SUCCESS; +} + +EXPORT_SYMBOL(opl_vmirror_u16); diff --git a/drivers/media/video/mxc/output/Kconfig b/drivers/media/video/mxc/output/Kconfig new file mode 100644 index 000000000000..b73cc58f3aa8 --- /dev/null +++ b/drivers/media/video/mxc/output/Kconfig @@ -0,0 +1,21 @@ +config VIDEO_MXC_IPU_OUTPUT + bool + depends on VIDEO_MXC_OUTPUT && MXC_IPU + default y + ---help--- + This is the video4linux2 driver for IPU post processing video output. + +config VIDEO_MXC_EMMA_OUTPUT + bool + depends on VIDEO_MXC_OUTPUT && MXC_EMMA && FB_MXC_SYNC_PANEL + default y + ---help--- + This is the video4linux2 driver for EMMA post processing video output. + +config VIDEO_MXC_OUTPUT_FBSYNC + bool "Synchronize the output with LCDC refresh" + depends on VIDEO_MXC_EMMA_OUTPUT + default y + ---help--- + Synchronize the post-processing with LCDC EOF (End of Frame) to + prevent tearing issue. If unsure, say Y. diff --git a/drivers/media/video/mxc/output/Makefile b/drivers/media/video/mxc/output/Makefile new file mode 100644 index 000000000000..1b4445a7a68d --- /dev/null +++ b/drivers/media/video/mxc/output/Makefile @@ -0,0 +1,8 @@ +ifeq ($(CONFIG_VIDEO_MXC_EMMA_OUTPUT),y) + mx27_output-objs := mx27_v4l2_output.o mx27_pp.o + obj-$(CONFIG_VIDEO_MXC_OUTPUT) += mx27_output.o +endif + +ifeq ($(CONFIG_VIDEO_MXC_IPU_OUTPUT),y) + obj-$(CONFIG_VIDEO_MXC_OUTPUT) += mxc_v4l2_output.o +endif diff --git a/drivers/media/video/mxc/output/mx27_pp.c b/drivers/media/video/mxc/output/mx27_pp.c new file mode 100644 index 000000000000..7d2785a71faf --- /dev/null +++ b/drivers/media/video/mxc/output/mx27_pp.c @@ -0,0 +1,904 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mx27_pp.c + * + * @brief MX27 V4L2 Video Output Driver + * + * Video4Linux2 Output Device using MX27 eMMA Post-processing functionality. + * + * @ingroup MXC_V4L2_OUTPUT + */ +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/module.h> +#include <linux/fb.h> +#include <linux/clk.h> +#include <linux/interrupt.h> +#include <asm/io.h> + +#include "mx27_pp.h" +#include "mxc_v4l2_output.h" + +#define SCALE_RETRY 32 /* to be more relax, less precise */ +#define PP_SKIP 1 +#define PP_TBL_MAX 40 + +static unsigned short scale_tbl[PP_TBL_MAX]; +static int g_hlen, g_vlen; + +static emma_pp_cfg g_pp_cfg; +static int g_disp_num = 0; +static char pp_dev[] = "emma_pp"; + +/*! + * @brief PP resizing routines + */ +static int gcd(int x, int y); +static int ratio(int x, int y, int *den); +static int scale_0d(int k, int coeff, int base, int nxt); +static int scale_1d(int inv, int outv, int k); +static int scale_1d_smart(int *inv, int *outv, int index); +static int scale_2d(emma_pp_scale * sz); + +static irqreturn_t pp_isr(int irq, void *dev_id); +static int set_output_addr(emma_pp_cfg * cfg, vout_data * vout); +static int pphw_reset(void); +static int pphw_enable(int flag); +static int pphw_ptr(emma_pp_cfg * cfg); +static int pphw_outptr(emma_pp_cfg * cfg); +static int pphw_cfg(emma_pp_cfg * cfg); +static int pphw_isr(void); +static void pphw_init(void); +static void pphw_exit(void); + +#define PP_DUMP(reg) pr_debug("%s\t = 0x%08X\n", #reg, __raw_readl(reg)) +void pp_dump(void) +{ + PP_DUMP(PP_CNTL); + PP_DUMP(PP_INTRCNTL); + PP_DUMP(PP_INTRSTATUS); + PP_DUMP(PP_SOURCE_Y_PTR); + PP_DUMP(PP_SOURCE_CB_PTR); + PP_DUMP(PP_SOURCE_CR_PTR); + PP_DUMP(PP_DEST_RGB_PTR); + PP_DUMP(PP_QUANTIZER_PTR); + PP_DUMP(PP_PROCESS_FRAME_PARA); + PP_DUMP(PP_SOURCE_FRAME_WIDTH); + PP_DUMP(PP_DEST_DISPLAY_WIDTH); + PP_DUMP(PP_DEST_IMAGE_SIZE); + PP_DUMP(PP_DEST_FRAME_FMT_CNTL); + PP_DUMP(PP_RESIZE_INDEX); + PP_DUMP(PP_CSC_COEF_0123); + PP_DUMP(PP_CSC_COEF_4); +} + +/*! + * @brief Set PP input address. + * @param ptr The pointer to the Y value of input + * @return Zero on success, others on failure + */ +int pp_ptr(unsigned long ptr) +{ + g_pp_cfg.ptr.y = ptr; + g_pp_cfg.ptr.u = g_pp_cfg.ptr.v = g_pp_cfg.ptr.qp = 0; + + return pphw_ptr(&g_pp_cfg); +} + +/*! + * @brief Enable or disable PP. + * @param flag Zero to disable PP, others to enable PP + * @return Zero on success, others on failure + */ +int pp_enable(int flag) +{ + return pphw_enable(flag); +} + +/*! + * @brief Get the display No. of last completed PP frame. + * @return The display No. of last completed PP frame. + */ +int pp_num_last(void) +{ + return (g_disp_num ? 0 : 1); +} + +/*! + * @brief Initialize PP. + * @param vout Pointer to _vout_data structure + * @return Zero on success, others on failure + */ +int pp_init(vout_data * vout) +{ + pphw_init(); + pphw_enable(0); + enable_irq(INT_EMMAPP); + return request_irq(INT_EMMAPP, pp_isr, 0, pp_dev, vout); +} + +/*! + * @brief Deinitialize PP. + * @param vout Pointer to _vout_data structure + */ +void pp_exit(vout_data * vout) +{ + disable_irq(INT_EMMAPP); + free_irq(INT_EMMAPP, vout); + pphw_enable(0); + pphw_exit(); +} + +/*! + * @brief Configure PP. + * @param vout Pointer to _vout_data structure + * @return Zero on success, others on failure + */ +int pp_cfg(vout_data * vout) +{ + if (!vout) + return -1; + + /* PP accepts YUV420 input only */ + if (vout->v2f.fmt.pix.pixelformat != V4L2_PIX_FMT_YUV420) { + pr_debug("unsupported pixel format.\n"); + return -1; + } + + g_pp_cfg.operation = 0; + + memset(g_pp_cfg.csc_table, 0, sizeof(g_pp_cfg.csc_table)); + + /* Convert output pixel format to PP required format */ + switch (vout->v4l2_fb.fmt.pixelformat) { + case V4L2_PIX_FMT_BGR32: + g_pp_cfg.red_width = 8; + g_pp_cfg.green_width = 8; + g_pp_cfg.blue_width = 8; + g_pp_cfg.red_offset = 8; + g_pp_cfg.green_offset = 16; + g_pp_cfg.blue_offset = 24; + g_pp_cfg.rgb_resolution = 32; + break; + case V4L2_PIX_FMT_RGB32: + g_pp_cfg.red_width = 8; + g_pp_cfg.green_width = 8; + g_pp_cfg.blue_width = 8; + g_pp_cfg.red_offset = 24; + g_pp_cfg.green_offset = 16; + g_pp_cfg.blue_offset = 8; + g_pp_cfg.rgb_resolution = 32; + break; + case V4L2_PIX_FMT_YUYV: + g_pp_cfg.red_width = 0; + g_pp_cfg.green_width = 0; + g_pp_cfg.blue_width = 0; + g_pp_cfg.red_offset = 0; + g_pp_cfg.green_offset = 0; + g_pp_cfg.blue_offset = PP_PIX_YUYV; + g_pp_cfg.rgb_resolution = 16; + break; + case V4L2_PIX_FMT_UYVY: + g_pp_cfg.red_width = 0; + g_pp_cfg.green_width = 0; + g_pp_cfg.blue_width = 0; + g_pp_cfg.red_offset = 0; + g_pp_cfg.green_offset = 0; + g_pp_cfg.blue_offset = PP_PIX_UYVY; + g_pp_cfg.rgb_resolution = 16; + break; + case V4L2_PIX_FMT_RGB565: + default: + g_pp_cfg.red_width = 5; + g_pp_cfg.green_width = 6; + g_pp_cfg.blue_width = 5; + g_pp_cfg.red_offset = 11; + g_pp_cfg.green_offset = 5; + g_pp_cfg.blue_offset = 0; + g_pp_cfg.rgb_resolution = 16; + break; + } + + if (vout->ipu_buf[0] != -1) + g_pp_cfg.ptr.y = + (unsigned int)vout->queue_buf_paddr[vout->ipu_buf[0]]; + else + g_pp_cfg.ptr.y = 0; + + g_pp_cfg.ptr.u = g_pp_cfg.ptr.v = g_pp_cfg.ptr.qp = 0; + + g_pp_cfg.dim.in.width = vout->v2f.fmt.pix.width; + g_pp_cfg.dim.in.height = vout->v2f.fmt.pix.height; + g_pp_cfg.dim.out.width = vout->crop_current.width; + g_pp_cfg.dim.out.height = vout->crop_current.height; + g_pp_cfg.dim.num.width = 0; + g_pp_cfg.dim.num.height = 0; + g_pp_cfg.dim.den.width = 0; + g_pp_cfg.dim.den.height = 0; + + if (scale_2d(&g_pp_cfg.dim)) { + pr_debug("unsupported resize ratio.\n"); + return -1; + } + + g_pp_cfg.dim.out.width = vout->crop_current.width; + g_pp_cfg.dim.out.height = vout->crop_current.height; + + g_pp_cfg.in_y_stride = 0; + if (set_output_addr(&g_pp_cfg, vout)) { + pr_debug("failed to set pp output address.\n"); + return -1; + } + + return pphw_cfg(&g_pp_cfg); +} + +irqreturn_t mxc_v4l2out_pp_in_irq_handler(int irq, void *dev_id); + +/*! + * @brief PP IRQ handler. + */ +static irqreturn_t pp_isr(int irq, void *dev_id) +{ + int status; + vout_data *vout = dev_id; + + status = pphw_isr(); + if ((status & 0x1) == 0) { /* Not frame complete interrupt */ + pr_debug("not pp frame complete interrupt\n"); + return IRQ_HANDLED; + } + + if (vout->v4l2_fb.flags == V4L2_FBUF_FLAG_OVERLAY) { + g_disp_num = g_disp_num ? 0 : 1; + g_pp_cfg.outptr = (unsigned int)vout->display_bufs[g_disp_num]; + pphw_outptr(&g_pp_cfg); + } + + return mxc_v4l2out_pp_in_irq_handler(irq, dev_id); +} + +/*! + * @brief Set PP output address. + * @param cfg Pointer to emma_pp_cfg structure + * @param vout Pointer to _vout_data structure + * @return Zero on success, others on failure + */ +static int set_output_addr(emma_pp_cfg * cfg, vout_data * vout) +{ + if (vout->v4l2_fb.flags == V4L2_FBUF_FLAG_OVERLAY) { + g_disp_num = 0; + cfg->outptr = (unsigned int)vout->display_bufs[g_disp_num]; + cfg->out_stride = vout->crop_current.width; + return 0; + } else { + struct fb_info *fb; + + fb = registered_fb[vout->output_fb_num[vout->cur_disp_output]]; + if (!fb) + return -1; + + cfg->outptr = fb->fix.smem_start; + cfg->outptr += vout->crop_current.top * fb->var.xres_virtual + * (fb->var.bits_per_pixel >> 3) + + vout->crop_current.left * (fb->var.bits_per_pixel >> 3); + cfg->out_stride = fb->var.xres_virtual; + + return 0; + } +} + +/*! + * @brief Get maximum common divisor. + * @param x First input value + * @param y Second input value + * @return Maximum common divisor of x and y + */ +static int gcd(int x, int y) +{ + int k; + + if (x < y) { + k = x; + x = y; + y = k; + } + + while ((k = x % y)) { + x = y; + y = k; + } + + return y; +} + +/*! + * @brief Get ratio. + * @param x First input value + * @param y Second input value + * @param den Denominator of the ratio (corresponding to y) + * @return Numerator of the ratio (corresponding to x) + */ +static int ratio(int x, int y, int *den) +{ + int g; + + if (!x || !y) + return 0; + + g = gcd(x, y); + *den = y / g; + + return x / g; +} + +/*! + * @brief Build PP coefficient entry + * Build one or more coefficient entries for PP coefficient table based + * on given coefficient. + * + * @param k The index of the coefficient in coefficient table + * @param coeff The weighting coefficient + * @param base The base of the coefficient + * @param nxt Number of pixels to be read + * + * @return The index of the next coefficient entry on success + * -1 on failure + */ +static int scale_0d(int k, int coeff, int base, int nxt) +{ + if (k >= PP_TBL_MAX) { + /* no more space in table */ + pr_debug("no space in scale table, k = %d\n", k); + return -1; + } + + coeff = ((coeff << BC_COEF) + (base >> 1)) / base; + + /* + * Valid values for weighting coefficient are 0, 2 to 30, and 31. + * A value of 31 is treated as 32 and therefore 31 is an + * invalid co-efficient. + */ + if (coeff >= SZ_COEF - 1) + coeff--; + else if (coeff == 1) + coeff++; + coeff = coeff << BC_NXT; + + if (nxt < SZ_NXT) { + coeff |= nxt; + coeff <<= 1; + coeff |= 1; + } else { + /* + * src inc field is 2 bit wide, for 4+, use special + * code 0:0:1 to prevent dest inc + */ + coeff |= PP_SKIP; + coeff <<= 1; + coeff |= 1; + nxt -= PP_SKIP; + do { + pr_debug("tbl = %03X\n", coeff); + scale_tbl[k++] = coeff; + coeff = (nxt > PP_SKIP) ? PP_SKIP : nxt; + coeff <<= 1; + } while ((nxt -= PP_SKIP) > 0); + } + pr_debug("tbl = %03X\n", coeff); + scale_tbl[k++] = coeff; + + return k; +} + +/* + * @brief Build PP coefficient table + * Build PP coefficient table for one dimension (width or height) + * based on given input and output resolution + * + * @param inv input resolution + * @param outv output resolution + * @param k index of free table entry + * + * @return The index of the next free coefficient entry on success + * -1 on failure + */ +static int scale_1d(int inv, int outv, int k) +{ + int v; /* overflow counter */ + int coeff, nxt; /* table output */ + + if (inv == outv) + return scale_0d(k, 1, 1, 1); /* force scaling */ + + if (inv * 4 < outv) { + pr_debug("upscale err: ratio should be in range 1:1 to 1:4\n"); + return -1; + } + + v = 0; + if (inv < outv) { + /* upscale: mix <= 2 input pixels per output pixel */ + do { + coeff = outv - v; + v += inv; + if (v >= outv) { + v -= outv; + nxt = 1; + } else + nxt = 0; + pr_debug("upscale: coeff = %d/%d nxt = %d\n", coeff, + outv, nxt); + k = scale_0d(k, coeff, outv, nxt); + if (k < 0) + return -1; + } while (v); + } else if (inv >= 2 * outv) { + /* PP doesn't support resize ratio > 2:1 except 4:1. */ + if ((inv != 2 * outv) && (inv != 4 * outv)) + return -1; + /* downscale: >=2:1 bilinear approximation */ + coeff = inv - 2 * outv; + v = 0; + nxt = 0; + do { + v += coeff; + nxt = 2; + while (v >= outv) { + v -= outv; + nxt++; + } + pr_debug("downscale: coeff = 1/2 nxt = %d\n", nxt); + k = scale_0d(k, 1, 2, nxt); + if (k < 0) + return -1; + } while (v); + } else { + /* downscale: bilinear */ + int in_pos_inc = 2 * outv; + int out_pos = inv; + int out_pos_inc = 2 * inv; + int init_carry = inv - outv; + int carry = init_carry; + + v = outv + in_pos_inc; + do { + coeff = v - out_pos; + out_pos += out_pos_inc; + carry += out_pos_inc; + for (nxt = 0; v < out_pos; nxt++) { + v += in_pos_inc; + carry -= in_pos_inc; + } + pr_debug("downscale: coeff = %d/%d nxt = %d\n", coeff, + in_pos_inc, nxt); + k = scale_0d(k, coeff, in_pos_inc, nxt); + if (k < 0) + return -1; + } while (carry != init_carry); + } + return k; +} + +/* + * @brief Build PP coefficient table + * Build PP coefficient table for one dimension (width or height) + * based on given input and output resolution. The given input + * and output resolution might be not supported due to hardware + * limits. In this case this functin rounds the input and output + * to closest possible values and return them to caller. + * + * @param inv input resolution, might be modified after the call + * @param outv output resolution, might be modified after the call + * @param k index of free table entry + * + * @return The index of the next free coefficient entry on success + * -1 on failure + */ +static int scale_1d_smart(int *inv, int *outv, int index) +{ + int len, num, den, retry; + static int num1, den1; + + if (!inv || !outv) + return -1; + + /* Both should be non-zero */ + if (!(*inv) || !(*outv)) + return -1; + + retry = SCALE_RETRY; + + do { + num = ratio(*inv, *outv, &den); + pr_debug("num = %d, den = %d\n", num, den); + if (!num) + continue; + + if (index != 0) { + /* + * We are now resizing height. Check to see if the + * resize ratio for width can be reused by height + */ + if ((num == num1) && (den == den1)) + return index; + } + + if ((len = scale_1d(num, den, index)) < 0) + /* increase output dimension to try another ratio */ + (*outv)++; + else { + if (index == 0) { + /* + * We are now resizing width. The same resize + * ratio may be reused by height, so save the + * ratio. + */ + num1 = num; + den1 = den; + } + return len; + } + } while (retry--); + + pr_debug("pp scale err\n"); + return -1; +} + +/* + * @brief Build PP coefficient table for both width and height + * Build PP coefficient table for both width and height based on + * given resizing ratios. + * + * @param sz Structure contains resizing ratio informations + * + * @return 0 on success, others on failure + */ +static int scale_2d(emma_pp_scale * sz) +{ + int inv, outv; + + /* horizontal resizing. parameter check - must provide in size */ + if (!sz->in.width) + return -1; + + /* Resizing based on num:den */ + inv = sz->num.width; + outv = sz->den.width; + + if ((g_hlen = scale_1d_smart(&inv, &outv, 0)) > 0) { + /* Resizing succeeded */ + sz->den.width = outv; + sz->out.width = (sz->in.width * outv) / inv; + } else { + /* Resizing based on in:out */ + inv = sz->in.width; + outv = sz->out.width; + + if ((g_hlen = scale_1d_smart(&inv, &outv, 0)) > 0) { + /* Resizing succeeded */ + sz->out.width = outv; + sz->num.width = ratio(sz->in.width, sz->out.width, + &sz->den.width); + } else + return -1; + } + + sz->out.width &= ~1; + + /* vertical resizing. parameter check - must provide in size */ + if (!sz->in.height) + return -1; + + /* Resizing based on num:den */ + inv = sz->num.height; + outv = sz->den.height; + + if ((g_vlen = scale_1d_smart(&inv, &outv, g_hlen)) > 0) { + /* Resizing succeeded */ + sz->den.height = outv; + sz->out.height = (sz->in.height * outv) / inv; + } else { + /* Resizing based on in:out */ + inv = sz->in.height; + outv = sz->out.height; + + if ((g_vlen = scale_1d_smart(&inv, &outv, g_hlen)) > 0) { + /* Resizing succeeded */ + sz->out.height = outv; + sz->num.height = ratio(sz->in.height, sz->out.height, + &sz->den.height); + } else + return -1; + } + + return 0; +} + +/*! + * @brief Set PP resizing registers. + * @param sz Pointer to pp scaling structure + * @return Zero on success, others on failure + */ +static int pphw_scale(emma_pp_scale * sz) +{ + __raw_writel((sz->out.width << 16) | sz->out.height, + PP_DEST_IMAGE_SIZE); + __raw_writel(((g_hlen - 1) << 16) | (g_vlen == + g_hlen ? 0 : (g_hlen << 8)) | + (g_vlen - 1), PP_RESIZE_INDEX); + for (g_hlen = 0; g_hlen < g_vlen; g_hlen++) + __raw_writel(scale_tbl[g_hlen], + PP_RESIZE_COEF_TBL + g_hlen * 4); + + return 0; +} + +/*! + * @brief Reset PP. + * @return Zero on success, others on failure + */ +static int pphw_reset(void) +{ + int i; + + __raw_writel(0x100, PP_CNTL); + + /* timeout */ + for (i = 0; i < 1000; i++) { + if (!(__raw_readl(PP_CNTL) & 0x100)) { + pr_debug("pp reset over\n"); + break; + } + } + + /* check reset value */ + if (__raw_readl(PP_CNTL) != 0x876) { + pr_debug("pp reset value err = 0x%08X\n", __raw_readl(PP_CNTL)); + return -1; + } + + return 0; +} + +/*! + * @brief Enable or disable PP. + * @param flag Zero to disable PP, others to enable PP + * @return Zero on success, others on failure + */ +static int pphw_enable(int flag) +{ + int ret = 0; + + if (flag) + __raw_writel(__raw_readl(PP_CNTL) | 1, PP_CNTL); + else + ret = pphw_reset(); + + return ret; +} + +/*! + * @brief Set PP input address. + * @param cfg The pointer to PP configuration parameter + * @return Zero on success, others on failure + */ +static int pphw_ptr(emma_pp_cfg * cfg) +{ + if (!cfg->ptr.u) { + int size; + + /* yuv - packed */ + size = PP_CALC_Y_SIZE(cfg); + cfg->ptr.u = cfg->ptr.y + size; + cfg->ptr.v = cfg->ptr.u + (size >> 2); + + /* yuv packed with qp appended */ + if (!cfg->ptr.qp) + cfg->ptr.qp = cfg->ptr.v + (size >> 2); + } + __raw_writel(cfg->ptr.y, PP_SOURCE_Y_PTR); + __raw_writel(cfg->ptr.u, PP_SOURCE_CB_PTR); + __raw_writel(cfg->ptr.v, PP_SOURCE_CR_PTR); + __raw_writel(cfg->ptr.qp, PP_QUANTIZER_PTR); + + return 0; +} + +/*! + * @brief Set PP output address. + * @param cfg The pointer to PP configuration parameter + * @return Zero on success, others on failure + */ +static int pphw_outptr(emma_pp_cfg * cfg) +{ + __raw_writel(cfg->outptr, PP_DEST_RGB_PTR); + return 0; +} + +/*! + * @brief Configuration PP. + * @param cfg The pointer to PP configuration parameter + * @return Zero on success, others on failure + */ +static int pphw_cfg(emma_pp_cfg * cfg) +{ + int rt; + register int r; + + pphw_scale(&cfg->dim); + + if (!cfg->in_y_stride) + cfg->in_y_stride = cfg->dim.in.width; + + if (!cfg->out_stride) + cfg->out_stride = cfg->dim.out.width; + + r = __raw_readl(PP_CNTL) & ~EN_MASK; + + /* config parms */ + r |= cfg->operation & EN_MASK; + if (cfg->operation & EN_MACROBLOCK) { + /* Macroblock Mode */ + r |= 0x0200; + __raw_writel(0x06, PP_INTRCNTL); + } else { + /* Frame mode */ + __raw_writel(0x05, PP_INTRCNTL); + } + + if (cfg->red_width | cfg->green_width | cfg->blue_width) { + /* color conversion to be performed */ + r |= EN_CSC; + if (!(cfg->red_offset | cfg->green_offset)) { + /* auto offset B:G:R LSb to Msb */ + cfg->green_offset = cfg->blue_offset + cfg->blue_width; + cfg->red_offset = cfg->green_offset + cfg->green_width; + } + if (!cfg->rgb_resolution) { + /* derive minimum resolution required */ + int w, w2; + + w = cfg->red_offset + cfg->red_width; + w2 = cfg->blue_offset + cfg->blue_width; + if (w < w2) + w = w2; + w2 = cfg->green_offset + cfg->green_width; + if (w < w2) + w = w2; + if (w > 16) + w = 24; + else if (w > 8) + w = 16; + else + w = 8; + cfg->rgb_resolution = w; + } + /* 00,11 - 32 bpp, 10 - 16 bpp, 01 - 8 bpp */ + r &= ~0xC00; + if (cfg->rgb_resolution < 32) + r |= (cfg->rgb_resolution << 7); + __raw_writel((cfg->red_offset << 26) | + (cfg->green_offset << 21) | + (cfg->blue_offset << 16) | + (cfg->red_width << 8) | + (cfg->green_width << 4) | + cfg->blue_width, PP_DEST_FRAME_FMT_CNTL); + } else { + /* add YUV422 formatting */ + static const unsigned int _422[] = { + 0x62000888, + 0x60100888, + 0x43080888, + 0x41180888 + }; + + __raw_writel(_422[(cfg->blue_offset >> 3) & 3], + PP_DEST_FRAME_FMT_CNTL); + cfg->rgb_resolution = 16; + r &= ~0xC00; + r |= (cfg->rgb_resolution << 7); + } + + /* add csc formatting */ + if (!cfg->csc_table[1]) { + static const unsigned short _csc[][6] = { + {0x80, 0xb4, 0x2c, 0x5b, 0x0e4, 0}, + {0x95, 0xcc, 0x32, 0x68, 0x104, 1}, + {0x80, 0xca, 0x18, 0x3c, 0x0ec, 0}, + {0x95, 0xe5, 0x1b, 0x44, 0x10e, 1}, + }; + memcpy(cfg->csc_table, _csc[cfg->csc_table[0]], + sizeof(_csc[0])); + } + __raw_writel((cfg->csc_table[0] << 24) | + (cfg->csc_table[1] << 16) | + (cfg->csc_table[2] << 8) | + cfg->csc_table[3], PP_CSC_COEF_0123); + __raw_writel((cfg->csc_table[5] ? (1 << 9) : 0) | cfg->csc_table[4], + PP_CSC_COEF_4); + + __raw_writel(r, PP_CNTL); + + pphw_ptr(cfg); + pphw_outptr(cfg); + + /* + * #MB in a row = input_width / 16pix + * 1 byte per QP per MB + * QP must be formatted to be 4-byte aligned + * YUV lines are to be 4-byte aligned as well + * So Y is 8 byte aligned, as U = V = Y/2 for 420 + * MPEG MBs are 16x16 anyway + */ + __raw_writel((cfg->dim.in.width << 16) | cfg->dim.in.height, + PP_PROCESS_FRAME_PARA); + __raw_writel(cfg->in_y_stride | (PP_CALC_QP_WIDTH(cfg) << 16), + PP_SOURCE_FRAME_WIDTH); + + /* in bytes */ + rt = cfg->rgb_resolution >> 3; + if (rt == 3) + rt = 4; + __raw_writel(cfg->out_stride * rt, PP_DEST_DISPLAY_WIDTH); + + pp_dump(); + return 0; +} + +/*! + * @brief Check PP interrupt status. + * @return PP interrupt status + */ +static int pphw_isr(void) +{ + unsigned long status; + + pr_debug("pp: in isr.\n"); + status = __raw_readl(PP_INTRSTATUS) & 7; + if (!status) { + pr_debug("pp: not my isr err.\n"); + return status; + } + + if (status & 4) + pr_debug("pp: isr state error.\n"); + + /* clear interrupt status */ + __raw_writel(status, PP_INTRSTATUS); + + return status; +} + +static struct clk *emma_clk; + +/*! + * @brief PP module clock enable + */ +static void pphw_init(void) +{ + emma_clk = clk_get(NULL, "emma_clk"); + clk_enable(emma_clk); +} + +/*! + * @brief PP module clock disable + */ +static void pphw_exit(void) +{ + clk_disable(emma_clk); + clk_put(emma_clk); +} diff --git a/drivers/media/video/mxc/output/mx27_pp.h b/drivers/media/video/mxc/output/mx27_pp.h new file mode 100644 index 000000000000..7bc65ddda3a1 --- /dev/null +++ b/drivers/media/video/mxc/output/mx27_pp.h @@ -0,0 +1,180 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mx27_pp.h + * + * @brief Header file for MX27 V4L2 Video Output Driver + * + * @ingroup MXC_V4L2_OUTPUT + */ +#ifndef __MX27_PP_H__ +#define __MX27_PP_H__ + +#include "mxc_v4l2_output.h" + +/* PP register definitions */ +#define PP_REG(ofs) (IO_ADDRESS(EMMA_BASE_ADDR) - 0x400 + ofs) + +/* Register offsets */ +#define PP_CNTL PP_REG(0x00) +#define PP_INTRCNTL PP_REG(0x04) +#define PP_INTRSTATUS PP_REG(0x08) +#define PP_SOURCE_Y_PTR PP_REG(0x0C) +#define PP_SOURCE_CB_PTR PP_REG(0x10) +#define PP_SOURCE_CR_PTR PP_REG(0x14) +#define PP_DEST_RGB_PTR PP_REG(0x18) +#define PP_QUANTIZER_PTR PP_REG(0x1C) +#define PP_PROCESS_FRAME_PARA PP_REG(0x20) +#define PP_SOURCE_FRAME_WIDTH PP_REG(0x24) +#define PP_DEST_DISPLAY_WIDTH PP_REG(0x28) +#define PP_DEST_IMAGE_SIZE PP_REG(0x2C) +#define PP_DEST_FRAME_FMT_CNTL PP_REG(0x30) +#define PP_RESIZE_INDEX PP_REG(0x34) +#define PP_CSC_COEF_0123 PP_REG(0x38) +#define PP_CSC_COEF_4 PP_REG(0x3C) +#define PP_RESIZE_COEF_TBL PP_REG(0x100) + +/* resize table dimensions + dest pixel index left/32 right/32 #src pixels to read + 0 [BC_COEF] [BC_COEF] [BC_NXT] + : + pp_tbl_max-1 +*/ +#define BC_NXT 2 +#define BC_COEF 5 +#define SZ_COEF (1 << BC_COEF) +#define SZ_NXT (1 << BC_NXT) + +/* PP operations */ +#define EN_DEBLOCK 0x02 +#define EN_DERING 0x04 +#define EN_CSC 0x10 +#define EN_MACROBLOCK 0x20 +#define EN_DEF 0x16 +#define EN_MASK 0x36 +#define EN_BIGDATA 0x1000 +#define EN_BIGQP 0x2000 + +/* PP CSC tables */ +#define CSC_TBL_NONE 0x80 +#define CSC_TBL_REUSE 0x81 +#define CSC_TBL_A1 0x00 +#define CSC_TBL_A0 0x20 +#define CSC_TBL_B1 0x40 +#define CSC_TBL_B0 0x60 +/* converts from 4 decimal fixed point to hw setting & vice versa */ +#define PP_CSC_FP4_2_HW(coeff) ((((coeff) << 7) + 5000) / 10000) +#define PP_CSC_HW_2_FP4(coeff) ((((coeff) * 10000) + 64) >> 7) + +#define PP_PIX_YUYV 0 +#define PP_PIX_YVYU 8 +#define PP_PIX_UYVY 16 +#define PP_PIX_VYUY 24 + +/* PP size & width calculation macros */ +#define PP_CALC_QP_WIDTH(cfg) \ + (!((cfg)->operation & (EN_DEBLOCK | EN_DERING)) ? 0 : \ + (((((cfg)->dim.in.width + 15) >> 4) + 3) & ~3)) +#define PP_CALC_Y_SIZE(cfg) \ + ((cfg)->in_y_stride * (cfg)->dim.in.height) +#define PP_CALC_CH_SIZE(cfg) (PP_CALC_Y_SIZE(cfg) >> 2) +#define PP_CALC_BPP(cfg) \ + ((cfg)->rgb_resolution > 16 ? 4 : ((cfg)->rgb_resolution >> 3)) +#define PP_CALC_YUV_SIZE(cfg) \ + ((PP_CALC_Y_SIZE(cfg) * 3) >> 1) +#define PP_CALC_QP_SIZE(cfg) \ + (PP_CALC_QP_WIDTH(cfg) * (((cfg)->dim.in.height + 15) >> 4)) +#define PP_CALC_DEST_WIDTH(cfg) \ + (((cfg)->out_stride & ~1) * PP_CALC_BPP(cfg)) +#define PP_CALC_DEST_SIZE(cfg) \ + ((cfg)->dim.out.height * PP_CALC_DEST_WIDTH(cfg)) + +/* + * physical addresses for bus mastering + * v=0 -> yuv packed + * v=0 & qp=0 -> yuv packed with qp appended + */ +typedef struct _emma_pp_ptr { + unsigned int y; /* Y data (line align8) */ + unsigned int u; /* U data (line align4) */ + unsigned int v; /* V data (line align4) */ + unsigned int qp; /* Quantization (line align4) */ +} emma_pp_ptr; + +typedef struct _emma_pp_size { + int width; + int height; +} emma_pp_size; + +/* + * if num.width != 0 + * resize ratio = num.width : den.width + * else + * resize ratio = in.width : out.width + * same for height + */ +typedef struct _emma_pp_scale { + emma_pp_size num; + emma_pp_size den; + emma_pp_size in; /* clip */ + emma_pp_size out; /* 0 -> same as in */ +} emma_pp_scale; + +typedef struct _emma_pp_cfg { + unsigned char operation; /* OR of EN_xx defines */ + + /* + * input color coeff + * fixed pt 8 bits, steps of 1/128 + * csc[5] is 1 or 0 to indicate Y + 16 + * csc[0] is matrix id 0-3 while csc[1-5]=0 + */ + unsigned short csc_table[6]; + + /* + * Output color (shade width, shade offset, pixel resolution) + * Eg. 16bpp RGB565 resolution, the values could be: + * red_width = 5, green_width = 6, blue_width = 6 + * red_offset = 11, green_offset = 5, blue_offset = 0 (defaults) + * rgb_resolution = 16 (default) + * For YUV422: xxx_width=0, blue_offset=PP_PIX_xxx + */ + unsigned short red_width; + unsigned short green_width; + unsigned short blue_width; + /* if offsets are 0, the offsets are by width LSb to MSb B:G:R */ + unsigned short red_offset; + unsigned short blue_offset; + unsigned short green_offset; + /* if resolution is 0, the minimum for the sum of widths is chosen */ + short rgb_resolution; /* 8,16,24 bpp only */ + + emma_pp_ptr ptr; /* dma buffer pointers */ + unsigned int outptr; /* RGB/YUV output */ + emma_pp_scale dim; /* in/out dimensions */ + + /* pixels between two adjacent input Y rows */ + unsigned short in_y_stride; /* 0 = in_width */ + /* PIXELS between two adjacent output rows */ + unsigned short out_stride; /* 0 = out_width */ +} emma_pp_cfg; + +int pp_ptr(unsigned long ptr); +int pp_enable(int flag); +int pp_cfg(vout_data * vout); +int pp_init(vout_data * vout); +int pp_num_last(void); +void pp_exit(vout_data * vout); + +#endif /* __MX27_PP_H__ */ diff --git a/drivers/media/video/mxc/output/mx27_v4l2_output.c b/drivers/media/video/mxc/output/mx27_v4l2_output.c new file mode 100644 index 000000000000..144454a4faca --- /dev/null +++ b/drivers/media/video/mxc/output/mx27_v4l2_output.c @@ -0,0 +1,1446 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mx27_v4l2_output.c + * + * @brief MX27 V4L2 Video Output Driver + * + * Video4Linux2 Output Device using MX27 eMMA Post-processing functionality. + * + * @ingroup MXC_V4L2_OUTPUT + */ +#include <linux/module.h> +#include <linux/init.h> +#include <linux/fs.h> +#include <linux/delay.h> +#include <linux/fb.h> +#include <linux/pci.h> +#include <linux/platform_device.h> +#include <media/v4l2-dev.h> +#include <asm/poll.h> +#include <asm/io.h> +#include <asm/semaphore.h> + +#include "mxc_v4l2_output.h" +#include "mx27_pp.h" +#include "../drivers/video/mxc/mx2fb.h" + +#define SDC_FG_FB_FORMAT V4L2_PIX_FMT_RGB565 + +struct v4l2_output mxc_outputs[1] = { + { + .index = 0, + .name = "DISP0 Video Out", + .type = V4L2_OUTPUT_TYPE_ANALOG, /* not really correct, + but no other choice */ + .audioset = 0, + .modulator = 0, + .std = V4L2_STD_UNKNOWN}, +}; + +static int video_nr = 16; +static spinlock_t g_lock = SPIN_LOCK_UNLOCKED; +vout_data *g_vout; + +/* debug counters */ +uint32_t g_irq_cnt; +uint32_t g_buf_output_cnt; +uint32_t g_buf_q_cnt; +uint32_t g_buf_dq_cnt; + +#ifdef CONFIG_VIDEO_MXC_OUTPUT_FBSYNC +static uint32_t g_output_fb = -1; +static uint32_t g_fb_enabled = 0; +static uint32_t g_pp_ready = 0; + +static int fb_event_notify(struct notifier_block *self, + unsigned long action, void *data) +{ + struct fb_event *event = data; + struct fb_info *info = event->info; + unsigned long lock_flags; + int blank, i; + + for (i = 0; i < num_registered_fb; i++) + if (registered_fb[i] == info) + break; + + /* + * Check if the event is sent by the framebuffer in which + * the video is displayed. + */ + if (i != g_output_fb) + return 0; + + switch (action) { + case FB_EVENT_BLANK: + blank = *(int *)event->data; + spin_lock_irqsave(&g_lock, lock_flags); + g_fb_enabled = !blank; + if (blank && g_pp_ready) { + if (pp_enable(1)) + pr_debug("unable to enable PP\n"); + g_pp_ready = 0; + } + spin_unlock_irqrestore(&g_lock, lock_flags); + break; + case FB_EVENT_MXC_EOF: + spin_lock_irqsave(&g_lock, lock_flags); + g_fb_enabled = 1; + if (g_pp_ready) { + if (pp_enable(1)) + pr_debug("unable to enable PP\n"); + g_pp_ready = 0; + } + spin_unlock_irqrestore(&g_lock, lock_flags); + break; + } + + return 0; +} + +static struct notifier_block fb_event_notifier = { + .notifier_call = fb_event_notify, +}; + +static struct notifier_block mx2fb_event_notifier = { + .notifier_call = fb_event_notify, +}; +#endif + +#define QUEUE_SIZE (MAX_FRAME_NUM + 1) +static __inline int queue_size(v4l_queue * q) +{ + if (q->tail >= q->head) + return (q->tail - q->head); + else + return ((q->tail + QUEUE_SIZE) - q->head); +} + +static __inline int queue_buf(v4l_queue * q, int idx) +{ + if (((q->tail + 1) % QUEUE_SIZE) == q->head) + return -1; /* queue full */ + q->list[q->tail] = idx; + q->tail = (q->tail + 1) % QUEUE_SIZE; + return 0; +} + +static __inline int dequeue_buf(v4l_queue * q) +{ + int ret; + if (q->tail == q->head) + return -1; /* queue empty */ + ret = q->list[q->head]; + q->head = (q->head + 1) % QUEUE_SIZE; + return ret; +} + +static __inline int peek_next_buf(v4l_queue * q) +{ + if (q->tail == q->head) + return -1; /* queue empty */ + return q->list[q->head]; +} + +static __inline unsigned long get_jiffies(struct timeval *t) +{ + struct timeval cur; + + if (t->tv_usec >= 1000000) { + t->tv_sec += t->tv_usec / 1000000; + t->tv_usec = t->tv_usec % 1000000; + } + + do_gettimeofday(&cur); + if ((t->tv_sec < cur.tv_sec) + || ((t->tv_sec == cur.tv_sec) && (t->tv_usec < cur.tv_usec))) + return jiffies; + + if (t->tv_usec < cur.tv_usec) { + cur.tv_sec = t->tv_sec - cur.tv_sec - 1; + cur.tv_usec = t->tv_usec + 1000000 - cur.tv_usec; + } else { + cur.tv_sec = t->tv_sec - cur.tv_sec; + cur.tv_usec = t->tv_usec - cur.tv_usec; + } + + return jiffies + timeval_to_jiffies(&cur); +} + +/*! + * Private function to free buffers + * + * @param bufs_paddr Array of physical address of buffers to be freed + * + * @param bufs_vaddr Array of virtual address of buffers to be freed + * + * @param num_buf Number of buffers to be freed + * + * @param size Size for each buffer to be free + * + * @return status 0 success. + */ +static int mxc_free_buffers(dma_addr_t bufs_paddr[], void *bufs_vaddr[], + int num_buf, int size) +{ + int i; + + for (i = 0; i < num_buf; i++) { + if (bufs_vaddr[i] != 0) { + dma_free_coherent(0, size, bufs_vaddr[i], + bufs_paddr[i]); + pr_debug("freed @ paddr=0x%08X\n", (u32) bufs_paddr[i]); + bufs_paddr[i] = 0; + bufs_vaddr[i] = NULL; + } + } + return 0; +} + +/*! + * Private function to allocate buffers + * + * @param bufs_paddr Output array of physical address of buffers allocated + * + * @param bufs_vaddr Output array of virtual address of buffers allocated + * + * @param num_buf Input number of buffers to allocate + * + * @param size Input size for each buffer to allocate + * + * @return status -0 Successfully allocated a buffer, -ENOBUFS failed. + */ +static int mxc_allocate_buffers(dma_addr_t bufs_paddr[], void *bufs_vaddr[], + int num_buf, int size) +{ + int i; + + for (i = 0; i < num_buf; i++) { + bufs_vaddr[i] = dma_alloc_coherent(0, size, + &bufs_paddr[i], + GFP_DMA | GFP_KERNEL); + + if (bufs_vaddr[i] == 0) { + mxc_free_buffers(bufs_paddr, bufs_vaddr, i, size); + pr_debug("dma_alloc_coherent failed.\n"); + return -ENOBUFS; + } + pr_debug("allocated @ paddr=0x%08X, size=%d.\n", + (u32) bufs_paddr[i], size); + } + + return 0; +} + +static void mxc_v4l2out_timer_handler(unsigned long arg) +{ + int index; + unsigned long timeout; + unsigned long lock_flags; + vout_data *vout = (vout_data *) arg; + + pr_debug("timer handler: %lu\n", jiffies); + + spin_lock_irqsave(&g_lock, lock_flags); + + if ((vout->state == STATE_STREAM_OFF) + || (vout->state == STATE_STREAM_STOPPING)) { + pr_debug("stream has stopped\n"); + goto exit0; + } + + /* + * If timer occurs before PP h/w is ready, then set the state to + * paused and the timer will be set again when next buffer is queued + * or PP completes. + */ + if (vout->ipu_buf[0] != -1) { + pr_debug("buffer is busy\n"); + vout->state = STATE_STREAM_PAUSED; + goto exit0; + } + + /* Dequeue buffer and pass to PP */ + index = dequeue_buf(&vout->ready_q); + if (index == -1) { /* no buffers ready, should never occur */ + pr_debug("mxc_v4l2out: timer - no queued buffers ready\n"); + goto exit0; + } + + g_buf_dq_cnt++; + vout->frame_count++; + vout->ipu_buf[0] = index; + + if (pp_ptr((unsigned int)vout->queue_buf_paddr[index])) { + pr_debug("unable to update buffer\n"); + goto exit0; + } +#ifdef CONFIG_VIDEO_MXC_OUTPUT_FBSYNC + if (g_fb_enabled && (vout->v4l2_fb.flags != V4L2_FBUF_FLAG_OVERLAY)) + g_pp_ready = 1; + else if (pp_enable(1)) { + pr_debug("unable to enable PP\n"); + goto exit0; + } +#else + if (pp_enable(1)) { + pr_debug("unable to enable PP\n"); + goto exit0; + } +#endif + pr_debug("enabled index %d\n", index); + + /* Setup timer for next buffer */ + index = peek_next_buf(&vout->ready_q); + pr_debug("next index %d\n", index); + if (index != -1) { + /* if timestamp is 0, then default to 30fps */ + if ((vout->v4l2_bufs[index].timestamp.tv_sec == 0) + && (vout->v4l2_bufs[index].timestamp.tv_usec == 0)) + timeout = + vout->start_jiffies + vout->frame_count * HZ / 30; + else + timeout = + get_jiffies(&vout->v4l2_bufs[index].timestamp); + + if (jiffies >= timeout) { + pr_debug("warning: timer timeout already expired.\n"); + } + + if (mod_timer(&vout->output_timer, timeout)) + pr_debug("warning: timer was already set\n"); + + pr_debug("timer handler next schedule: %lu\n", timeout); + } else { + vout->state = STATE_STREAM_PAUSED; + pr_debug("timer handler paused\n"); + } + + exit0: + spin_unlock_irqrestore(&g_lock, lock_flags); +} + +irqreturn_t mxc_v4l2out_pp_in_irq_handler(int irq, void *dev_id) +{ + int last_buf; + int index; + unsigned long timeout; + unsigned long lock_flags; + vout_data *vout = dev_id; + + spin_lock_irqsave(&g_lock, lock_flags); + + g_irq_cnt++; + + if ((vout->state == STATE_STREAM_OFF) + || (vout->state == STATE_STREAM_STOPPING)) { + spin_unlock_irqrestore(&g_lock, lock_flags); + return IRQ_HANDLED; + } + + if (vout->v4l2_fb.flags == V4L2_FBUF_FLAG_OVERLAY) { + struct fb_gwinfo gwinfo; + + gwinfo.enabled = 1; + gwinfo.alpha_value = 255; + gwinfo.ck_enabled = 0; + gwinfo.xpos = vout->crop_current.left; + gwinfo.ypos = vout->crop_current.top; + gwinfo.base = (unsigned long)vout->display_bufs[pp_num_last()]; + gwinfo.xres = vout->crop_current.width; + gwinfo.yres = vout->crop_current.height; + gwinfo.xres_virtual = vout->crop_current.width; + gwinfo.vs_reversed = 0; + + mx2_gw_set(&gwinfo); + } + + /* Process previous buffer */ + last_buf = vout->ipu_buf[0]; + pr_debug("last_buf %d g_irq_cnt %d\n", last_buf, g_irq_cnt); + if (last_buf != -1) { + g_buf_output_cnt++; + vout->v4l2_bufs[last_buf].flags = V4L2_BUF_FLAG_DONE; + queue_buf(&vout->done_q, last_buf); + vout->ipu_buf[0] = -1; + wake_up_interruptible(&vout->v4l_bufq); + } + + /* Setup timer for next buffer, when stream has been paused */ + if ((vout->state == STATE_STREAM_PAUSED) + && ((index = peek_next_buf(&vout->ready_q)) != -1)) { + pr_debug("next index %d\n", index); + /* if timestamp is 0, then default to 30fps */ + if ((vout->v4l2_bufs[index].timestamp.tv_sec == 0) + && (vout->v4l2_bufs[index].timestamp.tv_usec == 0)) + timeout = + vout->start_jiffies + vout->frame_count * HZ / 30; + else + timeout = + get_jiffies(&vout->v4l2_bufs[index].timestamp); + + if (jiffies >= timeout) { + pr_debug("warning: timer timeout already expired.\n"); + } + + vout->state = STATE_STREAM_ON; + + if (mod_timer(&vout->output_timer, timeout)) + pr_debug("warning: timer was already set\n"); + + pr_debug("timer handler next schedule: %lu\n", timeout); + } + + spin_unlock_irqrestore(&g_lock, lock_flags); + + return IRQ_HANDLED; +} + +/*! + * Start the output stream + * + * @param vout structure vout_data * + * + * @return status 0 Success + */ +static int mxc_v4l2out_streamon(vout_data * vout) +{ + unsigned long timeout; + int index; + + if (!vout) + return -EINVAL; + + if (vout->state != STATE_STREAM_OFF) + return -EBUSY; + + if (queue_size(&vout->ready_q) < 1) { + pr_debug("no buffers queued yet!\n"); + return -EINVAL; + } + + vout->ipu_buf[0] = -1; + + if (vout->v4l2_fb.flags == V4L2_FBUF_FLAG_OVERLAY) { + /* Free previously allocated buffer */ + mxc_free_buffers(vout->display_bufs, vout->display_bufs_vaddr, + 2, vout->display_buf_size); + /* Allocate buffers for foreground */ + if (mxc_allocate_buffers(vout->display_bufs, + vout->display_bufs_vaddr, 2, + vout->display_buf_size) < 0) { + pr_debug("unable to allocate SDC FG buffers\n"); + return -ENOMEM; + } + } + + /* Configure PP */ + if (pp_cfg(vout)) { + /* Free previously allocated buffer */ + mxc_free_buffers(vout->display_bufs, vout->display_bufs_vaddr, + 2, vout->display_buf_size); + pr_debug("failed to config PP.\n"); + return -EINVAL; + } +#ifdef CONFIG_VIDEO_MXC_OUTPUT_FBSYNC + g_output_fb = vout->output_fb_num[vout->cur_disp_output]; + g_fb_enabled = 0; + g_pp_ready = 0; + fb_register_client(&fb_event_notifier); + mx2fb_register_client(&mx2fb_event_notifier); +#endif + vout->frame_count = 0; + vout->state = STATE_STREAM_ON; + index = peek_next_buf(&vout->ready_q); + + /* if timestamp is 0, then default to 30fps */ + if ((vout->v4l2_bufs[index].timestamp.tv_sec == 0) + && (vout->v4l2_bufs[index].timestamp.tv_usec == 0)) + timeout = jiffies; + else + timeout = get_jiffies(&vout->v4l2_bufs[index].timestamp); + + if (jiffies >= timeout) { + pr_debug("warning: timer timeout already expired.\n"); + } + + vout->start_jiffies = vout->output_timer.expires = timeout; + pr_debug("STREAMON:Add timer %d timeout @ %lu jiffies, current = %lu\n", + index, timeout, jiffies); + add_timer(&vout->output_timer); + + return 0; +} + +/*! + * Shut down the voutera + * + * @param vout structure vout_data * + * + * @return status 0 Success + */ +static int mxc_v4l2out_streamoff(vout_data * vout) +{ + int i, retval = 0; + unsigned long lock_flag = 0; + + if (!vout) + return -EINVAL; + + if (vout->state == STATE_STREAM_OFF) { + return 0; + } + + spin_lock_irqsave(&g_lock, lock_flag); + + del_timer(&vout->output_timer); + pp_enable(0); /* Disable PP */ + + if (vout->state == STATE_STREAM_ON) { + vout->state = STATE_STREAM_STOPPING; + } + + spin_unlock_irqrestore(&g_lock, lock_flag); + + vout->ready_q.head = vout->ready_q.tail = 0; + vout->done_q.head = vout->done_q.tail = 0; + for (i = 0; i < vout->buffer_cnt; i++) { + vout->v4l2_bufs[i].flags = 0; + vout->v4l2_bufs[i].timestamp.tv_sec = 0; + vout->v4l2_bufs[i].timestamp.tv_usec = 0; + } + + vout->state = STATE_STREAM_OFF; + + if (vout->v4l2_fb.flags == V4L2_FBUF_FLAG_OVERLAY) { + struct fb_gwinfo gwinfo; + + /* Disable graphic window */ + gwinfo.enabled = 0; + mx2_gw_set(&gwinfo); + } +#ifdef CONFIG_VIDEO_MXC_OUTPUT_FBSYNC + g_output_fb = -1; + g_fb_enabled = 0; + g_pp_ready = 0; + fb_unregister_client(&fb_event_notifier); + mx2fb_unregister_client(&mx2fb_event_notifier); +#endif + + mxc_free_buffers(vout->display_bufs, vout->display_bufs_vaddr, + 2, vout->display_buf_size); + + return retval; +} + +/* + * Valid whether the palette is supported + * + * @param palette V4L2_PIX_FMT_RGB565, V4L2_PIX_FMT_BGR24 or V4L2_PIX_FMT_BGR32 + * + * @return 1 if supported, 0 if failed + */ +static inline int valid_mode(u32 palette) +{ + return (palette == V4L2_PIX_FMT_YUV420); +} + +/* + * Returns bits per pixel for given pixel format + * + * @param pixelformat V4L2_PIX_FMT_RGB565, V4L2_PIX_FMT_BGR24 or V4L2_PIX_FMT_BGR32 + * + * @return bits per pixel of pixelformat + */ +static u32 fmt_to_bpp(u32 pixelformat) +{ + u32 bpp; + + switch (pixelformat) { + case V4L2_PIX_FMT_RGB565: + bpp = 16; + break; + case V4L2_PIX_FMT_BGR24: + case V4L2_PIX_FMT_RGB24: + bpp = 24; + break; + case V4L2_PIX_FMT_BGR32: + case V4L2_PIX_FMT_RGB32: + bpp = 32; + break; + default: + bpp = 8; + break; + } + return bpp; +} + +/* + * V4L2 - Handles VIDIOC_G_FMT Ioctl + * + * @param vout structure vout_data * + * + * @param v4l2_format structure v4l2_format * + * + * @return status 0 success, EINVAL failed + */ +static int mxc_v4l2out_g_fmt(vout_data * vout, struct v4l2_format *f) +{ + if (f->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) { + return -EINVAL; + } + *f = vout->v2f; + return 0; +} + +/* + * V4L2 - Handles VIDIOC_S_FMT Ioctl + * + * @param vout structure vout_data * + * + * @param v4l2_format structure v4l2_format * + * + * @return status 0 success, EINVAL failed + */ +static int mxc_v4l2out_s_fmt(vout_data * vout, struct v4l2_format *f) +{ + int retval = 0; + u32 size = 0; + u32 bytesperline; + + if (f->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) { + retval = -EINVAL; + goto err0; + } + if (!valid_mode(f->fmt.pix.pixelformat)) { + pr_debug("pixel format not supported\n"); + retval = -EINVAL; + goto err0; + } + + bytesperline = (f->fmt.pix.width * fmt_to_bpp(f->fmt.pix.pixelformat)) / + 8; + if (f->fmt.pix.bytesperline < bytesperline) { + f->fmt.pix.bytesperline = bytesperline; + } else { + bytesperline = f->fmt.pix.bytesperline; + } + + switch (f->fmt.pix.pixelformat) { + case V4L2_PIX_FMT_YUV422P: + /* byteperline for YUV planar formats is for + Y plane only */ + size = bytesperline * f->fmt.pix.height * 2; + break; + case V4L2_PIX_FMT_YUV420: + size = (bytesperline * f->fmt.pix.height * 3) / 2; + break; + default: + size = bytesperline * f->fmt.pix.height; + break; + } + + /* Return the actual size of the image to the app */ + f->fmt.pix.sizeimage = size; + + vout->v2f.fmt.pix.sizeimage = size; + vout->v2f.fmt.pix.width = f->fmt.pix.width; + vout->v2f.fmt.pix.height = f->fmt.pix.height; + vout->v2f.fmt.pix.pixelformat = f->fmt.pix.pixelformat; + vout->v2f.fmt.pix.bytesperline = f->fmt.pix.bytesperline; + + retval = 0; + err0: + return retval; +} + +/* + * V4L2 - Handles VIDIOC_G_CTRL Ioctl + * + * @param vout structure vout_data * + * + * @param c structure v4l2_control * + * + * @return status 0 success, EINVAL failed + */ +static int mxc_get_v42lout_control(vout_data * vout, struct v4l2_control *c) +{ + switch (c->id) { + case V4L2_CID_HFLIP: + return (vout->rotate & IPU_ROTATE_HORIZ_FLIP) ? 1 : 0; + case V4L2_CID_VFLIP: + return (vout->rotate & IPU_ROTATE_VERT_FLIP) ? 1 : 0; + case (V4L2_CID_PRIVATE_BASE + 1): + return vout->rotate; + default: + return -EINVAL; + } +} + +/* + * V4L2 - Handles VIDIOC_S_CTRL Ioctl + * + * @param vout structure vout_data * + * + * @param c structure v4l2_control * + * + * @return status 0 success, EINVAL failed + */ +static int mxc_set_v42lout_control(vout_data * vout, struct v4l2_control *c) +{ + switch (c->id) { + case V4L2_CID_HFLIP: + case V4L2_CID_VFLIP: + case V4L2_CID_MXC_ROT: + return 0; + default: + return -EINVAL; + } + return 0; +} + +/*! + * V4L2 interface - open function + * + * @param inode structure inode * + * + * @param file structure file * + * + * @return status 0 success, ENODEV invalid device instance, + * ENODEV timeout, ERESTARTSYS interrupted by user + */ +static int mxc_v4l2out_open(struct inode *inode, struct file *file) +{ + struct video_device *dev = video_devdata(file); + vout_data *vout = video_get_drvdata(dev); + int err; + + if (!vout) { + pr_info("Internal error, vout_data not found!\n"); + return -ENODEV; + } + + down(&vout->busy_lock); + + err = -EINTR; + if (signal_pending(current)) + goto oops; + + if (vout->open_count++ == 0) { + pp_init(vout); + + init_waitqueue_head(&vout->v4l_bufq); + + init_timer(&vout->output_timer); + vout->output_timer.function = mxc_v4l2out_timer_handler; + vout->output_timer.data = (unsigned long)vout; + + vout->state = STATE_STREAM_OFF; + g_irq_cnt = g_buf_output_cnt = g_buf_q_cnt = g_buf_dq_cnt = 0; + + } + + file->private_data = dev; + up(&vout->busy_lock); + return 0; + + oops: + up(&vout->busy_lock); + return err; +} + +/*! + * V4L2 interface - close function + * + * @param inode struct inode * + * + * @param file struct file * + * + * @return 0 success + */ +static int mxc_v4l2out_close(struct inode *inode, struct file *file) +{ + struct video_device *dev = file->private_data; + vout_data *vout = video_get_drvdata(dev); + + if (--vout->open_count == 0) { + pr_debug("release resource\n"); + + pp_exit(vout); + if (vout->state != STATE_STREAM_OFF) + mxc_v4l2out_streamoff(vout); + + file->private_data = NULL; + + mxc_free_buffers(vout->queue_buf_paddr, + vout->queue_buf_vaddr, + vout->buffer_cnt, vout->queue_buf_size); + vout->buffer_cnt = 0; + mxc_free_buffers(vout->display_bufs, + vout->display_bufs_vaddr, + 2, vout->display_buf_size); + + /* capture off */ + wake_up_interruptible(&vout->v4l_bufq); + } + + return 0; +} + +/*! + * V4L2 interface - ioctl function + * + * @param inode struct inode * + * + * @param file struct file * + * + * @param ioctlnr unsigned int + * + * @param arg void * + * + * @return 0 success, ENODEV for invalid device instance, + * -1 for other errors. + */ +static int +mxc_v4l2out_do_ioctl(struct inode *inode, struct file *file, + unsigned int ioctlnr, void *arg) +{ + struct video_device *dev = file->private_data; + vout_data *vout = video_get_drvdata(dev); + int retval = 0; + int i = 0; + + if (!vout) + return -EBADF; + + /* make this _really_ smp-safe */ + if (down_interruptible(&vout->busy_lock)) + return -EBUSY; + + switch (ioctlnr) { + case VIDIOC_QUERYCAP: + { + struct v4l2_capability *cap = arg; + strcpy(cap->driver, "mxc_v4l2_output"); + cap->version = 0; + cap->capabilities = + V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_STREAMING; + cap->card[0] = '\0'; + cap->bus_info[0] = '\0'; + retval = 0; + break; + } + case VIDIOC_G_FMT: + { + struct v4l2_format *gf = arg; + retval = mxc_v4l2out_g_fmt(vout, gf); + break; + } + case VIDIOC_S_FMT: + { + struct v4l2_format *sf = arg; + if (vout->state != STATE_STREAM_OFF) { + retval = -EBUSY; + break; + } + retval = mxc_v4l2out_s_fmt(vout, sf); + break; + } + case VIDIOC_REQBUFS: + { + struct v4l2_requestbuffers *req = arg; + if ((req->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) || + (req->memory != V4L2_MEMORY_MMAP)) { + pr_debug + ("VIDIOC_REQBUFS: incorrect buffer type\n"); + retval = -EINVAL; + break; + } + + if (req->count == 0) + mxc_v4l2out_streamoff(vout); + + if (vout->state == STATE_STREAM_OFF) { + if (vout->queue_buf_paddr[0] != 0) { + mxc_free_buffers(vout->queue_buf_paddr, + vout->queue_buf_vaddr, + vout->buffer_cnt, + vout->queue_buf_size); + pr_debug + ("VIDIOC_REQBUFS: freed buffers\n"); + } + vout->buffer_cnt = 0; + } else { + pr_debug("VIDIOC_REQBUFS: Buffer is in use\n"); + retval = -EBUSY; + break; + } + + if (req->count == 0) + break; + + if (req->count < MIN_FRAME_NUM) { + req->count = MIN_FRAME_NUM; + } else if (req->count > MAX_FRAME_NUM) { + req->count = MAX_FRAME_NUM; + } + vout->buffer_cnt = req->count; + vout->queue_buf_size = + PAGE_ALIGN(vout->v2f.fmt.pix.sizeimage); + + retval = mxc_allocate_buffers(vout->queue_buf_paddr, + vout->queue_buf_vaddr, + vout->buffer_cnt, + vout->queue_buf_size); + if (retval < 0) + break; + + /* Init buffer queues */ + vout->done_q.head = 0; + vout->done_q.tail = 0; + vout->ready_q.head = 0; + vout->ready_q.tail = 0; + + for (i = 0; i < vout->buffer_cnt; i++) { + memset(&(vout->v4l2_bufs[i]), 0, + sizeof(vout->v4l2_bufs[i])); + vout->v4l2_bufs[i].flags = 0; + vout->v4l2_bufs[i].memory = V4L2_MEMORY_MMAP; + vout->v4l2_bufs[i].index = i; + vout->v4l2_bufs[i].type = + V4L2_BUF_TYPE_VIDEO_OUTPUT; + vout->v4l2_bufs[i].length = + PAGE_ALIGN(vout->v2f.fmt.pix.sizeimage); + vout->v4l2_bufs[i].m.offset = + (unsigned long)vout->queue_buf_paddr[i]; + vout->v4l2_bufs[i].timestamp.tv_sec = 0; + vout->v4l2_bufs[i].timestamp.tv_usec = 0; + } + break; + } + case VIDIOC_QUERYBUF: + { + struct v4l2_buffer *buf = arg; + u32 type = buf->type; + int index = buf->index; + + if ((type != V4L2_BUF_TYPE_VIDEO_OUTPUT) || + (index >= vout->buffer_cnt)) { + pr_debug + ("VIDIOC_QUERYBUFS: incorrect buffer type\n"); + retval = -EINVAL; + break; + } + down(&vout->param_lock); + memcpy(buf, &(vout->v4l2_bufs[index]), sizeof(*buf)); + up(&vout->param_lock); + break; + } + case VIDIOC_QBUF: + { + struct v4l2_buffer *buf = arg; + int index = buf->index; + unsigned long lock_flags; + unsigned long timeout; + + if ((buf->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) || + (index >= vout->buffer_cnt) || (buf->flags != 0)) { + retval = -EINVAL; + break; + } + + pr_debug("VIDIOC_QBUF: %d\n", buf->index); + + spin_lock_irqsave(&g_lock, lock_flags); + + memcpy(&(vout->v4l2_bufs[index]), buf, sizeof(*buf)); + vout->v4l2_bufs[index].flags |= V4L2_BUF_FLAG_QUEUED; + + g_buf_q_cnt++; + queue_buf(&vout->ready_q, index); + + if (vout->state == STATE_STREAM_PAUSED) { + index = peek_next_buf(&vout->ready_q); + + /* if timestamp is 0, then default to 30fps */ + if ((vout->v4l2_bufs[index].timestamp.tv_sec == + 0) + && (vout->v4l2_bufs[index].timestamp. + tv_usec == 0)) + timeout = + vout->start_jiffies + + vout->frame_count * HZ / 30; + else + timeout = + get_jiffies(&vout->v4l2_bufs[index]. + timestamp); + + if (jiffies >= timeout) { + pr_debug + ("warning: timer timeout already expired.\n"); + } + + vout->output_timer.expires = timeout; + pr_debug + ("QBUF:Add timer %d timeout @ %lu jiffies, " + "current = %lu\n", index, timeout, + jiffies); + add_timer(&vout->output_timer); + vout->state = STATE_STREAM_ON; + } + + spin_unlock_irqrestore(&g_lock, lock_flags); + break; + } + case VIDIOC_DQBUF: + { + struct v4l2_buffer *buf = arg; + int idx; + + pr_debug("VIDIOC_DQBUF: q size = %d\n", + queue_size(&vout->done_q)); + + if ((queue_size(&vout->done_q) == 0) && + (file->f_flags & O_NONBLOCK)) { + retval = -EAGAIN; + break; + } + + if (!wait_event_interruptible_timeout(vout->v4l_bufq, + queue_size(&vout-> + done_q) + != 0, 10 * HZ)) { + pr_debug("VIDIOC_DQBUF: timeout\n"); + retval = -ETIME; + break; + } else if (signal_pending(current)) { + pr_debug("VIDIOC_DQBUF: interrupt received\n"); + retval = -ERESTARTSYS; + break; + } + idx = dequeue_buf(&vout->done_q); + if (idx == -1) { /* No frame free */ + pr_debug + ("VIDIOC_DQBUF: no free buffers, returning\n"); + retval = -EAGAIN; + break; + } + if ((vout->v4l2_bufs[idx].flags & V4L2_BUF_FLAG_DONE) == + 0) + pr_debug + ("VIDIOC_DQBUF: buffer in done q, but not " + "flagged as done\n"); + + vout->v4l2_bufs[idx].flags = 0; + memcpy(buf, &(vout->v4l2_bufs[idx]), sizeof(*buf)); + pr_debug("VIDIOC_DQBUF: %d\n", buf->index); + break; + } + case VIDIOC_STREAMON: + { + retval = mxc_v4l2out_streamon(vout); + break; + } + case VIDIOC_STREAMOFF: + { + retval = mxc_v4l2out_streamoff(vout); + break; + } + case VIDIOC_G_CTRL: + { + retval = mxc_get_v42lout_control(vout, arg); + break; + } + case VIDIOC_S_CTRL: + { + retval = mxc_set_v42lout_control(vout, arg); + break; + } + case VIDIOC_CROPCAP: + { + struct v4l2_cropcap *cap = arg; + + if (cap->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) { + retval = -EINVAL; + break; + } + cap->bounds = vout->crop_bounds[vout->cur_disp_output]; + cap->defrect = vout->crop_bounds[vout->cur_disp_output]; + retval = 0; + break; + } + case VIDIOC_G_CROP: + { + struct v4l2_crop *crop = arg; + + if (crop->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) { + retval = -EINVAL; + break; + } + crop->c = vout->crop_current; + break; + } + case VIDIOC_S_CROP: + { + struct v4l2_crop *crop = arg; + struct v4l2_rect *b = + &(vout->crop_bounds[vout->cur_disp_output]); + + if (crop->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) { + retval = -EINVAL; + break; + } + if (crop->c.height < 0) { + retval = -EINVAL; + break; + } + if (crop->c.width < 0) { + retval = -EINVAL; + break; + } + + if (crop->c.top < b->top) + crop->c.top = b->top; + if (crop->c.top > b->top + b->height) + crop->c.top = b->top + b->height; + if (crop->c.height > b->top - crop->c.top + b->height) + crop->c.height = + b->top - crop->c.top + b->height; + + if (crop->c.left < b->left) + crop->c.top = b->left; + if (crop->c.left > b->left + b->width) + crop->c.top = b->left + b->width; + if (crop->c.width > b->left - crop->c.left + b->width) + crop->c.width = + b->left - crop->c.left + b->width; + + /* stride line limitation */ + crop->c.height -= crop->c.height % 8; + crop->c.width -= crop->c.width % 8; + + vout->crop_current = crop->c; + + vout->display_buf_size = vout->crop_current.width * + vout->crop_current.height; + vout->display_buf_size *= + fmt_to_bpp(SDC_FG_FB_FORMAT) / 8; + break; + } + case VIDIOC_ENUMOUTPUT: + { + struct v4l2_output *output = arg; + + if ((output->index >= 2) || + (vout->output_enabled[output->index] == false)) { + retval = -EINVAL; + break; + } + + *output = mxc_outputs[0]; + output->name[4] = '0' + output->index; + break; + } + case VIDIOC_G_OUTPUT: + { + int *p_output_num = arg; + + *p_output_num = vout->cur_disp_output; + break; + } + case VIDIOC_S_OUTPUT: + { + int *p_output_num = arg; + + if ((*p_output_num >= 2) || + (vout->output_enabled[*p_output_num] == false)) { + retval = -EINVAL; + break; + } + + if (vout->state != STATE_STREAM_OFF) { + retval = -EBUSY; + break; + } + + vout->cur_disp_output = *p_output_num; + break; + } + case VIDIOC_G_FBUF: + { + struct v4l2_framebuffer *fb = arg; + *fb = vout->v4l2_fb; + break; + } + case VIDIOC_S_FBUF: + { + struct v4l2_framebuffer *fb = arg; + vout->v4l2_fb = *fb; + vout->v4l2_fb.capability = V4L2_FBUF_CAP_EXTERNOVERLAY; + break; + } + case VIDIOC_ENUM_FMT: + case VIDIOC_TRY_FMT: + case VIDIOC_QUERYCTRL: + case VIDIOC_G_PARM: + case VIDIOC_ENUMSTD: + case VIDIOC_G_STD: + case VIDIOC_S_STD: + case VIDIOC_G_TUNER: + case VIDIOC_S_TUNER: + case VIDIOC_G_FREQUENCY: + case VIDIOC_S_FREQUENCY: + default: + retval = -EINVAL; + break; + } + + up(&vout->busy_lock); + return retval; +} + +/* + * V4L2 interface - ioctl function + * + * @return None + */ +static int +mxc_v4l2out_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return video_usercopy(inode, file, cmd, arg, mxc_v4l2out_do_ioctl); +} + +/*! + * V4L2 interface - mmap function + * + * @param file structure file * + * + * @param vma structure vm_area_struct * + * + * @return status 0 Success, EINTR busy lock error, + * ENOBUFS remap_page error + */ +static int mxc_v4l2out_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct video_device *dev = file->private_data; + unsigned long start = vma->vm_start; + unsigned long size = vma->vm_end - vma->vm_start; + int res = 0; + vout_data *vout = video_get_drvdata(dev); + + /* make this _really_ smp-safe */ + if (down_interruptible(&vout->busy_lock)) + return -EINTR; + + /* make buffers write-thru cacheable */ + vma->vm_page_prot = __pgprot(pgprot_val(vma->vm_page_prot) & + ~L_PTE_BUFFERABLE); + + if (remap_pfn_range(vma, start, vma->vm_pgoff, size, vma->vm_page_prot)) { + pr_debug("mxc_mmap(V4L)i - remap_pfn_range failed\n"); + res = -ENOBUFS; + goto mxc_mmap_exit; + } + + mxc_mmap_exit: + up(&vout->busy_lock); + return res; +} + +/*! + * V4L2 interface - poll function + * + * @param file structure file * + * + * @param wait structure poll_table * + * + * @return status POLLIN | POLLRDNORM + */ +static unsigned int mxc_v4l2out_poll(struct file *file, poll_table * wait) +{ + struct video_device *dev = file->private_data; + vout_data *vout = video_get_drvdata(dev); + + wait_queue_head_t *queue = NULL; + int res = POLLIN | POLLRDNORM; + + if (down_interruptible(&vout->busy_lock)) + return -EINTR; + + queue = &vout->v4l_bufq; + poll_wait(file, queue, wait); + + up(&vout->busy_lock); + return res; +} + +static struct file_operations mxc_v4l2out_fops = { + .owner = THIS_MODULE, + .open = mxc_v4l2out_open, + .release = mxc_v4l2out_close, + .ioctl = mxc_v4l2out_ioctl, + .mmap = mxc_v4l2out_mmap, + .poll = mxc_v4l2out_poll, +}; + +static struct video_device mxc_v4l2out_template = { + .owner = THIS_MODULE, + .name = "MXC Video Output", + .type = 0, + .type2 = V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_STREAMING, + .hardware = 39, + .fops = &mxc_v4l2out_fops, + .release = video_device_release, +}; + +/*! + * Probe routine for the framebuffer driver. It is called during the + * driver binding process. The following functions are performed in + * this routine: Framebuffer initialization, Memory allocation and + * mapping, Framebuffer registration, IPU initialization. + * + * @return Appropriate error code to the kernel common code + */ +static int mxc_v4l2out_probe(struct platform_device *pdev) +{ + int i; + vout_data *vout; + + /* + * Allocate sufficient memory for the fb structure + */ + g_vout = vout = kmalloc(sizeof(vout_data), GFP_KERNEL); + + if (!vout) + return 0; + + memset(vout, 0, sizeof(vout_data)); + + vout->video_dev = video_device_alloc(); + if (vout->video_dev == NULL) + return -1; + vout->video_dev->dev = &pdev->dev; + vout->video_dev->minor = -1; + + *(vout->video_dev) = mxc_v4l2out_template; + + /* register v4l device */ + if (video_register_device(vout->video_dev, + VFL_TYPE_GRABBER, video_nr) == -1) { + pr_debug("video_register_device failed\n"); + return 0; + } + pr_debug("mxc_v4l2out: registered device video%d\n", + vout->video_dev->minor & 0x1f); + + video_set_drvdata(vout->video_dev, vout); + + init_MUTEX(&vout->param_lock); + init_MUTEX(&vout->busy_lock); + + /* setup outputs and cropping */ + vout->cur_disp_output = -1; + for (i = 0; i < num_registered_fb; i++) { + char *idstr = registered_fb[i]->fix.id; + if (strncmp(idstr, "DISP", 4) == 0) { + int disp_num = i; + vout->crop_bounds[disp_num].left = 0; + vout->crop_bounds[disp_num].top = 0; + vout->crop_bounds[disp_num].width = + registered_fb[i]->var.xres; + vout->crop_bounds[disp_num].height = + registered_fb[i]->var.yres; + vout->output_enabled[disp_num] = true; + vout->output_fb_num[disp_num] = i; + if (vout->cur_disp_output == -1) + vout->cur_disp_output = disp_num; + } + + } + vout->crop_current = vout->crop_bounds[vout->cur_disp_output]; + + /* Setup framebuffer parameters */ + vout->v4l2_fb.capability = V4L2_FBUF_CAP_EXTERNOVERLAY; + vout->v4l2_fb.flags = V4L2_FBUF_FLAG_PRIMARY; + + return 0; +} + +/*! + * This structure contains pointers to the power management callback functions. + */ +static struct platform_driver mxc_v4l2out_driver = { + .driver = { + .name = "MXC Video Output", + .owner = THIS_MODULE, + .bus = &platform_bus_type, + }, + .probe = mxc_v4l2out_probe, + .remove = NULL, +}; + +static void camera_platform_release(struct device *device) +{ +} + +static struct platform_device mxc_v4l2out_device = { + .name = "MXC Video Output", + .dev = { + .release = camera_platform_release, + }, + .id = 0, +}; + +/*! + * mxc v4l2 init function + * + */ +static int mxc_v4l2out_init(void) +{ + u8 err = 0; + + err = platform_driver_register(&mxc_v4l2out_driver); + if (err == 0) { + platform_device_register(&mxc_v4l2out_device); + } + return err; +} + +/*! + * mxc v4l2 cleanup function + * + */ +static void mxc_v4l2out_clean(void) +{ + pr_debug("unregistering video\n"); + + video_unregister_device(g_vout->video_dev); + + platform_driver_unregister(&mxc_v4l2out_driver); + platform_device_unregister(&mxc_v4l2out_device); + kfree(g_vout); + g_vout = NULL; +} + +module_init(mxc_v4l2out_init); +module_exit(mxc_v4l2out_clean); + +module_param(video_nr, int, 0444); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("V4L2-driver for MXC video output"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("video"); diff --git a/drivers/media/video/mxc/output/mxc_v4l2_output.c b/drivers/media/video/mxc/output/mxc_v4l2_output.c new file mode 100644 index 000000000000..08b0be63b2d7 --- /dev/null +++ b/drivers/media/video/mxc/output/mxc_v4l2_output.c @@ -0,0 +1,1710 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file drivers/media/video/mxc/output/mxc_v4l2_output.c + * + * @brief MXC V4L2 Video Output Driver + * + * Video4Linux2 Output Device using MXC IPU Post-processing functionality. + * + * @ingroup MXC_V4L2_OUTPUT + */ +#include <linux/module.h> +#include <linux/init.h> +#include <linux/console.h> +#include <linux/fs.h> +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <asm/cacheflush.h> +#include <asm/io.h> +#include <asm/semaphore.h> +#include <linux/dma-mapping.h> + +#include <asm/arch/mxcfb.h> +#include "mxc_v4l2_output.h" + +vout_data *g_vout; +#define SDC_FG_FB_FORMAT IPU_PIX_FMT_RGB565 + +struct v4l2_output mxc_outputs[2] = { + { + .index = MXC_V4L2_OUT_2_SDC, + .name = "DISP3 Video Out", + .type = V4L2_OUTPUT_TYPE_ANALOG, /* not really correct, + but no other choice */ + .audioset = 0, + .modulator = 0, + .std = V4L2_STD_UNKNOWN}, + { + .index = MXC_V4L2_OUT_2_ADC, + .name = "DISPx Video Out", + .type = V4L2_OUTPUT_TYPE_ANALOG, /* not really correct, + but no other choice */ + .audioset = 0, + .modulator = 0, + .std = V4L2_STD_UNKNOWN} +}; + +static int video_nr = 16; +static spinlock_t g_lock = SPIN_LOCK_UNLOCKED; + +/* debug counters */ +uint32_t g_irq_cnt; +uint32_t g_buf_output_cnt; +uint32_t g_buf_q_cnt; +uint32_t g_buf_dq_cnt; + +#define QUEUE_SIZE (MAX_FRAME_NUM + 1) +static __inline int queue_size(v4l_queue * q) +{ + if (q->tail >= q->head) + return (q->tail - q->head); + else + return ((q->tail + QUEUE_SIZE) - q->head); +} + +static __inline int queue_buf(v4l_queue * q, int idx) +{ + if (((q->tail + 1) % QUEUE_SIZE) == q->head) + return -1; /* queue full */ + q->list[q->tail] = idx; + q->tail = (q->tail + 1) % QUEUE_SIZE; + return 0; +} + +static __inline int dequeue_buf(v4l_queue * q) +{ + int ret; + if (q->tail == q->head) + return -1; /* queue empty */ + ret = q->list[q->head]; + q->head = (q->head + 1) % QUEUE_SIZE; + return ret; +} + +static __inline int peek_next_buf(v4l_queue * q) +{ + if (q->tail == q->head) + return -1; /* queue empty */ + return q->list[q->head]; +} + +static __inline unsigned long get_jiffies(struct timeval *t) +{ + struct timeval cur; + + if (t->tv_usec >= 1000000) { + t->tv_sec += t->tv_usec / 1000000; + t->tv_usec = t->tv_usec % 1000000; + } + + do_gettimeofday(&cur); + if ((t->tv_sec < cur.tv_sec) + || ((t->tv_sec == cur.tv_sec) && (t->tv_usec < cur.tv_usec))) + return jiffies; + + if (t->tv_usec < cur.tv_usec) { + cur.tv_sec = t->tv_sec - cur.tv_sec - 1; + cur.tv_usec = t->tv_usec + 1000000 - cur.tv_usec; + } else { + cur.tv_sec = t->tv_sec - cur.tv_sec; + cur.tv_usec = t->tv_usec - cur.tv_usec; + } + + return jiffies + timeval_to_jiffies(&cur); +} + +/*! + * Private function to free buffers + * + * @param bufs_paddr Array of physical address of buffers to be freed + * + * @param bufs_vaddr Array of virtual address of buffers to be freed + * + * @param num_buf Number of buffers to be freed + * + * @param size Size for each buffer to be free + * + * @return status 0 success. + */ +static int mxc_free_buffers(dma_addr_t bufs_paddr[], void *bufs_vaddr[], + int num_buf, int size) +{ + int i; + + for (i = 0; i < num_buf; i++) { + if (bufs_vaddr[i] != 0) { + dma_free_coherent(0, size, bufs_vaddr[i], + bufs_paddr[i]); + pr_debug("freed @ paddr=0x%08X\n", (u32) bufs_paddr[i]); + bufs_paddr[i] = 0; + bufs_vaddr[i] = NULL; + } + } + return 0; +} + +/*! + * Private function to allocate buffers + * + * @param bufs_paddr Output array of physical address of buffers allocated + * + * @param bufs_vaddr Output array of virtual address of buffers allocated + * + * @param num_buf Input number of buffers to allocate + * + * @param size Input size for each buffer to allocate + * + * @return status -0 Successfully allocated a buffer, -ENOBUFS failed. + */ +static int mxc_allocate_buffers(dma_addr_t bufs_paddr[], void *bufs_vaddr[], + int num_buf, int size) +{ + int i; + + for (i = 0; i < num_buf; i++) { + bufs_vaddr[i] = dma_alloc_coherent(0, size, + &bufs_paddr[i], + GFP_DMA | GFP_KERNEL); + + if (bufs_vaddr[i] == 0) { + mxc_free_buffers(bufs_paddr, bufs_vaddr, i, size); + printk(KERN_ERR "dma_alloc_coherent failed.\n"); + return -ENOBUFS; + } + pr_debug("allocated @ paddr=0x%08X, size=%d.\n", + (u32) bufs_paddr[i], size); + } + + return 0; +} + +/* + * Returns bits per pixel for given pixel format + * + * @param pixelformat V4L2_PIX_FMT_RGB565, V4L2_PIX_FMT_BGR24 or V4L2_PIX_FMT_BGR32 + * + * @return bits per pixel of pixelformat + */ +static u32 fmt_to_bpp(u32 pixelformat) +{ + u32 bpp; + + switch (pixelformat) { + case V4L2_PIX_FMT_RGB565: + bpp = 16; + break; + case V4L2_PIX_FMT_BGR24: + case V4L2_PIX_FMT_RGB24: + bpp = 24; + break; + case V4L2_PIX_FMT_BGR32: + case V4L2_PIX_FMT_RGB32: + bpp = 32; + break; + default: + bpp = 8; + break; + } + return bpp; +} + +static void mxc_v4l2out_timer_handler(unsigned long arg) +{ + int index; + unsigned long timeout; + unsigned long lock_flags = 0; + vout_data *vout = (vout_data *) arg; + + dev_dbg(vout->video_dev->dev, "timer handler: %lu\n", jiffies); + + spin_lock_irqsave(&g_lock, lock_flags); + + if ((vout->state == STATE_STREAM_STOPPING) + || (vout->state == STATE_STREAM_OFF)) + goto exit0; + /* + * If timer occurs before IPU h/w is ready, then set the state to + * paused and the timer will be set again when next buffer is queued + * or PP comletes + */ + if (vout->ipu_buf[vout->next_rdy_ipu_buf] != -1) { + dev_dbg(vout->video_dev->dev, "IPU buffer busy\n"); + vout->state = STATE_STREAM_PAUSED; + goto exit0; + } + + /* Dequeue buffer and pass to IPU */ + index = dequeue_buf(&vout->ready_q); + if (index == -1) { /* no buffers ready, should never occur */ + dev_err(vout->video_dev->dev, + "mxc_v4l2out: timer - no queued buffers ready\n"); + goto exit0; + } + + g_buf_dq_cnt++; + vout->frame_count++; + vout->ipu_buf[vout->next_rdy_ipu_buf] = index; + if (ipu_update_channel_buffer(vout->post_proc_ch, IPU_INPUT_BUFFER, + vout->next_rdy_ipu_buf, + vout->v4l2_bufs[index].m.offset) < 0) { + dev_err(vout->video_dev->dev, + "unable to update buffer %d address\n", + vout->next_rdy_ipu_buf); + goto exit0; + } + if (ipu_select_buffer(vout->post_proc_ch, IPU_INPUT_BUFFER, + vout->next_rdy_ipu_buf) < 0) { + dev_err(vout->video_dev->dev, + "unable to set IPU buffer ready\n"); + } + vout->next_rdy_ipu_buf = !vout->next_rdy_ipu_buf; + + /* Setup timer for next buffer */ + index = peek_next_buf(&vout->ready_q); + if (index != -1) { + /* if timestamp is 0, then default to 30fps */ + if ((vout->v4l2_bufs[index].timestamp.tv_sec == 0) + && (vout->v4l2_bufs[index].timestamp.tv_usec == 0)) + timeout = + vout->start_jiffies + vout->frame_count * HZ / 30; + else + timeout = + get_jiffies(&vout->v4l2_bufs[index].timestamp); + + if (jiffies >= timeout) { + dev_dbg(vout->video_dev->dev, + "warning: timer timeout already expired.\n"); + } + if (mod_timer(&vout->output_timer, timeout)) + dev_dbg(vout->video_dev->dev, + "warning: timer was already set\n"); + + dev_dbg(vout->video_dev->dev, + "timer handler next schedule: %lu\n", timeout); + } else { + vout->state = STATE_STREAM_PAUSED; + } + + exit0: + spin_unlock_irqrestore(&g_lock, lock_flags); +} + +static irqreturn_t mxc_v4l2out_pp_in_irq_handler(int irq, void *dev_id) +{ + int last_buf; + int index; + unsigned long timeout; + unsigned long lock_flags = 0; + vout_data *vout = dev_id; + + spin_lock_irqsave(&g_lock, lock_flags); + + g_irq_cnt++; + + /* Process previous buffer */ + last_buf = vout->ipu_buf[vout->next_done_ipu_buf]; + if (last_buf != -1) { + g_buf_output_cnt++; + vout->v4l2_bufs[last_buf].flags = V4L2_BUF_FLAG_DONE; + queue_buf(&vout->done_q, last_buf); + vout->ipu_buf[vout->next_done_ipu_buf] = -1; + wake_up_interruptible(&vout->v4l_bufq); + /* printk("pp_irq: buf %d done\n", vout->next_done_ipu_buf); */ + vout->next_done_ipu_buf = !vout->next_done_ipu_buf; + } + + if (vout->state == STATE_STREAM_STOPPING) { + if ((vout->ipu_buf[0] == -1) && (vout->ipu_buf[1] == -1)) { + vout->state = STATE_STREAM_OFF; + } + } else if ((vout->state == STATE_STREAM_PAUSED) + && ((index = peek_next_buf(&vout->ready_q)) != -1)) { + /* Setup timer for next buffer, when stream has been paused */ + pr_debug("next index %d\n", index); + + /* if timestamp is 0, then default to 30fps */ + if ((vout->v4l2_bufs[index].timestamp.tv_sec == 0) + && (vout->v4l2_bufs[index].timestamp.tv_usec == 0)) + timeout = + vout->start_jiffies + vout->frame_count * HZ / 30; + else + timeout = + get_jiffies(&vout->v4l2_bufs[index].timestamp); + + if (jiffies >= timeout) { + pr_debug("warning: timer timeout already expired.\n"); + } + + vout->state = STATE_STREAM_ON; + + if (mod_timer(&vout->output_timer, timeout)) + pr_debug("warning: timer was already set\n"); + + pr_debug("timer handler next schedule: %lu\n", timeout); + } + + spin_unlock_irqrestore(&g_lock, lock_flags); + + return IRQ_HANDLED; +} + +/*! + * Start the output stream + * + * @param vout structure vout_data * + * + * @return status 0 Success + */ +static int mxc_v4l2out_streamon(vout_data * vout) +{ + struct device *dev = vout->video_dev->dev; + ipu_channel_params_t params; + struct mxcfb_pos fb_pos; + struct fb_var_screeninfo fbvar; + struct fb_info *fbi = + registered_fb[vout->output_fb_num[vout->cur_disp_output]]; + int pp_in_buf[2]; + u16 out_width; + u16 out_height; + ipu_channel_t display_input_ch = MEM_PP_MEM; + bool use_direct_adc = false; + + if (!vout) + return -EINVAL; + + if (vout->state != STATE_STREAM_OFF) + return -EBUSY; + + if (queue_size(&vout->ready_q) < 2) { + dev_err(dev, "2 buffers not been queued yet!\n"); + return -EINVAL; + } + + out_width = vout->crop_current.width; + out_height = vout->crop_current.height; + + vout->next_done_ipu_buf = vout->next_rdy_ipu_buf = 0; + vout->ipu_buf[0] = pp_in_buf[0] = dequeue_buf(&vout->ready_q); + vout->ipu_buf[1] = pp_in_buf[1] = dequeue_buf(&vout->ready_q); + vout->frame_count = 2; + + ipu_enable_irq(IPU_IRQ_PP_IN_EOF); + + /* Init Display Channel */ +#ifdef CONFIG_FB_MXC_ASYNC_PANEL + if (vout->cur_disp_output < DISP3) { + mxcfb_set_refresh_mode(fbi, MXCFB_REFRESH_OFF, 0); + if (vout->rotate < IPU_ROTATE_90_RIGHT) { + dev_dbg(dev, "Using PP direct to ADC channel\n"); + use_direct_adc = true; + vout->display_ch = MEM_PP_ADC; + vout->post_proc_ch = MEM_PP_ADC; + + memset(¶ms, 0, sizeof(params)); + params.mem_pp_adc.in_width = vout->v2f.fmt.pix.width; + params.mem_pp_adc.in_height = vout->v2f.fmt.pix.height; + params.mem_pp_adc.in_pixel_fmt = + vout->v2f.fmt.pix.pixelformat; + params.mem_pp_adc.out_width = out_width; + params.mem_pp_adc.out_height = out_height; + params.mem_pp_adc.out_pixel_fmt = SDC_FG_FB_FORMAT; +#ifdef CONFIG_FB_MXC_EPSON_PANEL + params.mem_pp_adc.out_left = + 2 + vout->crop_current.left; +#else + params.mem_pp_adc.out_left = + 12 + vout->crop_current.left; +#endif + params.mem_pp_adc.out_top = vout->crop_current.top; + if (ipu_init_channel(vout->post_proc_ch, ¶ms) != 0) { + dev_err(dev, "Error initializing PP chan\n"); + return -EINVAL; + } + + if (ipu_init_channel_buffer(vout->post_proc_ch, + IPU_INPUT_BUFFER, + params.mem_pp_adc. + in_pixel_fmt, + params.mem_pp_adc.in_width, + params.mem_pp_adc.in_height, + vout->v2f.fmt.pix. + bytesperline / + bytes_per_pixel(params. + mem_pp_adc. + in_pixel_fmt), + vout->rotate, + vout-> + v4l2_bufs[pp_in_buf[0]].m. + offset, + vout-> + v4l2_bufs[pp_in_buf[1]].m. + offset, + vout->offset.u_offset, + vout->offset.v_offset) != + 0) { + dev_err(dev, "Error initializing PP in buf\n"); + return -EINVAL; + } + + if (ipu_init_channel_buffer(vout->post_proc_ch, + IPU_OUTPUT_BUFFER, + params.mem_pp_adc. + out_pixel_fmt, out_width, + out_height, out_width, + vout->rotate, 0, 0, 0, + 0) != 0) { + dev_err(dev, + "Error initializing PP output buffer\n"); + return -EINVAL; + } + + } else { + dev_dbg(dev, "Using ADC SYS2 channel\n"); + vout->display_ch = ADC_SYS2; + vout->post_proc_ch = MEM_PP_MEM; + + if (vout->display_bufs[0]) { + mxc_free_buffers(vout->display_bufs, + vout->display_bufs_vaddr, + 2, vout->display_buf_size); + } + + vout->display_buf_size = vout->crop_current.width * + vout->crop_current.height * + fmt_to_bpp(SDC_FG_FB_FORMAT) / 8; + mxc_allocate_buffers(vout->display_bufs, + vout->display_bufs_vaddr, + 2, vout->display_buf_size); + + memset(¶ms, 0, sizeof(params)); + params.adc_sys2.disp = vout->cur_disp_output; + params.adc_sys2.ch_mode = WriteTemplateNonSeq; +#ifdef CONFIG_FB_MXC_EPSON_PANEL + params.adc_sys2.out_left = 2 + vout->crop_current.left; +#else + params.adc_sys2.out_left = 12 + vout->crop_current.left; +#endif + params.adc_sys2.out_top = vout->crop_current.top; + if (ipu_init_channel(ADC_SYS2, ¶ms) < 0) + return -EINVAL; + + if (ipu_init_channel_buffer(vout->display_ch, + IPU_INPUT_BUFFER, + SDC_FG_FB_FORMAT, + out_width, out_height, + out_width, IPU_ROTATE_NONE, + vout->display_bufs[0], + vout->display_bufs[1], 0, + 0) != 0) { + dev_err(dev, + "Error initializing SDC FG buffer\n"); + return -EINVAL; + } + } + } else +#endif + { /* Use SDC */ + dev_dbg(dev, "Using SDC channel\n"); + + fbvar = fbi->var; + fbvar.xres = fbvar.xres_virtual = out_width; + fbvar.yres = out_height; + fbvar.yres_virtual = out_height * 2; + fbvar.bits_per_pixel = 16; + fb_set_var(fbi, &fbvar); + + fb_pos.x = vout->crop_current.left; + fb_pos.y = vout->crop_current.top; + if (fbi->fbops->fb_ioctl) + fbi->fbops->fb_ioctl(fbi, MXCFB_SET_OVERLAY_POS, + (unsigned long)&fb_pos); + + vout->display_bufs[0] = fbi->fix.smem_start; + vout->display_bufs[1] = fbi->fix.smem_start + + (fbi->fix.line_length * fbi->var.yres); + vout->display_buf_size = vout->crop_current.width * + vout->crop_current.height * + fmt_to_bpp(SDC_FG_FB_FORMAT) / 8; + + if (vout->cur_disp_output == 3) + vout->display_ch = MEM_SDC_FG; + else + vout->display_ch = MEM_SDC_BG; + + vout->post_proc_ch = MEM_PP_MEM; + } + + /* Init PP */ + if (use_direct_adc == false) { + if (vout->rotate >= IPU_ROTATE_90_RIGHT) { + out_width = vout->crop_current.height; + out_height = vout->crop_current.width; + } + memset(¶ms, 0, sizeof(params)); + params.mem_pp_mem.in_width = vout->v2f.fmt.pix.width; + params.mem_pp_mem.in_height = vout->v2f.fmt.pix.height; + params.mem_pp_mem.in_pixel_fmt = vout->v2f.fmt.pix.pixelformat; + params.mem_pp_mem.out_width = out_width; + params.mem_pp_mem.out_height = out_height; + params.mem_pp_mem.out_pixel_fmt = SDC_FG_FB_FORMAT; + if (ipu_init_channel(vout->post_proc_ch, ¶ms) != 0) { + dev_err(dev, "Error initializing PP channel\n"); + return -EINVAL; + } + + if (ipu_init_channel_buffer(vout->post_proc_ch, + IPU_INPUT_BUFFER, + params.mem_pp_mem.in_pixel_fmt, + params.mem_pp_mem.in_width, + params.mem_pp_mem.in_height, + vout->v2f.fmt.pix.bytesperline / + bytes_per_pixel(params.mem_pp_mem. + in_pixel_fmt), + IPU_ROTATE_NONE, + vout->v4l2_bufs[pp_in_buf[0]].m. + offset, + vout->v4l2_bufs[pp_in_buf[1]].m. + offset, vout->offset.u_offset, + vout->offset.v_offset) != 0) { + dev_err(dev, "Error initializing PP input buffer\n"); + return -EINVAL; + } + + if (vout->rotate >= IPU_ROTATE_90_RIGHT) { + if (vout->rot_pp_bufs[0]) { + mxc_free_buffers(vout->rot_pp_bufs, + vout->rot_pp_bufs_vaddr, 2, + vout->display_buf_size); + } + if (mxc_allocate_buffers + (vout->rot_pp_bufs, vout->rot_pp_bufs_vaddr, 2, + vout->display_buf_size) < 0) { + return -ENOBUFS; + } + + if (ipu_init_channel_buffer(vout->post_proc_ch, + IPU_OUTPUT_BUFFER, + params.mem_pp_mem. + out_pixel_fmt, out_width, + out_height, out_width, + IPU_ROTATE_NONE, + vout->rot_pp_bufs[0], + vout->rot_pp_bufs[1], 0, + 0) != 0) { + dev_err(dev, + "Error initializing PP output buffer\n"); + return -EINVAL; + } + + if (ipu_init_channel(MEM_ROT_PP_MEM, NULL) != 0) { + dev_err(dev, + "Error initializing PP ROT channel\n"); + return -EINVAL; + } + + if (ipu_init_channel_buffer(MEM_ROT_PP_MEM, + IPU_INPUT_BUFFER, + params.mem_pp_mem. + out_pixel_fmt, out_width, + out_height, out_width, + vout->rotate, + vout->rot_pp_bufs[0], + vout->rot_pp_bufs[1], 0, + 0) != 0) { + dev_err(dev, + "Error initializing PP ROT input buffer\n"); + return -EINVAL; + } + + /* swap width and height */ + out_width = vout->crop_current.width; + out_height = vout->crop_current.height; + + if (ipu_init_channel_buffer(MEM_ROT_PP_MEM, + IPU_OUTPUT_BUFFER, + params.mem_pp_mem. + out_pixel_fmt, out_width, + out_height, out_width, + IPU_ROTATE_NONE, + vout->display_bufs[0], + vout->display_bufs[1], 0, + 0) != 0) { + dev_err(dev, + "Error initializing PP output buffer\n"); + return -EINVAL; + } + + if (ipu_link_channels(vout->post_proc_ch, + MEM_ROT_PP_MEM) < 0) { + return -EINVAL; + } + ipu_select_buffer(MEM_ROT_PP_MEM, IPU_OUTPUT_BUFFER, 0); + ipu_select_buffer(MEM_ROT_PP_MEM, IPU_OUTPUT_BUFFER, 1); + + ipu_enable_channel(MEM_ROT_PP_MEM); + + display_input_ch = MEM_ROT_PP_MEM; + } else { + if (ipu_init_channel_buffer(vout->post_proc_ch, + IPU_OUTPUT_BUFFER, + params.mem_pp_mem. + out_pixel_fmt, out_width, + out_height, out_width, + vout->rotate, + vout->display_bufs[0], + vout->display_bufs[1], 0, + 0) != 0) { + dev_err(dev, + "Error initializing PP output buffer\n"); + return -EINVAL; + } + } + if (ipu_link_channels(display_input_ch, vout->display_ch) < 0) { + dev_err(dev, "Error linking ipu channels\n"); + return -EINVAL; + } + } + + vout->state = STATE_STREAM_PAUSED; + + ipu_select_buffer(vout->post_proc_ch, IPU_INPUT_BUFFER, 0); + ipu_select_buffer(vout->post_proc_ch, IPU_INPUT_BUFFER, 1); + + if (use_direct_adc == false) { + ipu_select_buffer(vout->post_proc_ch, IPU_OUTPUT_BUFFER, 0); + ipu_select_buffer(vout->post_proc_ch, IPU_OUTPUT_BUFFER, 1); + + ipu_enable_channel(vout->post_proc_ch); + if ((vout->display_ch == MEM_SDC_FG) || + (vout->display_ch == MEM_SDC_BG)) { + acquire_console_sem(); + fb_blank(fbi, FB_BLANK_UNBLANK); + release_console_sem(); + } else { + ipu_enable_channel(vout->display_ch); + } + } else { + ipu_enable_channel(vout->post_proc_ch); + } + + vout->start_jiffies = jiffies; + dev_dbg(dev, + "streamon: start time = %lu jiffies\n", vout->start_jiffies); + + return 0; +} + +/*! + * Shut down the voutera + * + * @param vout structure vout_data * + * + * @return status 0 Success + */ +static int mxc_v4l2out_streamoff(vout_data * vout) +{ + struct fb_info *fbi = + registered_fb[vout->output_fb_num[vout->cur_disp_output]]; + int i, retval = 0; + unsigned long lockflag = 0; + + if (!vout) + return -EINVAL; + + if (vout->state == STATE_STREAM_OFF) { + return 0; + } + + spin_lock_irqsave(&g_lock, lockflag); + + del_timer(&vout->output_timer); + + if (vout->state == STATE_STREAM_ON) { + vout->state = STATE_STREAM_STOPPING; + } + + ipu_disable_irq(IPU_IRQ_PP_IN_EOF); + + spin_unlock_irqrestore(&g_lock, lockflag); + + if (vout->post_proc_ch == MEM_PP_MEM) { /* SDC or ADC with Rotation */ + if (vout->rotate >= IPU_ROTATE_90_RIGHT) { + ipu_unlink_channels(MEM_PP_MEM, MEM_ROT_PP_MEM); + ipu_unlink_channels(MEM_ROT_PP_MEM, vout->display_ch); + ipu_disable_channel(MEM_ROT_PP_MEM, true); + } else { + ipu_unlink_channels(MEM_PP_MEM, vout->display_ch); + } + ipu_disable_channel(MEM_PP_MEM, true); + if ((vout->display_ch != MEM_SDC_FG) && + (vout->display_ch != MEM_SDC_BG)) { + ipu_disable_channel(vout->display_ch, true); + ipu_uninit_channel(vout->display_ch); + } else { + fbi->var.activate |= FB_ACTIVATE_FORCE; + fb_set_var(fbi, &fbi->var); + } + + ipu_uninit_channel(MEM_PP_MEM); + if (vout->rotate >= IPU_ROTATE_90_RIGHT) + ipu_uninit_channel(MEM_ROT_PP_MEM); + } else { /* ADC Direct */ + ipu_disable_channel(MEM_PP_ADC, true); + ipu_uninit_channel(MEM_PP_ADC); + } + vout->ready_q.head = vout->ready_q.tail = 0; + vout->done_q.head = vout->done_q.tail = 0; + for (i = 0; i < vout->buffer_cnt; i++) { + vout->v4l2_bufs[i].flags = 0; + vout->v4l2_bufs[i].timestamp.tv_sec = 0; + vout->v4l2_bufs[i].timestamp.tv_usec = 0; + } + + vout->state = STATE_STREAM_OFF; + + if (vout->display_bufs[0] != 0) { + mxc_free_buffers(vout->display_bufs, + vout->display_bufs_vaddr, 2, + vout->display_buf_size); + } +#ifdef CONFIG_FB_MXC_ASYNC_PANEL + if (vout->cur_disp_output < DISP3) { + mxcfb_set_refresh_mode(registered_fb + [vout-> + output_fb_num[vout->cur_disp_output]], + MXCFB_REFRESH_PARTIAL, 0); + } +#endif + + return retval; +} + +/* + * Valid whether the palette is supported + * + * @param palette V4L2_PIX_FMT_RGB565, V4L2_PIX_FMT_BGR24 or V4L2_PIX_FMT_BGR32 + * + * @return 1 if supported, 0 if failed + */ +static inline int valid_mode(u32 palette) +{ + return ((palette == V4L2_PIX_FMT_RGB565) || + (palette == V4L2_PIX_FMT_BGR24) || + (palette == V4L2_PIX_FMT_RGB24) || + (palette == V4L2_PIX_FMT_BGR32) || + (palette == V4L2_PIX_FMT_RGB32) || + (palette == V4L2_PIX_FMT_YUV422P) || + (palette == V4L2_PIX_FMT_YUV420)); +} + +/* + * V4L2 - Handles VIDIOC_G_FMT Ioctl + * + * @param vout structure vout_data * + * + * @param v4l2_format structure v4l2_format * + * + * @return status 0 success, EINVAL failed + */ +static int mxc_v4l2out_g_fmt(vout_data * vout, struct v4l2_format *f) +{ + if (f->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) { + return -EINVAL; + } + *f = vout->v2f; + return 0; +} + +/* + * V4L2 - Handles VIDIOC_S_FMT Ioctl + * + * @param vout structure vout_data * + * + * @param v4l2_format structure v4l2_format * + * + * @return status 0 success, EINVAL failed + */ +static int mxc_v4l2out_s_fmt(vout_data * vout, struct v4l2_format *f) +{ + int retval = 0; + u32 size = 0; + u32 bytesperline; + + if (f->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) { + retval = -EINVAL; + goto err0; + } + if (!valid_mode(f->fmt.pix.pixelformat)) { + dev_err(vout->video_dev->dev, "pixel format not supported\n"); + retval = -EINVAL; + goto err0; + } + + bytesperline = (f->fmt.pix.width * fmt_to_bpp(f->fmt.pix.pixelformat)) / + 8; + if (f->fmt.pix.bytesperline < bytesperline) { + f->fmt.pix.bytesperline = bytesperline; + } else { + bytesperline = f->fmt.pix.bytesperline; + } + + switch (f->fmt.pix.pixelformat) { + case V4L2_PIX_FMT_YUV422P: + /* byteperline for YUV planar formats is for + Y plane only */ + size = bytesperline * f->fmt.pix.height * 2; + break; + case V4L2_PIX_FMT_YUV420: + size = (bytesperline * f->fmt.pix.height * 3) / 2; + break; + default: + size = bytesperline * f->fmt.pix.height; + break; + } + + /* Return the actual size of the image to the app */ + if (f->fmt.pix.sizeimage < size) { + f->fmt.pix.sizeimage = size; + } else { + size = f->fmt.pix.sizeimage; + } + + vout->v2f.fmt.pix = f->fmt.pix; + if (vout->v2f.fmt.pix.priv != 0) { + if (copy_from_user(&vout->offset, + (void *)vout->v2f.fmt.pix.priv, + sizeof(vout->offset))) { + retval = -EFAULT; + goto err0; + } + } + + retval = 0; + err0: + return retval; +} + +/* + * V4L2 - Handles VIDIOC_G_CTRL Ioctl + * + * @param vout structure vout_data * + * + * @param c structure v4l2_control * + * + * @return status 0 success, EINVAL failed + */ +static int mxc_get_v42lout_control(vout_data * vout, struct v4l2_control *c) +{ + switch (c->id) { + case V4L2_CID_HFLIP: + return (vout->rotate & IPU_ROTATE_HORIZ_FLIP) ? 1 : 0; + case V4L2_CID_VFLIP: + return (vout->rotate & IPU_ROTATE_VERT_FLIP) ? 1 : 0; + case (V4L2_CID_PRIVATE_BASE + 1): + return vout->rotate; + default: + return -EINVAL; + } +} + +/* + * V4L2 - Handles VIDIOC_S_CTRL Ioctl + * + * @param vout structure vout_data * + * + * @param c structure v4l2_control * + * + * @return status 0 success, EINVAL failed + */ +static int mxc_set_v42lout_control(vout_data * vout, struct v4l2_control *c) +{ + switch (c->id) { + case V4L2_CID_HFLIP: + vout->rotate |= c->value ? IPU_ROTATE_HORIZ_FLIP : + IPU_ROTATE_NONE; + break; + case V4L2_CID_VFLIP: + vout->rotate |= c->value ? IPU_ROTATE_VERT_FLIP : + IPU_ROTATE_NONE; + break; + case V4L2_CID_MXC_ROT: + vout->rotate = c->value; + break; + default: + return -EINVAL; + } + return 0; +} + +/*! + * V4L2 interface - open function + * + * @param inode structure inode * + * + * @param file structure file * + * + * @return status 0 success, ENODEV invalid device instance, + * ENODEV timeout, ERESTARTSYS interrupted by user + */ +static int mxc_v4l2out_open(struct inode *inode, struct file *file) +{ + struct video_device *dev = video_devdata(file); + vout_data *vout = video_get_drvdata(dev); + int err; + + if (!vout) { + return -ENODEV; + } + + down(&vout->busy_lock); + + err = -EINTR; + if (signal_pending(current)) + goto oops; + + if (vout->open_count++ == 0) { + ipu_request_irq(IPU_IRQ_PP_IN_EOF, + mxc_v4l2out_pp_in_irq_handler, + 0, dev->name, vout); + + init_waitqueue_head(&vout->v4l_bufq); + + init_timer(&vout->output_timer); + vout->output_timer.function = mxc_v4l2out_timer_handler; + vout->output_timer.data = (unsigned long)vout; + + vout->state = STATE_STREAM_OFF; + g_irq_cnt = g_buf_output_cnt = g_buf_q_cnt = g_buf_dq_cnt = 0; + + } + + file->private_data = dev; + + up(&vout->busy_lock); + + return 0; + + oops: + up(&vout->busy_lock); + return err; +} + +/*! + * V4L2 interface - close function + * + * @param inode struct inode * + * + * @param file struct file * + * + * @return 0 success + */ +static int mxc_v4l2out_close(struct inode *inode, struct file *file) +{ + struct video_device *dev = video_devdata(file); + vout_data *vout = video_get_drvdata(dev); + + if (--vout->open_count == 0) { + if (vout->state != STATE_STREAM_OFF) + mxc_v4l2out_streamoff(vout); + + ipu_free_irq(IPU_IRQ_PP_IN_EOF, vout); + + file->private_data = NULL; + + mxc_free_buffers(vout->queue_buf_paddr, vout->queue_buf_vaddr, + vout->buffer_cnt, vout->queue_buf_size); + vout->buffer_cnt = 0; + mxc_free_buffers(vout->rot_pp_bufs, vout->rot_pp_bufs_vaddr, 2, + vout->display_buf_size); + + /* capture off */ + wake_up_interruptible(&vout->v4l_bufq); + } + + return 0; +} + +/*! + * V4L2 interface - ioctl function + * + * @param inode struct inode * + * + * @param file struct file * + * + * @param ioctlnr unsigned int + * + * @param arg void * + * + * @return 0 success, ENODEV for invalid device instance, + * -1 for other errors. + */ +static int +mxc_v4l2out_do_ioctl(struct inode *inode, struct file *file, + unsigned int ioctlnr, void *arg) +{ + struct video_device *vdev = file->private_data; + vout_data *vout = video_get_drvdata(vdev); + int retval = 0; + int i = 0; + + if (!vout) + return -EBADF; + + /* make this _really_ smp-safe */ + if (down_interruptible(&vout->busy_lock)) + return -EBUSY; + + switch (ioctlnr) { + case VIDIOC_QUERYCAP: + { + struct v4l2_capability *cap = arg; + strcpy(cap->driver, "mxc_v4l2_output"); + cap->version = 0; + cap->capabilities = + V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_STREAMING; + cap->card[0] = '\0'; + cap->bus_info[0] = '\0'; + retval = 0; + break; + } + case VIDIOC_G_FMT: + { + struct v4l2_format *gf = arg; + retval = mxc_v4l2out_g_fmt(vout, gf); + break; + } + case VIDIOC_S_FMT: + { + struct v4l2_format *sf = arg; + if (vout->state != STATE_STREAM_OFF) { + retval = -EBUSY; + break; + } + retval = mxc_v4l2out_s_fmt(vout, sf); + break; + } + case VIDIOC_REQBUFS: + { + struct v4l2_requestbuffers *req = arg; + if ((req->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) || + (req->memory != V4L2_MEMORY_MMAP)) { + dev_dbg(vdev->dev, + "VIDIOC_REQBUFS: incorrect buffer type\n"); + retval = -EINVAL; + break; + } + + if (req->count == 0) + mxc_v4l2out_streamoff(vout); + + if (vout->state == STATE_STREAM_OFF) { + if (vout->queue_buf_paddr[0] != 0) { + mxc_free_buffers(vout->queue_buf_paddr, + vout->queue_buf_vaddr, + vout->buffer_cnt, + vout->queue_buf_size); + dev_dbg(vdev->dev, + "VIDIOC_REQBUFS: freed buffers\n"); + } + vout->buffer_cnt = 0; + } else { + dev_dbg(vdev->dev, + "VIDIOC_REQBUFS: Buffer is in use\n"); + retval = -EBUSY; + break; + } + + if (req->count == 0) + break; + + if (req->count < MIN_FRAME_NUM) { + req->count = MIN_FRAME_NUM; + } else if (req->count > MAX_FRAME_NUM) { + req->count = MAX_FRAME_NUM; + } + vout->buffer_cnt = req->count; + vout->queue_buf_size = + PAGE_ALIGN(vout->v2f.fmt.pix.sizeimage); + + retval = mxc_allocate_buffers(vout->queue_buf_paddr, + vout->queue_buf_vaddr, + vout->buffer_cnt, + vout->queue_buf_size); + if (retval < 0) + break; + + /* Init buffer queues */ + vout->done_q.head = 0; + vout->done_q.tail = 0; + vout->ready_q.head = 0; + vout->ready_q.tail = 0; + + for (i = 0; i < vout->buffer_cnt; i++) { + memset(&(vout->v4l2_bufs[i]), 0, + sizeof(vout->v4l2_bufs[i])); + vout->v4l2_bufs[i].flags = 0; + vout->v4l2_bufs[i].memory = V4L2_MEMORY_MMAP; + vout->v4l2_bufs[i].index = i; + vout->v4l2_bufs[i].type = + V4L2_BUF_TYPE_VIDEO_OUTPUT; + vout->v4l2_bufs[i].length = + PAGE_ALIGN(vout->v2f.fmt.pix.sizeimage); + vout->v4l2_bufs[i].m.offset = + (unsigned long)vout->queue_buf_paddr[i]; + vout->v4l2_bufs[i].timestamp.tv_sec = 0; + vout->v4l2_bufs[i].timestamp.tv_usec = 0; + } + break; + } + case VIDIOC_QUERYBUF: + { + struct v4l2_buffer *buf = arg; + u32 type = buf->type; + int index = buf->index; + + if ((type != V4L2_BUF_TYPE_VIDEO_OUTPUT) || + (index >= vout->buffer_cnt)) { + dev_dbg(vdev->dev, + "VIDIOC_QUERYBUFS: incorrect buffer type\n"); + retval = -EINVAL; + break; + } + down(&vout->param_lock); + memcpy(buf, &(vout->v4l2_bufs[index]), sizeof(*buf)); + up(&vout->param_lock); + break; + } + case VIDIOC_QBUF: + { + struct v4l2_buffer *buf = arg; + int index = buf->index; + unsigned long lock_flags; + + if ((buf->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) || + (index >= vout->buffer_cnt)) { + retval = -EINVAL; + break; + } + + dev_dbg(vdev->dev, "VIDIOC_QBUF: %d\n", buf->index); + + /* mmapped buffers are L1 WB cached, + * so we need to clean them */ + if (buf->flags & V4L2_BUF_FLAG_MAPPED) { + flush_cache_all(); + } + + spin_lock_irqsave(&g_lock, lock_flags); + + memcpy(&(vout->v4l2_bufs[index]), buf, sizeof(*buf)); + vout->v4l2_bufs[index].flags |= V4L2_BUF_FLAG_QUEUED; + + g_buf_q_cnt++; + queue_buf(&vout->ready_q, index); + if (vout->state == STATE_STREAM_PAUSED) { + unsigned long timeout; + + index = peek_next_buf(&vout->ready_q); + + /* if timestamp is 0, then default to 30fps */ + if ((vout->v4l2_bufs[index].timestamp.tv_sec == + 0) + && (vout->v4l2_bufs[index].timestamp. + tv_usec == 0)) + timeout = + vout->start_jiffies + + vout->frame_count * HZ / 30; + else + timeout = + get_jiffies(&vout->v4l2_bufs[index]. + timestamp); + + if (jiffies >= timeout) { + dev_dbg(vout->video_dev->dev, + "warning: timer timeout already expired.\n"); + } + vout->output_timer.expires = timeout; + dev_dbg(vdev->dev, + "QBUF: frame #%u timeout @ %lu jiffies, current = %lu\n", + vout->frame_count, timeout, jiffies); + add_timer(&vout->output_timer); + vout->state = STATE_STREAM_ON; + } + + spin_unlock_irqrestore(&g_lock, lock_flags); + break; + } + case VIDIOC_DQBUF: + { + struct v4l2_buffer *buf = arg; + int idx; + + if ((queue_size(&vout->done_q) == 0) && + (file->f_flags & O_NONBLOCK)) { + retval = -EAGAIN; + break; + } + + if (!wait_event_interruptible_timeout(vout->v4l_bufq, + queue_size(&vout-> + done_q) + != 0, 10 * HZ)) { + dev_dbg(vdev->dev, "VIDIOC_DQBUF: timeout\n"); + retval = -ETIME; + break; + } else if (signal_pending(current)) { + dev_dbg(vdev->dev, + "VIDIOC_DQBUF: interrupt received\n"); + retval = -ERESTARTSYS; + break; + } + idx = dequeue_buf(&vout->done_q); + if (idx == -1) { /* No frame free */ + dev_dbg(vdev->dev, + "VIDIOC_DQBUF: no free buffers, returning\n"); + retval = -EAGAIN; + break; + } + if ((vout->v4l2_bufs[idx].flags & V4L2_BUF_FLAG_DONE) == + 0) + dev_dbg(vdev->dev, + "VIDIOC_DQBUF: buffer in done q, but not " + "flagged as done\n"); + + vout->v4l2_bufs[idx].flags = 0; + memcpy(buf, &(vout->v4l2_bufs[idx]), sizeof(*buf)); + dev_dbg(vdev->dev, "VIDIOC_DQBUF: %d\n", buf->index); + break; + } + case VIDIOC_STREAMON: + { + retval = mxc_v4l2out_streamon(vout); + break; + } + case VIDIOC_STREAMOFF: + { + retval = mxc_v4l2out_streamoff(vout); + break; + } + case VIDIOC_G_CTRL: + { + retval = mxc_get_v42lout_control(vout, arg); + break; + } + case VIDIOC_S_CTRL: + { + retval = mxc_set_v42lout_control(vout, arg); + break; + } + case VIDIOC_CROPCAP: + { + struct v4l2_cropcap *cap = arg; + + if (cap->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) { + retval = -EINVAL; + break; + } + cap->bounds = vout->crop_bounds[vout->cur_disp_output]; + cap->defrect = vout->crop_bounds[vout->cur_disp_output]; + retval = 0; + break; + } + case VIDIOC_G_CROP: + { + struct v4l2_crop *crop = arg; + + if (crop->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) { + retval = -EINVAL; + break; + } + crop->c = vout->crop_current; + break; + } + case VIDIOC_S_CROP: + { + struct v4l2_crop *crop = arg; + struct v4l2_rect *b = + &(vout->crop_bounds[vout->cur_disp_output]); + + if (crop->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) { + retval = -EINVAL; + break; + } + if (crop->c.height < 0) { + retval = -EINVAL; + break; + } + if (crop->c.width < 0) { + retval = -EINVAL; + break; + } + + /* only full screen supported for SDC BG */ + if (vout->cur_disp_output == 4) { + crop->c = vout->crop_current; + break; + } + + if (crop->c.top < b->top) + crop->c.top = b->top; + if (crop->c.top >= b->top + b->height) + crop->c.top = b->top + b->height - 1; + if (crop->c.height > b->top - crop->c.top + b->height) + crop->c.height = + b->top - crop->c.top + b->height; + + if (crop->c.left < b->left) + crop->c.left = b->left; + if (crop->c.left >= b->left + b->width) + crop->c.left = b->left + b->width - 1; + if (crop->c.width > b->left - crop->c.left + b->width) + crop->c.width = + b->left - crop->c.left + b->width; + + /* stride line limitation */ + crop->c.height -= crop->c.height % 8; + crop->c.width -= crop->c.width % 8; + + vout->crop_current = crop->c; + break; + } + case VIDIOC_ENUMOUTPUT: + { + struct v4l2_output *output = arg; + + if ((output->index >= 5) || + (vout->output_enabled[output->index] == false)) { + retval = -EINVAL; + break; + } + + if (output->index < 3) { + *output = mxc_outputs[MXC_V4L2_OUT_2_ADC]; + output->name[4] = '0' + output->index; + } else { + *output = mxc_outputs[MXC_V4L2_OUT_2_SDC]; + } + break; + } + case VIDIOC_G_OUTPUT: + { + int *p_output_num = arg; + + *p_output_num = vout->cur_disp_output; + break; + } + case VIDIOC_S_OUTPUT: + { + int *p_output_num = arg; + + if ((*p_output_num >= 5) || + (vout->output_enabled[*p_output_num] == false)) { + retval = -EINVAL; + break; + } + + if (vout->state != STATE_STREAM_OFF) { + retval = -EBUSY; + break; + } + + vout->cur_disp_output = *p_output_num; + vout->crop_current = + vout->crop_bounds[vout->cur_disp_output]; + break; + } + case VIDIOC_ENUM_FMT: + case VIDIOC_TRY_FMT: + case VIDIOC_QUERYCTRL: + case VIDIOC_G_PARM: + case VIDIOC_ENUMSTD: + case VIDIOC_G_STD: + case VIDIOC_S_STD: + case VIDIOC_G_TUNER: + case VIDIOC_S_TUNER: + case VIDIOC_G_FREQUENCY: + case VIDIOC_S_FREQUENCY: + default: + retval = -EINVAL; + break; + } + + up(&vout->busy_lock); + return retval; +} + +/* + * V4L2 interface - ioctl function + * + * @return None + */ +static int +mxc_v4l2out_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return video_usercopy(inode, file, cmd, arg, mxc_v4l2out_do_ioctl); +} + +/*! + * V4L2 interface - mmap function + * + * @param file structure file * + * + * @param vma structure vm_area_struct * + * + * @return status 0 Success, EINTR busy lock error, + * ENOBUFS remap_page error + */ +static int mxc_v4l2out_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct video_device *vdev = video_devdata(file); + unsigned long size = vma->vm_end - vma->vm_start; + int res = 0; + int i; + vout_data *vout = video_get_drvdata(vdev); + + dev_dbg(vdev->dev, "pgoff=0x%lx, start=0x%lx, end=0x%lx\n", + vma->vm_pgoff, vma->vm_start, vma->vm_end); + + /* make this _really_ smp-safe */ + if (down_interruptible(&vout->busy_lock)) + return -EINTR; + + for (i = 0; i < vout->buffer_cnt; i++) { + if ((vout->v4l2_bufs[i].m.offset == + (vma->vm_pgoff << PAGE_SHIFT)) && + (vout->v4l2_bufs[i].length >= size)) { + vout->v4l2_bufs[i].flags |= V4L2_BUF_FLAG_MAPPED; + break; + } + } + if (i == vout->buffer_cnt) { + res = -ENOBUFS; + goto mxc_mmap_exit; + } + + /* make buffers inner write-back, outer write-thru cacheable */ + vma->vm_page_prot = pgprot_outer_wrthru(vma->vm_page_prot); + + if (remap_pfn_range(vma, vma->vm_start, + vma->vm_pgoff, size, vma->vm_page_prot)) { + dev_dbg(vdev->dev, "mmap remap_pfn_range failed\n"); + res = -ENOBUFS; + goto mxc_mmap_exit; + } + + vma->vm_flags &= ~VM_IO; /* using shared anonymous pages */ + + mxc_mmap_exit: + up(&vout->busy_lock); + return res; +} + +/*! + * V4L2 interface - poll function + * + * @param file structure file * + * + * @param wait structure poll_table * + * + * @return status POLLIN | POLLRDNORM + */ +static unsigned int mxc_v4l2out_poll(struct file *file, poll_table * wait) +{ + struct video_device *dev = video_devdata(file); + vout_data *vout = video_get_drvdata(dev); + + wait_queue_head_t *queue = NULL; + int res = POLLIN | POLLRDNORM; + + if (down_interruptible(&vout->busy_lock)) + return -EINTR; + + queue = &vout->v4l_bufq; + poll_wait(file, queue, wait); + + up(&vout->busy_lock); + return res; +} + +static struct +file_operations mxc_v4l2out_fops = { + .owner = THIS_MODULE, + .open = mxc_v4l2out_open, + .release = mxc_v4l2out_close, + .ioctl = mxc_v4l2out_ioctl, + .mmap = mxc_v4l2out_mmap, + .poll = mxc_v4l2out_poll, +}; + +static struct video_device mxc_v4l2out_template = { + .owner = THIS_MODULE, + .name = "MXC Video Output", + .type = 0, + .type2 = V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_STREAMING, + .hardware = 0, + .fops = &mxc_v4l2out_fops, + .release = video_device_release, +}; + +/*! + * Probe routine for the framebuffer driver. It is called during the + * driver binding process. The following functions are performed in + * this routine: Framebuffer initialization, Memory allocation and + * mapping, Framebuffer registration, IPU initialization. + * + * @return Appropriate error code to the kernel common code + */ +static int mxc_v4l2out_probe(struct platform_device *pdev) +{ + int i; + vout_data *vout; + + /* + * Allocate sufficient memory for the fb structure + */ + g_vout = vout = kmalloc(sizeof(vout_data), GFP_KERNEL); + + if (!vout) + return 0; + + memset(vout, 0, sizeof(vout_data)); + + vout->video_dev = video_device_alloc(); + if (vout->video_dev == NULL) + return -1; + vout->video_dev->dev = &pdev->dev; + vout->video_dev->minor = -1; + + *(vout->video_dev) = mxc_v4l2out_template; + + /* register v4l device */ + if (video_register_device(vout->video_dev, + VFL_TYPE_GRABBER, video_nr) == -1) { + dev_dbg(&pdev->dev, "video_register_device failed\n"); + return 0; + } + dev_info(&pdev->dev, "Registered device video%d\n", + vout->video_dev->minor & 0x1f); + vout->video_dev->dev = &pdev->dev; + + video_set_drvdata(vout->video_dev, vout); + + init_MUTEX(&vout->param_lock); + init_MUTEX(&vout->busy_lock); + + /* setup outputs and cropping */ + vout->cur_disp_output = -1; + for (i = 0; i < num_registered_fb; i++) { + char *idstr = registered_fb[i]->fix.id; + if (strncmp(idstr, "DISP", 4) == 0) { + int disp_num = idstr[4] - '0'; + if ((disp_num == 3) && + (strncmp(idstr, "DISP3 BG", 8) == 0)) { + disp_num = 4; + } + vout->crop_bounds[disp_num].left = 0; + vout->crop_bounds[disp_num].top = 0; + vout->crop_bounds[disp_num].width = + registered_fb[i]->var.xres; + vout->crop_bounds[disp_num].height = + registered_fb[i]->var.yres; + vout->output_enabled[disp_num] = true; + vout->output_fb_num[disp_num] = i; + if (vout->cur_disp_output == -1) { + vout->cur_disp_output = disp_num; + } + } + + } + vout->crop_current = vout->crop_bounds[vout->cur_disp_output]; + + platform_set_drvdata(pdev, vout); + + return 0; +} + +static int mxc_v4l2out_remove(struct platform_device *pdev) +{ + vout_data *vout = platform_get_drvdata(pdev); + + if (vout->video_dev) { + if (-1 != vout->video_dev->minor) + video_unregister_device(vout->video_dev); + else + video_device_release(vout->video_dev); + vout->video_dev = NULL; + } + + platform_set_drvdata(pdev, NULL); + + kfree(vout); + + return 0; +} + +/*! + * This structure contains pointers to the power management callback functions. + */ +static struct platform_driver mxc_v4l2out_driver = { + .driver = { + .name = "MXC Video Output", + }, + .probe = mxc_v4l2out_probe, + .remove = mxc_v4l2out_remove, +}; + +static struct platform_device mxc_v4l2out_device = { + .name = "MXC Video Output", + .id = 0, +}; + +/*! + * mxc v4l2 init function + * + */ +static int mxc_v4l2out_init(void) +{ + u8 err = 0; + + err = platform_driver_register(&mxc_v4l2out_driver); + if (err == 0) { + platform_device_register(&mxc_v4l2out_device); + } + return err; +} + +/*! + * mxc v4l2 cleanup function + * + */ +static void mxc_v4l2out_clean(void) +{ + video_unregister_device(g_vout->video_dev); + + platform_driver_unregister(&mxc_v4l2out_driver); + platform_device_unregister(&mxc_v4l2out_device); + kfree(g_vout); + g_vout = NULL; +} + +module_init(mxc_v4l2out_init); +module_exit(mxc_v4l2out_clean); + +module_param(video_nr, int, 0444); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("V4L2-driver for MXC video output"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("video"); diff --git a/drivers/media/video/mxc/output/mxc_v4l2_output.h b/drivers/media/video/mxc/output/mxc_v4l2_output.h new file mode 100644 index 000000000000..6fb6f5a667bb --- /dev/null +++ b/drivers/media/video/mxc/output/mxc_v4l2_output.h @@ -0,0 +1,130 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @defgroup MXC_V4L2_OUTPUT MXC V4L2 Video Output Driver + */ +/*! + * @file mxc_v4l2_output.h + * + * @brief MXC V4L2 Video Output Driver Header file + * + * Video4Linux2 Output Device using MXC IPU Post-processing functionality. + * + * @ingroup MXC_V4L2_OUTPUT + */ +#ifndef __MXC_V4L2_OUTPUT_H__ +#define __MXC_V4L2_OUTPUT_H__ + +#include <media/v4l2-dev.h> + +#ifdef __KERNEL__ + +#include <asm/arch/ipu.h> +#include <asm/arch/mxc_v4l2.h> + +#define MIN_FRAME_NUM 2 +#define MAX_FRAME_NUM 30 + +#define MXC_V4L2_OUT_NUM_OUTPUTS 5 +#define MXC_V4L2_OUT_2_SDC 0 +#define MXC_V4L2_OUT_2_ADC 1 + +typedef struct { + int list[MAX_FRAME_NUM + 1]; + int head; + int tail; +} v4l_queue; + +/*! + * States for the video stream + */ +typedef enum { + STATE_STREAM_OFF, + STATE_STREAM_ON, + STATE_STREAM_PAUSED, + STATE_STREAM_STOPPING, +} v4lout_state; + +/*! + * common v4l2 driver structure. + */ +typedef struct _vout_data { + struct video_device *video_dev; + /*! + * semaphore guard against SMP multithreading + */ + struct semaphore busy_lock; + + /*! + * number of process that have device open + */ + int open_count; + + /*! + * params lock for this camera + */ + struct semaphore param_lock; + + struct timer_list output_timer; + unsigned long start_jiffies; + u32 frame_count; + + v4l_queue ready_q; + v4l_queue done_q; + + s8 next_rdy_ipu_buf; + s8 next_done_ipu_buf; + s8 ipu_buf[2]; + volatile v4lout_state state; + + int cur_disp_output; + int output_fb_num[MXC_V4L2_OUT_NUM_OUTPUTS]; + int output_enabled[MXC_V4L2_OUT_NUM_OUTPUTS]; + struct v4l2_framebuffer v4l2_fb; + ipu_channel_t display_ch; + ipu_channel_t post_proc_ch; + + /*! + * FRAME_NUM-buffering, so we need a array + */ + int buffer_cnt; + dma_addr_t queue_buf_paddr[MAX_FRAME_NUM]; + void *queue_buf_vaddr[MAX_FRAME_NUM]; + u32 queue_buf_size; + struct v4l2_buffer v4l2_bufs[MAX_FRAME_NUM]; + u32 display_buf_size; + dma_addr_t display_bufs[2]; + void *display_bufs_vaddr[2]; + dma_addr_t rot_pp_bufs[2]; + void *rot_pp_bufs_vaddr[2]; + + /*! + * Poll wait queue + */ + wait_queue_head_t v4l_bufq; + + /*! + * v4l2 format + */ + struct v4l2_format v2f; + struct v4l2_mxc_offset offset; + ipu_rotate_mode_t rotate; + + /* crop */ + struct v4l2_rect crop_bounds[MXC_V4L2_OUT_NUM_OUTPUTS]; + struct v4l2_rect crop_current; +} vout_data; + +#endif +#endif /* __MXC_V4L2_OUTPUT_H__ */ diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig index 5fef6783c716..625dbdcf7142 100644 --- a/drivers/mmc/host/Kconfig +++ b/drivers/mmc/host/Kconfig @@ -130,3 +130,12 @@ config MMC_SPI If unsure, or if your system has no SPI master driver, say N. + If unsure, say N. + +config MMC_MXC + tristate "Freescale MXC Multimedia Card Interface support" + depends on ARCH_MXC && MMC + help + This selects the Freescale MXC Multimedia card Interface. + If you have a MXC platform with a Multimedia Card slot, + say Y or M here. diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile index 3877c87e6da2..9d00dc1e1600 100644 --- a/drivers/mmc/host/Makefile +++ b/drivers/mmc/host/Makefile @@ -17,4 +17,5 @@ obj-$(CONFIG_MMC_OMAP) += omap.o obj-$(CONFIG_MMC_AT91) += at91_mci.o obj-$(CONFIG_MMC_TIFM_SD) += tifm_sd.o obj-$(CONFIG_MMC_SPI) += mmc_spi.o +obj-$(CONFIG_MMC_MXC) += mxc_mmc.o diff --git a/drivers/mmc/host/mxc_mmc.c b/drivers/mmc/host/mxc_mmc.c new file mode 100644 index 000000000000..29d23824c1a8 --- /dev/null +++ b/drivers/mmc/host/mxc_mmc.c @@ -0,0 +1,1394 @@ +/* + * linux/drivers/mmc/host/mxc_mmc.c - Freescale MXC/i.MX MMC driver + * + * based on imxmmc.c + * Copyright (C) 2004 Sascha Hauer, Pengutronix <sascha@saschahauer.de> + * + * derived from pxamci.c by Russell King + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mxc_mmc.c + * + * @brief Driver for the Freescale Semiconductor MXC SDHC modules. + * + * This driver code is based on imxmmc.c, by Sascha Hauer, + * Pengutronix <sascha@saschahauer.de>. This driver supports both Secure Digital + * Host Controller modules (SDHC1 and SDHC2) of MXC. SDHC is also referred as + * MMC/SD controller. This code is not tested for SD cards. + * + * @ingroup MMC_SD + */ + +/* + * Include Files + */ +#include <linux/module.h> +#include <linux/init.h> +#include <linux/ioport.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/blkdev.h> +#include <linux/dma-mapping.h> +#include <linux/mmc/mmc.h> +#include <linux/mmc/host.h> +#include <linux/mmc/card.h> +#include <linux/delay.h> +#include <linux/timer.h> +#include <linux/clk.h> + +#include <asm/dma.h> +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/sizes.h> +#include <asm/mach-types.h> +#include <asm/mach/irq.h> +#include <asm/arch/mmc.h> + +#include "mxc_mmc.h" + +#if defined(CONFIG_MXC_MC13783_POWER) +#include <asm/arch/pmic_power.h> +#endif + +#define RSP_TYPE(x) ((x) & ~(MMC_RSP_BUSY|MMC_RSP_OPCODE)) + +static const int vdd_mapping[] = { + 0, 0, + 0, /* MMC_VDD_160 */ + 0, 0, + 1, /* MMC_VDD_180 */ + 0, + 2, /* MMC_VDD_200 */ + 0, 0, 0, 0, 0, + 3, /* MMC_VDD_260 */ + 4, /* MMC_VDD_270 */ + 5, /* MMC_VDD_280 */ + 6, /* MMC_VDD_290 */ + 7, /* MMC_VDD_300 */ + 7, /* MMC_VDD_310 - HACK for LP1070, actually 3.0V */ + 7, /* MMC_VDD_320 - HACK for LP1070, actually 3.0V */ + 0, 0, 0, 0 +}; + +/* + * This define is used to test the driver without using DMA + */ +#define MXC_MMC_DMA_ENABLE + +/*! + * Maxumum length of s/g list, only length of 1 is currently supported + */ +#define NR_SG 1 + +#ifdef CONFIG_MMC_DEBUG +static void dump_cmd(struct mmc_command *cmd) +{ + printk(KERN_INFO "%s: CMD: opcode: %d ", DRIVER_NAME, cmd->opcode); + printk(KERN_INFO "arg: 0x%08x ", cmd->arg); + printk(KERN_INFO "flags: 0x%08x\n", cmd->flags); +} + +static void dump_status(const char *func, int sts) +{ + unsigned int bitset; + printk(KERN_INFO "%s:status: ", func); + while (sts) { + /* Find the next bit set */ + bitset = sts & ~(sts - 1); + switch (bitset) { + case STATUS_CARD_INSERTION: + printk(KERN_INFO "CARD_INSERTION|"); + break; + case STATUS_CARD_REMOVAL: + printk(KERN_INFO "CARD_REMOVAL |"); + break; + case STATUS_YBUF_EMPTY: + printk(KERN_INFO "YBUF_EMPTY |"); + break; + case STATUS_XBUF_EMPTY: + printk(KERN_INFO "XBUF_EMPTY |"); + break; + case STATUS_YBUF_FULL: + printk(KERN_INFO "YBUF_FULL |"); + break; + case STATUS_XBUF_FULL: + printk(KERN_INFO "XBUF_FULL |"); + break; + case STATUS_BUF_UND_RUN: + printk(KERN_INFO "BUF_UND_RUN |"); + break; + case STATUS_BUF_OVFL: + printk(KERN_INFO "BUF_OVFL |"); + break; + case STATUS_READ_OP_DONE: + printk(KERN_INFO "READ_OP_DONE |"); + break; + case STATUS_WR_CRC_ERROR_CODE_MASK: + printk(KERN_INFO "WR_CRC_ERROR_CODE |"); + break; + case STATUS_READ_CRC_ERR: + printk(KERN_INFO "READ_CRC_ERR |"); + break; + case STATUS_WRITE_CRC_ERR: + printk(KERN_INFO "WRITE_CRC_ERR |"); + break; + case STATUS_SDIO_INT_ACTIVE: + printk(KERN_INFO "SDIO_INT_ACTIVE |"); + break; + case STATUS_END_CMD_RESP: + printk(KERN_INFO "END_CMD_RESP |"); + break; + case STATUS_WRITE_OP_DONE: + printk(KERN_INFO "WRITE_OP_DONE |"); + break; + case STATUS_CARD_BUS_CLK_RUN: + printk(KERN_INFO "CARD_BUS_CLK_RUN |"); + break; + case STATUS_BUF_READ_RDY: + printk(KERN_INFO "BUF_READ_RDY |"); + break; + case STATUS_BUF_WRITE_RDY: + printk(KERN_INFO "BUF_WRITE_RDY |"); + break; + case STATUS_RESP_CRC_ERR: + printk(KERN_INFO "RESP_CRC_ERR |"); + break; + case STATUS_TIME_OUT_RESP: + printk(KERN_INFO "TIME_OUT_RESP |"); + break; + case STATUS_TIME_OUT_READ: + printk(KERN_INFO "TIME_OUT_READ |"); + break; + default: + printk(KERN_INFO "Invalid Status Register value0x%x\n", + bitset); + break; + } + sts &= ~bitset; + } + printk(KERN_INFO "\n"); +} +#endif + +/*! + * This structure is a way for the low level driver to define their own + * \b mmc_host structure. This structure includes the core \b mmc_host + * structure that is provided by Linux MMC/SD Bus protocol driver as an + * element and has other elements that are specifically required by this + * low-level driver. + */ +struct mxcmci_host { + /*! + * The mmc structure holds all the information about the device + * structure, current SDHC io bus settings, the current OCR setting, + * devices attached to this host, and so on. + */ + struct mmc_host *mmc; + + /*! + * This variable is used for locking the host data structure from + * multiple access. + */ + spinlock_t lock; + + /*! + * Resource structure, which will maintain base addresses and IRQs. + */ + struct resource *res; + + /*! + * Base address of SDHC, used in readl and writel. + */ + void *base; + + /*! + * SDHC IRQ number. + */ + int irq; + + /*! + * Card Detect IRQ number. + */ + int detect_irq; + + /*! + * Clock id to hold ipg_perclk. + */ + struct clk *clk; + /*! + * MMC mode. + */ + int mode; + + /*! + * DMA channel number. + */ + int dma; + + /*! + * Pointer to hold MMC/SD request. + */ + struct mmc_request *req; + + /*! + * Pointer to hold MMC/SD command. + */ + struct mmc_command *cmd; + + /*! + * Pointer to hold MMC/SD data. + */ + struct mmc_data *data; + + /*! + * Holds the number of bytes to transfer using DMA. + */ + unsigned int dma_size; + + /*! + * Value to store in Command and Data Control Register + * - currently unused + */ + unsigned int cmdat; + + /*! + * Power mode - currently unused + */ + unsigned int power_mode; + + /*! + * DMA address for scatter-gather transfers + */ + dma_addr_t sg_dma; + + /*! + * Length of the scatter-gather list + */ + unsigned int dma_len; + + /*! + * Holds the direction of data transfer. + */ + unsigned int dma_dir; + + /*! + * Id for MMC block. + */ + unsigned int id; + + /*! + * Note whether this driver has been suspended. + */ + unsigned int mxc_mmc_suspend_flag; + + /*! + * Platform specific data + */ + struct mxc_mmc_platform_data *plat_data; +}; + +extern void gpio_sdhc_active(int module); +extern void gpio_sdhc_inactive(int module); + +#ifdef MXC_MMC_DMA_ENABLE +static void mxcmci_dma_irq(void *devid, int error, unsigned int cnt); +#endif +static int mxcmci_data_done(struct mxcmci_host *host, unsigned int stat); + +/* Wait count to start the clock */ +#define CMD_WAIT_CNT 100 + +/*! + * This function sets the SDHC register to stop the clock and waits for the + * clock stop indication. + */ +static void mxcmci_stop_clock(struct mxcmci_host *host, bool wait) +{ + int wait_cnt = 0; + while (1) { + __raw_writel(STR_STP_CLK_IPG_CLK_GATE_DIS | + STR_STP_CLK_IPG_PERCLK_GATE_DIS | + STR_STP_CLK_STOP_CLK, + host->base + MMC_STR_STP_CLK); + + if (!wait) + break; + + wait_cnt = CMD_WAIT_CNT; + while (wait_cnt--) { + if (!(__raw_readl(host->base + MMC_STATUS) & + STATUS_CARD_BUS_CLK_RUN)) + break; + } + + if (!(__raw_readl(host->base + MMC_STATUS) & + STATUS_CARD_BUS_CLK_RUN)) + break; + } +} + +/*! + * This function sets the SDHC register to start the clock and waits for the + * clock start indication. When the clock starts SDHC module starts processing + * the command in CMD Register with arguments in ARG Register. + * + * @param host Pointer to MMC/SD host structure + * @param wait Boolean value to indicate whether to wait for the clock to start or come out instantly + */ +static void mxcmci_start_clock(struct mxcmci_host *host, bool wait) +{ + int wait_cnt; + +#ifdef CONFIG_MMC_DEBUG + dump_status(__FUNCTION__, __raw_readl(host->base + MMC_STATUS)); +#endif + + while (1) { + __raw_writel(STR_STP_CLK_IPG_CLK_GATE_DIS | + STR_STP_CLK_IPG_PERCLK_GATE_DIS | + STR_STP_CLK_START_CLK, + host->base + MMC_STR_STP_CLK); + if (!wait) + break; + + wait_cnt = CMD_WAIT_CNT; + while (wait_cnt--) { + if (__raw_readl(host->base + MMC_STATUS) & + STATUS_CARD_BUS_CLK_RUN) { + break; + } + } + + if (__raw_readl(host->base + MMC_STATUS) & + STATUS_CARD_BUS_CLK_RUN) { + break; + } + } +#ifdef CONFIG_MMC_DEBUG + dump_status(__FUNCTION__, __raw_readl(host->base + MMC_STATUS)); +#endif + pr_debug("%s:CLK_RATE: 0x%08x\n", DRIVER_NAME, + __raw_readl(host->base + MMC_CLK_RATE)); +} + +/*! + * This function resets the SDHC host. + * + * @param host Pointer to MMC/SD host structure + */ +static void mxcmci_softreset(struct mxcmci_host *host) +{ + /* reset sequence */ + __raw_writel(0x8, host->base + MMC_STR_STP_CLK); + __raw_writel(0x9, host->base + MMC_STR_STP_CLK); + __raw_writel(0x1, host->base + MMC_STR_STP_CLK); + __raw_writel(0x1, host->base + MMC_STR_STP_CLK); + __raw_writel(0x1, host->base + MMC_STR_STP_CLK); + __raw_writel(0x1, host->base + MMC_STR_STP_CLK); + __raw_writel(0x1, host->base + MMC_STR_STP_CLK); + __raw_writel(0x1, host->base + MMC_STR_STP_CLK); + __raw_writel(0x1, host->base + MMC_STR_STP_CLK); + __raw_writel(0x1, host->base + MMC_STR_STP_CLK); + __raw_writel(0x3f, host->base + MMC_CLK_RATE); + + __raw_writel(0xff, host->base + MMC_RES_TO); + __raw_writel(512, host->base + MMC_BLK_LEN); + __raw_writel(1, host->base + MMC_NOB); +} + +/*! + * This function is called to setup SDHC register for data transfer. + * The function allocates DMA buffers, configures the DMA channel. + * Start the DMA channel to transfer data. When DMA is not enabled this + * function set ups only Number of Block and Block Length registers. + * + * @param host Pointer to MMC/SD host structure + * @param data Pointer to MMC/SD data structure + */ +static void mxcmci_setup_data(struct mxcmci_host *host, struct mmc_data *data) +{ + unsigned int nob = data->blocks; + + if (data->flags & MMC_DATA_STREAM) { + nob = 0xffff; + } + + host->data = data; + + __raw_writel(nob, host->base + MMC_NOB); + __raw_writel(data->blksz, host->base + MMC_BLK_LEN); + + host->dma_size = data->blocks * data->blksz; + pr_debug("%s:Request bytes to transfer:%d\n", DRIVER_NAME, + host->dma_size); + +#ifdef MXC_MMC_DMA_ENABLE + if (host->dma_size <= (16 << host->mmc->ios.bus_width)) { + return; + } + + if (data->blksz & 0x3) { + printk(KERN_ERR + "mxc_mci: block size not multiple of 4 bytes\n"); + } + + if (data->flags & MMC_DATA_READ) { + host->dma_dir = DMA_FROM_DEVICE; + } else { + host->dma_dir = DMA_TO_DEVICE; + } + host->dma_len = dma_map_sg(mmc_dev(host->mmc), data->sg, data->sg_len, + host->dma_dir); + + if (data->flags & MMC_DATA_READ) { + mxc_dma_sg_config(host->dma, data->sg, data->sg_len, + host->dma_size, MXC_DMA_MODE_READ); + } else { + mxc_dma_sg_config(host->dma, data->sg, data->sg_len, + host->dma_size, MXC_DMA_MODE_WRITE); + } +#endif +} + +/*! + * This function is called by \b mxcmci_request() function to setup the SDHC + * register to issue command. This function disables the card insertion and + * removal detection interrupt. + * + * @param host Pointer to MMC/SD host structure + * @param cmd Pointer to MMC/SD command structure + * @param cmdat Value to store in Command and Data Control Register + */ +static void mxcmci_start_cmd(struct mxcmci_host *host, struct mmc_command *cmd, + unsigned int cmdat) +{ + WARN_ON(host->cmd != NULL); + host->cmd = cmd; + + switch (RSP_TYPE(mmc_resp_type(cmd))) { + case RSP_TYPE(MMC_RSP_R1): /* r1, r1b, r6 */ + cmdat |= CMD_DAT_CONT_RESPONSE_FORMAT_R1; + break; + case RSP_TYPE(MMC_RSP_R3): + cmdat |= CMD_DAT_CONT_RESPONSE_FORMAT_R3; + break; + case RSP_TYPE(MMC_RSP_R2): + cmdat |= CMD_DAT_CONT_RESPONSE_FORMAT_R2; + break; + default: + /* No Response required */ + break; + } + + if (cmd->opcode == MMC_GO_IDLE_STATE) { + cmdat |= CMD_DAT_CONT_INIT; /* This command needs init */ + } + + if (host->mmc->ios.bus_width == MMC_BUS_WIDTH_4) { + cmdat |= CMD_DAT_CONT_BUS_WIDTH_4; + } + + __raw_writel(cmd->opcode, host->base + MMC_CMD); + __raw_writel(cmd->arg, host->base + MMC_ARG); + + __raw_writel(cmdat, host->base + MMC_CMD_DAT_CONT); + + if (!(cmdat & CMD_DAT_CONT_DATA_ENABLE) || (cmdat & CMD_DAT_CONT_WRITE)) { + mxcmci_start_clock(host, true); + } else { + __raw_writel(STR_STP_CLK_IPG_CLK_GATE_DIS | + STR_STP_CLK_IPG_PERCLK_GATE_DIS, + host->base + MMC_STR_STP_CLK); + } +} + +/*! + * This function is called to complete the command request. + * This function enables insertion or removal interrupt. + * + * @param host Pointer to MMC/SD host structure + * @param req Pointer to MMC/SD command request structure + */ +static void mxcmci_finish_request(struct mxcmci_host *host, + struct mmc_request *req) +{ + + host->req = NULL; + host->cmd = NULL; + host->data = NULL; + + if (!(req->cmd->flags & MMC_KEEP_CLK_RUN)) { + mxcmci_stop_clock(host, true); + } + mmc_request_done(host->mmc, req); +} + +/*! + * This function is called when the requested command is completed. + * This function reads the response from the card and data if the command is for + * data transfer. This function checks for CRC error in response FIFO or + * data FIFO. + * + * @param host Pointer to MMC/SD host structure + * @param stat Content of SDHC Status Register + * + * @return This function returns 0 if there is no pending command, otherwise 1 + * always. + */ +static int mxcmci_cmd_done(struct mxcmci_host *host, unsigned int stat) +{ + struct mmc_command *cmd = host->cmd; + struct mmc_data *data = host->data; + int i; + u32 a, b, c; + u32 temp_data; + unsigned int status; + unsigned long *buf; + u8 *buf8; + int no_of_bytes; + int no_of_words; + + if (!cmd) { + /* There is no command for completion */ + return 0; + } + + /* As this function finishes the command, initialize cmd to NULL */ + host->cmd = NULL; + + /* check for Time out errors */ + if (stat & STATUS_TIME_OUT_RESP) { + __raw_writel(STATUS_TIME_OUT_RESP, host->base + MMC_STATUS); + pr_debug("%s: CMD TIMEOUT\n", DRIVER_NAME); + cmd->error = MMC_ERR_TIMEOUT; + } else if (stat & STATUS_RESP_CRC_ERR && cmd->flags & MMC_RSP_CRC) { + __raw_writel(STATUS_RESP_CRC_ERR, host->base + MMC_STATUS); + printk(KERN_ERR "%s: cmd crc error\n", DRIVER_NAME); + cmd->error = MMC_ERR_BADCRC; + } + + /* Read response from the card */ + switch (RSP_TYPE(mmc_resp_type(cmd))) { + case RSP_TYPE(MMC_RSP_R1): /* r1, r1b, r6 */ + a = __raw_readl(host->base + MMC_RES_FIFO) & 0xffff; + b = __raw_readl(host->base + MMC_RES_FIFO) & 0xffff; + c = __raw_readl(host->base + MMC_RES_FIFO) & 0xffff; + cmd->resp[0] = a << 24 | b << 8 | c >> 8; + break; + case RSP_TYPE(MMC_RSP_R3): /* r3, r4 */ + a = __raw_readl(host->base + MMC_RES_FIFO) & 0xffff; + b = __raw_readl(host->base + MMC_RES_FIFO) & 0xffff; + c = __raw_readl(host->base + MMC_RES_FIFO) & 0xffff; + cmd->resp[0] = a << 24 | b << 8 | c >> 8; + break; + case RSP_TYPE(MMC_RSP_R2): + for (i = 0; i < 4; i++) { + a = __raw_readl(host->base + MMC_RES_FIFO) & 0xffff; + b = __raw_readl(host->base + MMC_RES_FIFO) & 0xffff; + cmd->resp[i] = a << 16 | b; + } + break; + default: + break; + } + + pr_debug("%s: 0x%08x, 0x%08x, 0x%08x, 0x%08x\n", DRIVER_NAME, + cmd->resp[0], cmd->resp[1], cmd->resp[2], cmd->resp[3]); + + if (!host->data || cmd->error != MMC_ERR_NONE) { + /* complete the command */ + mxcmci_finish_request(host, host->req); + return 1; + } + + /* The command has a data transfer */ +#ifdef MXC_MMC_DMA_ENABLE + /* Use DMA if transfer size is greater than fifo size */ + if (host->dma_size > (16 << host->mmc->ios.bus_width)) { + mxc_dma_enable(host->dma); + return 1; + } +#endif + /* Use PIO tranfer of data */ + buf = + (unsigned long *)(page_address(data->sg->page) + data->sg->offset); + buf8 = (u8 *) buf; + + /* calculate the number of bytes requested for transfer */ + no_of_bytes = data->blocks * data->blksz; + no_of_words = (no_of_bytes + 3) / 4; + pr_debug("no_of_words=%d\n", no_of_words); + + if (data->flags & MMC_DATA_READ) { + for (i = 0; i < no_of_words; i++) { + /* wait for buffers to be ready for read */ + while (!(__raw_readl(host->base + MMC_STATUS) & + (STATUS_BUF_READ_RDY | STATUS_READ_OP_DONE))) ; + + /* read 32 bit data */ + temp_data = __raw_readl(host->base + MMC_BUFFER_ACCESS); + if (no_of_bytes >= 4) { + *buf++ = temp_data; + no_of_bytes -= 4; + } else { + do { + *buf8++ = temp_data; + temp_data = temp_data >> 8; + } while (--no_of_bytes); + } + } + + /* wait for read operation completion bit */ + while (!(__raw_readl(host->base + MMC_STATUS) & + STATUS_READ_OP_DONE)) ; + + /* check for time out and CRC errors */ + status = __raw_readl(host->base + MMC_STATUS); + if (status & STATUS_TIME_OUT_READ) { + pr_debug("%s: Read time out occurred\n", DRIVER_NAME); + data->error = MMC_ERR_TIMEOUT; + __raw_writel(STATUS_TIME_OUT_READ, + host->base + MMC_STATUS); + } else if (status & STATUS_READ_CRC_ERR) { + pr_debug("%s: Read CRC error occurred\n", DRIVER_NAME); + data->error = MMC_ERR_BADCRC; + __raw_writel(STATUS_READ_CRC_ERR, + host->base + MMC_STATUS); + } + __raw_writel(STATUS_READ_OP_DONE, host->base + MMC_STATUS); + + pr_debug("%s: Read %u words\n", DRIVER_NAME, i); + } else { + for (i = 0; i < no_of_words; i++) { + + /* wait for buffers to be ready for write */ + while (!(__raw_readl(host->base + MMC_STATUS) & + STATUS_BUF_WRITE_RDY)) ; + + /* write 32 bit data */ + __raw_writel(*buf++, host->base + MMC_BUFFER_ACCESS); + if (__raw_readl(host->base + MMC_STATUS) & + STATUS_WRITE_OP_DONE) { + break; + } + } + + /* wait for write operation completion bit */ + while (!(__raw_readl(host->base + MMC_STATUS) & + STATUS_WRITE_OP_DONE)) ; + + /* check for CRC errors */ + status = __raw_readl(host->base + MMC_STATUS); + if (status & STATUS_WRITE_CRC_ERR) { + pr_debug("%s: Write CRC error occurred\n", DRIVER_NAME); + data->error = MMC_ERR_BADCRC; + __raw_writel(STATUS_WRITE_CRC_ERR, + host->base + MMC_STATUS); + } + __raw_writel(STATUS_WRITE_OP_DONE, host->base + MMC_STATUS); + pr_debug("%s: Written %u words\n", DRIVER_NAME, i); + } + + /* complete the data transfer request */ + mxcmci_data_done(host, status); + + return 1; +} + +/*! + * This function is called when the data transfer is completed either by DMA + * or by core. This function is called to clean up the DMA buffer and to send + * STOP transmission command for commands to transfer data. This function + * completes request issued by the MMC/SD core driver. + * + * @param host pointer to MMC/SD host structure. + * @param stat content of SDHC Status Register + * + * @return This function returns 0 if no data transfer otherwise return 1 + * always. + */ +static int mxcmci_data_done(struct mxcmci_host *host, unsigned int stat) +{ + struct mmc_data *data = host->data; + + if (!data) { + return 0; + } +#ifdef MXC_MMC_DMA_ENABLE + if (host->dma_size > (16 << host->mmc->ios.bus_width)) { + dma_unmap_sg(mmc_dev(host->mmc), data->sg, host->dma_len, + host->dma_dir); + } +#endif + if (__raw_readl(host->base + MMC_STATUS) & STATUS_ERR_MASK) { + pr_debug("%s: request failed. status: 0x%08x\n", + DRIVER_NAME, __raw_readl(host->base + MMC_STATUS)); + } + + host->data = NULL; + data->bytes_xfered = host->dma_size; + + if (host->req->stop && data->error == MMC_ERR_NONE) { + mxcmci_start_cmd(host, host->req->stop, 0); + } else { + mxcmci_finish_request(host, host->req); + } + + return 1; +} + +/*! + * GPIO interrupt service routine registered to handle the SDHC interrupts. + * This interrupt routine handles card insertion and card removal interrupts. + * + * @param irq the interrupt number + * @param devid driver private data + * @param regs holds a snapshot of the processor's context before the + * processor entered the interrupt code + * + * @return The function returns \b IRQ_RETVAL(1) + */ +static irqreturn_t mxcmci_gpio_irq(int irq, void *devid) +{ + struct mxcmci_host *host = devid; + int card_gpio_status = host->plat_data->status(host->mmc->parent); + + pr_debug("%s: MMC%d status=%d %s\n", DRIVER_NAME, host->id, + card_gpio_status, card_gpio_status ? "removed" : "inserted"); + + if (card_gpio_status == host->plat_data->card_inserted_state) { + mmc_detect_change(host->mmc, msecs_to_jiffies(100)); + } else { + mxcmci_cmd_done(host, STATUS_TIME_OUT_RESP); + mmc_detect_change(host->mmc, msecs_to_jiffies(50)); + } + + do { + card_gpio_status = host->plat_data->status(host->mmc->parent); + if (card_gpio_status) { + set_irq_type(host->detect_irq, IRQT_FALLING); + } else { + set_irq_type(host->detect_irq, IRQT_RISING); + } + } while (card_gpio_status != + host->plat_data->status(host->mmc->parent)); + + return IRQ_HANDLED; +} + +/*! + * Interrupt service routine registered to handle the SDHC interrupts. + * This interrupt routine handles end of command, card insertion and + * card removal interrupts. If the interrupt is card insertion or removal then + * inform the MMC/SD core driver to detect the change in physical connections. + * If the command is END_CMD_RESP read the Response FIFO. If DMA is not enabled + * and data transfer is associated with the command then read or write the data + * from or to the BUFFER_ACCESS FIFO. + * + * @param irq the interrupt number + * @param devid driver private data + * @param regs holds a snapshot of the processor's context before the + * processor entered the interrupt code + * + * @return The function returns \b IRQ_RETVAL(1) if interrupt was handled, + * returns \b IRQ_RETVAL(0) if the interrupt was not handled. + */ +static irqreturn_t mxcmci_irq(int irq, void *devid) +{ + struct mxcmci_host *host = devid; + unsigned int status = 0; + u32 intctrl; + + if (host->mxc_mmc_suspend_flag == 1) { + clk_enable(host->clk); + } + + status = __raw_readl(host->base + MMC_STATUS); + intctrl = __raw_readl(host->base + MMC_INT_CNTR); +#ifdef CONFIG_MMC_DEBUG + dump_status(__FUNCTION__, status); +#endif + if (status & STATUS_END_CMD_RESP) { + __raw_writel(STATUS_END_CMD_RESP, host->base + MMC_STATUS); + mxcmci_cmd_done(host, status); + } + + return IRQ_HANDLED; +} + +/*! + * This function is called by MMC/SD Bus Protocol driver to issue a MMC + * and SD commands to the SDHC. + * + * @param mmc Pointer to MMC/SD host structure + * @param req Pointer to MMC/SD command request structure + */ +static void mxcmci_request(struct mmc_host *mmc, struct mmc_request *req) +{ + struct mxcmci_host *host = mmc_priv(mmc); + /* Holds the value of Command and Data Control Register */ + unsigned long cmdat; + + WARN_ON(host->req != NULL); + + host->req = req; +#ifdef CONFIG_MMC_DEBUG + dump_cmd(req->cmd); + dump_status(__FUNCTION__, __raw_readl(host->base + MMC_STATUS)); +#endif + + cmdat = 0; + if (req->data) { + mxcmci_setup_data(host, req->data); + + cmdat |= CMD_DAT_CONT_DATA_ENABLE; + + if (req->data->flags & MMC_DATA_WRITE) { + cmdat |= CMD_DAT_CONT_WRITE; + } + if (req->data->flags & MMC_DATA_STREAM) { + printk(KERN_ERR + "MXC MMC does not support stream mode\n"); + } + } + mxcmci_start_cmd(host, req->cmd, cmdat); +} + +/*! + * This function is called by MMC/SD Bus Protocol driver to change the clock + * speed of MMC or SD card + * + * @param mmc Pointer to MMC/SD host structure + * @param ios Pointer to MMC/SD I/O type structure + */ +static void mxcmci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) +{ + struct mxcmci_host *host = mmc_priv(mmc); + /*This variable holds the value of clock prescaler */ + int prescaler; + int clk_rate = clk_get_rate(host->clk); +#ifdef MXC_MMC_DMA_ENABLE + mxc_dma_device_t dev_id = 0; +#endif + +#if defined(CONFIG_MXC_MC13783_POWER) + t_regulator_voltage voltage; +#endif + pr_debug("%s: clock %u, bus %lu, power %u, vdd %u\n", DRIVER_NAME, + ios->clock, 1UL << ios->bus_width, ios->power_mode, ios->vdd); + + host->dma_dir = DMA_NONE; + +#ifdef MXC_MMC_DMA_ENABLE + if (mmc->ios.bus_width != host->mode) { + mxc_dma_free(host->dma); + if (mmc->ios.bus_width == MMC_BUS_WIDTH_4) { + if (host->id == 0) { + dev_id = MXC_DMA_MMC1_WIDTH_4; + } else { + dev_id = MXC_DMA_MMC2_WIDTH_4; + } + } else { + if (host->id == 0) { + dev_id = MXC_DMA_MMC1_WIDTH_1; + } else { + dev_id = MXC_DMA_MMC2_WIDTH_1; + } + } + host->dma = mxc_dma_request(dev_id, "MXC MMC"); + if (host->dma < 0) { + pr_debug("Cannot allocate MMC DMA channel\n"); + } + host->mode = mmc->ios.bus_width; + mxc_dma_callback_set(host->dma, mxcmci_dma_irq, (void *)host); + } +#endif + +#if defined(CONFIG_MXC_MC13783_POWER) + switch (ios->power_mode) { + case MMC_POWER_UP: + if (host->id == 0) { + voltage.vmmc1 = vdd_mapping[ios->vdd]; + pmic_power_regulator_set_voltage(REGU_VMMC1, voltage); + pmic_power_regulator_set_lp_mode(REGU_VMMC1, + LOW_POWER_DISABLED); + pmic_power_regulator_on(REGU_VMMC1); + } + if (host->id == 1) { + voltage.vmmc2 = vdd_mapping[ios->vdd]; + pmic_power_regulator_set_voltage(REGU_VMMC2, voltage); + pmic_power_regulator_set_lp_mode(REGU_VMMC2, + LOW_POWER_DISABLED); + pmic_power_regulator_on(REGU_VMMC2); + } + pr_debug("mmc power on\n"); + msleep(300); + break; + case MMC_POWER_OFF: + if (host->id == 0) { + pmic_power_regulator_set_lp_mode(REGU_VMMC1, + LOW_POWER_EN); + pmic_power_regulator_off(REGU_VMMC1); + } + + if (host->id == 1) { + pmic_power_regulator_set_lp_mode(REGU_VMMC2, + LOW_POWER_EN); + pmic_power_regulator_off(REGU_VMMC2); + } + pr_debug("mmc power off\n"); + break; + default: + break; + } +#endif + + /* + * Vary divider first, then prescaler. + **/ + if (ios->clock) { + unsigned int clk_dev = 0; + + /* + * when prescaler = 16, CLK_20M = CLK_DIV / 2 + */ + if (ios->clock == mmc->f_min) + prescaler = 16; + else + prescaler = 0; + + /* clk_dev =1, CLK_DIV = ipg_perclk/2 */ + + while (prescaler <= 0x800) { + for (clk_dev = 1; clk_dev <= 0xF; clk_dev++) { + int x; + if (prescaler != 0) { + x = (clk_rate / (clk_dev + 1)) / + (prescaler * 2); + } else { + x = clk_rate / (clk_dev + 1); + } + + pr_debug("x=%d, clock=%d %d\n", x, ios->clock, + clk_dev); + if (x <= ios->clock) { + break; + } + } + if (clk_dev < 0x10) { + break; + } + if (prescaler == 0) + prescaler = 1; + else + prescaler <<= 1; + } + + pr_debug("prescaler = 0x%x, divider = 0x%x\n", prescaler, + clk_dev); + mxcmci_stop_clock(host, true); + __raw_writel((prescaler << 4) | clk_dev, + host->base + MMC_CLK_RATE); + mxcmci_start_clock(host, false); + } else { + mxcmci_stop_clock(host, true); + } +} + +/*! + * MMC/SD host operations structure. + * These functions are registered with MMC/SD Bus protocol driver. + */ +static struct mmc_host_ops mxcmci_ops = { + .request = mxcmci_request, + .set_ios = mxcmci_set_ios +}; + +#ifdef MXC_MMC_DMA_ENABLE +/*! + * This function is called by DMA Interrupt Service Routine to indicate + * requested DMA transfer is completed. + * + * @param devid pointer to device specific structure + * @param error any DMA error + * @param cnt amount of data that was transferred + */ +static void mxcmci_dma_irq(void *devid, int error, unsigned int cnt) +{ + struct mxcmci_host *host = devid; + struct mmc_data *data = host->data; + u32 status; + ulong nob, blk_size, i, blk_len; + + mxc_dma_disable(host->dma); + + if (error) { + pr_debug("Error in DMA transfer\n"); + status = __raw_readl(host->base + MMC_STATUS); +#ifdef CONFIG_MMC_DEBUG + dump_status(__FUNCTION__, status); +#endif + mxcmci_data_done(host, status); + return; + } + pr_debug("%s: Transfered bytes:%d\n", DRIVER_NAME, cnt); + nob = __raw_readl(host->base + MMC_REM_NOB); + blk_size = __raw_readl(host->base + MMC_REM_BLK_SIZE); + blk_len = __raw_readl(host->base + MMC_BLK_LEN); + pr_debug("%s: REM_NOB:%lu REM_BLK_SIZE:%lu\n", DRIVER_NAME, nob, + blk_size); + i = 0; + do { + status = __raw_readl(host->base + MMC_STATUS); + udelay(1); + } while (!(status & (STATUS_READ_OP_DONE | STATUS_WRITE_OP_DONE))); +#ifdef CONFIG_MMC_DEBUG + dump_status(__FUNCTION__, status); +#endif + if (status & (STATUS_READ_OP_DONE | STATUS_WRITE_OP_DONE)) { + pr_debug("%s:READ/WRITE OPERATION DONE\n", DRIVER_NAME); + /* check for time out and CRC errors */ + status = __raw_readl(host->base + MMC_STATUS); + if (status & STATUS_READ_OP_DONE) { + if (status & STATUS_TIME_OUT_READ) { + pr_debug("%s: Read time out occurred\n", + DRIVER_NAME); + data->error = MMC_ERR_TIMEOUT; + __raw_writel(STATUS_TIME_OUT_READ, + host->base + MMC_STATUS); + } else if (status & STATUS_READ_CRC_ERR) { + pr_debug("%s: Read CRC error occurred\n", + DRIVER_NAME); + data->error = MMC_ERR_BADCRC; + __raw_writel(STATUS_READ_CRC_ERR, + host->base + MMC_STATUS); + } + __raw_writel(STATUS_READ_OP_DONE, + host->base + MMC_STATUS); + } + + /* check for CRC errors */ + if (status & STATUS_WRITE_OP_DONE) { + if (status & STATUS_WRITE_CRC_ERR) { + pr_debug("%s: Write CRC error occurred\n", + DRIVER_NAME); + data->error = MMC_ERR_BADCRC; + __raw_writel(STATUS_WRITE_CRC_ERR, + host->base + MMC_STATUS); + } + __raw_writel(STATUS_WRITE_OP_DONE, + host->base + MMC_STATUS); + } + } else { + data->error = MMC_ERR_FAILED; + pr_debug("%s:%d: MXC MMC DMA transfer failed.\n", __FUNCTION__, + __LINE__); + } + + mxcmci_data_done(host, status); +} +#endif + +/*! + * This function is called during the driver binding process. Based on the SDHC + * module that is being probed this function adds the appropriate SDHC module + * structure in the core driver. + * + * @param pdev the device structure used to store device specific + * information that is used by the suspend, resume and remove + * functions. + * + * @return The function returns 0 on successful registration and initialization + * of SDHC module. Otherwise returns specific error code. + */ +static int mxcmci_probe(struct platform_device *pdev) +{ + struct mxc_mmc_platform_data *mmc_plat = pdev->dev.platform_data; + struct mmc_host *mmc; + struct mxcmci_host *host = NULL; + int card_gpio_status; + int ret = -ENODEV; + + if (!mmc_plat) { + return -EINVAL; + } + + mmc = mmc_alloc_host(sizeof(struct mxcmci_host), &pdev->dev); + if (!mmc) { + return -ENOMEM; + } + platform_set_drvdata(pdev, mmc); + + mmc->ops = &mxcmci_ops; + mmc->ocr_avail = mmc_plat->ocr_mask; + + /* Hack to work with LP1070 */ + mmc->ocr_avail |= MMC_VDD_31_32; + + mmc->max_phys_segs = NR_SG; + mmc->caps = MMC_CAP_4_BIT_DATA; + + host = mmc_priv(mmc); + host->mmc = mmc; + host->dma = -1; + host->dma_dir = DMA_NONE; + host->id = pdev->id; + host->mxc_mmc_suspend_flag = 0; + host->mode = -1; + host->plat_data = mmc_plat; + if (!host->plat_data) { + ret = -EINVAL; + goto out; + } + + host->clk = clk_get(&pdev->dev, "sdhc_clk"); + clk_enable(host->clk); + + mmc->f_min = mmc_plat->min_clk; + mmc->f_max = mmc_plat->max_clk; + pr_debug("SDHC:%d clock:%lu\n", pdev->id, clk_get_rate(host->clk)); + + spin_lock_init(&host->lock); + host->res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!host->res) { + ret = -ENOMEM; + goto out; + } + + if (!request_mem_region(host->res->start, + host->res->end - + host->res->start + 1, pdev->name)) { + printk(KERN_ERR "request_mem_region failed\n"); + ret = -ENOMEM; + goto out; + } + host->base = (void *)IO_ADDRESS(host->res->start); + if (!host->base) { + ret = -ENOMEM; + goto out1; + } + + host->irq = platform_get_irq(pdev, 0); + if (!host->irq) { + ret = -ENOMEM; + goto out1; + } + + host->detect_irq = platform_get_irq(pdev, 1); + if (!host->detect_irq) { + goto out1; + } + + do { + card_gpio_status = host->plat_data->status(host->mmc->parent); + if (card_gpio_status) { + set_irq_type(host->detect_irq, IRQT_FALLING); + } else { + set_irq_type(host->detect_irq, IRQT_RISING); + } + } while (card_gpio_status != + host->plat_data->status(host->mmc->parent)); + + ret = + request_irq(host->detect_irq, mxcmci_gpio_irq, 0, pdev->name, host); + if (ret) { + goto out1; + } + + mxcmci_softreset(host); + + if (__raw_readl(host->base + MMC_REV_NO) != SDHC_REV_NO) { + printk(KERN_ERR "%s: wrong rev.no. 0x%08x. aborting.\n", + pdev->name, MMC_REV_NO); + goto out3; + } + __raw_writel(READ_TO_VALUE, host->base + MMC_READ_TO); + + __raw_writel(INT_CNTR_END_CMD_RES, host->base + MMC_INT_CNTR); + + ret = request_irq(host->irq, mxcmci_irq, 0, pdev->name, host); + if (ret) { + goto out3; + } + + gpio_sdhc_active(pdev->id); + + if ((ret = mmc_add_host(mmc)) < 0) { + goto out4; + } + + printk(KERN_INFO "%s-%d found\n", pdev->name, pdev->id); + + return 0; + + out4: + gpio_sdhc_inactive(pdev->id); + free_irq(host->irq, host); + out3: + free_irq(host->detect_irq, host); + pr_debug("%s: Error in initializing....", pdev->name); + out1: + release_mem_region(pdev->resource[0].start, + pdev->resource[0].end - pdev->resource[0].start + 1); + out: + clk_disable(host->clk); + mmc_free_host(mmc); + platform_set_drvdata(pdev, NULL); + return ret; +} + +/*! + * Dissociates the driver from the SDHC device. Removes the appropriate SDHC + * module structure from the core driver. + * + * @param pdev the device structure used to give information on which SDHC + * to remove + * + * @return The function always returns 0. + */ +static int mxcmci_remove(struct platform_device *pdev) +{ + struct mmc_host *mmc = platform_get_drvdata(pdev); + platform_set_drvdata(pdev, NULL); + + if (mmc) { + struct mxcmci_host *host = mmc_priv(mmc); + + mmc_remove_host(mmc); + free_irq(host->irq, host); + free_irq(host->detect_irq, host); +#ifdef MXC_MMC_DMA_ENABLE + mxc_dma_free(host->dma); +#endif + release_mem_region(host->res->start, + host->res->end - host->res->start + 1); + mmc_free_host(mmc); + gpio_sdhc_inactive(pdev->id); + } + return 0; +} + +#ifdef CONFIG_PM + +/*! + * This function is called to put the SDHC in a low power state. Refer to the + * document driver-model/driver.txt in the kernel source tree for more + * information. + * + * @param pdev the device structure used to give information on which SDHC + * to suspend + * @param state the power state the device is entering + * + * @return The function always returns 0. + */ +static int mxcmci_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct mmc_host *mmc = platform_get_drvdata(pdev); + struct mxcmci_host *host = mmc_priv(mmc); + int ret = 0; + + if (mmc) { + host->mxc_mmc_suspend_flag = 1; + ret = mmc_suspend_host(mmc, state); + } + clk_disable(host->clk); + + return ret; +} + +/*! + * This function is called to bring the SDHC back from a low power state. Refer + * to the document driver-model/driver.txt in the kernel source tree for more + * information. + * + * @param pdev the device structure used to give information on which SDHC + * to resume + * + * @return The function always returns 0. + */ +static int mxcmci_resume(struct platform_device *pdev) +{ + struct mmc_host *mmc = platform_get_drvdata(pdev); + struct mxcmci_host *host = mmc_priv(mmc); + int ret = 0; + + /* + * Note that a card insertion interrupt will cause this + * driver to resume automatically. In that case we won't + * actually have to do any work here. Return success. + */ + if (!host->mxc_mmc_suspend_flag) { + return 0; + } + clk_enable(host->clk); + + if (mmc) { + ret = mmc_resume_host(mmc); + host->mxc_mmc_suspend_flag = 0; + } + return ret; +} +#else +#define mxcmci_suspend NULL +#define mxcmci_resume NULL +#endif /* CONFIG_PM */ + +/*! + * This structure contains pointers to the power management callback functions. + */ +static struct platform_driver mxcmci_driver = { + .driver = { + .name = "mxcmci", + }, + .probe = mxcmci_probe, + .remove = mxcmci_remove, + .suspend = mxcmci_suspend, + .resume = mxcmci_resume, +}; + +/*! + * This function is used to initialize the MMC/SD driver module. The function + * registers the power management callback functions with the kernel and also + * registers the MMC/SD callback functions with the core MMC/SD driver. + * + * @return The function returns 0 on success and a non-zero value on failure. + */ +static int __init mxcmci_init(void) +{ + printk(KERN_INFO "MXC MMC/SD driver\n"); + return platform_driver_register(&mxcmci_driver); +} + +/*! + * This function is used to cleanup all resources before the driver exits. + */ +static void __exit mxcmci_exit(void) +{ + platform_driver_unregister(&mxcmci_driver); +} + +module_init(mxcmci_init); +module_exit(mxcmci_exit); + +MODULE_DESCRIPTION("MXC Multimedia Card Interface Driver"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/mmc/host/mxc_mmc.h b/drivers/mmc/host/mxc_mmc.h new file mode 100644 index 000000000000..c11f70432213 --- /dev/null +++ b/drivers/mmc/host/mxc_mmc.h @@ -0,0 +1,128 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#ifndef __MXC_MMC_REG_H__ +#define __MXC_MMC_REG_H__ + +#include <asm/hardware.h> + +/*! + * @defgroup MMC_SD MMC/SD Driver + */ + +/*! + * @file mxc_mmc.h + * + * @brief Driver for the Freescale Semiconductor MXC SDHC modules. + * + * This file defines offsets and bits of SDHC registers. SDHC is also referred as + * MMC/SD controller + * + * @ingroup MMC_SD + */ + +/*! + * Number of SDHC modules + */ + +#define SDHC_MMC_WML 16 +#define SDHC_SD_WML 64 +#define DRIVER_NAME "MXCMMC" +#define SDHC_MEM_SIZE 16384 +#define SDHC_REV_NO 0x400 +#define READ_TO_VALUE 0x2db4 + +/* Address offsets of the SDHC registers */ +#define MMC_STR_STP_CLK 0x00 /* Clock Control Reg */ +#define MMC_STATUS 0x04 /* Status Reg */ +#define MMC_CLK_RATE 0x08 /* Clock Rate Reg */ +#define MMC_CMD_DAT_CONT 0x0C /* Command and Data Control Reg */ +#define MMC_RES_TO 0x10 /* Response Time-out Reg */ +#define MMC_READ_TO 0x14 /* Read Time-out Reg */ +#define MMC_BLK_LEN 0x18 /* Block Length Reg */ +#define MMC_NOB 0x1C /* Number of Blocks Reg */ +#define MMC_REV_NO 0x20 /* Revision Number Reg */ +#define MMC_INT_CNTR 0x24 /* Interrupt Control Reg */ +#define MMC_CMD 0x28 /* Command Number Reg */ +#define MMC_ARG 0x2C /* Command Argument Reg */ +#define MMC_RES_FIFO 0x34 /* Command Response Reg */ +#define MMC_BUFFER_ACCESS 0x38 /* Data Buffer Access Reg */ +#define MMC_REM_NOB 0x40 /* Remaining NOB Reg */ +#define MMC_REM_BLK_SIZE 0x44 /* Remaining Block Size Reg */ + +/* Bit definitions for STR_STP_CLK */ +#define STR_STP_CLK_IPG_CLK_GATE_DIS (1<<15) +#define STR_STP_CLK_IPG_PERCLK_GATE_DIS (1<<14) +#define STR_STP_CLK_RESET (1<<3) +#define STR_STP_CLK_START_CLK (1<<1) +#define STR_STP_CLK_STOP_CLK (1<<0) + +/* Bit definitions for STATUS */ +#define STATUS_CARD_INSERTION (1<<31) +#define STATUS_CARD_REMOVAL (1<<30) +#define STATUS_YBUF_EMPTY (1<<29) +#define STATUS_XBUF_EMPTY (1<<28) +#define STATUS_YBUF_FULL (1<<27) +#define STATUS_XBUF_FULL (1<<26) +#define STATUS_BUF_UND_RUN (1<<25) +#define STATUS_BUF_OVFL (1<<24) +#define STATUS_SDIO_INT_ACTIVE (1<<14) +#define STATUS_END_CMD_RESP (1<<13) +#define STATUS_WRITE_OP_DONE (1<<12) +#define STATUS_READ_OP_DONE (1<<11) +#define STATUS_WR_CRC_ERROR_CODE_MASK (3<<10) +#define STATUS_CARD_BUS_CLK_RUN (1<<8) +#define STATUS_BUF_READ_RDY (1<<7) +#define STATUS_BUF_WRITE_RDY (1<<6) +#define STATUS_RESP_CRC_ERR (1<<5) +#define STATUS_READ_CRC_ERR (1<<3) +#define STATUS_WRITE_CRC_ERR (1<<2) +#define STATUS_TIME_OUT_RESP (1<<1) +#define STATUS_TIME_OUT_READ (1<<0) +#define STATUS_ERR_MASK 0x3f + +/* Clock rate definitions */ +#define CLK_RATE_PRESCALER(x) ((x) & 0xF) +#define CLK_RATE_CLK_DIVIDER(x) (((x) & 0xF) << 4) + +/* Bit definitions for CMD_DAT_CONT */ +#define CMD_DAT_CONT_CMD_RESP_LONG_OFF (1<<12) +#define CMD_DAT_CONT_STOP_READWAIT (1<<11) +#define CMD_DAT_CONT_START_READWAIT (1<<10) +#define CMD_DAT_CONT_BUS_WIDTH_1 (0<<8) +#define CMD_DAT_CONT_BUS_WIDTH_4 (2<<8) +#define CMD_DAT_CONT_INIT (1<<7) +#define CMD_DAT_CONT_WRITE (1<<4) +#define CMD_DAT_CONT_DATA_ENABLE (1<<3) +#define CMD_DAT_CONT_RESPONSE_FORMAT_R1 (1) +#define CMD_DAT_CONT_RESPONSE_FORMAT_R2 (2) +#define CMD_DAT_CONT_RESPONSE_FORMAT_R3 (3) +#define CMD_DAT_CONT_RESPONSE_FORMAT_R4 (4) +#define CMD_DAT_CONT_RESPONSE_FORMAT_R5 (5) +#define CMD_DAT_CONT_RESPONSE_FORMAT_R6 (6) + +/* Bit definitions for INT_CNTR */ +#define INT_CNTR_SDIO_INT_WKP_EN (1<<18) +#define INT_CNTR_CARD_INSERTION_WKP_EN (1<<17) +#define INT_CNTR_CARD_REMOVAL_WKP_EN (1<<16) +#define INT_CNTR_CARD_INSERTION_EN (1<<15) +#define INT_CNTR_CARD_REMOVAL_EN (1<<14) +#define INT_CNTR_SDIO_IRQ_EN (1<<13) +#define INT_CNTR_DAT0_EN (1<<12) +#define INT_CNTR_BUF_READ_EN (1<<4) +#define INT_CNTR_BUF_WRITE_EN (1<<3) +#define INT_CNTR_END_CMD_RES (1<<2) +#define INT_CNTR_WRITE_OP_DONE (1<<1) +#define INT_CNTR_READ_OP_DONE (1<<0) + +#endif /* __MXC_MMC_REG_H__ */ diff --git a/drivers/mtd/chips/cfi_probe.c b/drivers/mtd/chips/cfi_probe.c index 60e11a0ada97..cee88928f51d 100644 --- a/drivers/mtd/chips/cfi_probe.c +++ b/drivers/mtd/chips/cfi_probe.c @@ -27,7 +27,8 @@ static void print_cfi_ident(struct cfi_ident *); static int cfi_probe_chip(struct map_info *map, __u32 base, unsigned long *chip_map, struct cfi_private *cfi); -static int cfi_chip_setup(struct map_info *map, struct cfi_private *cfi); +static int cfi_chip_setup(struct map_info *map, struct cfi_private *cfi, + int amd555); struct mtd_info *cfi_probe(struct map_info *map); @@ -50,12 +51,12 @@ do { \ xip_allowed(base, map); \ } while (0) -#define xip_disable_qry(base, map, cfi) \ +#define xip_disable_qry(base, map, cfi, amd555) \ do { \ xip_disable(); \ cfi_send_gen_cmd(0xF0, 0, base, map, cfi, cfi->device_type, NULL); \ cfi_send_gen_cmd(0xFF, 0, base, map, cfi, cfi->device_type, NULL); \ - cfi_send_gen_cmd(0x98, 0x55, base, map, cfi, cfi->device_type, NULL); \ + cfi_send_gen_cmd(0x98, amd555 ? 0x555 : 0x55, base, map, cfi, cfi->device_type, NULL);\ } while (0) #else @@ -63,7 +64,7 @@ do { \ #define xip_disable() do { } while (0) #define xip_allowed(base, map) do { } while (0) #define xip_enable(base, map, cfi) do { } while (0) -#define xip_disable_qry(base, map, cfi) do { } while (0) +#define xip_disable_qry(base, map, cfi, amd555) do { } while (0) #endif @@ -102,6 +103,7 @@ static int __xipram cfi_probe_chip(struct map_info *map, __u32 base, unsigned long *chip_map, struct cfi_private *cfi) { int i; + int amd555 = 0; if ((base + 0) >= map->size) { printk(KERN_NOTICE @@ -122,14 +124,20 @@ static int __xipram cfi_probe_chip(struct map_info *map, __u32 base, cfi_send_gen_cmd(0x98, 0x55, base, map, cfi, cfi->device_type, NULL); if (!qry_present(map,base,cfi)) { - xip_enable(base, map, cfi); - return 0; + cfi_send_gen_cmd(0x98, 0x555, base, map, cfi, cfi->device_type, + NULL); + if (!qry_present(map,base,cfi)) { + xip_enable(base, map, cfi); + return 0; + } + + amd555 = 1; } if (!cfi->numchips) { /* This is the first time we're called. Set up the CFI stuff accordingly and return */ - return cfi_chip_setup(map, cfi); + return cfi_chip_setup(map, cfi, amd555); } /* Check each previous chip to see if it's an alias */ @@ -189,7 +197,7 @@ static int __xipram cfi_probe_chip(struct map_info *map, __u32 base, } static int __xipram cfi_chip_setup(struct map_info *map, - struct cfi_private *cfi) + struct cfi_private *cfi, int amd555) { int ofs_factor = cfi->interleave*cfi->device_type; __u32 base = 0; @@ -214,7 +222,7 @@ static int __xipram cfi_chip_setup(struct map_info *map, cfi->cfi_mode = CFI_MODE_CFI; /* Read the CFI info structure */ - xip_disable_qry(base, map, cfi); + xip_disable_qry(base, map, cfi, amd555); for (i=0; i<(sizeof(struct cfi_ident) + num_erase_regions * 4); i++) ((unsigned char *)cfi->cfiq)[i] = cfi_read_query(map,base + (0x10 + i)*ofs_factor); diff --git a/drivers/mtd/chips/cfi_util.c b/drivers/mtd/chips/cfi_util.c index 2e51496c248e..820504bd26c6 100644 --- a/drivers/mtd/chips/cfi_util.c +++ b/drivers/mtd/chips/cfi_util.c @@ -50,8 +50,15 @@ __xipram cfi_read_pri(struct map_info *map, __u16 adr, __u16 size, const char* n local_irq_disable(); #endif - /* Switch it into Query Mode */ - cfi_send_gen_cmd(0x98, 0x55, base, map, cfi, cfi->device_type, NULL); + /* Switch it into Query Mode. Some chips want address 0x55, some + * want 0x555. + */ + if ((cfi->mfr == CFI_MFR_AMD) && (cfi->id == 0x227E)) + cfi_send_gen_cmd(0x98, 0x555, base, map, cfi, cfi->device_type, + NULL); + else + cfi_send_gen_cmd(0x98, 0x55, base, map, cfi, cfi->device_type, + NULL); /* Read in the Extended Query Table */ for (i=0; i<size; i++) { diff --git a/drivers/mtd/maps/Kconfig b/drivers/mtd/maps/Kconfig index a592fc04cf78..cd2b70f6fc15 100644 --- a/drivers/mtd/maps/Kconfig +++ b/drivers/mtd/maps/Kconfig @@ -597,5 +597,15 @@ config MTD_PLATRAM This selection automatically selects the map_ram driver. +config MTD_MXC + bool "Map driver for Freescale MXC boards" + depends on MTD && ARCH_MXC + default y + select MTD_CFI + select MTD_PARTITIONS + help + This enables access to the flash chips on Freescale MXC based + platforms. If you have such a board, say 'Y'. + endmenu diff --git a/drivers/mtd/maps/Makefile b/drivers/mtd/maps/Makefile index 316382a1401b..f858c14a50d1 100644 --- a/drivers/mtd/maps/Makefile +++ b/drivers/mtd/maps/Makefile @@ -69,3 +69,4 @@ obj-$(CONFIG_MTD_PLATRAM) += plat-ram.o obj-$(CONFIG_MTD_OMAP_NOR) += omap_nor.o obj-$(CONFIG_MTD_MTX1) += mtx-1_flash.o obj-$(CONFIG_MTD_INTEL_VR_NOR) += intel_vr_nor.o +obj-$(CONFIG_MTD_MXC) += mxc_nor.o diff --git a/drivers/mtd/maps/mxc_nor.c b/drivers/mtd/maps/mxc_nor.c new file mode 100644 index 000000000000..6c69e0164c73 --- /dev/null +++ b/drivers/mtd/maps/mxc_nor.c @@ -0,0 +1,184 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + * (c) 2005 MontaVista Software, Inc. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/err.h> +#include <linux/ioport.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/map.h> +#include <linux/mtd/partitions.h> +#include <linux/clocksource.h> +#include <asm/mach-types.h> +#include <asm/mach/flash.h> + +#define DVR_VER "2.0" + +#ifdef CONFIG_MTD_PARTITIONS +static const char *part_probes[] = { "RedBoot", "cmdlinepart", NULL }; +#endif + +struct clocksource *mtd_xip_clksrc; + +struct mxcflash_info { + struct mtd_partition *parts; + struct mtd_info *mtd; + struct map_info map; +}; + +/*! + * @defgroup NOR_MTD NOR Flash MTD Driver + */ + +/*! + * @file mxc_nor.c + * + * @brief This file contains the MTD Mapping information on the MXC. + * + * @ingroup NOR_MTD + */ + +static int __devinit mxcflash_probe(struct platform_device *pdev) +{ + int err, nr_parts = 0; + struct mxcflash_info *info; + struct flash_platform_data *flash = pdev->dev.platform_data; + struct resource *res = pdev->resource; + unsigned long size = res->end - res->start + 1; + + info = kzalloc(sizeof(struct mxcflash_info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + if (!request_mem_region(res->start, size, "flash")) { + err = -EBUSY; + goto out_free_info; + } + info->map.virt = ioremap(res->start, size); + if (!info->map.virt) { + err = -ENOMEM; + goto out_release_mem_region; + } + info->map.name = pdev->dev.bus_id; + info->map.phys = res->start; + info->map.size = size; + info->map.bankwidth = flash->width; + + mtd_xip_clksrc = clocksource_get_next(); + + simple_map_init(&info->map); + info->mtd = do_map_probe(flash->map_name, &info->map); + if (!info->mtd) { + err = -EIO; + goto out_iounmap; + } + info->mtd->owner = THIS_MODULE; + +#ifdef CONFIG_MTD_PARTITIONS + nr_parts = + parse_mtd_partitions(info->mtd, part_probes, &info->parts, 0); + if (nr_parts > 0) { + add_mtd_partitions(info->mtd, info->parts, nr_parts); + } else if (nr_parts < 0 && flash->parts) { + add_mtd_partitions(info->mtd, flash->parts, flash->nr_parts); + } else +#endif + { + printk(KERN_NOTICE "MXC flash: no partition info " + "available, registering whole flash\n"); + add_mtd_device(info->mtd); + } + + platform_set_drvdata(pdev, info); + return 0; + + out_iounmap: + iounmap(info->map.virt); + out_release_mem_region: + release_mem_region(res->start, size); + out_free_info: + kfree(info); + + return err; +} + +static int __devexit mxcflash_remove(struct platform_device *pdev) +{ + + struct mxcflash_info *info = platform_get_drvdata(pdev); + struct flash_platform_data *flash = pdev->dev.platform_data; + + platform_set_drvdata(pdev, NULL); + + if (info) { + if (info->parts) { + del_mtd_partitions(info->mtd); + kfree(info->parts); + } else if (flash->parts) + del_mtd_partitions(info->mtd); + else + del_mtd_device(info->mtd); + + map_destroy(info->mtd); + release_mem_region(info->map.phys, info->map.size); + iounmap((void __iomem *)info->map.virt); + kfree(info); + } + return 0; +} + +static struct platform_driver mxcflash_driver = { + .driver = { + .name = "mxc_nor_flash", + }, + .probe = mxcflash_probe, + .remove = __devexit_p(mxcflash_remove), +}; + +/*! + * This is the module's entry function. It passes board specific + * config details into the MTD physmap driver which then does the + * real work for us. After this function runs, our job is done. + * + * @return 0 if successful; non-zero otherwise + */ +static int __init mxc_mtd_init(void) +{ + pr_info("MXC MTD nor Driver %s\n", DVR_VER); + if (platform_driver_register(&mxcflash_driver) != 0) { + printk(KERN_ERR "Driver register failed for mxcflash_driver\n"); + return -ENODEV; + } + return 0; +} + +/*! + * This function is the module's exit function. It's empty because the + * MTD physmap driver is doing the real work and our job was done after + * mxc_mtd_init() runs. + */ +static void __exit mxc_mtd_exit(void) +{ + platform_driver_unregister(&mxcflash_driver); +} + +module_init(mxc_mtd_init); +module_exit(mxc_mtd_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MTD map and partitions for Freescale MXC boards"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mtd/nand/Kconfig b/drivers/mtd/nand/Kconfig index 246d4512f64b..ce7e28418d39 100644 --- a/drivers/mtd/nand/Kconfig +++ b/drivers/mtd/nand/Kconfig @@ -291,6 +291,56 @@ config MTD_NAND_NANDSIM The simulator may simulate various NAND flash chips for the MTD nand layer. +config MTD_NAND_MXC + tristate "MXC NAND support" + depends on MTD_NAND && ARCH_MXC_HAS_NFC_V1 + help + This enables the driver for the NAND flash controller on the + MXC processors. + +config MTD_NAND_MXC_V2 + tristate "MXC NAND Version 2 support" + depends on MTD_NAND && ARCH_MXC_HAS_NFC_V2 + help + This enables the driver for the version 2 of NAND flash controller + on the MXC processors. + +config MTD_NAND_MXC_V3 + tristate "MXC NAND Version 3 support" + depends on MTD_NAND && ARCH_MXC_HAS_NFC_V3 + help + This enables the driver for the version 3 of NAND flash controller + on the MXC processors. + +config MTD_NAND_MXC_SWECC + bool "Software ECC support " + depends on MTD_NAND_MXC || MTD_NAND_MXC_V2 || MTD_NAND_MXC_V3 + help + This enables the support for Software ECC handling. By + default MXC NAND controller Hardware ECC is supported. + + +config MTD_NAND_MXC_FORCE_CE + bool "NAND chip select operation support" + depends on MTD_NAND_MXC || MTD_NAND_MXC_V2|| MTD_NAND_MXC_V3 + help + This enables the NAND chip select by using CE control line. By + default CE operation is disabled. + +config MTD_NAND_MXC_ECC_CORRECTION_OPTION2 + bool "ECC correction in S/W" + depends on MTD_NAND_MXC + help + This enables the Option2 NFC ECC correction in software. By + default Option 1 is selected. Enable if you need option2 ECC correction. + +config MXC_NAND_LOW_LEVEL_ERASE + bool "Low level NAND erase" + depends on MTD_NAND_MXC || MTD_NAND_MXC_V2 || MTD_NAND_MXC_V3 + help + This enables the erase of whole NAND flash. By + default low level erase operation is disabled. + config MTD_NAND_PLATFORM tristate "Support for generic platform NAND driver" depends on MTD_NAND diff --git a/drivers/mtd/nand/Makefile b/drivers/mtd/nand/Makefile index 3ad6c0165da3..ad51311061fe 100644 --- a/drivers/mtd/nand/Makefile +++ b/drivers/mtd/nand/Makefile @@ -24,6 +24,9 @@ obj-$(CONFIG_MTD_NAND_TS7250) += ts7250.o obj-$(CONFIG_MTD_NAND_NANDSIM) += nandsim.o obj-$(CONFIG_MTD_NAND_CS553X) += cs553x_nand.o obj-$(CONFIG_MTD_NAND_NDFC) += ndfc.o +obj-$(CONFIG_MTD_NAND_MXC) += mxc_nd.o +obj-$(CONFIG_MTD_NAND_MXC_V2) += mxc_nd2.o +obj-$(CONFIG_MTD_NAND_MXC_V3) += mxc_nd2.o obj-$(CONFIG_MTD_NAND_AT91) += at91_nand.o obj-$(CONFIG_MTD_NAND_CM_X270) += cmx270_nand.o obj-$(CONFIG_MTD_NAND_BASLER_EXCITE) += excite_nandflash.o diff --git a/drivers/mtd/nand/mxc_nd.c b/drivers/mtd/nand/mxc_nd.c new file mode 100644 index 000000000000..f244436afd16 --- /dev/null +++ b/drivers/mtd/nand/mxc_nd.c @@ -0,0 +1,1372 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/nand.h> +#include <linux/mtd/partitions.h> +#include <linux/interrupt.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <linux/err.h> + +#include <asm/mach/flash.h> +#include <asm/io.h> + +#include "mxc_nd.h" + +/*! + * Number of static partitions on NAND Flash. + */ +#define NUM_PARTITIONS (sizeof(partition_info)/sizeof(struct mtd_partition)) + +#define DVR_VER "2.0" + +struct mxc_mtd_s { + struct mtd_info mtd; + struct nand_chip nand; + struct mtd_partition *parts; + struct device *dev; +}; + +static struct mxc_mtd_s *mxc_nand_data = NULL; + +/* + * Define delays in microsec for NAND device operations + */ +#define TROP_US_DELAY 2000 +/* + * Macros to get byte and bit positions of ECC + */ +#define COLPOS(x) ((x) >> 3) +#define BITPOS(x) ((x)& 0xf) + +/* Define single bit Error positions in Main & Spare area */ +#define MAIN_SINGLEBIT_ERROR 0x4 +#define SPARE_SINGLEBIT_ERROR 0x1 + +struct nand_info { + bool bSpareOnly; + bool bStatusRequest; + u16 colAddr; +}; + +static struct nand_info g_nandfc_info; + +#ifdef CONFIG_MTD_NAND_MXC_SWECC +static int hardware_ecc = 0; +#else +static int hardware_ecc = 1; +#endif + +#ifndef CONFIG_MTD_NAND_MXC_ECC_CORRECTION_OPTION2 +static int Ecc_disabled; +#endif + +static int is2k_Pagesize = 0; + +static struct clk *nfc_clk; + +/* + * OOB placement block for use with hardware ecc generation + */ +static struct nand_ecclayout nand_hw_eccoob_8 = { + .eccbytes = 5, + .eccpos = {6, 7, 8, 9, 10}, + .oobfree = {{0, 5}, {11, 5}} +}; + +static struct nand_ecclayout nand_hw_eccoob_16 = { + .eccbytes = 5, + .eccpos = {6, 7, 8, 9, 10}, + .oobfree = {{0, 6}, {12, 4}} +}; + +/*! + * @defgroup NAND_MTD NAND Flash MTD Driver for MXC processors + */ + +/*! + * @file mxc_nd.c + * + * @brief This file contains the hardware specific layer for NAND Flash on + * MXC processor + * + * @ingroup NAND_MTD + */ + +#ifdef CONFIG_MTD_PARTITIONS +static const char *part_probes[] = { "RedBoot", "cmdlinepart", NULL }; +#endif + +static wait_queue_head_t irq_waitq; + +static irqreturn_t mxc_nfc_irq(int irq, void *dev_id) +{ + NFC_CONFIG1 |= NFC_INT_MSK; /* Disable interrupt */ + wake_up(&irq_waitq); + + return IRQ_RETVAL(1); +} + +/*! + * This function polls the NANDFC to wait for the basic operation to complete by + * checking the INT bit of config2 register. + * + * @param maxRetries number of retry attempts (separated by 1 us) + * @param param parameter for debug + * @param useirq True if IRQ should be used rather than polling + */ +static void wait_op_done(int maxRetries, u16 param, bool useirq) +{ + if (useirq) { + if ((NFC_CONFIG2 & NFC_INT) == 0) { + NFC_CONFIG1 &= ~NFC_INT_MSK; /* Enable interrupt */ + wait_event(irq_waitq, NFC_CONFIG2 & NFC_INT); + NFC_CONFIG2 &= ~NFC_INT; + } + } else { + while (maxRetries-- > 0) { + if (NFC_CONFIG2 & NFC_INT) { + NFC_CONFIG2 &= ~NFC_INT; + break; + } + udelay(1); + } + if (maxRetries <= 0) + DEBUG(MTD_DEBUG_LEVEL0, "%s(%d): INT not set\n", + __FUNCTION__, param); + } +} + +/*! + * This function issues the specified command to the NAND device and + * waits for completion. + * + * @param cmd command for NAND Flash + * @param useirq True if IRQ should be used rather than polling + */ +static void send_cmd(u16 cmd, bool useirq) +{ + DEBUG(MTD_DEBUG_LEVEL3, "send_cmd(0x%x, %d)\n", cmd, useirq); + + NFC_FLASH_CMD = (u16) cmd; + NFC_CONFIG2 = NFC_CMD; + + /* Wait for operation to complete */ + wait_op_done(TROP_US_DELAY, cmd, useirq); +} + +/*! + * This function sends an address (or partial address) to the + * NAND device. The address is used to select the source/destination for + * a NAND command. + * + * @param addr address to be written to NFC. + * @param islast True if this is the last address cycle for command + */ +static void send_addr(u16 addr, bool islast) +{ + DEBUG(MTD_DEBUG_LEVEL3, "send_addr(0x%x %d)\n", addr, islast); + + NFC_FLASH_ADDR = addr; + NFC_CONFIG2 = NFC_ADDR; + + /* Wait for operation to complete */ + wait_op_done(TROP_US_DELAY, addr, islast); +} + +/*! + * This function requests the NANDFC to initate the transfer + * of data currently in the NANDFC RAM buffer to the NAND device. + * + * @param buf_id Specify Internal RAM Buffer number (0-3) + * @param bSpareOnly set true if only the spare area is transferred + */ +static void send_prog_page(u8 buf_id, bool bSpareOnly) +{ + DEBUG(MTD_DEBUG_LEVEL3, "send_prog_page (%d)\n", bSpareOnly); + + /* NANDFC buffer 0 is used for page read/write */ + + NFC_BUF_ADDR = buf_id; + + /* Configure spare or page+spare access */ + if (!is2k_Pagesize) { + if (bSpareOnly) { + NFC_CONFIG1 |= NFC_SP_EN; + /* Workaround ecc status register error for spare-only read */ + if (cpu_is_mxc91131_rev(CHIP_REV_2_0) >= 1) { + NFC_CONFIG1 &= ~(NFC_SP_EN); + } + } else { + NFC_CONFIG1 &= ~(NFC_SP_EN); + } + } + NFC_CONFIG2 = NFC_INPUT; + + /* Wait for operation to complete */ + wait_op_done(TROP_US_DELAY, bSpareOnly, true); +} + +/*! + * This function will correct the single bit ECC error + * + * @param buf_id Specify Internal RAM Buffer number (0-3) + * @param eccpos Ecc byte and bit position + * @param bSpareOnly set to true if only spare area needs correction + */ + +static void mxc_nd_correct_error(u8 buf_id, u16 eccpos, bool bSpareOnly) +{ + u16 col; + u8 pos; + volatile u16 *buf; + + /* Get col & bit position of error + these macros works for both 8 & 16 bits */ + col = COLPOS(eccpos); /* Get half-word position */ + pos = BITPOS(eccpos); /* Get bit position */ + + DEBUG(MTD_DEBUG_LEVEL3, + "mxc_nd_correct_error (col=%d pos=%d)\n", col, pos); + + /* Set the pointer for main / spare area */ + if (!bSpareOnly) { + buf = MAIN_AREA0 + (col >> 1) + (512 * buf_id); + } else { + buf = SPARE_AREA0 + (col >> 1) + (16 * buf_id); + } + + /* Fix the data */ + *buf ^= (1 << pos); +} + +/*! + * This function will maintains state of single bit Error + * in Main & spare area + * + * @param buf_id Specify Internal RAM Buffer number (0-3) + * @param spare set to true if only spare area needs correction + */ +static void mxc_nd_correct_ecc(u8 buf_id, bool spare) +{ +#ifdef CONFIG_MTD_NAND_MXC_ECC_CORRECTION_OPTION2 + static int lastErrMain = 0, lastErrSpare = 0; /* To maintain single bit + error in previous page */ +#endif + u16 value, ecc_status; + /* Read the ECC result */ + ecc_status = NFC_ECC_STATUS_RESULT; + DEBUG(MTD_DEBUG_LEVEL3, + "mxc_nd_correct_ecc (Ecc status=%x)\n", ecc_status); + +#ifdef CONFIG_MTD_NAND_MXC_ECC_CORRECTION_OPTION2 + /* Check for Error in Mainarea */ + if ((ecc_status & 0xC) == MAIN_SINGLEBIT_ERROR) { + /* Check for error in previous page */ + if (lastErrMain && !spare) { + value = NFC_RSLTMAIN_AREA; + /* Correct single bit error in Mainarea + NFC will not correct the error in + current page */ + mxc_nd_correct_error(buf_id, value, false); + } else { + /* Set if single bit error in current page */ + lastErrMain = 1; + } + } else { + /* Reset if no single bit error in current page */ + lastErrMain = 0; + } + + /* Check for Error in Sparearea */ + if ((ecc_status & 0x3) == SPARE_SINGLEBIT_ERROR) { + /* Check for error in previous page */ + if (lastErrSpare) { + value = NFC_RSLTSPARE_AREA; + /* Correct single bit error in Mainarea + NFC will not correct the error in + current page */ + mxc_nd_correct_error(buf_id, value, true); + } else { + /* Set if single bit error in current page */ + lastErrSpare = 1; + } + } else { + /* Reset if no single bit error in current page */ + lastErrSpare = 0; + } +#else + if (((ecc_status & 0xC) == MAIN_SINGLEBIT_ERROR) + || ((ecc_status & 0x3) == SPARE_SINGLEBIT_ERROR)) { + if (Ecc_disabled) { + if ((ecc_status & 0xC) == MAIN_SINGLEBIT_ERROR) { + value = NFC_RSLTMAIN_AREA; + /* Correct single bit error in Mainarea + NFC will not correct the error in + current page */ + mxc_nd_correct_error(buf_id, value, false); + } + if ((ecc_status & 0x3) == SPARE_SINGLEBIT_ERROR) { + value = NFC_RSLTSPARE_AREA; + /* Correct single bit error in Mainarea + NFC will not correct the error in + current page */ + mxc_nd_correct_error(buf_id, value, true); + } + + } else { + /* Disable ECC */ + NFC_CONFIG1 &= ~(NFC_ECC_EN); + Ecc_disabled = 1; + } + } else if (ecc_status == 0) { + if (Ecc_disabled) { + /* Enable ECC */ + NFC_CONFIG1 |= NFC_ECC_EN; + Ecc_disabled = 0; + } + } else { + /* 2-bit Error Do nothing */ + } +#endif /* CONFIG_MTD_NAND_MXC_ECC_CORRECTION_OPTION2 */ + +} + +/*! + * This function requests the NANDFC to initated the transfer + * of data from the NAND device into in the NANDFC ram buffer. + * + * @param buf_id Specify Internal RAM Buffer number (0-3) + * @param bSpareOnly set true if only the spare area is transferred + */ +static void send_read_page(u8 buf_id, bool bSpareOnly) +{ + DEBUG(MTD_DEBUG_LEVEL3, "send_read_page (%d)\n", bSpareOnly); + + /* NANDFC buffer 0 is used for page read/write */ + NFC_BUF_ADDR = buf_id; + + /* Configure spare or page+spare access */ + if (!is2k_Pagesize) { + if (bSpareOnly) { + NFC_CONFIG1 |= NFC_SP_EN; + /* Workaround ecc status register error for spare-only read */ + if (cpu_is_mxc91131_rev(CHIP_REV_2_0) >= 1) { + NFC_CONFIG1 &= ~(NFC_SP_EN); + } + } else { + NFC_CONFIG1 &= ~(NFC_SP_EN); + } + } + + NFC_CONFIG2 = NFC_OUTPUT; + + /* Wait for operation to complete */ + wait_op_done(TROP_US_DELAY, bSpareOnly, true); + + /* If there are single bit errors in + two consecutive page reads then + the error is not corrected by the + NFC for the second page. + Correct single bit error in driver */ + + /* Removed NFC workaround in MXC91231-P2.1 */ + if (cpu_is_mxc91231_rev(CHIP_REV_2_1) < 0) { + mxc_nd_correct_ecc(buf_id, bSpareOnly); + } else { + mxc_nd_correct_ecc(buf_id, bSpareOnly); + } + +} + +/*! + * This function requests the NANDFC to perform a read of the + * NAND device ID. + */ +static void send_read_id(void) +{ + struct nand_chip *this = &mxc_nand_data->nand; + + /* NANDFC buffer 0 is used for device ID output */ + NFC_BUF_ADDR = 0x0; + + /* Read ID into main buffer */ + NFC_CONFIG1 &= (~(NFC_SP_EN)); + NFC_CONFIG2 = NFC_ID; + + /* Wait for operation to complete */ + wait_op_done(TROP_US_DELAY, 0, true); + + if (this->options & NAND_BUSWIDTH_16) { + volatile u16 *mainBuf = MAIN_AREA0; + + /* + * Pack the every-other-byte result for 16-bit ID reads + * into every-byte as the generic code expects and various + * chips implement. + */ + + mainBuf[0] = (mainBuf[0] & 0xff) | ((mainBuf[1] & 0xff) << 8); + mainBuf[1] = (mainBuf[2] & 0xff) | ((mainBuf[3] & 0xff) << 8); + mainBuf[2] = (mainBuf[4] & 0xff) | ((mainBuf[5] & 0xff) << 8); + } +} + +/*! + * This function requests the NANDFC to perform a read of the + * NAND device status and returns the current status. + * + * @return device status + */ +static u16 get_dev_status(void) +{ + volatile u16 *mainBuf = MAIN_AREA1; + u32 store; + u16 ret; + /* Issue status request to NAND device */ + + /* store the main area1 first word, later do recovery */ + store = *((u32 *) mainBuf); + /* + * NANDFC buffer 1 is used for device status to prevent + * corruption of read/write buffer on status requests. + */ + NFC_BUF_ADDR = 1; + + /* Read status into main buffer */ + NFC_CONFIG1 &= (~(NFC_SP_EN)); + NFC_CONFIG2 = NFC_STATUS; + + /* Wait for operation to complete */ + wait_op_done(TROP_US_DELAY, 0, true); + + /* Status is placed in first word of main buffer */ + /* get status, then recovery area 1 data */ + ret = mainBuf[0]; + *((u32 *) mainBuf) = store; + + return ret; +} + +/*! + * This functions is used by upper layer to checks if device is ready + * + * @param mtd MTD structure for the NAND Flash + * + * @return 0 if device is busy else 1 + */ +static int mxc_nand_dev_ready(struct mtd_info *mtd) +{ + /* + * NFC handles R/B internally.Therefore,this function + * always returns status as ready. + */ + return 1; +} + +static void mxc_nand_enable_hwecc(struct mtd_info *mtd, int mode) +{ + /* + * If HW ECC is enabled, we turn it on during init. There is + * no need to enable again here. + */ +} + +static int mxc_nand_correct_data(struct mtd_info *mtd, u_char * dat, + u_char * read_ecc, u_char * calc_ecc) +{ + /* + * 1-Bit errors are automatically corrected in HW. No need for + * additional correction. 2-Bit errors cannot be corrected by + * HW ECC, so we need to return failure + */ + u16 ecc_status = NFC_ECC_STATUS_RESULT; + + if (((ecc_status & 0x3) == 2) || ((ecc_status >> 2) == 2)) { + DEBUG(MTD_DEBUG_LEVEL0, + "MXC_NAND: HWECC uncorrectable 2-bit ECC error\n"); + return -1; + } + + return 0; +} + +static int mxc_nand_calculate_ecc(struct mtd_info *mtd, const u_char * dat, + u_char * ecc_code) +{ + /* + * Just return success. HW ECC does not read/write the NFC spare + * buffer. Only the FLASH spare area contains the calcuated ECC. + */ + return 0; +} + +/*! + * This function reads byte from the NAND Flash + * + * @param mtd MTD structure for the NAND Flash + * + * @return data read from the NAND Flash + */ +static u_char mxc_nand_read_byte(struct mtd_info *mtd) +{ + u_char retVal = 0; + u16 col, rdWord; + volatile u16 *mainBuf = MAIN_AREA0; + volatile u16 *spareBuf = SPARE_AREA0; + + /* Check for status request */ + if (g_nandfc_info.bStatusRequest) { + return (get_dev_status() & 0xFF); + } + + /* Get column for 16-bit access */ + col = g_nandfc_info.colAddr >> 1; + + /* If we are accessing the spare region */ + if (g_nandfc_info.bSpareOnly) { + rdWord = spareBuf[col]; + } else { + rdWord = mainBuf[col]; + } + + /* Pick upper/lower byte of word from RAM buffer */ + if (g_nandfc_info.colAddr & 0x1) { + retVal = (rdWord >> 8) & 0xFF; + } else { + retVal = rdWord & 0xFF; + } + + /* Update saved column address */ + g_nandfc_info.colAddr++; + + return retVal; +} + +/*! + * This function reads word from the NAND Flash + * + * @param mtd MTD structure for the NAND Flash + * + * @return data read from the NAND Flash + */ +static u16 mxc_nand_read_word(struct mtd_info *mtd) +{ + u16 col; + u16 rdWord, retVal; + volatile u16 *p; + + DEBUG(MTD_DEBUG_LEVEL3, + "mxc_nand_read_word(col = %d)\n", g_nandfc_info.colAddr); + + col = g_nandfc_info.colAddr; + /* Adjust saved column address */ + if (col < mtd->writesize && g_nandfc_info.bSpareOnly) + col += mtd->writesize; + + if (col < mtd->writesize) + p = (MAIN_AREA0) + (col >> 1); + else + p = (SPARE_AREA0) + ((col - mtd->writesize) >> 1); + + if (col & 1) { + rdWord = *p; + retVal = (rdWord >> 8) & 0xff; + rdWord = *(p + 1); + retVal |= (rdWord << 8) & 0xff00; + + } else { + retVal = *p; + + } + + /* Update saved column address */ + g_nandfc_info.colAddr = col + 2; + + return retVal; +} + +/*! + * This function writes data of length \b len to buffer \b buf. The data to be + * written on NAND Flash is first copied to RAMbuffer. After the Data Input + * Operation by the NFC, the data is written to NAND Flash + * + * @param mtd MTD structure for the NAND Flash + * @param buf data to be written to NAND Flash + * @param len number of bytes to be written + */ +static void mxc_nand_write_buf(struct mtd_info *mtd, + const u_char * buf, int len) +{ + int n; + int col; + int i = 0; + + DEBUG(MTD_DEBUG_LEVEL3, + "mxc_nand_write_buf(col = %d, len = %d)\n", g_nandfc_info.colAddr, + len); + + col = g_nandfc_info.colAddr; + + /* Adjust saved column address */ + if (col < mtd->writesize && g_nandfc_info.bSpareOnly) + col += mtd->writesize; + + n = mtd->writesize + mtd->oobsize - col; + n = min(len, n); + + DEBUG(MTD_DEBUG_LEVEL3, + "%s:%d: col = %d, n = %d\n", __FUNCTION__, __LINE__, col, n); + + while (n) { + volatile u32 *p; + if (col < mtd->writesize) + p = (volatile u32 *)((ulong) (MAIN_AREA0) + (col & ~3)); + else + p = (volatile u32 *)((ulong) (SPARE_AREA0) - + mtd->writesize + (col & ~3)); + + DEBUG(MTD_DEBUG_LEVEL3, "%s:%d: p = %p\n", __FUNCTION__, + __LINE__, p); + + if (((col | (int)&buf[i]) & 3) || n < 16) { + u32 data = 0; + + if (col & 3 || n < 4) + data = *p; + + switch (col & 3) { + case 0: + if (n) { + data = (data & 0xffffff00) | + (buf[i++] << 0); + n--; + col++; + } + case 1: + if (n) { + data = (data & 0xffff00ff) | + (buf[i++] << 8); + n--; + col++; + } + case 2: + if (n) { + data = (data & 0xff00ffff) | + (buf[i++] << 16); + n--; + col++; + } + case 3: + if (n) { + data = (data & 0x00ffffff) | + (buf[i++] << 24); + n--; + col++; + } + } + + *p = data; + } else { + int m = mtd->writesize - col; + + if (col >= mtd->writesize) + m += mtd->oobsize; + + m = min(n, m) & ~3; + + DEBUG(MTD_DEBUG_LEVEL3, + "%s:%d: n = %d, m = %d, i = %d, col = %d\n", + __FUNCTION__, __LINE__, n, m, i, col); + + memcpy((void *)(p), &buf[i], m); + col += m; + i += m; + n -= m; + } + } + /* Update saved column address */ + g_nandfc_info.colAddr = col; + +} + +/*! + * This function id is used to read the data buffer from the NAND Flash. To + * read the data from NAND Flash first the data output cycle is initiated by + * the NFC, which copies the data to RAMbuffer. This data of length \b len is + * then copied to buffer \b buf. + * + * @param mtd MTD structure for the NAND Flash + * @param buf data to be read from NAND Flash + * @param len number of bytes to be read + */ +static void mxc_nand_read_buf(struct mtd_info *mtd, u_char * buf, int len) +{ + + int n; + int col; + int i = 0; + + DEBUG(MTD_DEBUG_LEVEL3, + "mxc_nand_read_buf(col = %d, len = %d)\n", g_nandfc_info.colAddr, + len); + + col = g_nandfc_info.colAddr; + /* Adjust saved column address */ + if (col < mtd->writesize && g_nandfc_info.bSpareOnly) + col += mtd->writesize; + + n = mtd->writesize + mtd->oobsize - col; + n = min(len, n); + + while (n) { + volatile u32 *p; + + if (col < mtd->writesize) + p = (volatile u32 *)((ulong) (MAIN_AREA0) + (col & ~3)); + else + p = (volatile u32 *)((ulong) (SPARE_AREA0) - + mtd->writesize + (col & ~3)); + + if (((col | (int)&buf[i]) & 3) || n < 16) { + u32 data; + + data = *p; + switch (col & 3) { + case 0: + if (n) { + buf[i++] = (u8) (data); + n--; + col++; + } + case 1: + if (n) { + buf[i++] = (u8) (data >> 8); + n--; + col++; + } + case 2: + if (n) { + buf[i++] = (u8) (data >> 16); + n--; + col++; + } + case 3: + if (n) { + buf[i++] = (u8) (data >> 24); + n--; + col++; + } + } + } else { + int m = mtd->writesize - col; + + if (col >= mtd->writesize) + m += mtd->oobsize; + + m = min(n, m) & ~3; + memcpy(&buf[i], (void *)(p), m); + col += m; + i += m; + n -= m; + } + } + /* Update saved column address */ + g_nandfc_info.colAddr = col; + +} + +/*! + * This function is used by the upper layer to verify the data in NAND Flash + * with the data in the \b buf. + * + * @param mtd MTD structure for the NAND Flash + * @param buf data to be verified + * @param len length of the data to be verified + * + * @return -EFAULT if error else 0 + * + */ +static int +mxc_nand_verify_buf(struct mtd_info *mtd, const u_char * buf, int len) +{ + return -EFAULT; +} + +/*! + * This function is used by upper layer for select and deselect of the NAND + * chip + * + * @param mtd MTD structure for the NAND Flash + * @param chip val indicating select or deselect + */ +static void mxc_nand_select_chip(struct mtd_info *mtd, int chip) +{ +#ifdef CONFIG_MTD_NAND_MXC_FORCE_CE + if (chip > 0) { + DEBUG(MTD_DEBUG_LEVEL0, + "ERROR: Illegal chip select (chip = %d)\n", chip); + return; + } + + if (chip == -1) { + NFC_CONFIG1 &= (~(NFC_CE)); + return; + } + + NFC_CONFIG1 |= NFC_CE; +#endif + + switch (chip) { + case -1: + /* Disable the NFC clock */ + clk_disable(nfc_clk); + break; + case 0: + /* Enable the NFC clock */ + clk_enable(nfc_clk); + break; + + default: + break; + } +} + +/*! + * This function is used by the upper layer to write command to NAND Flash for + * different operations to be carried out on NAND Flash + * + * @param mtd MTD structure for the NAND Flash + * @param command command for NAND Flash + * @param column column offset for the page read + * @param page_addr page to be read from NAND Flash + */ +static void mxc_nand_command(struct mtd_info *mtd, unsigned command, + int column, int page_addr) +{ + bool useirq = true; + + DEBUG(MTD_DEBUG_LEVEL3, + "mxc_nand_command (cmd = 0x%x, col = 0x%x, page = 0x%x)\n", + command, column, page_addr); + + /* + * Reset command state information + */ + g_nandfc_info.bStatusRequest = false; + + /* + * Command pre-processing step + */ + switch (command) { + + case NAND_CMD_STATUS: + g_nandfc_info.colAddr = 0; + g_nandfc_info.bStatusRequest = true; + break; + + case NAND_CMD_READ0: + g_nandfc_info.colAddr = column; + g_nandfc_info.bSpareOnly = false; + useirq = false; + break; + + case NAND_CMD_READOOB: + g_nandfc_info.colAddr = column; + g_nandfc_info.bSpareOnly = true; + useirq = false; + if (is2k_Pagesize) + command = NAND_CMD_READ0; /* only READ0 is valid */ + break; + + case NAND_CMD_SEQIN: + if (column >= mtd->writesize) { + if (is2k_Pagesize) { + /** + * FIXME: before send SEQIN command for write OOB, + * We must read one page out. + * For K9F1GXX has no READ1 command to set current HW + * pointer to spare area, we must write the whole page including OOB together. + */ + /* call itself to read a page */ + mxc_nand_command(mtd, NAND_CMD_READ0, 0, + page_addr); + } + g_nandfc_info.colAddr = column - mtd->writesize; + g_nandfc_info.bSpareOnly = true; + /* Set program pointer to spare region */ + if (!is2k_Pagesize) + send_cmd(NAND_CMD_READOOB, false); + } else { + g_nandfc_info.bSpareOnly = false; + g_nandfc_info.colAddr = column; + /* Set program pointer to page start */ + if (!is2k_Pagesize) + send_cmd(NAND_CMD_READ0, false); + } + useirq = false; + break; + + case NAND_CMD_PAGEPROG: +#ifndef CONFIG_MTD_NAND_MXC_ECC_CORRECTION_OPTION2 + if (Ecc_disabled) { + /* Enable Ecc for page writes */ + NFC_CONFIG1 |= NFC_ECC_EN; + } +#endif + + send_prog_page(0, g_nandfc_info.bSpareOnly); + + if (is2k_Pagesize) { + /* data in 4 areas datas */ + send_prog_page(1, g_nandfc_info.bSpareOnly); + send_prog_page(2, g_nandfc_info.bSpareOnly); + send_prog_page(3, g_nandfc_info.bSpareOnly); + } + + break; + + case NAND_CMD_ERASE1: + useirq = false; + break; + } + + /* + * Write out the command to the device. + */ + send_cmd(command, useirq); + + /* + * Write out column address, if necessary + */ + if (column != -1) { + /* + * MXC NANDFC can only perform full page+spare or + * spare-only read/write. When the upper layers + * layers perform a read/write buf operation, + * we will used the saved column adress to index into + * the full page. + */ + send_addr(0, page_addr == -1); + if (is2k_Pagesize) + send_addr(0, false); /* another col addr cycle for 2k page */ + } + + /* + * Write out page address, if necessary + */ + if (page_addr != -1) { + send_addr((page_addr & 0xff), false); /* paddr_0 - p_addr_7 */ + + if (is2k_Pagesize) { + send_addr((page_addr >> 8) & 0xFF, false); + if (mtd->size >= 0x42000000) { + send_addr((page_addr >> 16) & 0xff, true); + } + } else { + /* One more address cycle for higher density devices */ + if (mtd->size >= 0x4000000) { + send_addr((page_addr >> 8) & 0xff, false); /* paddr_8 - paddr_15 */ + send_addr((page_addr >> 16) & 0xff, true); + } else + send_addr((page_addr >> 8) & 0xff, true); /* paddr_8 - paddr_15 */ + } + } + + /* + * Command post-processing step + */ + switch (command) { + + case NAND_CMD_RESET: + break; + + case NAND_CMD_READOOB: + case NAND_CMD_READ0: + if (is2k_Pagesize) { + /* send read confirm command */ + send_cmd(NAND_CMD_READSTART, true); + /* read for each AREA */ + send_read_page(0, g_nandfc_info.bSpareOnly); + send_read_page(1, g_nandfc_info.bSpareOnly); + send_read_page(2, g_nandfc_info.bSpareOnly); + send_read_page(3, g_nandfc_info.bSpareOnly); + } else { + send_read_page(0, g_nandfc_info.bSpareOnly); + } + break; + + case NAND_CMD_READID: + send_read_id(); + break; + + case NAND_CMD_PAGEPROG: +#ifndef CONFIG_MTD_NAND_MXC_ECC_CORRECTION_OPTION2 + if (Ecc_disabled) { + /* Disble Ecc after page writes */ + NFC_CONFIG1 &= ~(NFC_ECC_EN); + } +#endif + break; + + case NAND_CMD_STATUS: + break; + + case NAND_CMD_ERASE2: + break; + } +} + +/* Define some generic bad / good block scan pattern which are used + * while scanning a device for factory marked good / bad blocks. */ +static uint8_t scan_ff_pattern[] = { 0xff, 0xff }; + +static struct nand_bbt_descr smallpage_memorybased = { + .options = NAND_BBT_SCAN2NDPAGE, + .offs = 5, + .len = 1, + .pattern = scan_ff_pattern +}; + +static struct nand_bbt_descr largepage_memorybased = { + .options = 0, + .offs = 0, + .len = 2, + .pattern = scan_ff_pattern +}; + +static int mxc_nand_scan_bbt(struct mtd_info *mtd) +{ + struct nand_chip *this = mtd->priv; + + /* Config before scanning */ + /* Do not rely on NFMS_BIT, set/clear NFMS bit based on mtd->writesize */ + if (mtd->writesize == 2048) { + NFMS |= (1 << NFMS_BIT); + is2k_Pagesize = 1; + } else { + if ((NFMS >> NFMS_BIT) & 0x1) { /* This case strangly happened on MXC91321 P1.2.2 */ + printk(KERN_INFO + "Oops... NFMS Bit set for 512B Page, resetting it. [RCSR: 0x%08x]\n", + NFMS); + NFMS &= ~(1 << NFMS_BIT); + } + is2k_Pagesize = 0; + } + + this->bbt_td = NULL; + this->bbt_md = NULL; + + if (!this->badblock_pattern) { + if (mtd->writesize == 2048) + this->badblock_pattern = &smallpage_memorybased; + else + this->badblock_pattern = (mtd->writesize > 512) ? + &largepage_memorybased : &smallpage_memorybased; + } + /* Build bad block table */ + return nand_scan_bbt(mtd, this->badblock_pattern); +} + +#ifdef CONFIG_MXC_NAND_LOW_LEVEL_ERASE +static void mxc_low_erase(struct mtd_info *mtd) +{ + + struct nand_chip *this = mtd->priv; + unsigned int page_addr, addr; + u_char status; + + DEBUG(MTD_DEBUG_LEVEL0, "MXC_ND : mxc_low_erase:Erasing NAND\n"); + for (addr = 0; addr < this->chipsize; addr += mtd->erasesize) { + page_addr = addr / mtd->writesize; + mxc_nand_command(mtd, NAND_CMD_ERASE1, -1, page_addr); + mxc_nand_command(mtd, NAND_CMD_ERASE2, -1, -1); + mxc_nand_command(mtd, NAND_CMD_STATUS, -1, -1); + status = mxc_nand_read_byte(mtd); + if (status & NAND_STATUS_FAIL) { + printk(KERN_ERR + "ERASE FAILED(block = %d,status = 0x%x)\n", + addr / mtd->erasesize, status); + } + } + +} +#endif +/*! + * This function is called during the driver binding process. + * + * @param pdev the device structure used to store device specific + * information that is used by the suspend, resume and + * remove functions + * + * @return The function always returns 0. + */ +static int __init mxcnd_probe(struct platform_device *pdev) +{ + struct nand_chip *this; + struct mtd_info *mtd; + struct flash_platform_data *flash = pdev->dev.platform_data; + int nr_parts = 0; + + int err = 0; + /* Allocate memory for MTD device structure and private data */ + mxc_nand_data = kmalloc(sizeof(struct mxc_mtd_s), GFP_KERNEL); + if (!mxc_nand_data) { + printk(KERN_ERR "%s: failed to allocate mtd_info\n", + __FUNCTION__); + err = -ENOMEM; + goto out; + } + memset(mxc_nand_data, 0, sizeof(struct mxc_mtd_s)); + memset((char *)&g_nandfc_info, 0, sizeof(g_nandfc_info)); + + mxc_nand_data->dev = &pdev->dev; + /* structures must be linked */ + this = &mxc_nand_data->nand; + mtd = &mxc_nand_data->mtd; + mtd->priv = this; + mtd->owner = THIS_MODULE; + + /* 50 us command delay time */ + this->chip_delay = 5; + + this->priv = mxc_nand_data; + this->dev_ready = mxc_nand_dev_ready; + this->cmdfunc = mxc_nand_command; + this->select_chip = mxc_nand_select_chip; + this->read_byte = mxc_nand_read_byte; + this->read_word = mxc_nand_read_word; + this->write_buf = mxc_nand_write_buf; + this->read_buf = mxc_nand_read_buf; + this->verify_buf = mxc_nand_verify_buf; + this->scan_bbt = mxc_nand_scan_bbt; + + nfc_clk = clk_get(&pdev->dev, "nfc_clk"); + clk_enable(nfc_clk); + + NFC_CONFIG1 |= NFC_INT_MSK; + init_waitqueue_head(&irq_waitq); + err = request_irq(INT_NANDFC, mxc_nfc_irq, 0, "mxc_nd", NULL); + if (err) { + goto out_1; + } + + if (hardware_ecc) { + this->ecc.calculate = mxc_nand_calculate_ecc; + this->ecc.hwctl = mxc_nand_enable_hwecc; + this->ecc.correct = mxc_nand_correct_data; + this->ecc.mode = NAND_ECC_HW; + this->ecc.size = 512; + this->ecc.bytes = 3; + this->ecc.layout = &nand_hw_eccoob_8; + NFC_CONFIG1 |= NFC_ECC_EN; + } else { + this->ecc.mode = NAND_ECC_SOFT; + } + + /* Reset NAND */ + this->cmdfunc(mtd, NAND_CMD_RESET, -1, -1); + + /* preset operation */ + /* Unlock the internal RAM Buffer */ + NFC_CONFIG = 0x2; + + /* Blocks to be unlocked */ + NFC_UNLOCKSTART_BLKADDR = 0x0; + NFC_UNLOCKEND_BLKADDR = 0x4000; + + /* support for One Flash Clock cycle new in rev 2.0 */ + if (cpu_is_mxc91131_rev(CHIP_REV_2_0) >= 1) { + NFC_CONFIG1 |= (NFC_ONE_CYCLE); + } + /* Unlock Block Command for given address range */ + NFC_WRPROT = 0x4; + + /* NAND bus width determines access funtions used by upper layer */ + if (flash->width == 2) { + this->options |= NAND_BUSWIDTH_16; + this->ecc.layout = &nand_hw_eccoob_16; + } else { + this->options |= 0; + } + + is2k_Pagesize = 0; + + /* Scan to find existence of the device */ + if (nand_scan(mtd, 1)) { + DEBUG(MTD_DEBUG_LEVEL0, + "MXC_ND: Unable to find any NAND device.\n"); + err = -ENXIO; + goto out_1; + } + + /* Register the partitions */ +#ifdef CONFIG_MTD_PARTITIONS + nr_parts = + parse_mtd_partitions(mtd, part_probes, &mxc_nand_data->parts, 0); + if (nr_parts > 0) + add_mtd_partitions(mtd, mxc_nand_data->parts, nr_parts); + else if (flash->parts) + add_mtd_partitions(mtd, flash->parts, flash->nr_parts); + else +#endif + { + pr_info("Registering %s as whole device\n", mtd->name); + add_mtd_device(mtd); + } +#ifdef CONFIG_MXC_NAND_LOW_LEVEL_ERASE + /* Erase all the blocks of a NAND */ + mxc_low_erase(mtd); +#endif + + platform_set_drvdata(pdev, mtd); + return 0; + + out_1: + kfree(mxc_nand_data); + out: + return err; + +} + + /*! + * Dissociates the driver from the device. + * + * @param pdev the device structure used to give information on which + * + * @return The function always returns 0. + */ + +static int __exit mxcnd_remove(struct platform_device *pdev) +{ + struct mtd_info *mtd = platform_get_drvdata(pdev); + + clk_put(nfc_clk); + platform_set_drvdata(pdev, NULL); + + if (mxc_nand_data) { + nand_release(mtd); + free_irq(INT_NANDFC, NULL); + kfree(mxc_nand_data); + } + + return 0; +} + +#ifdef CONFIG_PM +/*! + * This function is called to put the NAND in a low power state. Refer to the + * document driver-model/driver.txt in the kernel source tree for more + * information. + * + * @param pdev the device information structure + * + * @param state the power state the device is entering + * + * @return The function returns 0 on success and -1 on failure + */ + +static int mxcnd_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct mtd_info *info = platform_get_drvdata(pdev); + int ret = 0; + + DEBUG(MTD_DEBUG_LEVEL0, "MXC_ND : NAND suspend\n"); + if (info) + ret = info->suspend(info); + + /* Disable the NFC clock */ + clk_disable(nfc_clk); + + return ret; +} + +/*! + * This function is called to bring the NAND back from a low power state. Refer + * to the document driver-model/driver.txt in the kernel source tree for more + * information. + * + * @param pdev the device information structure + * + * @return The function returns 0 on success and -1 on failure + */ +static int mxcnd_resume(struct platform_device *pdev) +{ + struct mtd_info *info = platform_get_drvdata(pdev); + int ret = 0; + + DEBUG(MTD_DEBUG_LEVEL0, "MXC_ND : NAND resume\n"); + /* Enable the NFC clock */ + clk_enable(nfc_clk); + + if (info) { + info->resume(info); + } + + return ret; +} + +#else +#define mxcnd_suspend NULL +#define mxcnd_resume NULL +#endif /* CONFIG_PM */ + +/*! + * This structure contains pointers to the power management callback functions. + */ +static struct platform_driver mxcnd_driver = { + .driver = { + .name = "mxc_nand_flash", + }, + .probe = mxcnd_probe, + .remove = __exit_p(mxcnd_remove), + .suspend = mxcnd_suspend, + .resume = mxcnd_resume, +}; + +/*! + * Main initialization routine + * @return 0 if successful; non-zero otherwise + */ +static int __init mxc_nd_init(void) +{ + /* Register the device driver structure. */ + pr_info("MXC MTD nand Driver %s\n", DVR_VER); + if (platform_driver_register(&mxcnd_driver) != 0) { + printk(KERN_ERR "Driver register failed for mxcnd_driver\n"); + return -ENODEV; + } + return 0; +} + +/*! + * Clean up routine + */ +static void __exit mxc_nd_cleanup(void) +{ + /* Unregister the device structure */ + platform_driver_unregister(&mxcnd_driver); +} + +module_init(mxc_nd_init); +module_exit(mxc_nd_cleanup); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MXC NAND MTD driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mtd/nand/mxc_nd.h b/drivers/mtd/nand/mxc_nd.h new file mode 100644 index 000000000000..fd15cb48cb9c --- /dev/null +++ b/drivers/mtd/nand/mxc_nd.h @@ -0,0 +1,112 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mxc_nd.h + * + * @brief This file contains the NAND Flash Controller register information. + * + * + * @ingroup NAND_MTD + */ + +#ifndef __MXC_ND_H__ +#define __MXC_ND_H__ + +#include <asm/hardware.h> + +/* + * Addresses for NFC registers + */ +#define NFC_BUF_SIZE (*((volatile u16 *)IO_ADDRESS(NFC_BASE_ADDR + 0xE00))) +#define NFC_BUF_ADDR (*((volatile u16 *)IO_ADDRESS(NFC_BASE_ADDR + 0xE04))) +#define NFC_FLASH_ADDR (*((volatile u16 *)IO_ADDRESS(NFC_BASE_ADDR + 0xE06))) +#define NFC_FLASH_CMD (*((volatile u16 *)IO_ADDRESS(NFC_BASE_ADDR + 0xE08))) +#define NFC_CONFIG (*((volatile u16 *)IO_ADDRESS(NFC_BASE_ADDR + 0xE0A))) +#define NFC_ECC_STATUS_RESULT (*((volatile u16 *)IO_ADDRESS(NFC_BASE_ADDR + 0xE0C))) +#define NFC_RSLTMAIN_AREA (*((volatile u16 *)IO_ADDRESS(NFC_BASE_ADDR + 0xE0E))) +#define NFC_RSLTSPARE_AREA (*((volatile u16 *)IO_ADDRESS(NFC_BASE_ADDR + 0xE10))) +#define NFC_WRPROT (*((volatile u16 *)IO_ADDRESS(NFC_BASE_ADDR + 0xE12))) +#define NFC_UNLOCKSTART_BLKADDR (*((volatile u16 *)IO_ADDRESS(NFC_BASE_ADDR + 0xE14))) +#define NFC_UNLOCKEND_BLKADDR (*((volatile u16 *)IO_ADDRESS(NFC_BASE_ADDR + 0xE16))) +#define NFC_NF_WRPRST (*((volatile u16 *)IO_ADDRESS(NFC_BASE_ADDR + 0xE18))) +#define NFC_CONFIG1 (*((volatile u16 *)IO_ADDRESS(NFC_BASE_ADDR + 0xE1A))) +#define NFC_CONFIG2 (*((volatile u16 *)IO_ADDRESS(NFC_BASE_ADDR + 0xE1C))) + +/*! + * Addresses for NFC RAM BUFFER Main area 0 + */ +#define MAIN_AREA0 (volatile u16 *)IO_ADDRESS(NFC_BASE_ADDR + 0x000) +#define MAIN_AREA1 (volatile u16 *)IO_ADDRESS(NFC_BASE_ADDR + 0x200) +#define MAIN_AREA2 (volatile u16 *)IO_ADDRESS(NFC_BASE_ADDR + 0x400) +#define MAIN_AREA3 (volatile u16 *)IO_ADDRESS(NFC_BASE_ADDR + 0x600) + +/*! + * Addresses for NFC SPARE BUFFER Spare area 0 + */ +#define SPARE_AREA0 (volatile u16 *)IO_ADDRESS(NFC_BASE_ADDR + 0x800) +#define SPARE_AREA1 (volatile u16 *)IO_ADDRESS(NFC_BASE_ADDR + 0x810) +#define SPARE_AREA2 (volatile u16 *)IO_ADDRESS(NFC_BASE_ADDR + 0x820) +#define SPARE_AREA3 (volatile u16 *)IO_ADDRESS(NFC_BASE_ADDR + 0x830) + +/*! + * Set INT to 0, FCMD to 1, rest to 0 in NFC_CONFIG2 Register for Command + * operation + */ +#define NFC_CMD 0x1 + +/*! + * Set INT to 0, FADD to 1, rest to 0 in NFC_CONFIG2 Register for Address + * operation + */ +#define NFC_ADDR 0x2 + +/*! + * Set INT to 0, FDI to 1, rest to 0 in NFC_CONFIG2 Register for Input + * operation + */ +#define NFC_INPUT 0x4 + +/*! + * Set INT to 0, FDO to 001, rest to 0 in NFC_CONFIG2 Register for Data Output + * operation + */ +#define NFC_OUTPUT 0x8 + +/*! + * Set INT to 0, FD0 to 010, rest to 0 in NFC_CONFIG2 Register for Read ID + * operation + */ +#define NFC_ID 0x10 + +/*! + * Set INT to 0, FDO to 100, rest to 0 in NFC_CONFIG2 Register for Read Status + * operation + */ +#define NFC_STATUS 0x20 + +/*! + * Set INT to 1, rest to 0 in NFC_CONFIG2 Register for Read Status + * operation + */ +#define NFC_INT 0x8000 + +#define NFC_SP_EN (1 << 2) +#define NFC_ECC_EN (1 << 3) +#define NFC_INT_MSK (1 << 4) +#define NFC_BIG (1 << 5) +#define NFC_RST (1 << 6) +#define NFC_CE (1 << 7) +#define NFC_ONE_CYCLE (1 << 8) + +#endif /* MXCND_H */ diff --git a/drivers/mtd/nand/mxc_nd2.c b/drivers/mtd/nand/mxc_nd2.c new file mode 100644 index 000000000000..1092248395f7 --- /dev/null +++ b/drivers/mtd/nand/mxc_nd2.c @@ -0,0 +1,1354 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/nand.h> +#include <linux/interrupt.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/mtd/partitions.h> +#include <asm/mach/flash.h> +#include <asm/io.h> +#include "mxc_nd2.h" + +#define DVR_VER "2.3" + +/* Global address Variables */ +static u32 nfc_axi_base, nfc_ip_base; + +static void mxc_swap_2k_bi_main_sp(void); + +struct mxc_mtd_s { + struct mtd_info mtd; + struct nand_chip nand; + struct mtd_partition *parts; + struct device *dev; +}; + +static struct mxc_mtd_s *mxc_nand_data; + +/* + * Define delays in microsec for NAND device operations + */ +#define TROP_US_DELAY 2000 + +struct nand_info { + bool bSpareOnly; + bool bStatusRequest; + u16 colAddr; +}; + +static struct nand_info g_nandfc_info; + +#ifdef CONFIG_MTD_NAND_MXC_SWECC +static int hardware_ecc = 0; +#else +static int hardware_ecc = 1; +#endif + +static int page_to_block_shift; +static int g_page_mask; +static int scan_done; +static int skip_erase; +static u8 *oob_data_shadow_p; +/* + * OOB data that is shadowed in the SDRAM to prevent the Spare only access + * to the Nand chip. This is valid only for the JFFS2 File System. + */ +static uint8_t *shadow_oob_data; + +static uint8_t oob_data_512[] = { + 0x85, 0x19, 0x03, 0x20, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff +}; + +static uint8_t oob_data_2k[] = { + 0xff, 0xff, 0x85, 0x19, 0x03, 0x20, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff +}; + +static struct clk *nfc_clk; + +/* + * OOB placement block for use with hardware ecc generation + */ +static struct nand_ecclayout nand_hw_eccoob_512 = { + .eccbytes = 9, + .eccpos = {7, 8, 9, 10, 11, 12, 13, 14, 15}, + .oobavail = 4, + .oobfree = {{0, 4}} +}; + +static struct nand_ecclayout nand_hw_eccoob_2k = { + .eccbytes = 9, + .eccpos = {7, 8, 9, 10, 11, 12, 13, 14, 15}, + .oobavail = 4, + .oobfree = {{2, 4}} +}; + +/*! + * @defgroup NAND_MTD NAND Flash MTD Driver for MXC processors + */ + +/*! + * @file mxc_nd3.c + * + * @brief This file contains the hardware specific layer for NAND Flash on + * MXC processor + * + * @ingroup NAND_MTD + */ + +#ifdef CONFIG_MTD_PARTITIONS +static const char *part_probes[] = { "RedBoot", "cmdlinepart", NULL }; +#endif + +static wait_queue_head_t irq_waitq; + +static irqreturn_t mxc_nfc_irq(int irq, void *dev_id) +{ + /* Disable Interuupt */ + raw_write(raw_read(REG_NFC_INTRRUPT) | NFC_INT_MSK, REG_NFC_INTRRUPT); + wake_up(&irq_waitq); + + return IRQ_HANDLED; +} + +static u8 mxc_main_xfer_buf[2048] ____cacheline_aligned; + +/* + * Functions that operate on the shadow table maintained in the RAM. + * Each block in the Nand chip has one bit entry in this table + * indicating if the block has a JFFS2 clean marker. + * mark_oob_data_dirty - marks a block to indicate that the block has a JFFS2 + * clean marker + * is_oob_data_dirty - checks if the block has a JFFS2 clean marker + * mark_oob_data_clean - marks a block to indicate that the block is erased + * and doesnot contain JFFS2 clean marker. + */ + +static void mark_oob_data_dirty(u32 page, int update_sp) +{ + u32 blk = page >> page_to_block_shift; + u32 off = blk / 8; + u32 bit = blk % 8; + + oob_data_shadow_p[off] |= (1 << bit); +} + +static int is_oob_data_dirty(u32 page) +{ + u32 blk = page >> page_to_block_shift; + u32 off = blk / 8; + u32 bit = blk % 8; + + return oob_data_shadow_p[off] & (1 << bit); +} + +static void mark_oob_data_clean(u32 page) +{ + + u32 blk = page >> page_to_block_shift; + u32 off = blk / 8; + u32 bit = blk % 8; + + oob_data_shadow_p[off] &= ~(1 << bit); +} + +/* + * Functions to handle 32-bit aligned memcpy. + */ +static void nfc_memcpy(void *dst, const void *src, int len) +{ + volatile u16 *d = (volatile u16 *)dst; + volatile u16 *s = (volatile u16 *)src; + int wc; + + switch ((u32) dst & 3) { + case 2: + wc = len / 2; + /* adjust alignment */ + *d = *s; + memcpy((void *)(d + 1), (const void *)(s + 1), len - 4); + *(d + wc - 1) = *(s + wc - 1); + break; + + case 1: + case 3: + memcpy((void *)mxc_main_xfer_buf, (const void *)src, + (len + 3) & (~3)); + memcpy((void *)d, (const void *)mxc_main_xfer_buf, len); + break; + case 0: + memcpy((void *)d, (const void *)s, len); + } +} + +/*! + * This function polls the NFC to wait for the basic operation to complete by + * checking the INT bit of config2 register. + * + * @param maxRetries number of retry attempts (separated by 1 us) + * @param useirq True if IRQ should be used rather than polling + */ +static void wait_op_done(int maxRetries, bool useirq) +{ + + if (useirq) { + if ((raw_read(REG_NFC_OPS_STAT) & NFC_OPS_STAT) == 0) { + /* Enable Interuupt */ + raw_write(raw_read(REG_NFC_INTRRUPT) & ~NFC_INT_MSK, + REG_NFC_INTRRUPT); + wait_event(irq_waitq, + (raw_read(REG_NFC_OPS_STAT) & NFC_OPS_STAT)); + raw_write((raw_read(REG_NFC_OPS_STAT) & ~NFC_OPS_STAT), + REG_NFC_OPS_STAT); + } + } else { + while (1) { + maxRetries--; + if (raw_read(REG_NFC_OPS_STAT) & NFC_OPS_STAT) { + raw_write((raw_read(REG_NFC_OPS_STAT) & + ~NFC_OPS_STAT), REG_NFC_OPS_STAT); + break; + } + udelay(1); + } + if (maxRetries <= 0) { + DEBUG(MTD_DEBUG_LEVEL0, "%s(%d): INT not set\n", + __FUNCTION__); + } + } +} + +/*! + * This function issues the specified command to the NAND device and + * waits for completion. + * + * @param cmd command for NAND Flash + * @param useirq True if IRQ should be used rather than polling + */ +static void send_cmd(u16 cmd, bool useirq) +{ + DEBUG(MTD_DEBUG_LEVEL3, "send_cmd(0x%x, %d)\n", cmd, useirq); + + raw_write(cmd, REG_NFC_FLASH_CMD); + ACK_OPS; + raw_write(NFC_CMD, REG_NFC_OPS); + + /* Wait for operation to complete */ + wait_op_done(TROP_US_DELAY, useirq); +} + +/*! + * This function sends an address (or partial address) to the + * NAND device. The address is used to select the source/destination for + * a NAND command. + * + * @param addr address to be written to NFC. + * @param useirq True if IRQ should be used rather than polling + */ +static void send_addr(u16 addr, bool useirq) +{ + DEBUG(MTD_DEBUG_LEVEL3, "send_addr(0x%x %d)\n", addr, useirq); + raw_write((addr << NFC_FLASH_ADDR_SHIFT), REG_NFC_FLASH_ADDR); + + ACK_OPS; /* defined only for V3 */ + raw_write(NFC_ADDR, REG_NFC_OPS); + + /* Wait for operation to complete */ + wait_op_done(TROP_US_DELAY, useirq); +} + +/*! + * This function requests the NFC to initate the transfer + * of data currently in the NFC RAM buffer to the NAND device. + * + * @param buf_id Specify Internal RAM Buffer number (0-3) + */ +static void send_prog_page(u8 buf_id) +{ + u32 val = buf_id; + DEBUG(MTD_DEBUG_LEVEL3, "%s\n", __FUNCTION__); + + NFC_SET_RBA(val, RBA_BUFFER0); /* defined only for V3 */ + + /* Set RBA bits for BUFFER val */ + raw_write(val, REG_NFC_SET_RBA); + + ACK_OPS; /* defined only for V3 */ + raw_write(NFC_INPUT, REG_NFC_OPS); + + /* Wait for operation to complete */ + wait_op_done(TROP_US_DELAY, true); +} + +/*! + * This function requests the NFC to initated the transfer + * of data from the NAND device into in the NFC ram buffer. + * + * @param buf_id Specify Internal RAM Buffer number (0-3) + */ +static void send_read_page(u8 buf_id) +{ + u32 val = buf_id; + DEBUG(MTD_DEBUG_LEVEL3, "%s\n", __FUNCTION__); + + NFC_SET_RBA(val, RBA_BUFFER0); /* defined only for V3 */ + /* Set RBA bits for BUFFER val */ + raw_write(val, REG_NFC_SET_RBA); + + ACK_OPS; /* defined only for V3 */ + raw_write(NFC_OUTPUT, REG_NFC_OPS); + + /* Wait for operation to complete */ + wait_op_done(TROP_US_DELAY, true); +} + +/*! + * This function requests the NFC to perform a read of the + * NAND device ID. + */ +static void send_read_id(void) +{ + u32 val = 0; + + /* NFC buffer 0 is used for device ID output */ + /* Set RBA bits for BUFFER0 */ + + NFC_SET_RBA(val, RBA_BUFFER0); /* defined only for V3 */ + raw_write(val, REG_NFC_SET_RBA); + + ACK_OPS; /* defined only for V3 */ + /* Read ID into main buffer */ + raw_write(NFC_ID, REG_NFC_OPS); + + /* Wait for operation to complete */ + wait_op_done(TROP_US_DELAY, true); + +} + +/*! + * This function requests the NFC to perform a read of the + * NAND device status and returns the current status. + * + * @return device status + */ +static u16 get_dev_status(void) +{ + volatile u16 *mainBuf = MAIN_AREA1; + volatile u32 store; + u32 val = 1; + u16 ret; + /* Issue status request to NAND device */ + + /* store the main area1 first word, later do recovery */ + store = *((u32 *) mainBuf); + *(u32 *) mainBuf = 0x0; + + /* + * NFC buffer 1 is used for device status to prevent + * corruption of read/write buffer on status requests. + */ + + /* Set RBA bits for BUFFER1 */ + NFC_SET_RBA(val, RBA_BUFFER1); /* defined only for V3 */ + raw_write(val, REG_NFC_SET_RBA); + + ACK_OPS; /* defined only for V3 */ + /* Read status into main buffer */ + raw_write(NFC_STATUS, REG_NFC_OPS); + + /* Wait for operation to complete */ + wait_op_done(TROP_US_DELAY, true); + + /* Status is placed in first word of main buffer */ + /* get status, then recovery area 1 data */ + ret = mainBuf[0]; + *((u32 *) mainBuf) = store; + return ret; +} + +/*! + * This functions is used by upper layer to checks if device is ready + * + * @param mtd MTD structure for the NAND Flash + * + * @return 0 if device is busy else 1 + */ +static int mxc_nand_dev_ready(struct mtd_info *mtd) +{ + /* + * For V1/V2 NFC this function returns always 1. + */ + if (CHECK_NFC_RB) + return 1; + else + return 0; +} + +static void mxc_nand_enable_hwecc(struct mtd_info *mtd, int mode) +{ + raw_write((raw_read(REG_NFC_ECC_EN) | NFC_ECC_EN), REG_NFC_ECC_EN); + return; +} + +/* + * Function to record the ECC corrected/uncorrected errors resulted + * after a page read. This NFC detects and corrects upto to 4 symbols + * of 9-bits each. + */ +static int mxc_check_ecc_status(struct mtd_info *mtd) +{ + u16 ecc_stat, err; + int no_subpages = 1; + int ret = 0; + + if (IS_2K_PAGE_NAND) { + no_subpages = 4; + } + + ecc_stat = raw_read(REG_NFC_ECC_STATUS_RESULT); + do { + err = ecc_stat & 0x7; + if (err > 0x4) { + return -1; + } else { + ret += err; + } + ecc_stat >>= 4; + } while (--no_subpages); + + return ret; +} + +/* + * Function to correct the detected errors. This NFC corrects all the errors + * detected. So this function is not required. + */ +static int mxc_nand_correct_data(struct mtd_info *mtd, u_char * dat, + u_char * read_ecc, u_char * calc_ecc) +{ + panic("Shouldn't be called here: %d\n", __LINE__); + return 0; //FIXME +} + +/* + * Function to calculate the ECC for the data to be stored in the Nand device. + * This NFC has a hardware RS(511,503) ECC engine together with the RS ECC + * CONTROL blocks are responsible for detection and correction of up to + * 4 symbols of 9 bits each in 528 byte page. + * So this function is not required. + */ + +static int mxc_nand_calculate_ecc(struct mtd_info *mtd, const u_char * dat, + u_char * ecc_code) +{ + panic(KERN_ERR "Shouldn't be called here %d \n", __LINE__); + return 0; //FIXME +} + +/*! + * This function reads byte from the NAND Flash + * + * @param mtd MTD structure for the NAND Flash + * + * @return data read from the NAND Flash + */ +static u_char mxc_nand_read_byte(struct mtd_info *mtd) +{ + u_char retVal = 0; + u16 col, rdWord; + volatile u16 *mainBuf = MAIN_AREA0; + volatile u16 *spareBuf = SPARE_AREA0; + + /* Check for status request */ + if (g_nandfc_info.bStatusRequest) { + return (get_dev_status() & 0xFF); + } + + /* Get column for 16-bit access */ + col = g_nandfc_info.colAddr >> 1; + + /* If we are accessing the spare region */ + if (g_nandfc_info.bSpareOnly) { + rdWord = spareBuf[col]; + } else { + rdWord = mainBuf[col]; + } + + /* Pick upper/lower byte of word from RAM buffer */ + if (g_nandfc_info.colAddr & 0x1) { + retVal = (rdWord >> 8) & 0xFF; + } else { + retVal = rdWord & 0xFF; + } + + /* Update saved column address */ + g_nandfc_info.colAddr++; + + return retVal; +} + +/*! + * This function reads word from the NAND Flash + * + * @param mtd MTD structure for the NAND Flash + * + * @return data read from the NAND Flash + */ +static u16 mxc_nand_read_word(struct mtd_info *mtd) +{ + u16 col, rdWord; + volatile u16 *mainBuf = MAIN_AREA0; + volatile u16 *spareBuf = SPARE_AREA0; + + /* Get column for 16-bit access */ + col = g_nandfc_info.colAddr >> 1; + + /* If we are accessing the spare region */ + if (g_nandfc_info.bSpareOnly) { + rdWord = spareBuf[col]; + } else { + rdWord = mainBuf[col]; + } + + /* Update saved column address */ + g_nandfc_info.colAddr += 2; + + return rdWord; +} + +/*! + * This function reads byte from the NAND Flash + * + * @param mtd MTD structure for the NAND Flash + * + * @return data read from the NAND Flash + */ +static u_char mxc_nand_read_byte16(struct mtd_info *mtd) +{ + /* Check for status request */ + if (g_nandfc_info.bStatusRequest) { + return (get_dev_status() & 0xFF); + } + + return mxc_nand_read_word(mtd) & 0xFF; +} + +/*! + * This function writes data of length \b len from buffer \b buf to the NAND + * internal RAM buffer's MAIN area 0. + * + * @param mtd MTD structure for the NAND Flash + * @param buf data to be written to NAND Flash + * @param len number of bytes to be written + */ +static void mxc_nand_write_buf(struct mtd_info *mtd, + const u_char * buf, int len) +{ + volatile uint32_t *base; + panic("re-work needed\n"); + if (g_nandfc_info.colAddr >= mtd->writesize || g_nandfc_info.bSpareOnly) { + base = (uint32_t *) SPARE_AREA0; + } else { + g_nandfc_info.colAddr += len; + base = (uint32_t *) MAIN_AREA0; + } + memcpy((void *)base, (void *)buf, len); +} + +/*! + * This function id is used to read the data buffer from the NAND Flash. To + * read the data from NAND Flash first the data output cycle is initiated by + * the NFC, which copies the data to RAMbuffer. This data of length \b len is + * then copied to buffer \b buf. + * + * @param mtd MTD structure for the NAND Flash + * @param buf data to be read from NAND Flash + * @param len number of bytes to be read + */ +static void mxc_nand_read_buf(struct mtd_info *mtd, u_char * buf, int len) +{ + volatile uint32_t *base; + + if (g_nandfc_info.colAddr >= mtd->writesize || g_nandfc_info.bSpareOnly) { + base = (uint32_t *) SPARE_AREA0; + } else { + base = (uint32_t *) MAIN_AREA0; + g_nandfc_info.colAddr += len; + } + nfc_memcpy((void *)buf, (void *)base, len); +} + +/*! + * This function is used by the upper layer to verify the data in NAND Flash + * with the data in the \b buf. + * + * @param mtd MTD structure for the NAND Flash + * @param buf data to be verified + * @param len length of the data to be verified + * + * @return -EFAULT if error else 0 + * + */ +static int mxc_nand_verify_buf(struct mtd_info *mtd, const u_char * buf, + int len) +{ + volatile u32 *mainBuf = (u32 *) MAIN_AREA0; + /* check for 32-bit alignment? */ + uint32_t *p = (uint32_t *) buf; + if (IS_2K_PAGE_NAND) + mxc_swap_2k_bi_main_sp(); + for (; len > 0; len -= 4) { + if (*p++ != *mainBuf++) { + return -EFAULT; + } + } + + return 0; +} + +/*! + * This function is used by upper layer for select and deselect of the NAND + * chip + * + * @param mtd MTD structure for the NAND Flash + * @param chip val indicating select or deselect + */ +static void mxc_nand_select_chip(struct mtd_info *mtd, int chip) +{ +#ifdef CONFIG_MTD_NAND_MXC_FORCE_CE + if (chip > 0) { + DEBUG(MTD_DEBUG_LEVEL0, + "ERROR: Illegal chip select (chip = %d)\n", chip); + return; + } + + if (chip == -1) { + raw_write((raw_read(REG_NFC_CE) & ~NFC_CE), REG_NFC_CE); + return; + } + + raw_write((raw_read(REG_NFC_CE) | NFC_CE), REG_NFC_CE); + +#endif + + switch (chip) { + case -1: + /* Disable the NFC clock */ + clk_disable(nfc_clk); + break; + case 0: + /* Enable the NFC clock */ + clk_enable(nfc_clk); + break; + + default: + break; + } +} + +/* + * Function to perform the address cycles. + */ +static void mxc_do_addr_cycle(struct mtd_info *mtd, int column, int page_addr) +{ + u32 page_mask = g_page_mask; + + if (column != -1) { + send_addr(column & 0xFF, false); + if (IS_2K_PAGE_NAND) { + /* another col addr cycle for 2k page */ + send_addr((column >> 8) & 0xF, false); + } + } + if (page_addr != -1) { + do { + send_addr((page_addr & 0xff), false); + page_mask >>= 8; + page_addr >>= 8; + } while (page_mask != 0); + } + +} + +/* + * Function to read a page from nand device. + */ +static void read_full_page(struct mtd_info *mtd, int page_addr) +{ + send_cmd(NAND_CMD_READ0, false); + + mxc_do_addr_cycle(mtd, 0, page_addr); + + if (IS_2K_PAGE_NAND) { + send_cmd(NAND_CMD_READSTART, false); + READ_2K_PAGE; + mxc_swap_2k_bi_main_sp(); + } else { + send_read_page(0); + } +} + +/* + * Function to check if the page read is a clean page.(Valid only + * the first page of the block. + * It is done by checking if all spare data of the page is all 0xFF. + * This is valid even if ECC generated is all 0xFF as the JFFS2 places + * clean marker bytes in the first page of each block which is non 0xFF. + */ +static int is_page_clean(struct mtd_info *mtd) +{ + volatile u32 *p = (u32 *) SPARE_AREA0; + int len; + + /*Check spare page */ + len = mtd->oobsize; + for (; len > 0; len -= 4) { + if (*p++ != 0xFFFFFFFF) { + return 0; + } + } + + return 1; +} + +/*! + * This function is used by the upper layer to write command to NAND Flash for + * different operations to be carried out on NAND Flash + * + * @param mtd MTD structure for the NAND Flash + * @param command command for NAND Flash + * @param column column offset for the page read + * @param page_addr page to be read from NAND Flash + */ +static void mxc_nand_command(struct mtd_info *mtd, unsigned command, + int column, int page_addr) +{ + bool useirq = true; + + DEBUG(MTD_DEBUG_LEVEL3, + "mxc_nand_command (cmd = 0x%x, col = 0x%x, page = 0x%x)\n", + command, column, page_addr); + /* + * Reset command state information + */ + g_nandfc_info.bStatusRequest = false; + + /* Reset column address to 0 */ + g_nandfc_info.colAddr = 0; + + /* + * Command pre-processing step + */ + switch (command) { + case NAND_CMD_STATUS: + g_nandfc_info.bStatusRequest = true; + break; + + case NAND_CMD_READ0: + g_nandfc_info.bSpareOnly = false; + useirq = false; + break; + + case NAND_CMD_READOOB: + g_nandfc_info.colAddr = column; + g_nandfc_info.bSpareOnly = true; + useirq = false; + command = NAND_CMD_READ0; /* only READ0 is valid */ + break; + + case NAND_CMD_SEQIN: + if (column >= mtd->writesize) { + g_nandfc_info.bSpareOnly = true; + mark_oob_data_dirty(page_addr, 1); + } else { + if (is_oob_data_dirty(page_addr)) { + memcpy((void *)SPARE_AREA0, shadow_oob_data, + mtd->oobsize); + } else { + memset((void *)SPARE_AREA0, 0xFF, mtd->oobsize); + } + g_nandfc_info.bSpareOnly = false; + /* Set program pointer to page start */ + send_cmd(NAND_CMD_READ0, false); + } + useirq = false; + break; + + case NAND_CMD_PAGEPROG: + if (!g_nandfc_info.bSpareOnly) { + if (IS_2K_PAGE_NAND) { + PROG_2K_PAGE} else { + send_prog_page(0); + } + } else { + return; + } + break; + + case NAND_CMD_ERASE1: + /*Decide to erase */ + read_full_page(mtd, page_addr); + if (is_page_clean(mtd)) { + mark_oob_data_clean(page_addr); + skip_erase = 1; + return; + } + useirq = false; + break; + case NAND_CMD_ERASE2: + if (skip_erase) { + skip_erase = 0; + return; + } + useirq = false; + break; + } + + /* + * Write out the command to the device. + */ + send_cmd(command, useirq); + + mxc_do_addr_cycle(mtd, column, page_addr); + + /* + * Command post-processing step + */ + switch (command) { + + case NAND_CMD_READOOB: + case NAND_CMD_READ0: + if (IS_2K_PAGE_NAND) { + /* send read confirm command */ + send_cmd(NAND_CMD_READSTART, true); + /* read for each AREA */ + READ_2K_PAGE; + } else { + send_read_page(0); + } + break; + + case NAND_CMD_READID: + send_read_id(); + break; + } +} + +#ifdef CONFIG_MXC_NAND_LOW_LEVEL_ERASE +static void mxc_low_erase(struct mtd_info *mtd) +{ + + struct nand_chip *this = mtd->priv; + unsigned int page_addr, addr; + u_char status; + + DEBUG(MTD_DEBUG_LEVEL0, "MXC_ND : mxc_low_erase:Erasing NAND\n"); + for (addr = 0; addr < this->chipsize; addr += mtd->erasesize) { + page_addr = addr / mtd->writesize; + mxc_nand_command(mtd, NAND_CMD_ERASE1, -1, page_addr); + mxc_nand_command(mtd, NAND_CMD_ERASE2, -1, -1); + mxc_nand_command(mtd, NAND_CMD_STATUS, -1, -1); + status = mxc_nand_read_byte(mtd); + if (status & NAND_STATUS_FAIL) { + printk(KERN_ERR + "ERASE FAILED(block = %d,status = 0x%x)\n", + addr / mtd->erasesize, status); + } + } + +} +#else +#define mxc_low_erase(x) +#endif + +/* Kevin: why do we need this???, + * Yes, to avoid LED event trigger functions which will add code, -Raj*/ + +static int mxc_nand_wait(struct mtd_info *mtd, struct nand_chip *chip) +{ + unsigned long timeo = jiffies; + int status, state = chip->state; + + if (state == FL_ERASING) + timeo += (HZ * 400) / 1000; + else + timeo += (HZ * 20) / 1000; + + send_cmd(NAND_CMD_STATUS, 1); + + while (time_before(jiffies, timeo)) { +#ifdef CONFIG_ARCH_MXC_HAS_NFC_V3 + if (chip->dev_ready) { + if (chip->dev_ready(mtd)) + break; + } else +#endif + { + if (get_dev_status() & NAND_STATUS_READY) + break; + } + cond_resched(); + } + + status = (int)(get_dev_status()); + return status; +} + +static int mxc_nand_read_oob(struct mtd_info *mtd, struct nand_chip *chip, + int page, int sndcmd) +{ + if (scan_done && is_oob_data_dirty(page)) { + memcpy((void *)chip->oob_poi, (void *)shadow_oob_data, + mtd->oobsize); + return 0; + } + + if (sndcmd) { + read_full_page(mtd, page); + sndcmd = 0; + } + + nfc_memcpy((void *)chip->oob_poi, (void *)SPARE_AREA0, mtd->oobsize); + return sndcmd; +} + +static int mxc_nand_write_oob(struct mtd_info *mtd, struct nand_chip *chip, + int page) +{ + int status = 0; + const uint8_t *buf = chip->oob_poi; + int read_oob_col = 0; + volatile uint16_t *p_addr = SPARE_AREA0; + + //FIXME Check for bad block marking + if (0xFF == buf[chip->badblockpos]) { + mark_oob_data_dirty(page, 1); + } else { + send_cmd(NAND_CMD_READ0, false); + send_cmd(NAND_CMD_SEQIN, false); + mxc_do_addr_cycle(mtd, read_oob_col, page); + + memcpy((void *)p_addr, buf, mtd->oobsize); + /* Send command to program the OOB data */ + send_prog_page(0); + send_cmd(NAND_CMD_PAGEPROG, true); + + status = mxc_nand_wait(mtd, chip); + if (status & NAND_STATUS_FAIL) + return -EIO; + } + return 0; +} + +/* + * This function does the trick of swapping the 464th byte in the last RAM + * buffer in the main area with the 0th byte in the spare area. This seems + * to be the optimal way of addressing the NFC imcompatibility problem with + * the NAND flash out of factory in terms of BI field. + * Note: this function only operates on the NFC's internal RAM buffers and + * for 2K page only. + */ +static void mxc_swap_2k_bi_main_sp(void) +{ + u16 tmp1, tmp2, new_tmp1; + + tmp1 = __raw_readw(BAD_BLK_MARKER_464); + tmp2 = __raw_readw(BAD_BLK_MARKER_SP_0); + new_tmp1 = (tmp1 & 0xFF00) | (tmp2 >> 8); + tmp2 = (tmp1 << 8) | (tmp2 & 0xFF); + __raw_writew(new_tmp1, BAD_BLK_MARKER_464); + __raw_writew(tmp2, BAD_BLK_MARKER_SP_0); + +} + +/* Kevin: This is solid but need to optimize the nfc_memcpy */ +static int mxc_nand_read_page(struct mtd_info *mtd, struct nand_chip *chip, + uint8_t * buf) +{ + int stat; + + stat = mxc_check_ecc_status(mtd); + if (stat == -1) { + mtd->ecc_stats.failed++; + printk(KERN_WARNING "UnCorrectable RS-ECC Error\n"); + } else { + mtd->ecc_stats.corrected += stat; + if (stat) + pr_debug("%d Symbol Correctable RS-ECC Error\n", stat); + } + + if (IS_2K_PAGE_NAND) { + mxc_swap_2k_bi_main_sp(); + } + + nfc_memcpy((void *)buf, (void *)MAIN_AREA0, mtd->writesize); + + return 0; +} + +/* Kevin: This is clean and solid */ +static void mxc_nand_write_page(struct mtd_info *mtd, struct nand_chip *chip, + const uint8_t * buf) +{ + memcpy((void *)MAIN_AREA0, buf, mtd->writesize); + + if (IS_2K_PAGE_NAND) { + mxc_swap_2k_bi_main_sp(); + } +} + +/* Define some generic bad / good block scan pattern which are used + * while scanning a device for factory marked good / bad blocks. */ +static uint8_t scan_ff_pattern[] = { 0xff, 0xff }; + +static struct nand_bbt_descr smallpage_memorybased = { + .options = NAND_BBT_SCAN2NDPAGE, + .offs = 5, + .len = 1, + .pattern = scan_ff_pattern +}; + +static struct nand_bbt_descr largepage_memorybased = { + .options = 0, + .offs = 0, + .len = 2, + .pattern = scan_ff_pattern +}; + +static int mxc_nand_scan_bbt(struct mtd_info *mtd) +{ + struct nand_chip *this = mtd->priv; + + /* Do some configurations before scanning */ + page_to_block_shift = this->phys_erase_shift - this->page_shift; + g_page_mask = this->pagemask; + + if (IS_2K_PAGE_NAND) { + NFMS |= (1 << NFMS_NF_PG_SZ); + this->ecc.layout = &nand_hw_eccoob_2k; + shadow_oob_data = oob_data_2k; + } else { + this->ecc.layout = &nand_hw_eccoob_512; + shadow_oob_data = oob_data_512; + } + + /* propagate ecc.layout to mtd_info */ + mtd->ecclayout = this->ecc.layout; + + this->bbt_td = NULL; + this->bbt_md = NULL; + if (!this->badblock_pattern) { + this->badblock_pattern = (mtd->writesize > 512) ? + &largepage_memorybased : &smallpage_memorybased; + } + /* Build bad block table */ + return nand_scan_bbt(mtd, this->badblock_pattern); +} + +/*! + * This function is called during the driver binding process. + * + * @param pdev the device structure used to store device specific + * information that is used by the suspend, resume and + * remove functions + * + * @return The function always returns 0. + */ +static int __init mxcnd_probe(struct platform_device *pdev) +{ + struct nand_chip *this; + struct mtd_info *mtd; + struct flash_platform_data *flash = pdev->dev.platform_data; + int nr_parts = 0, n, err = 0; + + nfc_axi_base = IO_ADDRESS(NFC_AXI_BASE_ADDR); + nfc_ip_base = IO_ADDRESS(NFC_BASE_ADDR); + + /* Resetting NFC */ + raw_write(NFC_RST, REG_NFC_RST); + + /* Allocate memory for MTD device structure and private data */ + mxc_nand_data = kzalloc(sizeof(struct mxc_mtd_s), GFP_KERNEL); + if (!mxc_nand_data) { + printk(KERN_ERR "%s: failed to allocate mtd_info\n", + __FUNCTION__); + err = -ENOMEM; + goto out; + } + + memset((char *)&g_nandfc_info, 0, sizeof(g_nandfc_info)); + + mxc_nand_data->dev = &pdev->dev; + /* structures must be linked */ + this = &mxc_nand_data->nand; + mtd = &mxc_nand_data->mtd; + mtd->priv = this; + mtd->owner = THIS_MODULE; + + /* 5 us command delay time */ + this->chip_delay = 5; + this->priv = mxc_nand_data; + this->dev_ready = mxc_nand_dev_ready; + this->cmdfunc = mxc_nand_command; + this->waitfunc = mxc_nand_wait; + this->select_chip = mxc_nand_select_chip; + this->read_byte = mxc_nand_read_byte; + this->read_word = mxc_nand_read_word; + this->write_buf = mxc_nand_write_buf; + this->read_buf = mxc_nand_read_buf; + this->verify_buf = mxc_nand_verify_buf; + this->scan_bbt = mxc_nand_scan_bbt; + /* NAND bus width determines access funtions used by upper layer */ + if (flash->width == 2) { + this->read_byte = mxc_nand_read_byte16; + this->options |= NAND_BUSWIDTH_16; + NFMS |= (1 << NFMS_NF_DWIDTH); + } + + nfc_clk = clk_get(&pdev->dev, "nfc_clk"); + clk_enable(nfc_clk); /* Enabled here to satisfy following reset command to succeed */ + + /* Disable interrupt */ + raw_write((raw_read(REG_NFC_INTRRUPT) | NFC_INT_MSK), REG_NFC_INTRRUPT); + + init_waitqueue_head(&irq_waitq); + err = request_irq(INT_NANDFC, mxc_nfc_irq, 0, "mxc_nd", NULL); + if (err) { + goto out_1; + } + + if (hardware_ecc) { + this->ecc.read_page = mxc_nand_read_page; + this->ecc.write_page = mxc_nand_write_page; + this->ecc.read_oob = mxc_nand_read_oob; + this->ecc.write_oob = mxc_nand_write_oob; + this->ecc.layout = &nand_hw_eccoob_512; + this->ecc.calculate = mxc_nand_calculate_ecc; + this->ecc.hwctl = mxc_nand_enable_hwecc; + this->ecc.correct = mxc_nand_correct_data; + this->ecc.mode = NAND_ECC_HW; + this->ecc.size = 512; /* RS-ECC is applied for both MAIN+SPARE not MAIN alone */ + this->ecc.bytes = 9; /* used for both main and spare area */ + raw_write((raw_read(REG_NFC_ECC_EN) | NFC_ECC_EN), + REG_NFC_ECC_EN); + } else { + this->ecc.mode = NAND_ECC_SOFT; + raw_write((raw_read(REG_NFC_ECC_EN) & ~NFC_ECC_EN), + REG_NFC_ECC_EN); + } + + raw_write(raw_read(REG_NFC_SP_EN) & ~NFC_SP_EN, REG_NFC_SP_EN); + + /* Reset NAND */ + this->cmdfunc(mtd, NAND_CMD_RESET, -1, -1); + + /* preset operation */ + /* Unlock the internal RAM Buffer */ + raw_write(NFC_SET_BLS(NFC_BLS_UNLCOKED), REG_NFC_BLS); + + /* Blocks to be unlocked */ + /* Start Address = 0X0, End Address = 0xFFFF */ + UNLOCK_ADDR(0x0, 0xFFFF); + + /* Unlock Block Command for given address range */ + raw_write(NFC_SET_WPC(NFC_WPC_UNLOCK), REG_NFC_WPC); + + /* Scan to find existence of the device */ + if (nand_scan(mtd, 1)) { + DEBUG(MTD_DEBUG_LEVEL0, + "MXC_ND: Unable to find any NAND device.\n"); + err = -ENXIO; + goto out_1; + } + scan_done = 1; + + /* Register the partitions */ +#ifdef CONFIG_MTD_PARTITIONS + nr_parts = + parse_mtd_partitions(mtd, part_probes, &mxc_nand_data->parts, 0); + if (nr_parts > 0) + add_mtd_partitions(mtd, mxc_nand_data->parts, nr_parts); + else if (flash->parts) + add_mtd_partitions(mtd, flash->parts, flash->nr_parts); + else +#endif + { + pr_info("Registering %s as whole device\n", mtd->name); + add_mtd_device(mtd); + } + + platform_set_drvdata(pdev, mtd); + + n = mtd->size / mtd->erasesize; + /* each bit is used for one page's dirty information */ + oob_data_shadow_p = (u8 *) kzalloc(n / 8, GFP_KERNEL); + if (!oob_data_shadow_p) { + printk(KERN_ERR "%s: failed to allocate oob_data_shadow_p\n", + __FUNCTION__); + err = -ENOMEM; + goto out; + } + + /* Erase all the blocks of a NAND -- depend on the config */ + mxc_low_erase(mtd); + + return 0; + + out_1: + kfree(mxc_nand_data); + out: + return err; + +} + + /*! + * Dissociates the driver from the device. + * + * @param pdev the device structure used to give information on which + * + * @return The function always returns 0. + */ + +static int __exit mxcnd_remove(struct platform_device *pdev) +{ + struct mtd_info *mtd = platform_get_drvdata(pdev); + + clk_disable(nfc_clk); + clk_put(nfc_clk); + platform_set_drvdata(pdev, NULL); + + if (mxc_nand_data) { + nand_release(mtd); + free_irq(INT_NANDFC, NULL); + kfree(mxc_nand_data); + } + + return 0; +} + +#ifdef CONFIG_PM +/*! + * This function is called to put the NAND in a low power state. Refer to the + * document driver-model/driver.txt in the kernel source tree for more + * information. + * + * @param pdev the device information structure + * + * @param state the power state the device is entering + * + * @return The function returns 0 on success and -1 on failure + */ + +static int mxcnd_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct mtd_info *info = platform_get_drvdata(pdev); + int ret = 0; + + DEBUG(MTD_DEBUG_LEVEL0, "MXC_ND : NAND suspend\n"); + if (info) + ret = info->suspend(info); + + /* Disable the NFC clock */ + clk_disable(nfc_clk); + + return ret; +} + +/*! + * This function is called to bring the NAND back from a low power state. Refer + * to the document driver-model/driver.txt in the kernel source tree for more + * information. + * + * @param pdev the device information structure + * + * @return The function returns 0 on success and -1 on failure + */ +static int mxcnd_resume(struct platform_device *pdev) +{ + struct mtd_info *info = platform_get_drvdata(pdev); + int ret = 0; + + DEBUG(MTD_DEBUG_LEVEL0, "MXC_ND : NAND resume\n"); + /* Enable the NFC clock */ + clk_enable(nfc_clk); + + if (info) { + info->resume(info); + } + + return ret; +} + +#else +#define mxcnd_suspend NULL +#define mxcnd_resume NULL +#endif /* CONFIG_PM */ + +/*! + * This structure contains pointers to the power management callback functions. + */ +static struct platform_driver mxcnd_driver = { + .driver = { + .name = "mxc_nandv2_flash", + }, + .probe = mxcnd_probe, + .remove = __exit_p(mxcnd_remove), + .suspend = mxcnd_suspend, + .resume = mxcnd_resume, +}; + +/*! + * Main initialization routine + * @return 0 if successful; non-zero otherwise + */ +static int __init mxc_nd_init(void) +{ + /* Register the device driver structure. */ + pr_info("MXC MTD nand Driver %s\n", DVR_VER); + if (platform_driver_register(&mxcnd_driver) != 0) { + printk(KERN_ERR "Driver register failed for mxcnd_driver\n"); + return -ENODEV; + } + return 0; +} + +/*! + * Clean up routine + */ +static void __exit mxc_nd_cleanup(void) +{ + /* Unregister the device structure */ + platform_driver_unregister(&mxcnd_driver); +} + +module_init(mxc_nd_init); +module_exit(mxc_nd_cleanup); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MXC NAND MTD driver Version 2-3"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mtd/nand/mxc_nd2.h b/drivers/mtd/nand/mxc_nd2.h new file mode 100644 index 000000000000..174afdee9ebd --- /dev/null +++ b/drivers/mtd/nand/mxc_nd2.h @@ -0,0 +1,281 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mxc_nd.h + * + * @brief This file contains the NAND Flash Controller register information. + * + * + * @ingroup NAND_MTD + */ + +#ifndef __MXC_ND2_H__ +#define __MXC_ND2_H__ + +#include <asm/hardware.h> + +#define IS_2K_PAGE_NAND (mtd->writesize == NAND_PAGESIZE_2KB) +#define NAND_PAGESIZE_2KB NAND_MAX_PAGESIZE + +#ifdef CONFIG_ARCH_MXC_HAS_NFC_V3 +/* + * For V3 NFC registers Definition + */ + +/* AXI Bus Mapped */ +#define NFC_AXI_BASE_ADDR NFC_BASE_ADDR_AXI +#define NFC_FLASH_ADDR_CMD (nfc_axi_base + 0xE00) +#define NFC_CONFIG1 (nfc_axi_base + 0xE04) +#define NFC_ECC_STATUS_RESULT (nfc_axi_base + 0xE08) +#define LAUNCH_NFC (nfc_axi_base + 0xE0C) + +/* IP Bus Mapped */ +#define NFC_WRPROT (nfc_ip_base + 0x00) +#define NFC_WRPROT_UNLOCK_BLK_ADD0 (nfc_ip_base + 0x04) +#define NFC_WRPROT_UNLOCK_BLK_ADD1 (nfc_ip_base + 0x08) +#define NFC_WRPROT_UNLOCK_BLK_ADD2 (nfc_ip_base + 0x0C) +#define NFC_WRPROT_UNLOCK_BLK_ADD3 (nfc_ip_base + 0x10) +#define NFC_CONFIG2 (nfc_ip_base + 0x14) +#define NFC_IPC (nfc_ip_base + 0x18) +#define NFC_AXI_ERR_ADD (nfc_ip_base + 0x1C) + +/*! + * Addresses for NFC RAM BUFFER Main area 0 + */ +#define MAIN_AREA0 (volatile u16 *)IO_ADDRESS(NFC_BASE_ADDR_AXI + 0x000) +#define MAIN_AREA1 (volatile u16 *)IO_ADDRESS(NFC_BASE_ADDR_AXI + 0x200) +#define MAIN_AREA2 (volatile u16 *)IO_ADDRESS(NFC_BASE_ADDR_AXI + 0x400) +#define MAIN_AREA3 (volatile u16 *)IO_ADDRESS(NFC_BASE_ADDR_AXI + 0x600) + +/*! + * Addresses for NFC SPARE BUFFER Spare area 0 + */ +#define SPARE_AREA0 (volatile u16 *)IO_ADDRESS(NFC_BASE_ADDR_AXI + 0x800) +#define SPARE_AREA1 (volatile u16 *)IO_ADDRESS(NFC_BASE_ADDR_AXI + 0x810) +#define SPARE_AREA2 (volatile u16 *)IO_ADDRESS(NFC_BASE_ADDR_AXI + 0x820) +#define SPARE_AREA3 (volatile u16 *)IO_ADDRESS(NFC_BASE_ADDR_AXI + 0x830) + +/* read column 464-465 byte but only 464 for bad block marker */ +#define BAD_BLK_MARKER_464 IO_ADDRESS(NFC_BASE_ADDR_AXI + 0x600 + 464) +/* read column 0-1 byte, but only 1 is used for swapped main area data */ +#define BAD_BLK_MARKER_SP_0 IO_ADDRESS(NFC_BASE_ADDR_AXI + 0x800) + +/*! + * Set 1 to specific operation bit, rest to 0 in LAUNCH_NFC Register for + * Specific operation + */ +#define NFC_CMD 0x1 +#define NFC_ADDR 0x2 +#define NFC_INPUT 0x4 +#define NFC_OUTPUT 0x8 +#define NFC_ID 0x10 +#define NFC_STATUS 0x20 + +/* Bit Definitions */ +#define NFC_OPS_STAT (1 << 31) +#define NFC_INT_MSK (1 << 4) +#define NFC_BIG (1 << 5) +#define NFC_FLASH_ADDR_SHIFT 16 +#define NFC_UNLOCK_END_ADDR_SHIFT 16 +#define RBA_BUFFER0 (0 << 4) +#define RBA_BUFFER1 (1 << 4) +#define RBA_BUFFER2 (2 << 4) +#define RBA_BUFFER3 (3 << 4) +#define RBA_RESET ~(3 << 4) +#define NFC_RB (1 << 29) +#define NFC_ECC_EN (1 << 3) +#define NFC_CE (1 << 1) +#define NFC_RST (1 << 6) +#define NFC_PPB_32 (0 << 7) +#define NFC_PPB_64 (1 << 7) +#define NFC_PPB_128 (2 << 7) +#define NFC_PPB_256 (3 << 7) +#define NFC_PPB_RESET ~(3 << 7) +#define NFC_SP_EN (1) +#define NFC_BLS_LOCKED (0 << 16) +#define NFC_BLS_LOCKED_DEFAULT (1 << 16) +#define NFC_BLS_UNLCOKED (2 << 16) +#define NFC_BLS_RESET ~(3 << 16) +#define NFC_WPC_LOCK_TIGHT (1) +#define NFC_WPC_LOCK (1 << 1) +#define NFC_WPC_UNLOCK (1 << 2) +#define NFC_WPC_RESET ~(7) + +/* NFC Register Mapping */ +#define REG_NFC_OPS_STAT NFC_IPC +#define REG_NFC_INTRRUPT NFC_CONFIG2 +#define REG_NFC_FLASH_ADDR NFC_FLASH_ADDR_CMD +#define REG_NFC_FLASH_CMD NFC_FLASH_ADDR_CMD +#define REG_NFC_OPS LAUNCH_NFC +#define REG_NFC_SET_RBA NFC_CONFIG1 +#define REG_NFC_RB NFC_IPC +#define REG_NFC_ECC_EN NFC_CONFIG2 +#define REG_NFC_ECC_STATUS_RESULT NFC_ECC_STATUS_RESULT +#define REG_NFC_CE NFC_CONFIG1 +#define REG_NFC_RST NFC_CONFIG2 +#define REG_NFC_PPB NFC_CONFIG2 +#define REG_NFC_SP_EN NFC_CONFIG1 +#define REG_NFC_BLS NFC_WRPROT +#define REG_UNLOCK_BLK_ADD0 NFC_WRPROT_UNLOCK_BLK_ADD0 +#define REG_UNLOCK_BLK_ADD1 NFC_WRPROT_UNLOCK_BLK_ADD1 +#define REG_UNLOCK_BLK_ADD2 NFC_WRPROT_UNLOCK_BLK_ADD2 +#define REG_UNLOCK_BLK_ADD3 NFC_WRPROT_UNLOCK_BLK_ADD3 +#define REG_NFC_WPC NFC_WRPROT + +/* NFC V3 Specific MACRO functions definitions */ +#define raw_write(v,a) __raw_writel(v,a) +#define raw_read(a) __raw_readl(a) + +/* Explcit ack ops status (if any), before issue of any command */ +#define ACK_OPS raw_write((raw_read(REG_NFC_OPS_STAT) & ~NFC_OPS_STAT), REG_NFC_OPS_STAT); + +/* NFC buffer 0 to 3 are used for page read/write, starting with buffer0 */ +/* Set RBA bits for BUFFER0 */ +#define NFC_SET_RBA(val, buf_id) \ + val = ((raw_read(REG_NFC_SET_RBA) & RBA_RESET) | buf_id); + +#define UNLOCK_ADDR(start_addr,end_addr) \ + raw_write(start_addr | (end_addr << NFC_UNLOCK_END_ADDR_SHIFT), REG_UNLOCK_BLK_ADD0); + +#define NFC_SET_BLS(val) ((raw_read(REG_NFC_BLS) & NFC_BLS_RESET) | val ) +#define NFC_SET_WPC(val) ((raw_read(REG_NFC_WPC) & NFC_WPC_RESET) | val ) +#define CHECK_NFC_RB raw_read(REG_NFC_RB) & NFC_RB + +#define READ_2K_PAGE send_read_page(0); +#define PROG_2K_PAGE send_prog_page(0); + +#elif CONFIG_ARCH_MXC_HAS_NFC_V2 + +/* + * For V1/V2 NFC registers Definition + */ + +#define NFC_AXI_BASE_ADDR 0x00 +/* + * Addresses for NFC registers + */ +#define NFC_BUF_SIZE (nfc_ip_base + 0xE00) +#define NFC_BUF_ADDR (nfc_ip_base + 0xE04) +#define NFC_FLASH_ADDR (nfc_ip_base + 0xE06) +#define NFC_FLASH_CMD (nfc_ip_base + 0xE08) +#define NFC_CONFIG (nfc_ip_base + 0xE0A) +#define NFC_ECC_STATUS_RESULT (nfc_ip_base + 0xE0C) +#define NFC_RSLTMAIN_AREA (nfc_ip_base + 0xE0E) +#define NFC_RSLTSPARE_AREA (nfc_ip_base + 0xE10) +#define NFC_WRPROT (nfc_ip_base + 0xE12) +#define NFC_UNLOCKSTART_BLKADDR (nfc_ip_base + 0xE14) +#define NFC_UNLOCKEND_BLKADDR (nfc_ip_base + 0xE16) +#define NFC_NF_WRPRST (nfc_ip_base + 0xE18) +#define NFC_CONFIG1 (nfc_ip_base + 0xE1A) +#define NFC_CONFIG2 (nfc_ip_base + 0xE1C) + +/*! + * Addresses for NFC RAM BUFFER Main area 0 + */ +#define MAIN_AREA0 (volatile u16 *)IO_ADDRESS(NFC_BASE_ADDR + 0x000) +#define MAIN_AREA1 (volatile u16 *)IO_ADDRESS(NFC_BASE_ADDR + 0x200) +#define MAIN_AREA2 (volatile u16 *)IO_ADDRESS(NFC_BASE_ADDR + 0x400) +#define MAIN_AREA3 (volatile u16 *)IO_ADDRESS(NFC_BASE_ADDR + 0x600) + +/*! + * Addresses for NFC SPARE BUFFER Spare area 0 + */ +#define SPARE_AREA0 (volatile u16 *)IO_ADDRESS(NFC_BASE_ADDR + 0x800) +#define SPARE_AREA1 (volatile u16 *)IO_ADDRESS(NFC_BASE_ADDR + 0x810) +#define SPARE_AREA2 (volatile u16 *)IO_ADDRESS(NFC_BASE_ADDR + 0x820) +#define SPARE_AREA3 (volatile u16 *)IO_ADDRESS(NFC_BASE_ADDR + 0x830) + +/* read column 464-465 byte but only 464 for bad block marker */ +#define BAD_BLK_MARKER_464 IO_ADDRESS(NFC_BASE_ADDR + 0x600 + 464) +/* read column 0-1 byte, but only 1 is used for swapped main area data */ +#define BAD_BLK_MARKER_SP_0 IO_ADDRESS(NFC_BASE_ADDR + 0x800) + +/*! + * Set INT to 0, Set 1 to specific operation bit, rest to 0 in LAUNCH_NFC Register for + * Specific operation + */ +#define NFC_CMD 0x1 +#define NFC_ADDR 0x2 +#define NFC_INPUT 0x4 +#define NFC_OUTPUT 0x8 +#define NFC_ID 0x10 +#define NFC_STATUS 0x20 + +/* Bit Definitions */ +#define NFC_OPS_STAT (1 << 15) +#define NFC_SP_EN (1 << 2) +#define NFC_ECC_EN (1 << 3) +#define NFC_INT_MSK (1 << 4) +#define NFC_BIG (1 << 5) +#define NFC_RST (1 << 6) +#define NFC_CE (1 << 7) +#define NFC_ONE_CYCLE (1 << 8) +#define NFC_BLS_LOCKED 0 +#define NFC_BLS_LOCKED_DEFAULT 1 +#define NFC_BLS_UNLCOKED 2 +#define NFC_WPC_LOCK_TIGHT (1) +#define NFC_WPC_LOCK (1 << 1) +#define NFC_WPC_UNLOCK (1 << 2) +#define NFC_FLASH_ADDR_SHIFT 0 +#define NFC_UNLOCK_END_ADDR_SHIFT 0 + +/* NFC Register Mapping */ +#define REG_NFC_OPS_STAT NFC_CONFIG2 +#define REG_NFC_INTRRUPT NFC_CONFIG1 +#define REG_NFC_FLASH_ADDR NFC_FLASH_ADDR +#define REG_NFC_FLASH_CMD NFC_FLASH_CMD +#define REG_NFC_OPS NFC_CONFIG2 +#define REG_NFC_SET_RBA NFC_BUF_ADDR +#define REG_NFC_ECC_EN NFC_CONFIG1 +#define REG_NFC_ECC_STATUS_RESULT NFC_ECC_STATUS_RESULT +#define REG_NFC_CE NFC_CONFIG1 +#define REG_NFC_SP_EN NFC_CONFIG1 +#define REG_NFC_BLS NFC_CONFIG +#define REG_NFC_WPC NFC_WRPROT +#define REG_START_BLKADDR NFC_UNLOCKSTART_BLKADDR +#define REG_END_BLKADDR NFC_UNLOCKEND_BLKADDR +#define REG_NFC_RST NFC_CONFIG1 + +/* NFC V1/V2 Specific MACRO functions definitions */ + +#define raw_write(v,a) __raw_writew(v,a) +#define raw_read(a) __raw_readw(a) + +#define NFC_SET_BLS(val) val + +#define UNLOCK_ADDR(start_addr,end_addr) \ + raw_write(start_addr,REG_START_BLKADDR);\ + raw_write(end_addr,REG_END_BLKADDR); + +#define NFC_SET_WPC(val) val + +/* NULL Definitions */ +#define ACK_OPS +#define NFC_SET_RBA(val,buf_id) + +#define READ_2K_PAGE send_read_page(0);\ + send_read_page(1);\ + send_read_page(2);\ + send_read_page(3); + +#define PROG_2K_PAGE send_prog_page(0);\ + send_prog_page(1);\ + send_prog_page(2);\ + send_prog_page(3); + +#define CHECK_NFC_RB 1 + +#endif + +#endif /* MXCND_H */ diff --git a/drivers/mxc/Kconfig b/drivers/mxc/Kconfig new file mode 100644 index 000000000000..f913e03af789 --- /dev/null +++ b/drivers/mxc/Kconfig @@ -0,0 +1,18 @@ +# drivers/video/mxc/Kconfig + +if ARCH_MXC + +menu "MXC support drivers" + +source "drivers/mxc/ipu/Kconfig" +source "drivers/mxc/ssi/Kconfig" +source "drivers/mxc/dam/Kconfig" +source "drivers/mxc/pmic/Kconfig" +source "drivers/mxc/pm/Kconfig" +source "drivers/mxc/security/Kconfig" +source "drivers/mxc/hmp4e/Kconfig" +source "drivers/mxc/vpu/Kconfig" + +endmenu + +endif diff --git a/drivers/mxc/Makefile b/drivers/mxc/Makefile new file mode 100644 index 000000000000..4a7029df5200 --- /dev/null +++ b/drivers/mxc/Makefile @@ -0,0 +1,11 @@ +obj-$(CONFIG_MXC_IPU) += ipu/ +obj-$(CONFIG_MXC_SSI) += ssi/ +obj-$(CONFIG_MXC_DAM) += dam/ + +obj-$(CONFIG_MXC_PMIC) += pmic/ + +obj-$(CONFIG_MXC_DPTC) += pm/ +obj-$(CONFIG_MX27_DPTC) += pm/ +obj-$(CONFIG_MXC_HMP4E) += hmp4e/ +obj-y += security/ +obj-$(CONFIG_MXC_VPU) += vpu/ diff --git a/drivers/mxc/dam/Kconfig b/drivers/mxc/dam/Kconfig new file mode 100644 index 000000000000..7b4bee92f648 --- /dev/null +++ b/drivers/mxc/dam/Kconfig @@ -0,0 +1,13 @@ +# +# DAM API configuration +# + +menu "MXC Digital Audio Multiplexer support" + +config MXC_DAM + tristate "DAM support" + depends on ARCH_MXC + ---help--- + Say Y to get the Digital Audio Multiplexer services API available on MXC platform. + +endmenu diff --git a/drivers/mxc/dam/Makefile b/drivers/mxc/dam/Makefile new file mode 100644 index 000000000000..b5afdc143dfa --- /dev/null +++ b/drivers/mxc/dam/Makefile @@ -0,0 +1,9 @@ +# +# Makefile for the kernel Digital Audio MUX (DAM) device driver. +# + +ifeq ($(CONFIG_ARCH_MX27),y) + obj-$(CONFIG_MXC_DAM) += dam_v1.o +else + obj-$(CONFIG_MXC_DAM) += dam.o +endif diff --git a/drivers/mxc/dam/dam.c b/drivers/mxc/dam/dam.c new file mode 100644 index 000000000000..54c7dbc78625 --- /dev/null +++ b/drivers/mxc/dam/dam.c @@ -0,0 +1,427 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file dam.c + * @brief This is the brief documentation for this dam.c file. + * + * This file contains the implementation of the DAM driver main services + * + * @ingroup DAM + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <asm/uaccess.h> +#include "dam.h" + +/*! + * This include to define bool type, false and true definitions. + */ +#include <asm/hardware.h> + +#define ModifyRegister32(a,b,c) (c = ( ( (c)&(~(a)) ) | (b) )) + +#define DAM_VIRT_BASE_ADDR IO_ADDRESS(AUDMUX_BASE_ADDR) + +#ifndef _reg_DAM_PTCR1 +#define _reg_DAM_PTCR1 (*((volatile unsigned long *) \ + (DAM_VIRT_BASE_ADDR + 0x00))) +#endif + +#ifndef _reg_DAM_PDCR1 +#define _reg_DAM_PDCR1 (*((volatile unsigned long *) \ + (DAM_VIRT_BASE_ADDR + 0x04))) +#endif + +#ifndef _reg_DAM_PTCR2 +#define _reg_DAM_PTCR2 (*((volatile unsigned long *) \ + (DAM_VIRT_BASE_ADDR + 0x08))) +#endif + +#ifndef _reg_DAM_PDCR2 +#define _reg_DAM_PDCR2 (*((volatile unsigned long *) \ + (DAM_VIRT_BASE_ADDR + 0x0C))) +#endif + +#ifndef _reg_DAM_PTCR3 +#define _reg_DAM_PTCR3 (*((volatile unsigned long *) \ + (DAM_VIRT_BASE_ADDR + 0x10))) +#endif + +#ifndef _reg_DAM_PDCR3 +#define _reg_DAM_PDCR3 (*((volatile unsigned long *) \ + (DAM_VIRT_BASE_ADDR + 0x14))) +#endif + +#ifndef _reg_DAM_PTCR4 +#define _reg_DAM_PTCR4 (*((volatile unsigned long *) \ + (DAM_VIRT_BASE_ADDR + 0x18))) +#endif + +#ifndef _reg_DAM_PDCR4 +#define _reg_DAM_PDCR4 (*((volatile unsigned long *) \ + (DAM_VIRT_BASE_ADDR + 0x1C))) +#endif + +#ifndef _reg_DAM_PTCR5 +#define _reg_DAM_PTCR5 (*((volatile unsigned long *) \ + (DAM_VIRT_BASE_ADDR + 0x20))) +#endif + +#ifndef _reg_DAM_PDCR5 +#define _reg_DAM_PDCR5 (*((volatile unsigned long *) \ + (DAM_VIRT_BASE_ADDR + 0x24))) +#endif + +#ifndef _reg_DAM_PTCR6 +#define _reg_DAM_PTCR6 (*((volatile unsigned long *) \ + (DAM_VIRT_BASE_ADDR + 0x28))) +#endif + +#ifndef _reg_DAM_PDCR6 +#define _reg_DAM_PDCR6 (*((volatile unsigned long *) \ + (DAM_VIRT_BASE_ADDR + 0x2C))) +#endif + +#ifndef _reg_DAM_PTCR7 +#define _reg_DAM_PTCR7 (*((volatile unsigned long *) \ + (DAM_VIRT_BASE_ADDR + 0x30))) +#endif + +#ifndef _reg_DAM_PDCR7 +#define _reg_DAM_PDCR7 (*((volatile unsigned long *) \ + (DAM_VIRT_BASE_ADDR + 0x34))) +#endif + +#ifndef _reg_DAM_CNMCR +#define _reg_DAM_CNMCR (*((volatile unsigned long *) \ + (DAM_VIRT_BASE_ADDR + 0x38))) +#endif + +#ifndef _reg_DAM_PTCR +#define _reg_DAM_PTCR(a) (*((volatile unsigned long *) \ + (DAM_VIRT_BASE_ADDR + a*8))) +#endif + +#ifndef _reg_DAM_PDCR +#define _reg_DAM_PDCR(a) (*((volatile unsigned long *) \ + (DAM_VIRT_BASE_ADDR + 4 + a*8))) +#endif + +/*! + * PTCR Registers bit shift definitions + */ +#define dam_synchronous_mode_shift 11 +#define dam_receive_clock_select_shift 12 +#define dam_receive_clock_direction_shift 16 +#define dam_receive_frame_sync_select_shift 17 +#define dam_receive_frame_sync_direction_shift 21 +#define dam_transmit_clock_select_shift 22 +#define dam_transmit_clock_direction_shift 26 +#define dam_transmit_frame_sync_select_shift 27 +#define dam_transmit_frame_sync_direction_shift 31 +#define dam_selection_mask 0xF + +/*! + * HPDCR Register bit shift definitions + */ +#define dam_internal_network_mode_shift 0 +#define dam_mode_shift 8 +#define dam_transmit_receive_switch_shift 12 +#define dam_receive_data_select_shift 13 + +/*! + * HPDCR Register bit masq definitions + */ +#define dam_mode_masq 0x03 +#define dam_internal_network_mode_mask 0xFF + +/*! + * CNMCR Register bit shift definitions + */ +#define dam_ce_bus_port_cntlow_shift 0 +#define dam_ce_bus_port_cnthigh_shift 8 +#define dam_ce_bus_port_clkpol_shift 16 +#define dam_ce_bus_port_fspol_shift 17 +#define dam_ce_bus_port_enable_shift 18 + +#define DAM_NAME "dam" + +EXPORT_SYMBOL(dam_select_mode); +EXPORT_SYMBOL(dam_select_RxClk_direction); +EXPORT_SYMBOL(dam_select_RxClk_source); +EXPORT_SYMBOL(dam_select_RxD_source); +EXPORT_SYMBOL(dam_select_RxFS_direction); +EXPORT_SYMBOL(dam_select_RxFS_source); +EXPORT_SYMBOL(dam_select_TxClk_direction); +EXPORT_SYMBOL(dam_select_TxClk_source); +EXPORT_SYMBOL(dam_select_TxFS_direction); +EXPORT_SYMBOL(dam_select_TxFS_source); +EXPORT_SYMBOL(dam_set_internal_network_mode_mask); +EXPORT_SYMBOL(dam_set_synchronous); +EXPORT_SYMBOL(dam_switch_Tx_Rx); +EXPORT_SYMBOL(dam_reset_register); + +/*! + * This function selects the operation mode of the port. + * + * @param port the DAM port to configure + * @param the_mode the operation mode of the port + * + * @return This function returns the result of the operation + * (0 if successful, -1 otherwise). + */ +int dam_select_mode(dam_port port, dam_mode the_mode) +{ + int result; + result = 0; + + ModifyRegister32(dam_mode_masq << dam_mode_shift, + the_mode << dam_mode_shift, _reg_DAM_PDCR(port)); + + return result; +} + +/*! + * This function controls Receive clock signal direction for the port. + * + * @param port the DAM port to configure + * @param direction the Rx clock signal direction + */ +void dam_select_RxClk_direction(dam_port port, signal_direction direction) +{ + ModifyRegister32(1 << dam_receive_clock_direction_shift, + direction << dam_receive_clock_direction_shift, + _reg_DAM_PTCR(port)); +} + +/*! + * This function controls Receive clock signal source for the port. + * + * @param p_config the DAM port to configure + * @param from_RxClk the signal comes from RxClk or TxClk of + * the source port + * @param p_source the source port + */ +void dam_select_RxClk_source(dam_port p_config, + bool from_RxClk, dam_port p_source) +{ + ModifyRegister32(dam_selection_mask << dam_receive_clock_select_shift, + ((from_RxClk << 3) | p_source) << + dam_receive_clock_select_shift, + _reg_DAM_PTCR(p_config)); +} + +/*! + * This function selects the source port for the RxD data. + * + * @param p_config the DAM port to configure + * @param p_source the source port + */ +void dam_select_RxD_source(dam_port p_config, dam_port p_source) +{ + ModifyRegister32(dam_selection_mask << dam_receive_data_select_shift, + p_source << dam_receive_data_select_shift, + _reg_DAM_PDCR(p_config)); +} + +/*! + * This function controls Receive Frame Sync signal direction for the port. + * + * @param port the DAM port to configure + * @param direction the Rx Frame Sync signal direction + */ +void dam_select_RxFS_direction(dam_port port, signal_direction direction) +{ + ModifyRegister32(1 << dam_receive_frame_sync_direction_shift, + direction << dam_receive_frame_sync_direction_shift, + _reg_DAM_PTCR(port)); +} + +/*! + * This function controls Receive Frame Sync signal source for the port. + * + * @param p_config the DAM port to configure + * @param from_RxFS the signal comes from RxFS or TxFS of + * the source port + * @param p_source the source port + */ +void dam_select_RxFS_source(dam_port p_config, + bool from_RxFS, dam_port p_source) +{ + ModifyRegister32(dam_selection_mask << + dam_receive_frame_sync_select_shift, + ((from_RxFS << 3) | p_source) << + dam_receive_frame_sync_select_shift, + _reg_DAM_PTCR(p_config)); +} + +/*! + * This function controls Transmit clock signal direction for the port. + * + * @param port the DAM port to configure + * @param direction the Tx clock signal direction + */ +void dam_select_TxClk_direction(dam_port port, signal_direction direction) +{ + ModifyRegister32(1 << dam_transmit_clock_direction_shift, + direction << dam_transmit_clock_direction_shift, + _reg_DAM_PTCR(port)); +} + +/*! + * This function controls Transmit clock signal source for the port. + * + * @param p_config the DAM port to configure + * @param from_RxClk the signal comes from RxClk or TxClk of + * the source port + * @param p_source the source port + */ +void dam_select_TxClk_source(dam_port p_config, + bool from_RxClk, dam_port p_source) +{ + ModifyRegister32(dam_selection_mask << dam_transmit_clock_select_shift, + ((from_RxClk << 3) | p_source) << + dam_transmit_clock_select_shift, + _reg_DAM_PTCR(p_config)); +} + +/*! + * This function controls Transmit Frame Sync signal direction for the port. + * + * @param port the DAM port to configure + * @param direction the Tx Frame Sync signal direction + */ +void dam_select_TxFS_direction(dam_port port, signal_direction direction) +{ + ModifyRegister32(1 << dam_transmit_frame_sync_direction_shift, + direction << dam_transmit_frame_sync_direction_shift, + _reg_DAM_PTCR(port)); +} + +/*! + * This function controls Transmit Frame Sync signal source for the port. + * + * @param p_config the DAM port to configure + * @param from_RxFS the signal comes from RxFS or TxFS of + * the source port + * @param p_source the source port + */ +void dam_select_TxFS_source(dam_port p_config, + bool from_RxFS, dam_port p_source) +{ + ModifyRegister32(dam_selection_mask << + dam_transmit_frame_sync_select_shift, + ((from_RxFS << 3) | p_source) << + dam_transmit_frame_sync_select_shift, + _reg_DAM_PTCR(p_config)); +} + +/*! + * This function sets a bit mask that selects the port from which of the RxD + * signals are to be ANDed together for internal network mode. + * Bit 6 represents RxD from Port7 and bit0 represents RxD from Port1. + * 1 excludes RxDn from ANDing. 0 includes RxDn for ANDing. + * + * @param port the DAM port to configure + * @param bit_mask the bit mask + * + * @return This function returns the result of the operation + * (0 if successful, -1 otherwise). + */ +int dam_set_internal_network_mode_mask(dam_port port, unsigned char bit_mask) +{ + int result; + result = 0; + + ModifyRegister32(dam_internal_network_mode_mask << + dam_internal_network_mode_shift, + bit_mask << dam_internal_network_mode_shift, + _reg_DAM_PDCR(port)); + + return result; +} + +/*! + * This function controls whether or not the port is in synchronous mode. + * When the synchronous mode is selected, the receive and the transmit sections + * use common clock and frame sync signals. + * When the synchronous mode is not selected, separate clock and frame sync + * signals are used for the transmit and the receive sections. + * The defaut value is the synchronous mode selected. + * + * @param port the DAM port to configure + * @param synchronous the state to assign + */ +void dam_set_synchronous(dam_port port, bool synchronous) +{ + ModifyRegister32(1 << dam_synchronous_mode_shift, + synchronous << dam_synchronous_mode_shift, + _reg_DAM_PTCR(port)); +} + +/*! + * This function swaps the transmit and receive signals from (Da-TxD, Db-RxD) + * to (Da-RxD, Db-TxD). + * This default signal configuration is Da-TxD, Db-RxD. + * + * @param port the DAM port to configure + * @param value the switch state + */ +void dam_switch_Tx_Rx(dam_port port, bool value) +{ + ModifyRegister32(1 << dam_transmit_receive_switch_shift, + value << dam_transmit_receive_switch_shift, + _reg_DAM_PDCR(port)); +} + +/*! + * This function resets the two registers of the selected port. + * + * @param port the DAM port to reset + */ +void dam_reset_register(dam_port port) +{ + ModifyRegister32(0xFFFFFFFF, 0x00000000, _reg_DAM_PTCR(port)); + ModifyRegister32(0xFFFFFFFF, 0x00000000, _reg_DAM_PDCR(port)); +} + +/*! + * This function implements the init function of the DAM device. + * This function is called when the module is loaded. + * + * @return This function returns 0. + */ +static int __init dam_init(void) +{ + return 0; +} + +/*! + * This function implements the exit function of the SPI device. + * This function is called when the module is unloaded. + * + */ +static void __exit dam_exit(void) +{ +} + +module_init(dam_init); +module_exit(dam_exit); + +MODULE_DESCRIPTION("DAM char device driver"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/dam/dam.h b/drivers/mxc/dam/dam.h new file mode 100644 index 000000000000..cb9ead53f689 --- /dev/null +++ b/drivers/mxc/dam/dam.h @@ -0,0 +1,258 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + + /*! + * @defgroup DAM Digital Audio Multiplexer (AUDMUX) Driver + */ + + /*! + * @file dam.h + * @brief This is the brief documentation for this dam.h file. + * + * This header file contains DAM driver functions prototypes. + * + * @ingroup DAM + */ + +#ifndef __MXC_DAM_H__ +#define __MXC_DAM_H__ + +/*! + * This enumeration describes the Digital Audio Multiplexer mode. + */ +typedef enum { + + /*! + * Normal mode + */ + normal_mode = 0, + + /*! + * Internal network mode + */ + internal_network_mode = 1, + + /*! + * CE bus network mode + */ + CE_bus_network_mode = 2 +} dam_mode; + +/*! + * This enumeration describes the port. + */ +typedef enum { + + /*! + * The port 1 + */ + port_1 = 0, + + /*! + * The port 2 + */ + port_2 = 1, + + /*! + * The port 3 + */ + port_3 = 2, + + /*! + * The port 4 + */ + port_4 = 3, + + /*! + * The port 5 + */ + port_5 = 4, + + /*! + * The port 6 + */ + port_6 = 5, + + /*! + * The port 7 + */ + port_7 = 6 +} dam_port; + +/*! + * This enumeration describes the signal direction. + */ +typedef enum { + + /*! + * Signal In + */ + signal_in = 0, + + /*! + * Signal Out + */ + signal_out = 1 +} signal_direction; + +/*! + * Test purpose definition + */ +#define TEST_DAM 1 + +#ifdef TEST_DAM + +#define DAM_IOCTL 0x55 +#define DAM_CONFIG_SSI1_MC13783 _IOWR(DAM_IOCTL, 1, int) +#define DAM_CONFIG_SSI2_MC13783 _IOWR(DAM_IOCTL, 2, int) +#define DAM_CONFIG_SSI_NETWORK_MODE_MC13783 _IOWR(DAM_IOCTL, 3, int) +#endif + +/*! + * This function selects the operation mode of the port. + * + * @param port the DAM port to configure + * @param the_mode the operation mode of the port + * @return This function returns the result of the operation + * (0 if successful, -1 otherwise). + */ +int dam_select_mode(dam_port port, dam_mode the_mode); + +/*! + * This function controls Receive clock signal direction for the port. + * + * @param port the DAM port to configure + * @param direction the Rx clock signal direction + */ +void dam_select_RxClk_direction(dam_port port, signal_direction direction); + +/*! + * This function controls Receive clock signal source for the port. + * + * @param p_config the DAM port to configure + * @param from_RxClk the signal comes from RxClk or TxClk of + * the source port + * @param p_source the source port + */ +void dam_select_RxClk_source(dam_port p_config, bool from_RxClk, + dam_port p_source); + +/*! + * This function selects the source port for the RxD data. + * + * @param p_config the DAM port to configure + * @param p_source the source port + */ +void dam_select_RxD_source(dam_port p_config, dam_port p_source); + +/*! + * This function controls Receive Frame Sync signal direction for the port. + * + * @param port the DAM port to configure + * @param direction the Rx Frame Sync signal direction + */ +void dam_select_RxFS_direction(dam_port port, signal_direction direction); + +/*! + * This function controls Receive Frame Sync signal source for the port. + * + * @param p_config the DAM port to configure + * @param from_RxFS the signal comes from RxFS or TxFS of + * the source port + * @param p_source the source port + */ +void dam_select_RxFS_source(dam_port p_config, bool from_RxFS, + dam_port p_source); + +/*! + * This function controls Transmit clock signal direction for the port. + * + * @param port the DAM port to configure + * @param direction the Tx clock signal direction + */ +void dam_select_TxClk_direction(dam_port port, signal_direction direction); + +/*! + * This function controls Transmit clock signal source for the port. + * + * @param p_config the DAM port to configure + * @param from_RxClk the signal comes from RxClk or TxClk of + * the source port + * @param p_source the source port + */ +void dam_select_TxClk_source(dam_port p_config, bool from_RxClk, + dam_port p_source); + +/*! + * This function controls Transmit Frame Sync signal direction for the port. + * + * @param port the DAM port to configure + * @param direction the Tx Frame Sync signal direction + */ +void dam_select_TxFS_direction(dam_port port, signal_direction direction); + +/*! + * This function controls Transmit Frame Sync signal source for the port. + * + * @param p_config the DAM port to configure + * @param from_RxFS the signal comes from RxFS or TxFS of + * the source port + * @param p_source the source port + */ +void dam_select_TxFS_source(dam_port p_config, bool from_RxFS, + dam_port p_source); + +/*! + * This function sets a bit mask that selects the port from which of + * the RxD signals are to be ANDed together for internal network mode. + * Bit 6 represents RxD from Port7 and bit0 represents RxD from Port1. + * 1 excludes RxDn from ANDing. 0 includes RxDn for ANDing. + * + * @param port the DAM port to configure + * @param bit_mask the bit mask + * @return This function returns the result of the operation + * (0 if successful, -1 otherwise). + */ +int dam_set_internal_network_mode_mask(dam_port port, unsigned char bit_mask); + +/*! + * This function controls whether or not the port is in synchronous mode. + * When the synchronous mode is selected, the receive and the transmit sections + * use common clock and frame sync signals. + * When the synchronous mode is not selected, separate clock and frame sync + * signals are used for the transmit and the receive sections. + * The defaut value is the synchronous mode selected. + * + * @param port the DAM port to configure + * @param synchronous the state to assign + */ +void dam_set_synchronous(dam_port port, bool synchronous); + +/*! + * This function swaps the transmit and receive signals from (Da-TxD, Db-RxD) to + * (Da-RxD, Db-TxD). + * This default signal configuration is Da-TxD, Db-RxD. + * + * @param port the DAM port to configure + * @param value the switch state + */ +void dam_switch_Tx_Rx(dam_port port, bool value); + +/*! + * This function resets the two registers of the selected port. + * + * @param port the DAM port to reset + */ +void dam_reset_register(dam_port port); + +#endif diff --git a/drivers/mxc/dam/dam_v1.c b/drivers/mxc/dam/dam_v1.c new file mode 100644 index 000000000000..ee5575213e65 --- /dev/null +++ b/drivers/mxc/dam/dam_v1.c @@ -0,0 +1,616 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file dam_v1.c + * @brief This is the brief documentation for this dam_v1.c file. + * + * This file contains the implementation of the DAM driver main services + * + * @ingroup DAM + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/slab.h> +#include <linux/fs.h> +#include <asm/uaccess.h> +#include "dam.h" + +/*! + * This include to define bool type, false and true definitions. + */ +#include <asm/hardware.h> + +#define DAM_VIRT_BASE_ADDR IO_ADDRESS(AUDMUX_BASE_ADDR) + +#define ModifyRegister32(a,b,c) do{\ + __raw_writel( ((__raw_readl(c)) & (~(a))) | (b),(c) );\ +}while(0) + +#ifndef _reg_DAM_HPCR1 +#define _reg_DAM_HPCR1 (*((volatile unsigned long *) \ + (DAM_VIRT_BASE_ADDR + 0x00))) +#endif + +#ifndef _reg_DAM_HPCR2 +#define _reg_DAM_HPCR2 (*((volatile unsigned long *) \ + (DAM_VIRT_BASE_ADDR + 0x04))) +#endif + +#ifndef _reg_DAM_HPCR3 +#define _reg_DAM_HPCR3 (*((volatile unsigned long *) \ + (DAM_VIRT_BASE_ADDR + 0x08))) +#endif + +#ifndef _reg_DAM_PPCR1 +#define _reg_DAM_PPCR1 (*((volatile unsigned long *) \ + (DAM_VIRT_BASE_ADDR + 0x10))) +#endif + +#ifndef _reg_DAM_PPCR2 +#define _reg_DAM_PPCR2 (*((volatile unsigned long *) \ + (DAM_VIRT_BASE_ADDR + 0x14))) +#endif + +#ifndef _reg_DAM_PPCR3 +#define _reg_DAM_PPCR3 (*((volatile unsigned long *) \ + (DAM_VIRT_BASE_ADDR + 0x1c))) +#endif + +#ifndef _reg_DAM_HPCR +#define _reg_DAM_HPCR(a) ((volatile unsigned long *) \ + (DAM_VIRT_BASE_ADDR + (a)*4)) +#endif + +#ifndef _reg_DAM_PPCR +#define _reg_DAM_PPCR(a) ((volatile unsigned long *) \ + (DAM_VIRT_BASE_ADDR + 0x0c + (0x04 << (a-3)) )) +#endif + +/*! + * HPCR/PPCR Registers bit shift definitions + */ +#define dam_transmit_frame_sync_direction_shift 31 +#define dam_transmit_clock_direction_shift 30 +#define dam_transmit_frame_sync_select_shift 26 +#define dam_transmit_clock_select_shift 26 +#define dam_receive_frame_sync_direction_shift 25 +#define dam_receive_clock_direction_shift 24 +#define dam_receive_clock_select_shift 20 +#define dam_receive_frame_sync_select_shift 20 + +#define dam_receive_data_select_shift 13 +#define dam_synchronous_mode_shift 12 + +#define dam_transmit_receive_switch_shift 10 + +#define dam_mode_shift 8 +#define dam_internal_network_mode_shift 0 + +/*! + * HPCR/PPCR Register bit masq definitions + */ +//#define dam_selection_mask 0xF +#define dam_fs_selection_mask 0xF +#define dam_clk_selection_mask 0xF +#define dam_dat_selection_mask 0x7 +//#define dam_mode_masq 0x03 +#define dam_internal_network_mode_mask 0xFF + +/*! + * HPCR/PPCR Register reset value definitions + */ +#define dam_hpcr_default_value 0x00001000 +#define dam_ppcr_default_value 0x00001000 + +#define DAM_NAME "dam" +static struct class *mxc_dam_class; + +EXPORT_SYMBOL(dam_select_mode); +EXPORT_SYMBOL(dam_select_RxClk_direction); +EXPORT_SYMBOL(dam_select_RxClk_source); +EXPORT_SYMBOL(dam_select_RxD_source); +EXPORT_SYMBOL(dam_select_RxFS_direction); +EXPORT_SYMBOL(dam_select_RxFS_source); +EXPORT_SYMBOL(dam_select_TxClk_direction); +EXPORT_SYMBOL(dam_select_TxClk_source); +EXPORT_SYMBOL(dam_select_TxFS_direction); +EXPORT_SYMBOL(dam_select_TxFS_source); +EXPORT_SYMBOL(dam_set_internal_network_mode_mask); +EXPORT_SYMBOL(dam_set_synchronous); +EXPORT_SYMBOL(dam_switch_Tx_Rx); +EXPORT_SYMBOL(dam_reset_register); + +/*! + * DAM major + */ +#ifdef TEST_DAM +static int major_dam; + +typedef struct _mxc_cfg { + int reg; + int val; +} mxc_cfg; + +#endif + +/*! + * This function selects the operation mode of the port. + * + * @param port the DAM port to configure + * @param the_mode the operation mode of the port + * + * @return This function returns the result of the operation + * (0 if successful, -1 otherwise). + */ +int dam_select_mode(dam_port port, dam_mode the_mode) +{ + int result; + result = 0; + + if (port >= 3) + the_mode = normal_mode; + ModifyRegister32(1 << dam_mode_shift, + the_mode << dam_mode_shift, _reg_DAM_HPCR(port)); + + return result; +} + +/*! + * This function controls Receive clock signal direction for the port. + * + * @param port the DAM port to configure + * @param direction the Rx clock signal direction + */ +void dam_select_RxClk_direction(dam_port port, signal_direction direction) +{ + if (port < 3) { + ModifyRegister32(1 << dam_receive_clock_direction_shift, + direction << dam_receive_clock_direction_shift, + _reg_DAM_HPCR(port)); + } else { + ModifyRegister32(1 << dam_receive_clock_direction_shift, + direction << dam_receive_clock_direction_shift, + _reg_DAM_PPCR(port)); + } + return; +} + +/*! + * This function controls Receive clock signal source for the port. + * + * @param p_config the DAM port to configure + * @param from_RxClk the signal comes from RxClk or TxClk of + * the source port + * @param p_source the source port + */ +void dam_select_RxClk_source(dam_port p_config, + bool from_RxClk, dam_port p_source) +{ + if (p_config < 3) { + ModifyRegister32(dam_clk_selection_mask << + dam_receive_clock_select_shift, + ((from_RxClk << 3) | p_source) << + dam_receive_clock_select_shift, + _reg_DAM_HPCR(p_config)); + } else { + ModifyRegister32(dam_clk_selection_mask << + dam_receive_clock_select_shift, + ((from_RxClk << 3) | p_source) << + dam_receive_clock_select_shift, + _reg_DAM_PPCR(p_config)); + } + return; +} + +/*! + * This function selects the source port for the RxD data. + * + * @param p_config the DAM port to configure + * @param p_source the source port + */ +void dam_select_RxD_source(dam_port p_config, dam_port p_source) +{ + if (p_config < 3) { + ModifyRegister32(dam_dat_selection_mask << + dam_receive_data_select_shift, + p_source << dam_receive_data_select_shift, + _reg_DAM_HPCR(p_config)); + } else { + ModifyRegister32(dam_dat_selection_mask << + dam_receive_data_select_shift, + p_source << dam_receive_data_select_shift, + _reg_DAM_PPCR(p_config)); + } + return; +} + +/*! + * This function controls Receive Frame Sync signal direction for the port. + * + * @param port the DAM port to configure + * @param direction the Rx Frame Sync signal direction + */ +void dam_select_RxFS_direction(dam_port port, signal_direction direction) +{ + if (port < 3) { + ModifyRegister32(1 << dam_receive_frame_sync_direction_shift, + direction << + dam_receive_frame_sync_direction_shift, + _reg_DAM_HPCR(port)); + } else { + ModifyRegister32(1 << dam_receive_frame_sync_direction_shift, + direction << + dam_receive_frame_sync_direction_shift, + _reg_DAM_PPCR(port)); + } + return; +} + +/*! + * This function controls Receive Frame Sync signal source for the port. + * + * @param p_config the DAM port to configure + * @param from_RxFS the signal comes from RxFS or TxFS of + * the source port + * @param p_source the source port + */ +void dam_select_RxFS_source(dam_port p_config, + bool from_RxFS, dam_port p_source) +{ + if (p_config < 3) { + ModifyRegister32(dam_fs_selection_mask << + dam_receive_frame_sync_select_shift, + ((from_RxFS << 3) | p_source) << + dam_receive_frame_sync_select_shift, + _reg_DAM_HPCR(p_config)); + } else { + ModifyRegister32(dam_fs_selection_mask << + dam_receive_frame_sync_select_shift, + ((from_RxFS << 3) | p_source) << + dam_receive_frame_sync_select_shift, + _reg_DAM_PPCR(p_config)); + } + return; +} + +/*! + * This function controls Transmit clock signal direction for the port. + * + * @param port the DAM port to configure + * @param direction the Tx clock signal direction + */ +void dam_select_TxClk_direction(dam_port port, signal_direction direction) +{ + if (port < 3) { + ModifyRegister32(1 << dam_transmit_clock_direction_shift, + direction << + dam_transmit_clock_direction_shift, + _reg_DAM_HPCR(port)); + } else { + ModifyRegister32(1 << dam_transmit_clock_direction_shift, + direction << + dam_transmit_clock_direction_shift, + _reg_DAM_PPCR(port)); + } + return; +} + +/*! + * This function controls Transmit clock signal source for the port. + * + * @param p_config the DAM port to configure + * @param from_RxClk the signal comes from RxClk or TxClk of + * the source port + * @param p_source the source port + */ +void dam_select_TxClk_source(dam_port p_config, + bool from_RxClk, dam_port p_source) +{ + if (p_config < 3) { + ModifyRegister32(dam_clk_selection_mask << + dam_transmit_clock_select_shift, + ((from_RxClk << 3) | p_source) << + dam_transmit_clock_select_shift, + _reg_DAM_HPCR(p_config)); + } else { + ModifyRegister32(dam_clk_selection_mask << + dam_transmit_clock_select_shift, + ((from_RxClk << 3) | p_source) << + dam_transmit_clock_select_shift, + _reg_DAM_PPCR(p_config)); + } + return; +} + +/*! + * This function controls Transmit Frame Sync signal direction for the port. + * + * @param port the DAM port to configure + * @param direction the Tx Frame Sync signal direction + */ +void dam_select_TxFS_direction(dam_port port, signal_direction direction) +{ + if (port < 3) { + ModifyRegister32(1 << dam_transmit_frame_sync_direction_shift, + direction << + dam_transmit_frame_sync_direction_shift, + _reg_DAM_HPCR(port)); + } else { + ModifyRegister32(1 << dam_transmit_frame_sync_direction_shift, + direction << + dam_transmit_frame_sync_direction_shift, + _reg_DAM_HPCR(port)); + } + return; +} + +/*! + * This function controls Transmit Frame Sync signal source for the port. + * + * @param p_config the DAM port to configure + * @param from_RxFS the signal comes from RxFS or TxFS of + * the source port + * @param p_source the source port + */ +void dam_select_TxFS_source(dam_port p_config, + bool from_RxFS, dam_port p_source) +{ + if (p_config < 3) { + ModifyRegister32(dam_fs_selection_mask << + dam_transmit_frame_sync_select_shift, + ((from_RxFS << 3) | p_source) << + dam_transmit_frame_sync_select_shift, + _reg_DAM_HPCR(p_config)); + } else { + ModifyRegister32(dam_fs_selection_mask << + dam_transmit_frame_sync_select_shift, + ((from_RxFS << 3) | p_source) << + dam_transmit_frame_sync_select_shift, + _reg_DAM_PPCR(p_config)); + } + return; +} + +/*! + * This function sets a bit mask that selects the port from which of the RxD + * signals are to be ANDed together for internal network mode. + * Bit 6 represents RxD from Port7 and bit0 represents RxD from Port1. + * 1 excludes RxDn from ANDing. 0 includes RxDn for ANDing. + * + * @param port the DAM port to configure + * @param bit_mask the bit mask + * + * @return This function returns the result of the operation + * (0 if successful, -1 otherwise). + */ +int dam_set_internal_network_mode_mask(dam_port port, unsigned char bit_mask) +{ + int result; + result = 0; + + ModifyRegister32(dam_internal_network_mode_mask << + dam_internal_network_mode_shift, + bit_mask << dam_internal_network_mode_shift, + _reg_DAM_HPCR(port)); + return result; +} + +/*! + * This function controls whether or not the port is in synchronous mode. + * When the synchronous mode is selected, the receive and the transmit sections + * use common clock and frame sync signals. + * When the synchronous mode is not selected, separate clock and frame sync + * signals are used for the transmit and the receive sections. + * The defaut value is the synchronous mode selected. + * + * @param port the DAM port to configure + * @param synchronous the state to assign + */ +void dam_set_synchronous(dam_port port, bool synchronous) +{ + if (port < 3) { + ModifyRegister32(1 << dam_synchronous_mode_shift, + synchronous << dam_synchronous_mode_shift, + _reg_DAM_HPCR(port)); + } else { + ModifyRegister32(1 << dam_synchronous_mode_shift, + synchronous << dam_synchronous_mode_shift, + _reg_DAM_PPCR(port)); + } + return; +} + +/*! + * This function swaps the transmit and receive signals from (Da-TxD, Db-RxD) + * to (Da-RxD, Db-TxD). + * This default signal configuration is Da-TxD, Db-RxD. + * + * @param port the DAM port to configure + * @param value the switch state + */ +void dam_switch_Tx_Rx(dam_port port, bool value) +{ + if (port < 3) { + ModifyRegister32(1 << dam_transmit_receive_switch_shift, + value << dam_transmit_receive_switch_shift, + _reg_DAM_HPCR(port)); + } else { + ModifyRegister32(1 << dam_transmit_receive_switch_shift, + value << dam_transmit_receive_switch_shift, + _reg_DAM_PPCR(port)); + } + return; +} + +/*! + * This function resets the two registers of the selected port. + * + * @param port the DAM port to reset + */ +void dam_reset_register(dam_port port) +{ + if (port < 3) { + ModifyRegister32(0xFFFFFFFF, dam_hpcr_default_value, + _reg_DAM_HPCR(port)); + } else { + ModifyRegister32(0xFFFFFFFF, dam_ppcr_default_value, + _reg_DAM_PPCR(port)); + } + return; +} + +#ifdef TEST_DAM + +/*! + * This function implements IOCTL controls on a DAM device. + * + * @param inode pointer on the node + * @param file pointer on the file + * @param cmd the command + * @param arg the parameter :\n + * DAM_CONFIG_SSI1:\n + * data from port 1 to port 4, clock and FS from port 1 (SSI1)\n + * DAM_CONFIG_SSI2:\n + * data from port 2 to port 5, clock and FS from port 2 (SSI2)\n + * DAM_CONFIG_SSI_NETWORK_MODE:\n + * network mode for mix digital with data from port 1 to port4,\n + * data from port 2 to port 4, clock and FS from port 1 (SSI1) + * + * @return This function returns 0 if successful. + */ +static int dam_ioctl(struct inode *inode, + struct file *file, unsigned int cmd, unsigned long arg) +{ + return 0; +} + +/*! + * This function implements the open method on a DAM device. + * + * @param inode pointer on the node + * @param file pointer on the file + * + * @return This function returns 0. + */ +static int dam_open(struct inode *inode, struct file *file) +{ + /* DBG_PRINTK("ssi : dam_open()\n"); */ + return 0; +} + +/*! + * This function implements the release method on a DAM device. + * + * @param inode pointer on the node + * @param file pointer on the file + * + * @return This function returns 0. + */ +static int dam_free(struct inode *inode, struct file *file) +{ + /* DBG_PRINTK("ssi : dam_free()\n"); */ + return 0; +} + +/*! + * This structure defines file operations for a DAM device. + */ +static struct file_operations dam_fops = { + + /*! + * the owner + */ + .owner = THIS_MODULE, + + /*! + * the ioctl operation + */ + .ioctl = dam_ioctl, + + /*! + * the open operation + */ + .open = dam_open, + + /*! + * the release operation + */ + .release = dam_free, +}; + +#endif + +/*! + * This function implements the init function of the DAM device. + * This function is called when the module is loaded. + * + * @return This function returns 0. + */ +static int __init dam_init(void) +{ +#ifdef TEST_DAM + struct class_device *temp_class; + printk(KERN_DEBUG "dam : dam_init(void) \n"); + + major_dam = register_chrdev(0, DAM_NAME, &dam_fops); + if (major_dam < 0) { + printk(KERN_WARNING "Unable to get a major for dam"); + return major_dam; + } + + mxc_dam_class = class_create(THIS_MODULE, DAM_NAME); + if (IS_ERR(mxc_dam_class)) { + goto err_out; + } + + temp_class = class_device_create(mxc_dam_class, NULL, + MKDEV(major_dam, 0), NULL, DAM_NAME); + if (IS_ERR(temp_class)) { + goto err_out; + } +#endif + return 0; + + err_out: + printk(KERN_ERR "Error creating dam class device.\n"); + class_device_destroy(mxc_dam_class, MKDEV(major_dam, 0)); + class_destroy(mxc_dam_class); + unregister_chrdev(major_dam, DAM_NAME); + return -1; +} + +/*! + * This function implements the exit function of the SPI device. + * This function is called when the module is unloaded. + * + */ +static void __exit dam_exit(void) +{ +#ifdef TEST_DAM + class_device_destroy(mxc_dam_class, MKDEV(major_dam, 0)); + class_destroy(mxc_dam_class); + unregister_chrdev(major_dam, DAM_NAME); + printk(KERN_DEBUG "dam : successfully unloaded\n"); +#endif +} + +module_init(dam_init); +module_exit(dam_exit); + +MODULE_DESCRIPTION("DAM char device driver"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/hmp4e/Kconfig b/drivers/mxc/hmp4e/Kconfig new file mode 100644 index 000000000000..fdd7dbc041ba --- /dev/null +++ b/drivers/mxc/hmp4e/Kconfig @@ -0,0 +1,24 @@ +# +# MPEG4 Encoder kernel module configuration +# + +menu "MXC MPEG4 Encoder Kernel module support" + +config MXC_HMP4E + tristate "MPEG4 Encoder support" + depends on ARCH_MXC + depends on !ARCH_MX27 + default y + ---help--- + Say Y to get the MPEG4 Encoder kernel module available on + MXC platform. + +config MXC_HMP4E_DEBUG + bool "MXC MPEG4 Debug messages" + depends on MXC_HMP4E != n + default n + ---help--- + Say Y here if you need the Encoder driver to print debug messages. + This is an option for developers, most people should say N here. + +endmenu diff --git a/drivers/mxc/hmp4e/Makefile b/drivers/mxc/hmp4e/Makefile new file mode 100644 index 000000000000..0efe11fbdbc8 --- /dev/null +++ b/drivers/mxc/hmp4e/Makefile @@ -0,0 +1,8 @@ +# +# Makefile for the MPEG4 Encoder kernel module. + +obj-$(CONFIG_MXC_HMP4E) += mxc_hmp4e.o + +ifeq ($(CONFIG_MXC_HMP4E_DEBUG),y) +EXTRA_CFLAGS += -DDEBUG +endif diff --git a/drivers/mxc/hmp4e/mxc_hmp4e.c b/drivers/mxc/hmp4e/mxc_hmp4e.c new file mode 100644 index 000000000000..459d2f34a3e0 --- /dev/null +++ b/drivers/mxc/hmp4e/mxc_hmp4e.c @@ -0,0 +1,811 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/* + * Encoder device driver (kernel module) + * + * Copyright (C) 2005 Hantro Products Oy. + * + * 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. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> /* __init,__exit directives */ +#include <linux/mm.h> /* remap_page_range / remap_pfn_range */ +#include <linux/fs.h> /* for struct file_operations */ +#include <linux/errno.h> /* standard error codes */ +#include <linux/platform_device.h> /* for device registeration for PM */ +#include <linux/delay.h> /* for msleep_interruptible */ +#include <linux/interrupt.h> +#include <linux/dma-mapping.h> /* for dma_alloc_consistent */ +#include <linux/clk.h> +#include <asm/uaccess.h> /* for ioctl __get_user, __put_user */ +#include "mxc_hmp4e.h" /* MPEG4 encoder specific */ + +/* here's all the must remember stuff */ +typedef struct { + ulong buffaddr; + u32 buffsize; + ulong iobaseaddr; + u32 iosize; + volatile u32 *hwregs; + u32 irq; + u16 hwid_offset; + u16 intr_offset; + u16 busy_offset; + u16 type; /* Encoder type, CIF = 0, VGA = 1 */ + u16 clk_gate; + u16 busy_val; + struct fasync_struct *async_queue; +#ifdef CONFIG_PM + s32 suspend_state; + wait_queue_head_t power_queue; +#endif +} hmp4e_t; + +/* and this is our MAJOR; use 0 for dynamic allocation (recommended)*/ +static s32 hmp4e_major; + +static u32 hmp4e_phys; +static struct class *hmp4e_class; +static hmp4e_t hmp4e_data; + +/*! MPEG4 enc clock handle. */ +static struct clk *hmp4e_clk; + +/* + * avoid "enable_irq(x) unbalanced from ..." + * error messages from the kernel, since {ena,dis}able_irq() + * calls are stacked in kernel. + */ +static bool irq_enable = false; + +ulong base_port = MPEG4_ENC_BASE_ADDR; +u32 irq = INT_MPEG4_ENC; + +module_param(base_port, long, 000); +module_param(irq, int, 000); + +/*! + * These variables store the register values when HMP4E is in suspend mode. + */ +#ifdef CONFIG_PM +u32 io_regs[64]; +#endif + +static s32 hmp4e_map_buffer(struct file *filp, struct vm_area_struct *vma); +static s32 hmp4e_map_hwregs(struct file *filp, struct vm_area_struct *vma); +static void hmp4e_reset(hmp4e_t * dev); +irqreturn_t hmp4e_isr(s32 irq, void *dev_id); + +/*! + * This funtion is called to write h/w register. + * + * @param val value to be written into the register + * @param offset register offset + * + */ +static inline void hmp4e_write(u32 val, u32 offset) +{ + hmp4e_t *dev = &hmp4e_data; + __raw_writel(val, (dev->hwregs + offset)); +} + +/*! + * This funtion is called to read h/w register. + * + * @param offset register offset + * + * @return This function returns the value read from the register. + * + */ +static inline u32 hmp4e_read(u32 offset) +{ + hmp4e_t *dev = &hmp4e_data; + u32 val; + + val = __raw_readl(dev->hwregs + offset); + + return val; +} + +/*! + * The device's mmap method. The VFS has kindly prepared the process's + * vm_area_struct for us, so we examine this to see what was requested. + * + * @param filp pointer to struct file + * @param vma pointer to struct vma_area_struct + * + * @return This function returns 0 if successful or -ve value on error. + * + */ +static s32 hmp4e_mmap(struct file *filp, struct vm_area_struct *vma) +{ + s32 result; + ulong offset = vma->vm_pgoff << PAGE_SHIFT; + + pr_debug("hmp4e_mmap: size = %lu off = 0x%08lx\n", + (unsigned long)(vma->vm_end - vma->vm_start), offset); + + if (offset == 0) { + result = hmp4e_map_buffer(filp, vma); + } else if (offset == hmp4e_data.iobaseaddr) { + result = hmp4e_map_hwregs(filp, vma); + } else { + pr_debug("hmp4e: mmap invalid value\n"); + result = -EINVAL; + } + + return result; +} + +/*! + * This funtion is called to handle ioctls. + * + * @param inode pointer to struct inode + * @param filp pointer to struct file + * @param cmd ioctl command + * @param arg user data + * + * @return This function returns 0 if successful or -ve value on error. + * + */ +static s32 hmp4e_ioctl(struct inode *inode, struct file *filp, + u32 cmd, ulong arg) +{ + s32 err = 0, retval = 0; + ulong offset = 0; + hmp4e_t *dev = &hmp4e_data; + write_t bwrite; + +#ifdef CONFIG_PM + wait_event_interruptible(hmp4e_data.power_queue, + hmp4e_data.suspend_state == 0); +#endif + + /* + * extract the type and number bitfields, and don't decode + * wrong cmds: return ENOTTY (inappropriate ioctl) before access_ok() + */ + if (_IOC_TYPE(cmd) != HMP4E_IOC_MAGIC) { + pr_debug("hmp4e: ioctl invalid magic\n"); + return -ENOTTY; + } + + if (_IOC_NR(cmd) > HMP4E_IOC_MAXNR) { + pr_debug("hmp4e: ioctl exceeds max ioctl\n"); + return -ENOTTY; + } + + /* + * the direction is a bitmask, and VERIFY_WRITE catches R/W + * transfers. `Type' is user-oriented, while + * access_ok is kernel-oriented, so the concept of "read" and + * "write" is reversed + */ + if (_IOC_DIR(cmd) & _IOC_READ) { + err = !access_ok(VERIFY_WRITE, (void *)arg, _IOC_SIZE(cmd)); + } else if (_IOC_DIR(cmd) & _IOC_WRITE) { + err = !access_ok(VERIFY_READ, (void *)arg, _IOC_SIZE(cmd)); + } + + if (err) { + pr_debug("hmp4e: ioctl invalid direction\n"); + return -EFAULT; + } + + switch (cmd) { + case HMP4E_IOCHARDRESET: + break; + + case HMP4E_IOCGBUFBUSADDRESS: + retval = __put_user((ulong) hmp4e_phys, (u32 *) arg); + break; + + case HMP4E_IOCGBUFSIZE: + retval = __put_user(hmp4e_data.buffsize, (u32 *) arg); + break; + + case HMP4E_IOCSREGWRITE: + if (dev->type != 1) { /* This ioctl only for VGA */ + pr_debug("hmp4e: HMP4E_IOCSREGWRITE invalid\n"); + retval = -EINVAL; + break; + } + + retval = __copy_from_user(&bwrite, (u32 *) arg, + sizeof(write_t)); + + if (bwrite.offset <= hmp4e_data.iosize - 4) { + hmp4e_write(bwrite.data, (bwrite.offset / 4)); + } else { + pr_debug("hmp4e: HMP4E_IOCSREGWRITE failed\n"); + retval = -EFAULT; + } + break; + + case HMP4E_IOCXREGREAD: + if (dev->type != 1) { /* This ioctl only for VGA */ + pr_debug("hmp4e: HMP4E_IOCSREGWRITE invalid\n"); + retval = -EINVAL; + break; + } + + retval = __get_user(offset, (ulong *) arg); + if (offset <= hmp4e_data.iosize - 4) { + __put_user(hmp4e_read((offset / 4)), (ulong *) arg); + } else { + pr_debug("hmp4e: HMP4E_IOCXREGREAD failed\n"); + retval = -EFAULT; + } + break; + + case HMP4E_IOCGHWOFFSET: + __put_user(hmp4e_data.iobaseaddr, (ulong *) arg); + break; + + case HMP4E_IOCGHWIOSIZE: + __put_user(hmp4e_data.iosize, (u32 *) arg); + break; + + case HMP4E_IOC_CLI: + if (irq_enable == true) { + disable_irq(hmp4e_data.irq); + irq_enable = false; + } + break; + + case HMP4E_IOC_STI: + if (irq_enable == false) { + enable_irq(hmp4e_data.irq); + irq_enable = true; + } + break; + + default: + pr_debug("unknown case %x\n", cmd); + } + + return retval; +} + +/*! + * This funtion is called when the device is opened. + * + * @param inode pointer to struct inode + * @param filp pointer to struct file + * + * @return This function returns 0 if successful or -ve value on error. + * + */ +static s32 hmp4e_open(struct inode *inode, struct file *filp) +{ + hmp4e_t *dev = &hmp4e_data; + + filp->private_data = (void *)dev; + + if (request_irq(dev->irq, hmp4e_isr, 0, "mxc_hmp4e", dev) != 0) { + pr_debug("hmp4e: request irq failed\n"); + return -EBUSY; + } + + if (irq_enable == false) { + irq_enable = true; + } + clk_enable(hmp4e_clk); + return 0; +} + +static s32 hmp4e_fasync(s32 fd, struct file *filp, s32 mode) +{ + hmp4e_t *dev = (hmp4e_t *) filp->private_data; + return fasync_helper(fd, filp, mode, &dev->async_queue); +} + +/*! + * This funtion is called when the device is closed. + * + * @param inode pointer to struct inode + * @param filp pointer to struct file + * + * @return This function returns 0. + * + */ +static s32 hmp4e_release(struct inode *inode, struct file *filp) +{ + hmp4e_t *dev = (hmp4e_t *) filp->private_data; + + /* this is necessary if user process exited asynchronously */ + if (irq_enable == true) { + disable_irq(dev->irq); + irq_enable = false; + } + + /* reset hardware */ + hmp4e_reset(&hmp4e_data); + + /* free the encoder IRQ */ + free_irq(dev->irq, (void *)dev); + + /* remove this filp from the asynchronusly notified filp's */ + hmp4e_fasync(-1, filp, 0); + clk_disable(hmp4e_clk); + return 0; +} + +/* VFS methods */ +static struct file_operations hmp4e_fops = { + .owner = THIS_MODULE, + .open = hmp4e_open, + .release = hmp4e_release, + .ioctl = hmp4e_ioctl, + .mmap = hmp4e_mmap, + .fasync = hmp4e_fasync, +}; + +/*! + * This funtion allocates physical contigous memory. + * + * @param size size of memory to be allocated + * + * @return This function returns 0 if successful or -ve value on error. + * + */ +static s32 hmp4e_alloc(u32 size) +{ + hmp4e_data.buffsize = PAGE_ALIGN(size); + hmp4e_data.buffaddr = + (ulong) dma_alloc_coherent(NULL, hmp4e_data.buffsize, + (dma_addr_t *) & hmp4e_phys, + GFP_DMA | GFP_KERNEL); + + if (hmp4e_data.buffaddr == 0) { + printk(KERN_ERR "hmp4e: couldn't allocate data buffer\n"); + return -ENOMEM; + } + + memset((s8 *) hmp4e_data.buffaddr, 0, hmp4e_data.buffsize); + return 0; +} + +/*! + * This funtion frees the DMAed memory. + */ +static void hmp4e_free(void) +{ + if (hmp4e_data.buffaddr != 0) { + dma_free_coherent(NULL, hmp4e_data.buffsize, + (void *)hmp4e_data.buffaddr, hmp4e_phys); + hmp4e_data.buffaddr = 0; + } +} + +/*! + * This funtion maps the shared buffer in memory. + * + * @param filp pointer to struct file + * @param vma pointer to struct vm_area_struct + * + * @return This function returns 0 if successful or -ve value on error. + * + */ +static s32 hmp4e_map_buffer(struct file *filp, struct vm_area_struct *vma) +{ + ulong phys; + ulong start = (u32) vma->vm_start; + ulong size = (u32) (vma->vm_end - vma->vm_start); + + /* if userspace tries to mmap beyond end of our buffer, fail */ + if (size > hmp4e_data.buffsize) { + pr_debug("hmp4e: hmp4e_map_buffer, invalid size\n"); + return -EINVAL; + } + + vma->vm_flags |= VM_RESERVED | VM_IO; + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + + phys = hmp4e_phys; + + if (remap_pfn_range(vma, start, phys >> PAGE_SHIFT, size, + vma->vm_page_prot)) { + pr_debug("hmp4e: failed mmapping shared buffer\n"); + return -EAGAIN; + } + + return 0; +} + +/*! + * This funtion maps the h/w register space in memory. + * + * @param filp pointer to struct file + * @param vma pointer to struct vm_area_struct + * + * @return This function returns 0 if successful or -ve value on error. + * + */ +static s32 hmp4e_map_hwregs(struct file *filp, struct vm_area_struct *vma) +{ + ulong phys; + ulong start = (unsigned long)vma->vm_start; + ulong size = (unsigned long)(vma->vm_end - vma->vm_start); + + /* if userspace tries to mmap beyond end of our buffer, fail */ + if (size > PAGE_SIZE) { + pr_debug("hmp4e: hmp4e_map_hwregs, invalid size\n"); + return -EINVAL; + } + + vma->vm_flags |= VM_RESERVED | VM_IO; + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + + /* Remember this won't work for vmalloc()d memory ! */ + phys = hmp4e_data.iobaseaddr; + + if (remap_pfn_range(vma, start, phys >> PAGE_SHIFT, hmp4e_data.iosize, + vma->vm_page_prot)) { + pr_debug("hmp4e: failed mmapping HW registers\n"); + return -EAGAIN; + } + + return 0; +} + +/*! + * This function is the interrupt service routine. + * + * @param irq the irq number + * @param dev_id driver data when ISR was regiatered + * + * @return The return value is IRQ_HANDLED. + * + */ +irqreturn_t hmp4e_isr(s32 irq, void *dev_id) +{ + hmp4e_t *dev = (hmp4e_t *) dev_id; + u32 offset = dev->intr_offset; + + u32 irq_status = hmp4e_read(offset); + + /* clear enc IRQ */ + hmp4e_write(irq_status & (~0x01), offset); + + if (dev->async_queue) + kill_fasync(&dev->async_queue, SIGIO, POLL_IN); + + return IRQ_HANDLED; +} + +/*! + * This function is called to reset the encoder. + * + * @param dev pointer to struct hmp4e_data + * + */ +static void hmp4e_reset(hmp4e_t * dev) +{ + s32 i; + + /* enable HCLK for register reset */ + hmp4e_write(dev->clk_gate, 0); + + /* Reset registers, except ECR0 (0x00) and ID (read-only) */ + for (i = 1; i < (dev->iosize / 4); i += 1) { + if (i == dev->hwid_offset) /* ID is read only */ + continue; + + /* Only for CIF, not used */ + if ((dev->type == 0) && (i == 14)) + continue; + + hmp4e_write(0, i); + } + + /* disable HCLK */ + hmp4e_write(0, 0); + return; +} + +/*! + * This function is called during the driver binding process. This function + * does the hardware initialization. + * + * @param dev the device structure used to store device specific + * information that is used by the suspend, resume and remove + * functions + * + * @return The function returns 0 if successful. + */ +static s32 hmp4e_probe(struct platform_device *pdev) +{ + s32 result; + u32 hwid; + struct class_device *temp_class; + + hmp4e_data.iobaseaddr = base_port; + hmp4e_data.irq = irq; + hmp4e_data.buffaddr = 0; + + /* map hw i/o registers into kernel space */ + hmp4e_data.hwregs = (volatile void *)IO_ADDRESS(hmp4e_data.iobaseaddr); + + hmp4e_clk = clk_get(&pdev->dev, "mpeg4_clk"); + if (IS_ERR(hmp4e_clk)) { + printk(KERN_INFO "hmp4e: Unable to get clock\n"); + return -EIO; + } + + clk_enable(hmp4e_clk); + + /* check hw id for encoder signature */ + hwid = hmp4e_read(7); + if ((hwid & 0xffff) == 0x1882) { /* CIF first */ + hmp4e_data.type = 0; + hmp4e_data.iosize = (16 * 4); + hmp4e_data.hwid_offset = 7; + hmp4e_data.intr_offset = 5; + hmp4e_data.clk_gate = (1 << 1); + hmp4e_data.buffsize = 512000; + hmp4e_data.busy_offset = 0; + hmp4e_data.busy_val = 1; + } else { + hwid = hmp4e_read((0x88 / 4)); + if ((hwid & 0xffff0000) == 0x52510000) { /* VGA */ + hmp4e_data.type = 1; + hmp4e_data.iosize = (35 * 4); + hmp4e_data.hwid_offset = (0x88 / 4); + hmp4e_data.intr_offset = (0x10 / 4); + hmp4e_data.clk_gate = (1 << 12); + hmp4e_data.buffsize = 1048576; + hmp4e_data.busy_offset = (0x10 / 4); + hmp4e_data.busy_val = (1 << 1); + } else { + printk(KERN_INFO "hmp4e: HW ID not found\n"); + goto error1; + } + } + + /* Reset hardware */ + hmp4e_reset(&hmp4e_data); + + /* allocate memory shared with ewl */ + result = hmp4e_alloc(hmp4e_data.buffsize); + if (result < 0) + goto error1; + + result = register_chrdev(hmp4e_major, "hmp4e", &hmp4e_fops); + if (result <= 0) { + pr_debug("hmp4e: unable to get major %d\n", hmp4e_major); + goto error2; + } + + hmp4e_major = result; + + hmp4e_class = class_create(THIS_MODULE, "hmp4e"); + if (IS_ERR(hmp4e_class)) { + pr_debug("Error creating hmp4e class.\n"); + goto error3; + } + + temp_class = class_device_create(hmp4e_class, NULL, + MKDEV(hmp4e_major, 0), NULL, "hmp4e"); + if (IS_ERR(temp_class)) { + pr_debug("Error creating hmp4e class device.\n"); + goto error4; + } + + platform_set_drvdata(pdev, &hmp4e_data); + +#ifdef CONFIG_PM + hmp4e_data.async_queue = NULL; + hmp4e_data.suspend_state = 0; + init_waitqueue_head(&hmp4e_data.power_queue); +#endif + + printk(KERN_INFO "hmp4e: %s encoder initialized\n", + hmp4e_data.type ? "VGA" : "CIF"); + clk_disable(hmp4e_clk); + return 0; + + error4: + class_destroy(hmp4e_class); + error3: + unregister_chrdev(hmp4e_major, "hmp4e"); + error2: + hmp4e_free(); + error1: + clk_disable(hmp4e_clk); + clk_put(hmp4e_clk); + printk(KERN_INFO "hmp4e: module not inserted\n"); + return -EIO; +} + +/*! + * Dissociates the driver. + * + * @param dev the device structure + * + * @return The function always returns 0. + */ +static s32 hmp4e_remove(struct platform_device *pdev) +{ + class_device_destroy(hmp4e_class, MKDEV(hmp4e_major, 0)); + class_destroy(hmp4e_class); + unregister_chrdev(hmp4e_major, "hmp4e"); + hmp4e_free(); + clk_disable(hmp4e_clk); + clk_put(hmp4e_clk); + platform_set_drvdata(pdev, NULL); + return 0; +} + +#ifdef CONFIG_PM +/*! + * This is the suspend of power management for the Hantro MPEG4 module + * + * @param dev the device + * @param state the state + * + * @return This function always returns 0. + */ +static s32 hmp4e_suspend(struct platform_device *pdev, pm_message_t state) +{ + s32 i; + hmp4e_t *pdata = &hmp4e_data; + + /* + * how many times msleep_interruptible will be called before + * giving up + */ + s32 timeout = 10; + + pr_debug("hmp4e: Suspend\n"); + hmp4e_data.suspend_state = 1; + + /* check if encoder is currently running */ + while ((hmp4e_read(pdata->busy_offset) & (pdata->busy_val)) && + --timeout) { + pr_debug("hmp4e: encoder is running, going to sleep\n"); + msleep_interruptible((unsigned int)30); + } + + if (!timeout) { + pr_debug("hmp4e: timeout suspending, resetting encoder\n"); + hmp4e_write(hmp4e_read(pdata->busy_offset) & + (~pdata->busy_val), pdata->busy_offset); + } + + /* first read register 0 */ + io_regs[0] = hmp4e_read(0); + + /* then override HCLK to make sure other registers can be read */ + hmp4e_write(pdata->clk_gate, 0); + + /* read other registers */ + for (i = 1; i < (pdata->iosize / 4); i += 1) { + + /* Only for CIF, not used */ + if ((pdata->type == 0) && (i == 14)) + continue; + + io_regs[i] = hmp4e_read(i); + } + + /* restore value of register 0 */ + hmp4e_write(io_regs[0], 0); + + /* stop HCLK */ + hmp4e_write(0, 0); + clk_disable(hmp4e_clk); + return 0; +}; + +/*! + * This is the resume of power management for the Hantro MPEG4 module + * It suports RESTORE state. + * + * @param pdev the platform device + * + * @return This function always returns 0 + */ +static s32 hmp4e_resume(struct platform_device *pdev) +{ + s32 i; + u32 status; + hmp4e_t *pdata = &hmp4e_data; + + pr_debug("hmp4e: Resume\n"); + clk_enable(hmp4e_clk); + + /* override HCLK to make sure registers can be written */ + hmp4e_write(pdata->clk_gate, 0x00); + + for (i = 1; i < (pdata->iosize / 4); i += 1) { + if (i == pdata->hwid_offset) /* Read only */ + continue; + + /* Only for CIF, not used */ + if ((pdata->type == 0) && (i == 14)) + continue; + + hmp4e_write(io_regs[i], i); + } + + /* write register 0 last */ + hmp4e_write(io_regs[0], 0x00); + + /* Clear the suspend flag */ + hmp4e_data.suspend_state = 0; + + /* Unblock the wait queue */ + wake_up_interruptible(&hmp4e_data.power_queue); + + /* Continue operations */ + status = hmp4e_read(pdata->intr_offset); + if (status & 0x1) { + hmp4e_write(status & (~0x01), pdata->intr_offset); + if (hmp4e_data.async_queue) + kill_fasync(&hmp4e_data.async_queue, SIGIO, POLL_IN); + } + + return 0; +}; + +#endif + +static struct platform_driver hmp4e_driver = { + .driver = { + .name = "mxc_hmp4e", + }, + .probe = hmp4e_probe, + .remove = hmp4e_remove, +#ifdef CONFIG_PM + .suspend = hmp4e_suspend, + .resume = hmp4e_resume, +#endif +}; + +static s32 __init hmp4e_init(void) +{ + printk(KERN_INFO "hmp4e: init\n"); + platform_driver_register(&hmp4e_driver); + return 0; +} + +static void __exit hmp4e_cleanup(void) +{ + platform_driver_unregister(&hmp4e_driver); + printk(KERN_INFO "hmp4e: module removed\n"); +} + +module_init(hmp4e_init); +module_exit(hmp4e_cleanup); + +/* module description */ +MODULE_AUTHOR("Hantro Products Oy"); +MODULE_DESCRIPTION("Device driver for Hantro's hardware based MPEG4 encoder"); +MODULE_SUPPORTED_DEVICE("5251/4251 MPEG4 Encoder"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/hmp4e/mxc_hmp4e.h b/drivers/mxc/hmp4e/mxc_hmp4e.h new file mode 100644 index 000000000000..f58831716346 --- /dev/null +++ b/drivers/mxc/hmp4e/mxc_hmp4e.h @@ -0,0 +1,70 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/* + * Encoder device driver (kernel module headers) + * + * Copyright (C) 2005 Hantro Products Oy. + * + * 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. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ +#ifndef _HMP4ENC_H_ +#define _HMP4ENC_H_ +#include <linux/ioctl.h> /* needed for the _IOW etc stuff used later */ + +/* this is for writing data through ioctl to registers*/ +typedef struct { + unsigned long data; + unsigned long offset; +} write_t; + +/* + * Ioctl definitions + */ + +/* Use 'k' as magic number */ +#define HMP4E_IOC_MAGIC 'k' +/* + * S means "Set" through a ptr, + * T means "Tell" directly with the argument value + * G means "Get": reply by setting through a pointer + * Q means "Query": response is on the return value + * X means "eXchange": G and S atomically + * H means "sHift": T and Q atomically + */ +#define HMP4E_IOCGBUFBUSADDRESS _IOR(HMP4E_IOC_MAGIC, 1, unsigned long *) +#define HMP4E_IOCGBUFSIZE _IOR(HMP4E_IOC_MAGIC, 2, unsigned int *) +#define HMP4E_IOCGHWOFFSET _IOR(HMP4E_IOC_MAGIC, 3, unsigned long *) +#define HMP4E_IOCGHWIOSIZE _IOR(HMP4E_IOC_MAGIC, 4, unsigned int *) +#define HMP4E_IOC_CLI _IO(HMP4E_IOC_MAGIC, 5) +#define HMP4E_IOC_STI _IO(HMP4E_IOC_MAGIC, 6) +#define HMP4E_IOCHARDRESET _IO(HMP4E_IOC_MAGIC, 7) +#define HMP4E_IOCSREGWRITE _IOW(HMP4E_IOC_MAGIC, 8, write_t) +#define HMP4E_IOCXREGREAD _IOWR(HMP4E_IOC_MAGIC, 9, unsigned long) + +#define HMP4E_IOC_MAXNR 9 + +#endif /* !_HMP4ENC_H_ */ diff --git a/drivers/mxc/ipu/Kconfig b/drivers/mxc/ipu/Kconfig new file mode 100644 index 000000000000..c0b61e1db562 --- /dev/null +++ b/drivers/mxc/ipu/Kconfig @@ -0,0 +1,19 @@ +menu "MXC IPU" + +config MXC_IPU + bool "MXC Image Processing Unit" + depends on ARCH_MXC + depends on !ARCH_MX21 + depends on !ARCH_MX27 + help + If you plan to use the Image Processing unit in the MXC, say + Y here. If unsure, select Y. + +config MXC_IPU_LPMC + bool + depends on MXC_IPU + default y if ARCH_MXC91231 + +source "drivers/mxc/ipu/pf/Kconfig" + +endmenu diff --git a/drivers/mxc/ipu/Makefile b/drivers/mxc/ipu/Makefile new file mode 100644 index 000000000000..f94f0eb64dca --- /dev/null +++ b/drivers/mxc/ipu/Makefile @@ -0,0 +1,5 @@ +obj-$(CONFIG_MXC_IPU) += mxc_ipu.o + +mxc_ipu-objs := ipu_common.o ipu_sdc.o ipu_adc.o ipu_ic.o ipu_csi.o ipu_device.o + +obj-$(CONFIG_MXC_IPU_PF) += pf/ diff --git a/drivers/mxc/ipu/ipu_adc.c b/drivers/mxc/ipu/ipu_adc.c new file mode 100644 index 000000000000..85a53688b06e --- /dev/null +++ b/drivers/mxc/ipu/ipu_adc.c @@ -0,0 +1,677 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/* + * @file ipu_adc.c + * + * @brief IPU ADC functions + * + * @ingroup IPU + */ + +#include <linux/types.h> +#include <linux/init.h> +#include <linux/errno.h> +#include <linux/spinlock.h> +#include <linux/delay.h> +#include <asm/io.h> +#include <asm/arch/ipu.h> + +#include "ipu_prv.h" +#include "ipu_regs.h" +#include "ipu_param_mem.h" + +/*#define ADC_CHAN1_SA_MASK 0xFF800000 */ + +static void _ipu_set_cmd_data_mappings(display_port_t disp, + uint32_t pixel_fmt, int ifc_width); + +int32_t _ipu_adc_init_channel(ipu_channel_t chan, display_port_t disp, + mcu_mode_t cmd, int16_t x_pos, int16_t y_pos) +{ + uint32_t reg; + uint32_t start_addr, stride; + unsigned long lock_flags; + uint32_t size; + + size = 0; + + switch (disp) { + case DISP0: + reg = __raw_readl(ADC_DISP0_CONF); + stride = reg & ADC_DISP_CONF_SL_MASK; + break; + case DISP1: + reg = __raw_readl(ADC_DISP1_CONF); + stride = reg & ADC_DISP_CONF_SL_MASK; + break; + case DISP2: + reg = __raw_readl(ADC_DISP2_CONF); + stride = reg & ADC_DISP_CONF_SL_MASK; + break; + default: + return -EINVAL; + } + + if (stride == 0) + return -EINVAL; + + stride++; + start_addr = (y_pos * stride) + x_pos; + + spin_lock_irqsave(&ipu_lock, lock_flags); + reg = __raw_readl(ADC_CONF); + + switch (chan) { + case ADC_SYS1: + reg &= ~0x00FF4000; + reg |= + ((uint32_t) size << 21 | (uint32_t) disp << 19 | (uint32_t) + cmd << 16); + + __raw_writel(start_addr, ADC_SYSCHA1_SA); + break; + + case ADC_SYS2: + reg &= ~0xFF008000; + reg |= + ((uint32_t) size << 29 | (uint32_t) disp << 27 | (uint32_t) + cmd << 24); + + __raw_writel(start_addr, ADC_SYSCHA2_SA); + break; + + case CSI_PRP_VF_ADC: + case MEM_PRP_VF_ADC: + reg &= ~0x000000F9; + reg |= + ((uint32_t) size << 5 | (uint32_t) disp << 3 | + ADC_CONF_PRP_EN); + + __raw_writel(start_addr, ADC_PRPCHAN_SA); + break; + + case MEM_PP_ADC: + reg &= ~0x00003F02; + reg |= + ((uint32_t) size << 10 | (uint32_t) disp << 8 | + ADC_CONF_PP_EN); + + __raw_writel(start_addr, ADC_PPCHAN_SA); + break; + default: + spin_unlock_irqrestore(&ipu_lock, lock_flags); + return -1; + break; + } + __raw_writel(reg, ADC_CONF); + spin_unlock_irqrestore(&ipu_lock, lock_flags); + return 0; +} + +int32_t _ipu_adc_uninit_channel(ipu_channel_t chan) +{ + uint32_t reg; + unsigned long lock_flags; + + spin_lock_irqsave(&ipu_lock, lock_flags); + reg = __raw_readl(ADC_CONF); + + switch (chan) { + case ADC_SYS1: + reg &= ~0x00FF4000; + break; + case ADC_SYS2: + reg &= ~0xFF008000; + break; + case CSI_PRP_VF_ADC: + case MEM_PRP_VF_ADC: + reg &= ~0x000000F9; + break; + case MEM_PP_ADC: + reg &= ~0x00003F02; + break; + default: + spin_unlock_irqrestore(&ipu_lock, lock_flags); + return -1; + break; + } + __raw_writel(reg, ADC_CONF); + spin_unlock_irqrestore(&ipu_lock, lock_flags); + return 0; +} + +int32_t ipu_adc_write_template(display_port_t disp, uint32_t * pCmd, bool write) +{ + uint32_t ima_addr = 0; + uint32_t row_nu; + int i; + + /* Set IPU_IMA_ADDR (IPU Internal Memory Access Address) */ + /* MEM_NU = 0x0001 (CPM) */ + /* ROW_NU = 2*N ( N is channel number) */ + /* WORD_NU = 0 */ + if (write) { + row_nu = (uint32_t) disp *2 * ATM_ADDR_RANGE; + } else { + row_nu = ((uint32_t) disp * 2 + 1) * ATM_ADDR_RANGE; + } + + /* form template addr for IPU_IMA_ADDR */ + ima_addr = (0x3 << 16 /*Template memory */ | row_nu << 3); + + __raw_writel(ima_addr, IPU_IMA_ADDR); + + /* write template data for IPU_IMA_DATA */ + for (i = 0; i < TEMPLATE_BUF_SIZE; i++) + /* only DATA field are needed */ + __raw_writel(pCmd[i], IPU_IMA_DATA); + + return 0; +} + +int32_t +ipu_adc_write_cmd(display_port_t disp, cmddata_t type, + uint32_t cmd, const uint32_t * params, uint16_t numParams) +{ + uint16_t i; + int disable_di = 0; + u32 reg; + unsigned long lock_flags; + + spin_lock_irqsave(&ipu_lock, lock_flags); + reg = __raw_readl(IPU_CONF); + if ((reg & IPU_CONF_DI_EN) == 0) { + disable_di = 1; + reg |= IPU_CONF_DI_EN; + __raw_writel(reg, IPU_CONF); + } + spin_unlock_irqrestore(&ipu_lock, lock_flags); + + __raw_writel((uint32_t) ((type ? 0x0 : 0x1) | disp << 1 | 0x10), + DI_DISP_LLA_CONF); + __raw_writel(cmd, DI_DISP_LLA_DATA); + udelay(3); + + __raw_writel((uint32_t) (0x10 | disp << 1 | 0x11), DI_DISP_LLA_CONF); + for (i = 0; i < numParams; i++) { + __raw_writel(params[i], DI_DISP_LLA_DATA); + udelay(3); + } + + if (disable_di) { + spin_lock_irqsave(&ipu_lock, lock_flags); + reg = __raw_readl(IPU_CONF); + reg &= ~IPU_CONF_DI_EN; + __raw_writel(reg, IPU_CONF); + spin_unlock_irqrestore(&ipu_lock, lock_flags); + } + + return 0; +} + +int32_t ipu_adc_set_update_mode(ipu_channel_t channel, + ipu_adc_update_mode_t mode, + uint32_t refresh_rate, unsigned long addr, + uint32_t * size) +{ + int32_t err = 0; + uint32_t ref_per, reg, src = 0; + unsigned long lock_flags; + uint32_t ipu_freq; + + ipu_freq = clk_get_rate(g_ipu_clk); + + spin_lock_irqsave(&ipu_lock, lock_flags); + + reg = __raw_readl(IPU_FS_DISP_FLOW); + reg &= ~FS_AUTO_REF_PER_MASK; + switch (mode) { + case IPU_ADC_REFRESH_NONE: + src = 0; + break; + case IPU_ADC_AUTO_REFRESH: + if (refresh_rate == 0) { + err = -EINVAL; + goto err0; + } + ref_per = ipu_freq / ((1UL << 17) * refresh_rate); + ref_per--; + reg |= ref_per << FS_AUTO_REF_PER_OFFSET; + + src = FS_SRC_AUTOREF; + break; + case IPU_ADC_AUTO_REFRESH_SNOOP: + if (refresh_rate == 0) { + err = -EINVAL; + goto err0; + } + ref_per = ipu_freq / ((1UL << 17) * refresh_rate); + ref_per--; + reg |= ref_per << FS_AUTO_REF_PER_OFFSET; + + src = FS_SRC_AUTOREF_SNOOP; + break; + case IPU_ADC_SNOOPING: + src = FS_SRC_SNOOP; + break; + } + + switch (channel) { + case ADC_SYS1: + reg &= ~FS_ADC1_SRC_SEL_MASK; + reg |= src << FS_ADC1_SRC_SEL_OFFSET; + break; + case ADC_SYS2: + reg &= ~FS_ADC2_SRC_SEL_MASK; + reg |= src << FS_ADC2_SRC_SEL_OFFSET; + break; + default: + spin_unlock_irqrestore(&ipu_lock, lock_flags); + return -EINVAL; + } + __raw_writel(reg, IPU_FS_DISP_FLOW); + + /* Setup bus snooping */ + if ((mode == IPU_ADC_AUTO_REFRESH_SNOOP) || (mode == IPU_ADC_SNOOPING)) { + err = mxc_snoop_set_config(0, addr, *size); + if (err > 0) { + *size = err; + err = 0; + } + } else { + mxc_snoop_set_config(0, 0, 0); + } + + err0: + spin_unlock_irqrestore(&ipu_lock, lock_flags); + return err; +} + +int32_t ipu_adc_get_snooping_status(uint32_t * statl, uint32_t * stath) +{ + return mxc_snoop_get_status(0, statl, stath); +} + +int32_t ipu_adc_init_panel(display_port_t disp, + uint16_t width, uint16_t height, + uint32_t pixel_fmt, + uint32_t stride, + ipu_adc_sig_cfg_t sig, + display_addressing_t addr, + uint32_t vsync_width, vsync_t mode) +{ + uint32_t temp; + unsigned long lock_flags; + uint32_t ser_conf; + uint32_t disp_conf; + uint32_t adc_disp_conf; + uint32_t adc_disp_vsync; + uint32_t old_pol; + + if ((disp != DISP1) && (disp != DISP2) && + (sig.ifc_mode >= IPU_ADC_IFC_MODE_3WIRE_SERIAL)) { + return -EINVAL; + } +/* adc_disp_conf = ((uint32_t)((((size == 3)||(size == 2))?1:0)<<14) | */ +/* (uint32_t)addr<<12 | (stride-1)); */ + adc_disp_conf = (uint32_t) addr << 12 | (stride - 1); + + _ipu_set_cmd_data_mappings(disp, pixel_fmt, sig.ifc_width); + + spin_lock_irqsave(&ipu_lock, lock_flags); + disp_conf = __raw_readl(DI_DISP_IF_CONF); + old_pol = __raw_readl(DI_DISP_SIG_POL); + adc_disp_vsync = __raw_readl(ADC_DISP_VSYNC); + + switch (disp) { + case DISP0: + __raw_writel(adc_disp_conf, ADC_DISP0_CONF); + __raw_writel((((height - 1) << 16) | (width - 1)), + ADC_DISP0_SS); + + adc_disp_vsync &= ~(ADC_DISP_VSYNC_D0_MODE_MASK | + ADC_DISP_VSYNC_D0_WIDTH_MASK); + adc_disp_vsync |= (vsync_width << 16) | (uint32_t) mode; + + old_pol &= ~0x2000003FL; + old_pol |= sig.data_pol | sig.cs_pol << 1 | + sig.addr_pol << 2 | sig.read_pol << 3 | + sig.write_pol << 4 | sig.Vsync_pol << 5 | + sig.burst_pol << 29; + __raw_writel(old_pol, DI_DISP_SIG_POL); + + disp_conf &= ~0x0000001FL; + disp_conf |= (sig.burst_mode << 3) | (sig.ifc_mode << 1) | + DI_CONF_DISP0_EN; + __raw_writel(disp_conf, DI_DISP_IF_CONF); + break; + case DISP1: + __raw_writel(adc_disp_conf, ADC_DISP1_CONF); + __raw_writel((((height - 1) << 16) | (width - 1)), + ADC_DISP12_SS); + + adc_disp_vsync &= ~(ADC_DISP_VSYNC_D12_MODE_MASK | + ADC_DISP_VSYNC_D12_WIDTH_MASK); + adc_disp_vsync |= (vsync_width << 16) | (uint32_t) mode; + + old_pol &= ~0x4000FF00L; + old_pol |= (sig.Vsync_pol << 6 | sig.data_pol << 8 | + sig.cs_pol << 9 | sig.addr_pol << 10 | + sig.read_pol << 11 | sig.write_pol << 12 | + sig.clk_pol << 14 | sig.burst_pol << 30); + __raw_writel(old_pol, DI_DISP_SIG_POL); + + disp_conf &= ~0x00003F00L; + if (sig.ifc_mode >= IPU_ADC_IFC_MODE_3WIRE_SERIAL) { + ser_conf = (sig.ifc_width - 1) << + DI_SER_DISPx_CONF_SER_BIT_NUM_OFFSET; + if (sig.ser_preamble_len) { + ser_conf |= DI_SER_DISPx_CONF_PREAMBLE_EN; + ser_conf |= sig.ser_preamble << + DI_SER_DISPx_CONF_PREAMBLE_OFFSET; + ser_conf |= (sig.ser_preamble_len - 1) << + DI_SER_DISPx_CONF_PREAMBLE_LEN_OFFSET; + } + + ser_conf |= + sig.ser_rw_mode << DI_SER_DISPx_CONF_RW_CFG_OFFSET; + + if (sig.burst_mode == IPU_ADC_BURST_SERIAL) + ser_conf |= DI_SER_DISPx_CONF_BURST_MODE_EN; + __raw_writel(ser_conf, DI_SER_DISP1_CONF); + } else { /* parallel interface */ + disp_conf |= (uint32_t) (sig.burst_mode << 12); + } + disp_conf |= (sig.ifc_mode << 9) | DI_CONF_DISP1_EN; + __raw_writel(disp_conf, DI_DISP_IF_CONF); + break; + case DISP2: + __raw_writel(adc_disp_conf, ADC_DISP2_CONF); + __raw_writel((((height - 1) << 16) | (width - 1)), + ADC_DISP12_SS); + + adc_disp_vsync &= ~(ADC_DISP_VSYNC_D12_MODE_MASK | + ADC_DISP_VSYNC_D12_WIDTH_MASK); + adc_disp_vsync |= (vsync_width << 16) | (uint32_t) mode; + + old_pol &= ~0x80FF0000L; + temp = (uint32_t) (sig.data_pol << 16 | sig.cs_pol << 17 | + sig.addr_pol << 18 | sig.read_pol << 19 | + sig.write_pol << 20 | sig.Vsync_pol << 6 | + sig.burst_pol << 31 | sig.clk_pol << 22); + __raw_writel(temp | old_pol, DI_DISP_SIG_POL); + + disp_conf &= ~0x003F0000L; + if (sig.ifc_mode >= IPU_ADC_IFC_MODE_3WIRE_SERIAL) { + ser_conf = (sig.ifc_width - 1) << + DI_SER_DISPx_CONF_SER_BIT_NUM_OFFSET; + if (sig.ser_preamble_len) { + ser_conf |= DI_SER_DISPx_CONF_PREAMBLE_EN; + ser_conf |= sig.ser_preamble << + DI_SER_DISPx_CONF_PREAMBLE_OFFSET; + ser_conf |= (sig.ser_preamble_len - 1) << + DI_SER_DISPx_CONF_PREAMBLE_LEN_OFFSET; + + } + + ser_conf |= + sig.ser_rw_mode << DI_SER_DISPx_CONF_RW_CFG_OFFSET; + + if (sig.burst_mode == IPU_ADC_BURST_SERIAL) + ser_conf |= DI_SER_DISPx_CONF_BURST_MODE_EN; + __raw_writel(ser_conf, DI_SER_DISP2_CONF); + } else { /* parallel interface */ + disp_conf |= (uint32_t) (sig.burst_mode << 20); + } + disp_conf |= (sig.ifc_mode << 17) | DI_CONF_DISP2_EN; + __raw_writel(disp_conf, DI_DISP_IF_CONF); + break; + default: + break; + } + + __raw_writel(adc_disp_vsync, ADC_DISP_VSYNC); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + + return 0; +} + +int32_t ipu_adc_init_ifc_timing(display_port_t disp, bool read, + uint32_t cycle_time, + uint32_t up_time, + uint32_t down_time, + uint32_t read_latch_time, uint32_t pixel_clk) +{ + uint32_t reg; + uint32_t time_conf3 = 0; + uint32_t clk_per; + uint32_t up_per; + uint32_t down_per; + uint32_t read_per; + uint32_t pixclk_per = 0; + uint32_t ipu_freq; + + ipu_freq = clk_get_rate(g_ipu_clk); + + clk_per = (cycle_time * (ipu_freq / 1000L) * 16L) / 1000000L; + up_per = (up_time * (ipu_freq / 1000L) * 4L) / 1000000L; + down_per = (down_time * (ipu_freq / 1000L) * 4L) / 1000000L; + + reg = (clk_per << DISPx_IF_CLK_PER_OFFSET) | + (up_per << DISPx_IF_CLK_UP_OFFSET) | + (down_per << DISPx_IF_CLK_DOWN_OFFSET); + + if (read) { + read_per = + (read_latch_time * (ipu_freq / 1000L) * 4L) / 1000000L; + if (pixel_clk) + pixclk_per = (ipu_freq * 16L) / pixel_clk; + time_conf3 = (read_per << DISPx_IF_CLK_READ_EN_OFFSET) | + (pixclk_per << DISPx_PIX_CLK_PER_OFFSET); + } + + dev_dbg(g_ipu_dev, "DI_DISPx_TIME_CONF_1/2 = 0x%08X\n", reg); + dev_dbg(g_ipu_dev, "DI_DISPx_TIME_CONF_3 = 0x%08X\n", time_conf3); + + switch (disp) { + case DISP0: + if (read) { + __raw_writel(reg, DI_DISP0_TIME_CONF_2); + __raw_writel(time_conf3, DI_DISP0_TIME_CONF_3); + } else { + __raw_writel(reg, DI_DISP0_TIME_CONF_1); + } + break; + case DISP1: + if (read) { + __raw_writel(reg, DI_DISP1_TIME_CONF_2); + __raw_writel(time_conf3, DI_DISP1_TIME_CONF_3); + } else { + __raw_writel(reg, DI_DISP1_TIME_CONF_1); + } + break; + case DISP2: + if (read) { + __raw_writel(reg, DI_DISP2_TIME_CONF_2); + __raw_writel(time_conf3, DI_DISP2_TIME_CONF_3); + } else { + __raw_writel(reg, DI_DISP2_TIME_CONF_1); + } + break; + default: + return -EINVAL; + break; + } + + return 0; +} + +struct ipu_adc_di_map { + uint32_t map_byte1; + uint32_t map_byte2; + uint32_t map_byte3; + uint32_t cycle_cnt; +}; + +static const struct ipu_adc_di_map di_mappings[] = { + [0] = { + /* RGB888, 8-bit bus */ + .map_byte1 = 0x1600AAAA, + .map_byte2 = 0x00E05555, + .map_byte2 = 0x00070000, + .cycle_cnt = 3, + }, + [1] = { + /* RGB666, 8-bit bus */ + .map_byte1 = 0x1C00AAAF, + .map_byte2 = 0x00E0555F, + .map_byte3 = 0x0007000F, + .cycle_cnt = 3, + }, + [2] = { + /* RGB565, 8-bit bus */ + .map_byte1 = 0x008055BF, + .map_byte2 = 0x0142015F, + .map_byte3 = 0x0007003F, + .cycle_cnt = 2, + }, + [3] = { + /* RGB888, 24-bit bus */ + .map_byte1 = 0x0007000F, + .map_byte2 = 0x000F000F, + .map_byte3 = 0x0017000F, + .cycle_cnt = 1, + }, + [4] = { + /* RGB666, 18-bit bus */ + .map_byte1 = 0x0005000F, + .map_byte2 = 0x000B000F, + .map_byte3 = 0x0011000F, + .cycle_cnt = 1, + }, + [5] = { + /* RGB565, 16-bit bus */ + .map_byte1 = 0x0004003F, + .map_byte2 = 0x000A000F, + .map_byte3 = 0x000F003F, + .cycle_cnt = 1, + }, +}; + +/* Private methods */ +static void _ipu_set_cmd_data_mappings(display_port_t disp, + uint32_t pixel_fmt, int ifc_width) +{ + uint32_t reg; + u32 map = 0; + + if (ifc_width == 8) { + switch (pixel_fmt) { + case IPU_PIX_FMT_BGR24: + map = 0; + break; + case IPU_PIX_FMT_RGB666: + map = 1; + break; + case IPU_PIX_FMT_RGB565: + map = 2; + break; + default: + break; + } + } else if (ifc_width >= 16) { + switch (pixel_fmt) { + case IPU_PIX_FMT_BGR24: + map = 3; + break; + case IPU_PIX_FMT_RGB666: + map = 4; + break; + case IPU_PIX_FMT_RGB565: + map = 5; + break; + default: + break; + } + } + + switch (disp) { + case DISP0: + if (ifc_width == 8) { + __raw_writel(0x00070000, DI_DISP0_CB0_MAP); + __raw_writel(0x0000FFFF, DI_DISP0_CB1_MAP); + __raw_writel(0x0000FFFF, DI_DISP0_CB2_MAP); + } else { + __raw_writel(0x00070000, DI_DISP0_CB0_MAP); + __raw_writel(0x000F0000, DI_DISP0_CB1_MAP); + __raw_writel(0x0000FFFF, DI_DISP0_CB2_MAP); + } + __raw_writel(di_mappings[map].map_byte1, DI_DISP0_DB0_MAP); + __raw_writel(di_mappings[map].map_byte2, DI_DISP0_DB1_MAP); + __raw_writel(di_mappings[map].map_byte3, DI_DISP0_DB2_MAP); + reg = __raw_readl(DI_DISP_ACC_CC); + reg &= ~DISP0_IF_CLK_CNT_D_MASK; + reg |= (di_mappings[map].cycle_cnt - 1) << + DISP0_IF_CLK_CNT_D_OFFSET; + __raw_writel(reg, DI_DISP_ACC_CC); + break; + case DISP1: + if (ifc_width == 8) { + __raw_writel(0x00070000, DI_DISP1_CB0_MAP); + __raw_writel(0x0000FFFF, DI_DISP1_CB1_MAP); + __raw_writel(0x0000FFFF, DI_DISP1_CB2_MAP); + } else { + __raw_writel(0x00070000, DI_DISP1_CB0_MAP); + __raw_writel(0x000F0000, DI_DISP1_CB1_MAP); + __raw_writel(0x0000FFFF, DI_DISP1_CB2_MAP); + } + __raw_writel(di_mappings[map].map_byte1, DI_DISP1_DB0_MAP); + __raw_writel(di_mappings[map].map_byte2, DI_DISP1_DB1_MAP); + __raw_writel(di_mappings[map].map_byte3, DI_DISP1_DB2_MAP); + reg = __raw_readl(DI_DISP_ACC_CC); + reg &= ~DISP1_IF_CLK_CNT_D_MASK; + reg |= (di_mappings[map].cycle_cnt - 1) << + DISP1_IF_CLK_CNT_D_OFFSET; + __raw_writel(reg, DI_DISP_ACC_CC); + break; + case DISP2: + if (ifc_width == 8) { + __raw_writel(0x00070000, DI_DISP2_CB0_MAP); + __raw_writel(0x0000FFFF, DI_DISP2_CB1_MAP); + __raw_writel(0x0000FFFF, DI_DISP2_CB2_MAP); + } else { + __raw_writel(0x00070000, DI_DISP2_CB0_MAP); + __raw_writel(0x000F0000, DI_DISP2_CB1_MAP); + __raw_writel(0x0000FFFF, DI_DISP2_CB2_MAP); + } + __raw_writel(di_mappings[map].map_byte1, DI_DISP2_DB0_MAP); + __raw_writel(di_mappings[map].map_byte2, DI_DISP2_DB1_MAP); + __raw_writel(di_mappings[map].map_byte3, DI_DISP2_DB2_MAP); + reg = __raw_readl(DI_DISP_ACC_CC); + reg &= ~DISP2_IF_CLK_CNT_D_MASK; + reg |= (di_mappings[map].cycle_cnt - 1) << + DISP2_IF_CLK_CNT_D_OFFSET; + __raw_writel(reg, DI_DISP_ACC_CC); + break; + default: + break; + } +} + +EXPORT_SYMBOL(ipu_adc_write_template); +EXPORT_SYMBOL(ipu_adc_write_cmd); +EXPORT_SYMBOL(ipu_adc_set_update_mode); +EXPORT_SYMBOL(ipu_adc_init_panel); +EXPORT_SYMBOL(ipu_adc_init_ifc_timing); diff --git a/drivers/mxc/ipu/ipu_common.c b/drivers/mxc/ipu/ipu_common.c new file mode 100644 index 000000000000..8de829573540 --- /dev/null +++ b/drivers/mxc/ipu/ipu_common.c @@ -0,0 +1,1804 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file ipu_common.c + * + * @brief This file contains the IPU driver common API functions. + * + * @ingroup IPU + */ + +#include <linux/types.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/err.h> +#include <linux/spinlock.h> +#include <linux/delay.h> +#include <linux/clk.h> +#include <asm/io.h> +#include <asm/arch/ipu.h> + +#include "ipu_prv.h" +#include "ipu_regs.h" +#include "ipu_param_mem.h" + +/* + * This type definition is used to define a node in the GPIO interrupt queue for + * registered interrupts for GPIO pins. Each node contains the GPIO signal number + * associated with the ISR and the actual ISR function pointer. + */ +struct ipu_irq_node { + irqreturn_t(*handler) (int, void *); /*!< the ISR */ + const char *name; /*!< device associated with the interrupt */ + void *dev_id; /*!< some unique information for the ISR */ + __u32 flags; /*!< not used */ +}; + +/* Globals */ +struct clk *g_ipu_clk; +struct clk *g_ipu_csi_clk; +static struct clk *dfm_clk; +int g_ipu_irq[2]; +int g_ipu_hw_rev; +bool g_sec_chan_en[21]; +uint32_t g_channel_init_mask; +DEFINE_SPINLOCK(ipu_lock); +struct device *g_ipu_dev; + +static struct ipu_irq_node ipu_irq_list[IPU_IRQ_COUNT]; +static const char driver_name[] = "mxc_ipu"; + +static uint32_t g_ipu_config = 0; +static uint32_t g_channel_init_mask_backup = 0; +static bool g_csi_used = false; + +/* Static functions */ +static irqreturn_t ipu_irq_handler(int irq, void *desc); +static void _ipu_pf_init(ipu_channel_params_t * params); +static void _ipu_pf_uninit(void); + +inline static uint32_t channel_2_dma(ipu_channel_t ch, ipu_buffer_t type) +{ + return ((type == IPU_INPUT_BUFFER) ? ((uint32_t) ch & 0xFF) : + ((type == IPU_OUTPUT_BUFFER) ? (((uint32_t) ch >> 8) & 0xFF) + : (((uint32_t) ch >> 16) & 0xFF))); +}; + +inline static uint32_t DMAParamAddr(uint32_t dma_ch) +{ + return (0x10000 | (dma_ch << 4)); +}; + +/*! + * This function is called by the driver framework to initialize the IPU + * hardware. + * + * @param dev The device structure for the IPU passed in by the framework. + * + * @return This function returns 0 on success or negative error code on error + */ +static +int ipu_probe(struct platform_device *pdev) +{ +// struct platform_device *pdev = to_platform_device(dev); + struct mxc_ipu_config *ipu_conf = pdev->dev.platform_data; + + spin_lock_init(&ipu_lock); + + g_ipu_dev = &pdev->dev; + g_ipu_hw_rev = ipu_conf->rev; + + /* Register IPU interrupts */ + g_ipu_irq[0] = platform_get_irq(pdev, 0); + if (g_ipu_irq[0] < 0) + return -EINVAL; + + if (request_irq(g_ipu_irq[0], ipu_irq_handler, 0, driver_name, 0) != 0) { + dev_err(g_ipu_dev, "request SYNC interrupt failed\n"); + return -EBUSY; + } + /* Some platforms have 2 IPU interrupts */ + g_ipu_irq[1] = platform_get_irq(pdev, 1); + if (g_ipu_irq[1] >= 0) { + if (request_irq + (g_ipu_irq[1], ipu_irq_handler, 0, driver_name, 0) != 0) { + dev_err(g_ipu_dev, "request ERR interrupt failed\n"); + return -EBUSY; + } + } + + /* Enable IPU and CSI clocks */ + /* Get IPU clock freq */ + g_ipu_clk = clk_get(&pdev->dev, "ipu_clk"); + dev_dbg(g_ipu_dev, "ipu_clk = %lu\n", clk_get_rate(g_ipu_clk)); + + g_ipu_csi_clk = clk_get(&pdev->dev, "csi_clk"); + + dfm_clk = clk_get(NULL, "dfm_clk"); + + clk_enable(g_ipu_clk); + + __raw_writel(0x00100010L, DI_HSP_CLK_PER); + + /* Set SDC refresh channels as high priority */ + __raw_writel(0x0000C000L, IDMAC_CHA_PRI); + + /* Set to max back to back burst requests */ + __raw_writel(0x00000070L, IDMAC_CONF); + + register_ipu_device(); + + return 0; +} + +/*! + * This function is called to initialize a logical IPU channel. + * + * @param channel Input parameter for the logical channel ID to initalize. + * + * @param params Input parameter containing union of channel initialization + * parameters. + * + * @return This function returns 0 on success or negative error code on fail + */ +int32_t ipu_init_channel(ipu_channel_t channel, ipu_channel_params_t * params) +{ + uint32_t ipu_conf; + uint32_t reg; + unsigned long lock_flags; + + dev_dbg(g_ipu_dev, "init channel = %d\n", IPU_CHAN_ID(channel)); + + if ((channel != MEM_SDC_BG) && (channel != MEM_SDC_FG) && + (channel != MEM_ROT_ENC_MEM) && (channel != MEM_ROT_VF_MEM) && + (channel != MEM_ROT_PP_MEM) && (channel != CSI_MEM) + && (params == NULL)) { + return -EINVAL; + } + + spin_lock_irqsave(&ipu_lock, lock_flags); + + ipu_conf = __raw_readl(IPU_CONF); + if (ipu_conf == 0) { + clk_enable(g_ipu_clk); + } + + if (g_channel_init_mask & (1L << IPU_CHAN_ID(channel))) { + dev_err(g_ipu_dev, "Warning: channel already initialized %d\n", + IPU_CHAN_ID(channel)); + } + + switch (channel) { + case CSI_PRP_VF_MEM: + reg = __raw_readl(IPU_FS_PROC_FLOW); + __raw_writel(reg & ~FS_VF_IN_VALID, IPU_FS_PROC_FLOW); + + if (params->mem_prp_vf_mem.graphics_combine_en) + g_sec_chan_en[IPU_CHAN_ID(channel)] = true; + + _ipu_ic_init_prpvf(params, true); + break; + case CSI_PRP_VF_ADC: + reg = __raw_readl(IPU_FS_PROC_FLOW); + __raw_writel(reg | (FS_DEST_ADC << FS_PRPVF_DEST_SEL_OFFSET), + IPU_FS_PROC_FLOW); + + _ipu_adc_init_channel(CSI_PRP_VF_ADC, + params->csi_prp_vf_adc.disp, + WriteTemplateNonSeq, + params->csi_prp_vf_adc.out_left, + params->csi_prp_vf_adc.out_top); + + _ipu_ic_init_prpvf(params, true); + break; + case MEM_PRP_VF_MEM: + reg = __raw_readl(IPU_FS_PROC_FLOW); + __raw_writel(reg | FS_VF_IN_VALID, IPU_FS_PROC_FLOW); + + if (params->mem_prp_vf_mem.graphics_combine_en) + g_sec_chan_en[IPU_CHAN_ID(channel)] = true; + + _ipu_ic_init_prpvf(params, false); + break; + case MEM_ROT_VF_MEM: + _ipu_ic_init_rotate_vf(params); + break; + case CSI_PRP_ENC_MEM: + reg = __raw_readl(IPU_FS_PROC_FLOW); + __raw_writel(reg & ~FS_ENC_IN_VALID, IPU_FS_PROC_FLOW); + _ipu_ic_init_prpenc(params, true); + break; + case MEM_PRP_ENC_MEM: + reg = __raw_readl(IPU_FS_PROC_FLOW); + __raw_writel(reg | FS_ENC_IN_VALID, IPU_FS_PROC_FLOW); + _ipu_ic_init_prpenc(params, false); + break; + case MEM_ROT_ENC_MEM: + _ipu_ic_init_rotate_enc(params); + break; + case MEM_PP_ADC: + reg = __raw_readl(IPU_FS_PROC_FLOW); + __raw_writel(reg | (FS_DEST_ADC << FS_PP_DEST_SEL_OFFSET), + IPU_FS_PROC_FLOW); + + _ipu_adc_init_channel(MEM_PP_ADC, params->mem_pp_adc.disp, + WriteTemplateNonSeq, + params->mem_pp_adc.out_left, + params->mem_pp_adc.out_top); + + if (params->mem_pp_adc.graphics_combine_en) + g_sec_chan_en[IPU_CHAN_ID(channel)] = true; + + _ipu_ic_init_pp(params); + break; + case MEM_PP_MEM: + if (params->mem_pp_mem.graphics_combine_en) + g_sec_chan_en[IPU_CHAN_ID(channel)] = true; + + _ipu_ic_init_pp(params); + break; + case MEM_ROT_PP_MEM: + _ipu_ic_init_rotate_pp(params); + break; + case CSI_MEM: + _ipu_ic_init_csi(params); + break; + + case MEM_PF_Y_MEM: + case MEM_PF_U_MEM: + case MEM_PF_V_MEM: + /* Enable PF block */ + _ipu_pf_init(params); + break; + + case MEM_SDC_BG: + break; + case MEM_SDC_FG: + break; + case ADC_SYS1: + _ipu_adc_init_channel(ADC_SYS1, params->adc_sys1.disp, + params->adc_sys1.ch_mode, + params->adc_sys1.out_left, + params->adc_sys1.out_top); + break; + case ADC_SYS2: + _ipu_adc_init_channel(ADC_SYS2, params->adc_sys2.disp, + params->adc_sys2.ch_mode, + params->adc_sys2.out_left, + params->adc_sys2.out_top); + break; + default: + dev_err(g_ipu_dev, "Missing channel initialization\n"); + break; + } + + /* Enable IPU sub module */ + g_channel_init_mask |= 1L << IPU_CHAN_ID(channel); + + if (g_channel_init_mask & 0x00000066L) { /*CSI */ + ipu_conf |= IPU_CONF_CSI_EN; + if (cpu_is_mx31() || cpu_is_mx32()) { + g_csi_used = true; + } + } + if (g_channel_init_mask & 0x00001FFFL) { /*IC */ + ipu_conf |= IPU_CONF_IC_EN; + } + if (g_channel_init_mask & 0x00000A10L) { /*ROT */ + ipu_conf |= IPU_CONF_ROT_EN; + } + if (g_channel_init_mask & 0x0001C000L) { /*SDC */ + ipu_conf |= IPU_CONF_SDC_EN | IPU_CONF_DI_EN; + } + if (g_channel_init_mask & 0x00061140L) { /*ADC */ + ipu_conf |= IPU_CONF_ADC_EN | IPU_CONF_DI_EN; + } + if (g_channel_init_mask & 0x00380000L) { /*PF */ + ipu_conf |= IPU_CONF_PF_EN; + } + __raw_writel(ipu_conf, IPU_CONF); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + + return 0; +} + +/*! + * This function is called to uninitialize a logical IPU channel. + * + * @param channel Input parameter for the logical channel ID to uninitalize. + */ +void ipu_uninit_channel(ipu_channel_t channel) +{ + unsigned long lock_flags; + uint32_t reg; + uint32_t dma, mask = 0; + uint32_t ipu_conf; + + spin_lock_irqsave(&ipu_lock, lock_flags); + + if ((g_channel_init_mask & (1L << IPU_CHAN_ID(channel))) == 0) { + dev_err(g_ipu_dev, "Channel already uninitialized %d\n", + IPU_CHAN_ID(channel)); + spin_unlock_irqrestore(&ipu_lock, lock_flags); + return; + } + + /* Make sure channel is disabled */ + /* Get input and output dma channels */ + dma = channel_2_dma(channel, IPU_OUTPUT_BUFFER); + if (dma != IDMA_CHAN_INVALID) + mask |= 1UL << dma; + dma = channel_2_dma(channel, IPU_INPUT_BUFFER); + if (dma != IDMA_CHAN_INVALID) + mask |= 1UL << dma; + /* Get secondary input dma channel */ + if (g_sec_chan_en[IPU_CHAN_ID(channel)]) { + dma = channel_2_dma(channel, IPU_SEC_INPUT_BUFFER); + if (dma != IDMA_CHAN_INVALID) { + mask |= 1UL << dma; + } + } + if (mask & __raw_readl(IDMAC_CHA_EN)) { + dev_err(g_ipu_dev, + "Channel %d is not disabled, disable first\n", + IPU_CHAN_ID(channel)); + spin_unlock_irqrestore(&ipu_lock, lock_flags); + return; + } + + /* Reset the double buffer */ + reg = __raw_readl(IPU_CHA_DB_MODE_SEL); + __raw_writel(reg & ~mask, IPU_CHA_DB_MODE_SEL); + + g_sec_chan_en[IPU_CHAN_ID(channel)] = false; + + switch (channel) { + case CSI_MEM: + _ipu_ic_uninit_csi(); + break; + case CSI_PRP_VF_ADC: + reg = __raw_readl(IPU_FS_PROC_FLOW); + __raw_writel(reg & ~FS_PRPVF_DEST_SEL_MASK, IPU_FS_PROC_FLOW); + + _ipu_adc_uninit_channel(CSI_PRP_VF_ADC); + + /* Fall thru */ + case CSI_PRP_VF_MEM: + case MEM_PRP_VF_MEM: + _ipu_ic_uninit_prpvf(); + break; + case MEM_PRP_VF_ADC: + break; + case MEM_ROT_VF_MEM: + _ipu_ic_uninit_rotate_vf(); + break; + case CSI_PRP_ENC_MEM: + case MEM_PRP_ENC_MEM: + _ipu_ic_uninit_prpenc(); + break; + case MEM_ROT_ENC_MEM: + _ipu_ic_uninit_rotate_enc(); + break; + case MEM_PP_ADC: + reg = __raw_readl(IPU_FS_PROC_FLOW); + __raw_writel(reg & ~FS_PP_DEST_SEL_MASK, IPU_FS_PROC_FLOW); + + _ipu_adc_uninit_channel(MEM_PP_ADC); + + /* Fall thru */ + case MEM_PP_MEM: + _ipu_ic_uninit_pp(); + break; + case MEM_ROT_PP_MEM: + _ipu_ic_uninit_rotate_pp(); + break; + + case MEM_PF_Y_MEM: + _ipu_pf_uninit(); + break; + case MEM_PF_U_MEM: + case MEM_PF_V_MEM: + break; + + case MEM_SDC_BG: + break; + case MEM_SDC_FG: + break; + case ADC_SYS1: + _ipu_adc_uninit_channel(ADC_SYS1); + break; + case ADC_SYS2: + _ipu_adc_uninit_channel(ADC_SYS2); + break; + case MEM_SDC_MASK: + case CHAN_NONE: + break; + } + + g_channel_init_mask &= ~(1L << IPU_CHAN_ID(channel)); + + ipu_conf = __raw_readl(IPU_CONF); + if ((g_channel_init_mask & 0x00000066L) == 0) { /*CSI */ + ipu_conf &= ~IPU_CONF_CSI_EN; + } + if ((g_channel_init_mask & 0x00001FFFL) == 0) { /*IC */ + ipu_conf &= ~IPU_CONF_IC_EN; + } + if ((g_channel_init_mask & 0x00000A10L) == 0) { /*ROT */ + ipu_conf &= ~IPU_CONF_ROT_EN; + } + if ((g_channel_init_mask & 0x0001C000L) == 0) { /*SDC */ + ipu_conf &= ~IPU_CONF_SDC_EN; + } + if ((g_channel_init_mask & 0x00061140L) == 0) { /*ADC */ + ipu_conf &= ~IPU_CONF_ADC_EN; + } + if ((g_channel_init_mask & 0x0007D140L) == 0) { /*DI */ + ipu_conf &= ~IPU_CONF_DI_EN; + } + if ((g_channel_init_mask & 0x00380000L) == 0) { /*PF */ + ipu_conf &= ~IPU_CONF_PF_EN; + } + __raw_writel(ipu_conf, IPU_CONF); + if (ipu_conf == 0) { + clk_disable(g_ipu_clk); + } + + spin_unlock_irqrestore(&ipu_lock, lock_flags); +} + +/*! + * This function is called to initialize a buffer for logical IPU channel. + * + * @param channel Input parameter for the logical channel ID. + * + * @param type Input parameter which buffer to initialize. + * + * @param pixel_fmt Input parameter for pixel format of buffer. Pixel + * format is a FOURCC ASCII code. + * + * @param width Input parameter for width of buffer in pixels. + * + * @param height Input parameter for height of buffer in pixels. + * + * @param stride Input parameter for stride length of buffer + * in pixels. + * + * @param rot_mode Input parameter for rotation setting of buffer. + * A rotation setting other than \b IPU_ROTATE_VERT_FLIP + * should only be used for input buffers of rotation + * channels. + * + * @param phyaddr_0 Input parameter buffer 0 physical address. + * + * @param phyaddr_1 Input parameter buffer 1 physical address. + * Setting this to a value other than NULL enables + * double buffering mode. + * + * @param u private u offset for additional cropping, + * zero if not used. + * + * @param v private v offset for additional cropping, + * zero if not used. + * + * @return This function returns 0 on success or negative error code on fail + */ +int32_t ipu_init_channel_buffer(ipu_channel_t channel, ipu_buffer_t type, + uint32_t pixel_fmt, + uint16_t width, uint16_t height, + uint32_t stride, + ipu_rotate_mode_t rot_mode, + dma_addr_t phyaddr_0, dma_addr_t phyaddr_1, + uint32_t u, uint32_t v) +{ + uint32_t params[10]; + unsigned long lock_flags; + uint32_t reg; + uint32_t dma_chan; + uint32_t stride_bytes; + + dma_chan = channel_2_dma(channel, type); + stride_bytes = stride * bytes_per_pixel(pixel_fmt); + + if (dma_chan == IDMA_CHAN_INVALID) + return -EINVAL; + + if (stride_bytes % 4) { + dev_err(g_ipu_dev, + "Stride length must be 32-bit aligned, stride = %d, bytes = %d\n", + stride, stride_bytes); + return -EINVAL; + } + /* IC channels' stride must be multiple of 8 pixels */ + if ((dma_chan <= 13) && (stride % 8)) { + dev_err(g_ipu_dev, "Stride must be 8 pixel multiple\n"); + return -EINVAL; + } + /* Build parameter memory data for DMA channel */ + _ipu_ch_param_set_size(params, pixel_fmt, width, height, stride_bytes, + u, v); + _ipu_ch_param_set_buffer(params, phyaddr_0, phyaddr_1); + _ipu_ch_param_set_rotation(params, rot_mode); + /* Some channels (rotation) have restriction on burst length */ + if ((dma_chan == 10) || (dma_chan == 11) || (dma_chan == 13)) { + _ipu_ch_param_set_burst_size(params, 8); + } else if (dma_chan == 24) { /* PF QP channel */ + _ipu_ch_param_set_burst_size(params, 4); + } else if (dma_chan == 25) { /* PF H264 BS channel */ + _ipu_ch_param_set_burst_size(params, 16); + } else if (((dma_chan == 14) || (dma_chan == 15)) && + pixel_fmt == IPU_PIX_FMT_RGB565) { + _ipu_ch_param_set_burst_size(params, 16); + } + + spin_lock_irqsave(&ipu_lock, lock_flags); + + _ipu_write_param_mem(DMAParamAddr(dma_chan), params, 10); + + reg = __raw_readl(IPU_CHA_DB_MODE_SEL); + if (phyaddr_1) { + reg |= 1UL << dma_chan; + } else { + reg &= ~(1UL << dma_chan); + } + __raw_writel(reg, IPU_CHA_DB_MODE_SEL); + + /* Reset to buffer 0 */ + __raw_writel(1UL << dma_chan, IPU_CHA_CUR_BUF); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + + return 0; +} + +/*! + * This function is called to update the physical address of a buffer for + * a logical IPU channel. + * + * @param channel Input parameter for the logical channel ID. + * + * @param type Input parameter which buffer to initialize. + * + * @param bufNum Input parameter for which buffer number to update. + * 0 or 1 are the only valid values. + * + * @param phyaddr Input parameter buffer physical address. + * + * @return This function returns 0 on success or negative error code on + * fail. This function will fail if the buffer is set to ready. + */ +int32_t ipu_update_channel_buffer(ipu_channel_t channel, ipu_buffer_t type, + uint32_t bufNum, dma_addr_t phyaddr) +{ + uint32_t reg; + unsigned long lock_flags; + uint32_t dma_chan = channel_2_dma(channel, type); + + if (dma_chan == IDMA_CHAN_INVALID) + return -EINVAL; + + spin_lock_irqsave(&ipu_lock, lock_flags); + + if (bufNum == 0) { + reg = __raw_readl(IPU_CHA_BUF0_RDY); + if (reg & (1UL << dma_chan)) { + spin_unlock_irqrestore(&ipu_lock, lock_flags); + return -EACCES; + } + __raw_writel(DMAParamAddr(dma_chan) + 0x0008UL, IPU_IMA_ADDR); + __raw_writel(phyaddr, IPU_IMA_DATA); + } else { + reg = __raw_readl(IPU_CHA_BUF1_RDY); + if (reg & (1UL << dma_chan)) { + spin_unlock_irqrestore(&ipu_lock, lock_flags); + return -EACCES; + } + __raw_writel(DMAParamAddr(dma_chan) + 0x0009UL, IPU_IMA_ADDR); + __raw_writel(phyaddr, IPU_IMA_DATA); + } + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + dev_dbg(g_ipu_dev, "IPU: update IDMA ch %d buf %d = 0x%08X\n", + dma_chan, bufNum, phyaddr); + return 0; +} + +/*! + * This function is called to set a channel's buffer as ready. + * + * @param channel Input parameter for the logical channel ID. + * + * @param type Input parameter which buffer to initialize. + * + * @param bufNum Input parameter for which buffer number set to + * ready state. + * + * @return This function returns 0 on success or negative error code on fail + */ +int32_t ipu_select_buffer(ipu_channel_t channel, ipu_buffer_t type, + uint32_t bufNum) +{ + uint32_t dma_chan = channel_2_dma(channel, type); + + if (dma_chan == IDMA_CHAN_INVALID) + return -EINVAL; + + if (bufNum == 0) { + /*Mark buffer 0 as ready. */ + __raw_writel(1UL << dma_chan, IPU_CHA_BUF0_RDY); + } else { + /*Mark buffer 1 as ready. */ + __raw_writel(1UL << dma_chan, IPU_CHA_BUF1_RDY); + } + return 0; +} + +/*! + * This function links 2 channels together for automatic frame + * synchronization. The output of the source channel is linked to the input of + * the destination channel. + * + * @param src_ch Input parameter for the logical channel ID of + * the source channel. + * + * @param dest_ch Input parameter for the logical channel ID of + * the destination channel. + * + * @return This function returns 0 on success or negative error code on + * fail. + */ +int32_t ipu_link_channels(ipu_channel_t src_ch, ipu_channel_t dest_ch) +{ + unsigned long lock_flags; + uint32_t out_dma; + uint32_t in_dma; + bool isProc; + uint32_t value; + uint32_t mask; + uint32_t offset; + uint32_t fs_proc_flow; + uint32_t fs_disp_flow; + + spin_lock_irqsave(&ipu_lock, lock_flags); + + fs_proc_flow = __raw_readl(IPU_FS_PROC_FLOW); + fs_disp_flow = __raw_readl(IPU_FS_DISP_FLOW); + + out_dma = (1UL << channel_2_dma(src_ch, IPU_OUTPUT_BUFFER)); + in_dma = (1UL << channel_2_dma(dest_ch, IPU_INPUT_BUFFER)); + + /* PROCESS THE OUTPUT DMA CH */ + switch (out_dma) { + /*VF-> */ + case IDMA_IC_1: + pr_debug("Link VF->"); + isProc = true; + mask = FS_PRPVF_DEST_SEL_MASK; + offset = FS_PRPVF_DEST_SEL_OFFSET; + value = (in_dma == IDMA_IC_11) ? FS_DEST_ROT : /*->VF_ROT */ + (in_dma == IDMA_ADC_SYS1_WR) ? FS_DEST_ADC1 : /* ->ADC1 */ + (in_dma == IDMA_ADC_SYS2_WR) ? FS_DEST_ADC2 : /* ->ADC2 */ + (in_dma == IDMA_SDC_BG) ? FS_DEST_SDC_BG : /*->SDC_BG */ + (in_dma == IDMA_SDC_FG) ? FS_DEST_SDC_FG : /*->SDC_FG */ + (in_dma == IDMA_ADC_SYS1_WR) ? FS_DEST_ADC1 : /*->ADC1 */ + /* ->ADCDirect */ + 0; + break; + + /*VF_ROT-> */ + case IDMA_IC_9: + pr_debug("Link VF_ROT->"); + isProc = true; + mask = FS_PRPVF_ROT_DEST_SEL_MASK; + offset = FS_PRPVF_ROT_DEST_SEL_OFFSET; + value = (in_dma == IDMA_ADC_SYS1_WR) ? FS_DEST_ADC1 : /*->ADC1 */ + (in_dma == IDMA_ADC_SYS2_WR) ? FS_DEST_ADC2 : /* ->ADC2 */ + (in_dma == IDMA_SDC_BG) ? FS_DEST_SDC_BG : /*->SDC_BG */ + (in_dma == IDMA_SDC_FG) ? FS_DEST_SDC_FG : /*->SDC_FG */ + 0; + break; + + /*ENC-> */ + case IDMA_IC_0: + pr_debug("Link ENC->"); + isProc = true; + mask = 0; + offset = 0; + value = (in_dma == IDMA_IC_10) ? FS_PRPENC_DEST_SEL : /*->ENC_ROT */ + 0; + break; + + /*PP-> */ + case IDMA_IC_2: + pr_debug("Link PP->"); + isProc = true; + mask = FS_PP_DEST_SEL_MASK; + offset = FS_PP_DEST_SEL_OFFSET; + value = (in_dma == IDMA_IC_13) ? FS_DEST_ROT : /*->PP_ROT */ + (in_dma == IDMA_ADC_SYS1_WR) ? FS_DEST_ADC1 : /* ->ADC1 */ + (in_dma == IDMA_ADC_SYS2_WR) ? FS_DEST_ADC2 : /* ->ADC2 */ + (in_dma == IDMA_SDC_BG) ? FS_DEST_SDC_BG : /*->SDC_BG */ + (in_dma == IDMA_SDC_FG) ? FS_DEST_SDC_FG : /*->SDC_FG */ + /* ->ADCDirect */ + 0; + break; + + /*PP_ROT-> */ + case IDMA_IC_12: + pr_debug("Link PP_ROT->"); + isProc = true; + mask = FS_PP_ROT_DEST_SEL_MASK; + offset = FS_PP_ROT_DEST_SEL_OFFSET; + value = (in_dma == IDMA_IC_5) ? FS_DEST_ROT : /*->PP */ + (in_dma == IDMA_ADC_SYS1_WR) ? FS_DEST_ADC1 : /* ->ADC1 */ + (in_dma == IDMA_ADC_SYS2_WR) ? FS_DEST_ADC2 : /* ->ADC2 */ + (in_dma == IDMA_SDC_BG) ? FS_DEST_SDC_BG : /*->SDC_BG */ + (in_dma == IDMA_SDC_FG) ? FS_DEST_SDC_FG : /*->SDC_FG */ + 0; + break; + + /*PF-> */ + case IDMA_PF_Y_OUT: + case IDMA_PF_U_OUT: + case IDMA_PF_V_OUT: + pr_debug("Link PF->"); + isProc = true; + mask = FS_PF_DEST_SEL_MASK; + offset = FS_PF_DEST_SEL_OFFSET; + value = (in_dma == IDMA_IC_5) ? FS_PF_DEST_PP : + (in_dma == IDMA_IC_13) ? FS_PF_DEST_ROT : 0; + break; + + /* Invalid Chainings: ENC_ROT-> */ + default: + pr_debug("Link Invalid->"); + value = 0; + break; + + } + + if (value) { + if (isProc) { + fs_proc_flow &= ~mask; + fs_proc_flow |= (value << offset); + } else { + fs_disp_flow &= ~mask; + fs_disp_flow |= (value << offset); + } + } else { + dev_err(g_ipu_dev, "Invalid channel chaining %d -> %d\n", + out_dma, in_dma); + return -EINVAL; + } + + /* PROCESS THE INPUT DMA CH */ + switch (in_dma) { + /* ->VF_ROT */ + case IDMA_IC_11: + pr_debug("VF_ROT\n"); + isProc = true; + mask = 0; + offset = 0; + value = (out_dma == IDMA_IC_1) ? FS_PRPVF_ROT_SRC_SEL : /*VF-> */ + 0; + break; + + /* ->ENC_ROT */ + case IDMA_IC_10: + pr_debug("ENC_ROT\n"); + isProc = true; + mask = 0; + offset = 0; + value = (out_dma == IDMA_IC_0) ? FS_PRPENC_ROT_SRC_SEL : /*ENC-> */ + 0; + break; + + /* ->PP */ + case IDMA_IC_5: + pr_debug("PP\n"); + isProc = true; + mask = FS_PP_SRC_SEL_MASK; + offset = FS_PP_SRC_SEL_OFFSET; + value = (out_dma == IDMA_PF_Y_OUT) ? FS_PP_SRC_PF : /*PF-> */ + (out_dma == IDMA_PF_U_OUT) ? FS_PP_SRC_PF : /*PF-> */ + (out_dma == IDMA_PF_V_OUT) ? FS_PP_SRC_PF : /*PF-> */ + (out_dma == IDMA_IC_12) ? FS_PP_SRC_ROT : /*PP_ROT-> */ + 0; + break; + + /* ->PP_ROT */ + case IDMA_IC_13: + pr_debug("PP_ROT\n"); + isProc = true; + mask = FS_PP_ROT_SRC_SEL_MASK; + offset = FS_PP_ROT_SRC_SEL_OFFSET; + value = (out_dma == IDMA_PF_Y_OUT) ? FS_PP_SRC_PF : /*PF-> */ + (out_dma == IDMA_PF_U_OUT) ? FS_PP_SRC_PF : /*PF-> */ + (out_dma == IDMA_PF_V_OUT) ? FS_PP_SRC_PF : /*PF-> */ + (out_dma == IDMA_IC_2) ? FS_ROT_SRC_PP : /*PP-> */ + 0; + break; + + /* ->SDC_BG */ + case IDMA_SDC_BG: + pr_debug("SDC_BG\n"); + isProc = false; + mask = FS_SDC_BG_SRC_SEL_MASK; + offset = FS_SDC_BG_SRC_SEL_OFFSET; + value = (out_dma == IDMA_IC_9) ? FS_SRC_ROT_VF : /*VF_ROT-> */ + (out_dma == IDMA_IC_12) ? FS_SRC_ROT_PP : /*PP_ROT-> */ + (out_dma == IDMA_IC_1) ? FS_SRC_VF : /*VF-> */ + (out_dma == IDMA_IC_2) ? FS_SRC_PP : /*PP-> */ + 0; + break; + + /* ->SDC_FG */ + case IDMA_SDC_FG: + pr_debug("SDC_FG\n"); + isProc = false; + mask = FS_SDC_FG_SRC_SEL_MASK; + offset = FS_SDC_FG_SRC_SEL_OFFSET; + value = (out_dma == IDMA_IC_9) ? FS_SRC_ROT_VF : /*VF_ROT-> */ + (out_dma == IDMA_IC_12) ? FS_SRC_ROT_PP : /*PP_ROT-> */ + (out_dma == IDMA_IC_1) ? FS_SRC_VF : /*VF-> */ + (out_dma == IDMA_IC_2) ? FS_SRC_PP : /*PP-> */ + 0; + break; + + /* ->ADC1 */ + case IDMA_ADC_SYS1_WR: + pr_debug("ADC_SYS1\n"); + isProc = false; + mask = FS_ADC1_SRC_SEL_MASK; + offset = FS_ADC1_SRC_SEL_OFFSET; + value = (out_dma == IDMA_IC_9) ? FS_SRC_ROT_VF : /*VF_ROT-> */ + (out_dma == IDMA_IC_12) ? FS_SRC_ROT_PP : /*PP_ROT-> */ + (out_dma == IDMA_IC_1) ? FS_SRC_VF : /*VF-> */ + (out_dma == IDMA_IC_2) ? FS_SRC_PP : /*PP-> */ + 0; + break; + + /* ->ADC2 */ + case IDMA_ADC_SYS2_WR: + pr_debug("ADC_SYS2\n"); + isProc = false; + mask = FS_ADC2_SRC_SEL_MASK; + offset = FS_ADC2_SRC_SEL_OFFSET; + value = (out_dma == IDMA_IC_9) ? FS_SRC_ROT_VF : /*VF_ROT-> */ + (out_dma == IDMA_IC_12) ? FS_SRC_ROT_PP : /*PP_ROT-> */ + (out_dma == IDMA_IC_1) ? FS_SRC_VF : /*VF-> */ + (out_dma == IDMA_IC_2) ? FS_SRC_PP : /*PP-> */ + 0; + break; + + /*Invalid chains: */ + /* ->ENC, ->VF, ->PF, ->VF_COMBINE, ->PP_COMBINE */ + default: + pr_debug("Invalid\n"); + value = 0; + break; + + } + + if (value) { + if (isProc) { + fs_proc_flow &= ~mask; + fs_proc_flow |= (value << offset); + } else { + fs_disp_flow &= ~mask; + fs_disp_flow |= (value << offset); + } + } else { + dev_err(g_ipu_dev, "Invalid channel chaining %d -> %d\n", + out_dma, in_dma); + return -EINVAL; + } + + __raw_writel(fs_proc_flow, IPU_FS_PROC_FLOW); + __raw_writel(fs_disp_flow, IPU_FS_DISP_FLOW); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + return 0; +} + +/*! + * This function unlinks 2 channels and disables automatic frame + * synchronization. + * + * @param src_ch Input parameter for the logical channel ID of + * the source channel. + * + * @param dest_ch Input parameter for the logical channel ID of + * the destination channel. + * + * @return This function returns 0 on success or negative error code on + * fail. + */ +int32_t ipu_unlink_channels(ipu_channel_t src_ch, ipu_channel_t dest_ch) +{ + unsigned long lock_flags; + uint32_t out_dma; + uint32_t in_dma; + uint32_t fs_proc_flow; + uint32_t fs_disp_flow; + + spin_lock_irqsave(&ipu_lock, lock_flags); + + fs_proc_flow = __raw_readl(IPU_FS_PROC_FLOW); + fs_disp_flow = __raw_readl(IPU_FS_DISP_FLOW); + + out_dma = (1UL << channel_2_dma(src_ch, IPU_OUTPUT_BUFFER)); + in_dma = (1UL << channel_2_dma(dest_ch, IPU_INPUT_BUFFER)); + + /*clear the src_ch's output destination */ + switch (out_dma) { + /*VF-> */ + case IDMA_IC_1: + pr_debug("Unlink VF->"); + fs_proc_flow &= ~FS_PRPVF_DEST_SEL_MASK; + break; + + /*VF_ROT-> */ + case IDMA_IC_9: + pr_debug("Unlink VF_Rot->"); + fs_proc_flow &= ~FS_PRPVF_ROT_DEST_SEL_MASK; + break; + + /*ENC-> */ + case IDMA_IC_0: + pr_debug("Unlink ENC->"); + fs_proc_flow &= ~FS_PRPENC_DEST_SEL; + break; + + /*PP-> */ + case IDMA_IC_2: + pr_debug("Unlink PP->"); + fs_proc_flow &= ~FS_PP_DEST_SEL_MASK; + break; + + /*PP_ROT-> */ + case IDMA_IC_12: + pr_debug("Unlink PP_ROT->"); + fs_proc_flow &= ~FS_PP_ROT_DEST_SEL_MASK; + break; + + /*PF-> */ + case IDMA_PF_Y_OUT: + case IDMA_PF_U_OUT: + case IDMA_PF_V_OUT: + pr_debug("Unlink PF->"); + fs_proc_flow &= ~FS_PF_DEST_SEL_MASK; + break; + + default: /*ENC_ROT-> */ + pr_debug("Unlink Invalid->"); + break; + } + + /*clear the dest_ch's input source */ + switch (in_dma) { + /*->VF_ROT*/ + case IDMA_IC_11: + pr_debug("VF_ROT\n"); + fs_proc_flow &= ~FS_PRPVF_ROT_SRC_SEL; + break; + + /*->Enc_ROT*/ + case IDMA_IC_10: + pr_debug("ENC_ROT\n"); + fs_proc_flow &= ~FS_PRPENC_ROT_SRC_SEL; + break; + + /*->PP*/ + case IDMA_IC_5: + pr_debug("PP\n"); + fs_proc_flow &= ~FS_PP_SRC_SEL_MASK; + break; + + /*->PP_ROT*/ + case IDMA_IC_13: + pr_debug("PP_ROT\n"); + fs_proc_flow &= ~FS_PP_ROT_SRC_SEL_MASK; + break; + + /*->SDC_FG*/ + case IDMA_SDC_FG: + pr_debug("SDC_FG\n"); + fs_disp_flow &= ~FS_SDC_FG_SRC_SEL_MASK; + break; + + /*->SDC_BG*/ + case IDMA_SDC_BG: + pr_debug("SDC_BG\n"); + fs_disp_flow &= ~FS_SDC_BG_SRC_SEL_MASK; + break; + + /*->ADC1*/ + case IDMA_ADC_SYS1_WR: + pr_debug("ADC_SYS1\n"); + fs_disp_flow &= ~FS_ADC1_SRC_SEL_MASK; + break; + + /*->ADC2*/ + case IDMA_ADC_SYS2_WR: + pr_debug("ADC_SYS2\n"); + fs_disp_flow &= ~FS_ADC2_SRC_SEL_MASK; + break; + + default: /*->VF, ->ENC, ->VF_COMBINE, ->PP_COMBINE, ->PF*/ + pr_debug("Invalid\n"); + break; + } + + __raw_writel(fs_proc_flow, IPU_FS_PROC_FLOW); + __raw_writel(fs_disp_flow, IPU_FS_DISP_FLOW); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + return 0; +} + +/*! + * This function enables a logical channel. + * + * @param channel Input parameter for the logical channel ID. + * + * @return This function returns 0 on success or negative error code on + * fail. + */ +int32_t ipu_enable_channel(ipu_channel_t channel) +{ + uint32_t reg; + unsigned long lock_flags; + uint32_t in_dma; + uint32_t sec_dma; + uint32_t out_dma; + uint32_t chan_mask = 0; + + spin_lock_irqsave(&ipu_lock, lock_flags); + + reg = __raw_readl(IDMAC_CHA_EN); + + /* Get input and output dma channels */ + out_dma = channel_2_dma(channel, IPU_OUTPUT_BUFFER); + if (out_dma != IDMA_CHAN_INVALID) + reg |= 1UL << out_dma; + in_dma = channel_2_dma(channel, IPU_INPUT_BUFFER); + if (in_dma != IDMA_CHAN_INVALID) + reg |= 1UL << in_dma; + + /* Get secondary input dma channel */ + if (g_sec_chan_en[IPU_CHAN_ID(channel)]) { + sec_dma = channel_2_dma(channel, IPU_SEC_INPUT_BUFFER); + if (sec_dma != IDMA_CHAN_INVALID) { + reg |= 1UL << sec_dma; + } + } + + __raw_writel(reg | chan_mask, IDMAC_CHA_EN); + + if (IPU_CHAN_ID(channel) <= IPU_CHAN_ID(MEM_PP_ADC)) { + _ipu_ic_enable_task(channel); + } else if (channel == MEM_SDC_BG) { + dev_dbg(g_ipu_dev, "Initializing SDC BG\n"); + _ipu_sdc_bg_init(NULL); + } else if (channel == MEM_SDC_FG) { + dev_dbg(g_ipu_dev, "Initializing SDC FG\n"); + _ipu_sdc_fg_init(NULL); + } + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + return 0; +} + +/*! + * This function disables a logical channel. + * + * @param channel Input parameter for the logical channel ID. + * + * @param wait_for_stop Flag to set whether to wait for channel end + * of frame or return immediately. + * + * @return This function returns 0 on success or negative error code on + * fail. + */ +int32_t ipu_disable_channel(ipu_channel_t channel, bool wait_for_stop) +{ + uint32_t reg; + unsigned long lock_flags; + uint32_t sec_dma; + uint32_t in_dma; + uint32_t out_dma; + uint32_t chan_mask = 0; + uint32_t timeout; + uint32_t eof_intr; + uint32_t enabled; + + /* Get input and output dma channels */ + out_dma = channel_2_dma(channel, IPU_OUTPUT_BUFFER); + if (out_dma != IDMA_CHAN_INVALID) + chan_mask = 1UL << out_dma; + in_dma = channel_2_dma(channel, IPU_INPUT_BUFFER); + if (in_dma != IDMA_CHAN_INVALID) + chan_mask |= 1UL << in_dma; + sec_dma = channel_2_dma(channel, IPU_SEC_INPUT_BUFFER); + if (sec_dma != IDMA_CHAN_INVALID) + chan_mask |= 1UL << sec_dma; + + if (wait_for_stop && channel != MEM_SDC_FG && channel != MEM_SDC_BG) { + timeout = 40; + while ((__raw_readl(IDMAC_CHA_BUSY) & chan_mask) || + (_ipu_channel_status(channel) == TASK_STAT_ACTIVE)) { + timeout--; + msleep(10); + if (timeout == 0) { + printk + (KERN_INFO + "MXC IPU: Warning - timeout waiting for channel to stop,\n" + "\tbuf0_rdy = 0x%08X, buf1_rdy = 0x%08X\n" + "\tbusy = 0x%08X, tstat = 0x%08X\n\tmask = 0x%08X\n", + __raw_readl(IPU_CHA_BUF0_RDY), + __raw_readl(IPU_CHA_BUF1_RDY), + __raw_readl(IDMAC_CHA_BUSY), + __raw_readl(IPU_TASKS_STAT), chan_mask); + break; + } + } + dev_dbg(g_ipu_dev, "timeout = %d * 10ms\n", 40 - timeout); + } + /* SDC BG and FG must be disabled before DMA is disabled */ + if ((channel == MEM_SDC_BG) || (channel == MEM_SDC_FG)) { + + if (channel == MEM_SDC_BG) + eof_intr = IPU_IRQ_SDC_BG_EOF; + else + eof_intr = IPU_IRQ_SDC_FG_EOF; + + /* Wait for any buffer flips to finsh */ + timeout = 4; + while (timeout && + ((__raw_readl(IPU_CHA_BUF0_RDY) & chan_mask) || + (__raw_readl(IPU_CHA_BUF1_RDY) & chan_mask))) { + msleep(10); + timeout--; + } + + spin_lock_irqsave(&ipu_lock, lock_flags); + ipu_clear_irq(eof_intr); + if (channel == MEM_SDC_BG) + enabled = _ipu_sdc_bg_uninit(); + else + enabled = _ipu_sdc_fg_uninit(); + spin_unlock_irqrestore(&ipu_lock, lock_flags); + + if (enabled && wait_for_stop) { + timeout = 5; + } else { + timeout = 0; + } + while (timeout && !ipu_get_irq_status(eof_intr)) { + msleep(5); + timeout--; + } + } + + spin_lock_irqsave(&ipu_lock, lock_flags); + + /* Disable IC task */ + if (IPU_CHAN_ID(channel) <= IPU_CHAN_ID(MEM_PP_ADC)) { + _ipu_ic_disable_task(channel); + } + + /* Disable DMA channel(s) */ + reg = __raw_readl(IDMAC_CHA_EN); + __raw_writel(reg & ~chan_mask, IDMAC_CHA_EN); + + /* Reset to buffer 0 */ + __raw_writel(chan_mask, IPU_CHA_CUR_BUF); + + /* Clear DMA related interrupts */ + __raw_writel(chan_mask, IPU_INT_STAT_1); + __raw_writel(chan_mask, IPU_INT_STAT_2); + __raw_writel(chan_mask, IPU_INT_STAT_4); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + + return 0; +} + +static +irqreturn_t ipu_irq_handler(int irq, void *desc) +{ + uint32_t line_base = 0; + uint32_t line; + irqreturn_t result = IRQ_NONE; + uint32_t int_stat; + + if (g_ipu_irq[1]) { + disable_irq(g_ipu_irq[0]); + disable_irq(g_ipu_irq[1]); + } + + int_stat = __raw_readl(IPU_INT_STAT_1); + int_stat &= __raw_readl(IPU_INT_CTRL_1); + __raw_writel(int_stat, IPU_INT_STAT_1); + while ((line = ffs(int_stat)) != 0) { + int_stat &= ~(1UL << (line - 1)); + line += line_base - 1; + result |= + ipu_irq_list[line].handler(line, ipu_irq_list[line].dev_id); + } + + line_base = 32; + int_stat = __raw_readl(IPU_INT_STAT_2); + int_stat &= __raw_readl(IPU_INT_CTRL_2); + __raw_writel(int_stat, IPU_INT_STAT_2); + while ((line = ffs(int_stat)) != 0) { + int_stat &= ~(1UL << (line - 1)); + line += line_base - 1; + result |= + ipu_irq_list[line].handler(line, ipu_irq_list[line].dev_id); + } + + line_base = 64; + int_stat = __raw_readl(IPU_INT_STAT_3); + int_stat &= __raw_readl(IPU_INT_CTRL_3); + __raw_writel(int_stat, IPU_INT_STAT_3); + while ((line = ffs(int_stat)) != 0) { + int_stat &= ~(1UL << (line - 1)); + line += line_base - 1; + result |= + ipu_irq_list[line].handler(line, ipu_irq_list[line].dev_id); + } + + line_base = 96; + int_stat = __raw_readl(IPU_INT_STAT_4); + int_stat &= __raw_readl(IPU_INT_CTRL_4); + __raw_writel(int_stat, IPU_INT_STAT_4); + while ((line = ffs(int_stat)) != 0) { + int_stat &= ~(1UL << (line - 1)); + line += line_base - 1; + result |= + ipu_irq_list[line].handler(line, ipu_irq_list[line].dev_id); + } + + line_base = 128; + int_stat = __raw_readl(IPU_INT_STAT_5); + int_stat &= __raw_readl(IPU_INT_CTRL_5); + __raw_writel(int_stat, IPU_INT_STAT_5); + while ((line = ffs(int_stat)) != 0) { + int_stat &= ~(1UL << (line - 1)); + line += line_base - 1; + result |= + ipu_irq_list[line].handler(line, ipu_irq_list[line].dev_id); + } + + if (g_ipu_irq[1]) { + enable_irq(g_ipu_irq[0]); + enable_irq(g_ipu_irq[1]); + } + return result; +} + +/*! + * This function enables the interrupt for the specified interrupt line. + * The interrupt lines are defined in \b ipu_irq_line enum. + * + * @param irq Interrupt line to enable interrupt for. + * + */ +void ipu_enable_irq(uint32_t irq) +{ + uint32_t reg; + unsigned long lock_flags; + + spin_lock_irqsave(&ipu_lock, lock_flags); + + reg = __raw_readl(IPUIRQ_2_CTRLREG(irq)); + reg |= IPUIRQ_2_MASK(irq); + __raw_writel(reg, IPUIRQ_2_CTRLREG(irq)); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); +} + +/*! + * This function disables the interrupt for the specified interrupt line. + * The interrupt lines are defined in \b ipu_irq_line enum. + * + * @param irq Interrupt line to disable interrupt for. + * + */ +void ipu_disable_irq(uint32_t irq) +{ + uint32_t reg; + unsigned long lock_flags; + + spin_lock_irqsave(&ipu_lock, lock_flags); + + reg = __raw_readl(IPUIRQ_2_CTRLREG(irq)); + reg &= ~IPUIRQ_2_MASK(irq); + __raw_writel(reg, IPUIRQ_2_CTRLREG(irq)); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); +} + +/*! + * This function clears the interrupt for the specified interrupt line. + * The interrupt lines are defined in \b ipu_irq_line enum. + * + * @param irq Interrupt line to clear interrupt for. + * + */ +void ipu_clear_irq(uint32_t irq) +{ + __raw_writel(IPUIRQ_2_MASK(irq), IPUIRQ_2_STATREG(irq)); +} + +/*! + * This function returns the current interrupt status for the specified interrupt + * line. The interrupt lines are defined in \b ipu_irq_line enum. + * + * @param irq Interrupt line to get status for. + * + * @return Returns true if the interrupt is pending/asserted or false if + * the interrupt is not pending. + */ +bool ipu_get_irq_status(uint32_t irq) +{ + uint32_t reg = __raw_readl(IPUIRQ_2_STATREG(irq)); + + if (reg & IPUIRQ_2_MASK(irq)) + return true; + else + return false; +} + +/*! + * This function registers an interrupt handler function for the specified + * interrupt line. The interrupt lines are defined in \b ipu_irq_line enum. + * + * @param irq Interrupt line to get status for. + * + * @param handler Input parameter for address of the handler + * function. + * + * @param irq_flags Flags for interrupt mode. Currently not used. + * + * @param devname Input parameter for string name of driver + * registering the handler. + * + * @param dev_id Input parameter for pointer of data to be passed + * to the handler. + * + * @return This function returns 0 on success or negative error code on + * fail. + */ +int ipu_request_irq(uint32_t irq, + irqreturn_t(*handler) (int, void *), + uint32_t irq_flags, const char *devname, void *dev_id) +{ + unsigned long lock_flags; + + BUG_ON(irq >= IPU_IRQ_COUNT); + + spin_lock_irqsave(&ipu_lock, lock_flags); + + if (ipu_irq_list[irq].handler != NULL) { + dev_err(g_ipu_dev, + "ipu_request_irq - handler already installed on irq %d\n", + irq); + spin_unlock_irqrestore(&ipu_lock, lock_flags); + return -EINVAL; + } + + ipu_irq_list[irq].handler = handler; + ipu_irq_list[irq].flags = irq_flags; + ipu_irq_list[irq].dev_id = dev_id; + ipu_irq_list[irq].name = devname; + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + + ipu_enable_irq(irq); /* enable the interrupt */ + + return 0; +} + +/*! + * This function unregisters an interrupt handler for the specified interrupt + * line. The interrupt lines are defined in \b ipu_irq_line enum. + * + * @param irq Interrupt line to get status for. + * + * @param dev_id Input parameter for pointer of data to be passed + * to the handler. This must match value passed to + * ipu_request_irq(). + * + */ +void ipu_free_irq(uint32_t irq, void *dev_id) +{ + ipu_disable_irq(irq); /* disable the interrupt */ + + if (ipu_irq_list[irq].dev_id == dev_id) { + ipu_irq_list[irq].handler = NULL; + } +} + +/*! + * This function sets the post-filter pause row for h.264 mode. + * + * @param pause_row The last row to process before pausing. + * + * @return This function returns 0 on success or negative error code on + * fail. + * + */ +int32_t ipu_pf_set_pause_row(uint32_t pause_row) +{ + int32_t retval = 0; + uint32_t timeout = 5; + unsigned long lock_flags; + uint32_t reg; + + reg = __raw_readl(IPU_TASKS_STAT); + while ((reg & TSTAT_PF_MASK) && ((reg & TSTAT_PF_H264_PAUSE) == 0)) { + timeout--; + msleep(5); + if (timeout == 0) { + dev_err(g_ipu_dev, "PF Timeout - tstat = 0x%08X\n", + __raw_readl(IPU_TASKS_STAT)); + retval = -ETIMEDOUT; + goto err0; + } + } + + spin_lock_irqsave(&ipu_lock, lock_flags); + + reg = __raw_readl(PF_CONF); + + /* Set the pause row */ + if (pause_row) { + reg &= ~PF_CONF_PAUSE_ROW_MASK; + reg |= PF_CONF_PAUSE_EN | pause_row << PF_CONF_PAUSE_ROW_SHIFT; + } else { + reg &= ~(PF_CONF_PAUSE_EN | PF_CONF_PAUSE_ROW_MASK); + } + __raw_writel(reg, PF_CONF); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + err0: + return retval; +} + +/* Private functions */ +void _ipu_write_param_mem(uint32_t addr, uint32_t * data, uint32_t numWords) +{ + for (; numWords > 0; numWords--) { + dev_dbg(g_ipu_dev, + "write param mem - addr = 0x%08X, data = 0x%08X\n", + addr, *data); + __raw_writel(addr, IPU_IMA_ADDR); + __raw_writel(*data++, IPU_IMA_DATA); + addr++; + if ((addr & 0x7) == 5) { + addr &= ~0x7; /* set to word 0 */ + addr += 8; /* increment to next row */ + } + } +} + +static void _ipu_pf_init(ipu_channel_params_t * params) +{ + uint32_t reg; + + /*Setup the type of filtering required */ + switch (params->mem_pf_mem.operation) { + case PF_MPEG4_DEBLOCK: + case PF_MPEG4_DERING: + case PF_MPEG4_DEBLOCK_DERING: + g_sec_chan_en[IPU_CHAN_ID(MEM_PF_Y_MEM)] = true; + g_sec_chan_en[IPU_CHAN_ID(MEM_PF_U_MEM)] = false; + break; + case PF_H264_DEBLOCK: + g_sec_chan_en[IPU_CHAN_ID(MEM_PF_Y_MEM)] = true; + g_sec_chan_en[IPU_CHAN_ID(MEM_PF_U_MEM)] = true; + break; + default: + g_sec_chan_en[IPU_CHAN_ID(MEM_PF_Y_MEM)] = false; + g_sec_chan_en[IPU_CHAN_ID(MEM_PF_U_MEM)] = false; + return; + break; + } + reg = params->mem_pf_mem.operation; + __raw_writel(reg, PF_CONF); +} + +static void _ipu_pf_uninit(void) +{ + __raw_writel(0x0L, PF_CONF); + g_sec_chan_en[IPU_CHAN_ID(MEM_PF_Y_MEM)] = false; + g_sec_chan_en[IPU_CHAN_ID(MEM_PF_U_MEM)] = false; +} + +uint32_t _ipu_channel_status(ipu_channel_t channel) +{ + uint32_t stat = 0; + uint32_t task_stat_reg = __raw_readl(IPU_TASKS_STAT); + + switch (channel) { + case CSI_MEM: + stat = + (task_stat_reg & TSTAT_CSI2MEM_MASK) >> + TSTAT_CSI2MEM_OFFSET; + break; + case CSI_PRP_VF_ADC: + case CSI_PRP_VF_MEM: + case MEM_PRP_VF_ADC: + case MEM_PRP_VF_MEM: + stat = (task_stat_reg & TSTAT_VF_MASK) >> TSTAT_VF_OFFSET; + break; + case MEM_ROT_VF_MEM: + stat = + (task_stat_reg & TSTAT_VF_ROT_MASK) >> TSTAT_VF_ROT_OFFSET; + break; + case CSI_PRP_ENC_MEM: + case MEM_PRP_ENC_MEM: + stat = (task_stat_reg & TSTAT_ENC_MASK) >> TSTAT_ENC_OFFSET; + break; + case MEM_ROT_ENC_MEM: + stat = + (task_stat_reg & TSTAT_ENC_ROT_MASK) >> + TSTAT_ENC_ROT_OFFSET; + break; + case MEM_PP_ADC: + case MEM_PP_MEM: + stat = (task_stat_reg & TSTAT_PP_MASK) >> TSTAT_PP_OFFSET; + break; + case MEM_ROT_PP_MEM: + stat = + (task_stat_reg & TSTAT_PP_ROT_MASK) >> TSTAT_PP_ROT_OFFSET; + break; + + case MEM_PF_Y_MEM: + case MEM_PF_U_MEM: + case MEM_PF_V_MEM: + stat = (task_stat_reg & TSTAT_PF_MASK) >> TSTAT_PF_OFFSET; + break; + case MEM_SDC_BG: + break; + case MEM_SDC_FG: + break; + case ADC_SYS1: + stat = + (task_stat_reg & TSTAT_ADCSYS1_MASK) >> + TSTAT_ADCSYS1_OFFSET; + break; + case ADC_SYS2: + stat = + (task_stat_reg & TSTAT_ADCSYS2_MASK) >> + TSTAT_ADCSYS2_OFFSET; + break; + case MEM_SDC_MASK: + default: + stat = TASK_STAT_IDLE; + break; + } + return stat; +} + +uint32_t bytes_per_pixel(uint32_t fmt) +{ + switch (fmt) { + case IPU_PIX_FMT_GENERIC: /*generic data */ + case IPU_PIX_FMT_RGB332: + case IPU_PIX_FMT_YUV420P: + case IPU_PIX_FMT_YUV422P: + return 1; + break; + case IPU_PIX_FMT_RGB565: + case IPU_PIX_FMT_YUYV: + case IPU_PIX_FMT_UYVY: + return 2; + break; + case IPU_PIX_FMT_BGR24: + case IPU_PIX_FMT_RGB24: + return 3; + break; + case IPU_PIX_FMT_GENERIC_32: /*generic data */ + case IPU_PIX_FMT_BGR32: + case IPU_PIX_FMT_RGB32: + case IPU_PIX_FMT_ABGR32: + return 4; + break; + default: + return 1; + break; + } + return 0; +} + +ipu_color_space_t format_to_colorspace(uint32_t fmt) +{ + switch (fmt) { + case IPU_PIX_FMT_RGB565: + case IPU_PIX_FMT_BGR24: + case IPU_PIX_FMT_RGB24: + case IPU_PIX_FMT_BGR32: + case IPU_PIX_FMT_RGB32: + return RGB; + break; + + default: + return YCbCr; + break; + } + return RGB; + +} + +static u32 saved_disp3_time_conf; +static bool in_lpdr_mode; +static struct clk *default_ipu_parent_clk; + +int ipu_lowpwr_display_enable(void) +{ + unsigned long rate, div; + struct clk *parent_clk = g_ipu_clk; + + if (in_lpdr_mode || IS_ERR(dfm_clk)) { + return -EINVAL; + } + + if (g_channel_init_mask != (1L << IPU_CHAN_ID(MEM_SDC_BG))) { + dev_err(g_ipu_dev, "LPDR mode requires only SDC BG active.\n"); + return -EINVAL; + } + + default_ipu_parent_clk = clk_get_parent(g_ipu_clk); + in_lpdr_mode = true; + + /* Calculate current pixel clock rate */ + rate = clk_get_rate(g_ipu_clk) * 16; + saved_disp3_time_conf = __raw_readl(DI_DISP3_TIME_CONF); + div = saved_disp3_time_conf & 0xFFF; + rate /= div; + rate *= 4; /* min hsp clk is 4x pixel clk */ + + /* Initialize DFM clock */ + rate = clk_round_rate(dfm_clk, rate); + clk_set_rate(dfm_clk, rate); + clk_enable(dfm_clk); + + /* Wait for next VSYNC */ + __raw_writel(IPUIRQ_2_MASK(IPU_IRQ_SDC_DISP3_VSYNC), + IPUIRQ_2_STATREG(IPU_IRQ_SDC_DISP3_VSYNC)); + while ((__raw_readl(IPUIRQ_2_STATREG(IPU_IRQ_SDC_DISP3_VSYNC)) & + IPUIRQ_2_MASK(IPU_IRQ_SDC_DISP3_VSYNC)) == 0) + msleep_interruptible(1); + + /* Set display clock divider to divide by 4 */ + __raw_writel(((0x8) << 22) | 0x40, DI_DISP3_TIME_CONF); + + clk_set_parent(parent_clk, dfm_clk); + + return 0; +} + +int ipu_lowpwr_display_disable(void) +{ + struct clk *parent_clk = g_ipu_clk; + + if (!in_lpdr_mode || IS_ERR(dfm_clk)) { + return -EINVAL; + } + + if (g_channel_init_mask != (1L << IPU_CHAN_ID(MEM_SDC_BG))) { + dev_err(g_ipu_dev, "LPDR mode requires only SDC BG active.\n"); + return -EINVAL; + } + + in_lpdr_mode = false; + + /* Wait for next VSYNC */ + __raw_writel(IPUIRQ_2_MASK(IPU_IRQ_SDC_DISP3_VSYNC), + IPUIRQ_2_STATREG(IPU_IRQ_SDC_DISP3_VSYNC)); + while ((__raw_readl(IPUIRQ_2_STATREG(IPU_IRQ_SDC_DISP3_VSYNC)) & + IPUIRQ_2_MASK(IPU_IRQ_SDC_DISP3_VSYNC)) == 0) + msleep_interruptible(1); + + __raw_writel(saved_disp3_time_conf, DI_DISP3_TIME_CONF); + clk_set_parent(parent_clk, default_ipu_parent_clk); + clk_disable(dfm_clk); + + return 0; +} + +static int ipu_suspend(struct platform_device *pdev, pm_message_t state) +{ + if (cpu_is_mx31() || cpu_is_mx32()) { + /* work-around for i.Mx31 SR mode after camera related test */ + if (g_csi_used) { + g_ipu_config = __raw_readl(IPU_CONF); + clk_enable(g_ipu_csi_clk); + __raw_writel(0x51, IPU_CONF); + g_channel_init_mask_backup = g_channel_init_mask; + g_channel_init_mask |= 2; + } + } + return 0; +} + +static int ipu_resume(struct platform_device *pdev) +{ + if (cpu_is_mx31() || cpu_is_mx32()) { + /* work-around for i.Mx31 SR mode after camera related test */ + if (g_csi_used) { + __raw_writel(g_ipu_config, IPU_CONF); + clk_disable(g_ipu_csi_clk); + g_channel_init_mask = g_channel_init_mask_backup; + } + } + return 0; +} + +/*! + * This structure contains pointers to the power management callback functions. + */ +static struct platform_driver mxcipu_driver = { + .driver = { + .name = "mxc_ipu", + }, + .probe = ipu_probe, + .suspend = ipu_suspend, + .resume = ipu_resume, +}; + +int32_t __init ipu_gen_init(void) +{ + int32_t ret; + + ret = platform_driver_register(&mxcipu_driver); + return 0; +} + +subsys_initcall(ipu_gen_init); + +static void __exit ipu_gen_uninit(void) +{ + if (g_ipu_irq[0]) + free_irq(g_ipu_irq[0], 0); + if (g_ipu_irq[1]) + free_irq(g_ipu_irq[1], 0); + + platform_driver_unregister(&mxcipu_driver); +} + +module_exit(ipu_gen_uninit); + +EXPORT_SYMBOL(ipu_init_channel); +EXPORT_SYMBOL(ipu_uninit_channel); +EXPORT_SYMBOL(ipu_init_channel_buffer); +EXPORT_SYMBOL(ipu_unlink_channels); +EXPORT_SYMBOL(ipu_update_channel_buffer); +EXPORT_SYMBOL(ipu_select_buffer); +EXPORT_SYMBOL(ipu_link_channels); +EXPORT_SYMBOL(ipu_enable_channel); +EXPORT_SYMBOL(ipu_disable_channel); +EXPORT_SYMBOL(ipu_enable_irq); +EXPORT_SYMBOL(ipu_disable_irq); +EXPORT_SYMBOL(ipu_clear_irq); +EXPORT_SYMBOL(ipu_get_irq_status); +EXPORT_SYMBOL(ipu_request_irq); +EXPORT_SYMBOL(ipu_free_irq); +EXPORT_SYMBOL(ipu_pf_set_pause_row); +EXPORT_SYMBOL(bytes_per_pixel); diff --git a/drivers/mxc/ipu/ipu_csi.c b/drivers/mxc/ipu/ipu_csi.c new file mode 100644 index 000000000000..ece04f003f85 --- /dev/null +++ b/drivers/mxc/ipu/ipu_csi.c @@ -0,0 +1,217 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file ipu_csi.c + * + * @brief IPU CMOS Sensor interface functions + * + * @ingroup IPU + */ +#include <linux/types.h> +#include <linux/init.h> +#include <linux/errno.h> +#include <linux/spinlock.h> +#include <linux/delay.h> +#include <asm/arch/ipu.h> + +#include "ipu_prv.h" +#include "ipu_regs.h" + +static bool gipu_csi_get_mclk_flag = false; +static int csi_mclk_flag = 0; + +extern void gpio_sensor_suspend(bool flag); + +/*! + * ipu_csi_init_interface + * Sets initial values for the CSI registers. + * The width and height of the sensor and the actual frame size will be + * set to the same values. + * @param width Sensor width + * @param height Sensor height + * @param pixel_fmt pixel format + * @param sig ipu_csi_signal_cfg_t structure + * + * @return 0 for success, -EINVAL for error + */ +int32_t +ipu_csi_init_interface(uint16_t width, uint16_t height, uint32_t pixel_fmt, + ipu_csi_signal_cfg_t sig) +{ + uint32_t data = 0; + + /* Set SENS_DATA_FORMAT bits (8 and 9) + RGB or YUV444 is 0 which is current value in data so not set explicitly + This is also the default value if attempts are made to set it to + something invalid. */ + switch (pixel_fmt) { + case IPU_PIX_FMT_UYVY: + data = CSI_SENS_CONF_DATA_FMT_YUV422; + break; + case IPU_PIX_FMT_RGB24: + case IPU_PIX_FMT_BGR24: + data = CSI_SENS_CONF_DATA_FMT_RGB_YUV444; + break; + case IPU_PIX_FMT_GENERIC: + data = CSI_SENS_CONF_DATA_FMT_BAYER; + break; + default: + return -EINVAL; + } + + /* Set the CSI_SENS_CONF register remaining fields */ + data |= sig.data_width << CSI_SENS_CONF_DATA_WIDTH_SHIFT | + sig.data_pol << CSI_SENS_CONF_DATA_POL_SHIFT | + sig.Vsync_pol << CSI_SENS_CONF_VSYNC_POL_SHIFT | + sig.Hsync_pol << CSI_SENS_CONF_HSYNC_POL_SHIFT | + sig.pixclk_pol << CSI_SENS_CONF_PIX_CLK_POL_SHIFT | + sig.ext_vsync << CSI_SENS_CONF_EXT_VSYNC_SHIFT | + sig.clk_mode << CSI_SENS_CONF_SENS_PRTCL_SHIFT | + sig.sens_clksrc << CSI_SENS_CONF_SENS_CLKSRC_SHIFT; + + __raw_writel(data, CSI_SENS_CONF); + + /* Setup frame size */ + __raw_writel(width | height << 16, CSI_SENS_FRM_SIZE); + + __raw_writel(width << 16, CSI_FLASH_STROBE_1); + __raw_writel(height << 16 | 0x22, CSI_FLASH_STROBE_2); + + /* Set CCIR registers */ + if ((sig.clk_mode == IPU_CSI_CLK_MODE_CCIR656_PROGRESSIVE) || + (sig.clk_mode == IPU_CSI_CLK_MODE_CCIR656_INTERLACED)) { + __raw_writel(0x40030, CSI_CCIR_CODE_1); + __raw_writel(0xFF0000, CSI_CCIR_CODE_3); + } + + dev_dbg(g_ipu_dev, "CSI_SENS_CONF = 0x%08X\n", + __raw_readl(CSI_SENS_CONF)); + dev_dbg(g_ipu_dev, "CSI_ACT_FRM_SIZE = 0x%08X\n", + __raw_readl(CSI_ACT_FRM_SIZE)); + + return 0; +} + +/*! + * ipu_csi_flash_strobe + * + * @param flag true to turn on flash strobe + * + * @return 0 for success + */ +void ipu_csi_flash_strobe(bool flag) +{ + if (flag == true) { + __raw_writel(__raw_readl(CSI_FLASH_STROBE_2) | 0x1, + CSI_FLASH_STROBE_2); + } +} + +/*! + * ipu_csi_enable_mclk + * + * @param src enum define which source to control the clk + * CSI_MCLK_VF CSI_MCLK_ENC CSI_MCLK_RAW CSI_MCLK_I2C + * @param flag true to enable mclk, false to disable mclk + * @param wait true to wait 100ms make clock stable, false not wait + * + * @return 0 for success + */ +int32_t ipu_csi_enable_mclk(int src, bool flag, bool wait) +{ + if (flag == true) { + csi_mclk_flag |= src; + } else { + csi_mclk_flag &= ~src; + } + + if (gipu_csi_get_mclk_flag == flag) + return 0; + + if (flag == true) { + clk_enable(g_ipu_csi_clk); + if (wait == true) + msleep(10); + /*printk("enable csi clock from source %d\n", src); */ + gipu_csi_get_mclk_flag = true; + } else if (csi_mclk_flag == 0) { + clk_disable(g_ipu_csi_clk); + /*printk("disable csi clock from source %d\n", src); */ + gipu_csi_get_mclk_flag = flag; + } + + return 0; +} + +/*! + * ipu_csi_read_mclk_flag + * + * @return csi_mclk_flag + */ +int ipu_csi_read_mclk_flag(void) +{ + return csi_mclk_flag; +} + +/*! + * ipu_csi_get_window_size + * + * @param width pointer to window width + * @param height pointer to window height + * + */ +void ipu_csi_get_window_size(uint32_t * width, uint32_t * height) +{ + uint32_t reg; + + reg = __raw_readl(CSI_ACT_FRM_SIZE); + *width = (reg & 0xFFFF) + 1; + *height = (reg >> 16 & 0xFFFF) + 1; +} + +/*! + * ipu_csi_set_window_size + * + * @param width window width + * @param height window height + * + */ +void ipu_csi_set_window_size(uint32_t width, uint32_t height) +{ + __raw_writel((width - 1) | (height - 1) << 16, CSI_ACT_FRM_SIZE); +} + +/*! + * ipu_csi_set_window_pos + * + * @param left uint32 window x start + * @param top uint32 window y start + * + */ +void ipu_csi_set_window_pos(uint32_t left, uint32_t top) +{ + uint32_t temp = __raw_readl(CSI_OUT_FRM_CTRL); + temp &= 0xffff0000; + temp = top | (left << 8) | temp; + __raw_writel(temp, CSI_OUT_FRM_CTRL); +} + +/* Exported symbols for modules. */ +EXPORT_SYMBOL(ipu_csi_set_window_pos); +EXPORT_SYMBOL(ipu_csi_set_window_size); +EXPORT_SYMBOL(ipu_csi_get_window_size); +EXPORT_SYMBOL(ipu_csi_read_mclk_flag); +EXPORT_SYMBOL(ipu_csi_enable_mclk); +EXPORT_SYMBOL(ipu_csi_flash_strobe); +EXPORT_SYMBOL(ipu_csi_init_interface); diff --git a/drivers/mxc/ipu/ipu_device.c b/drivers/mxc/ipu/ipu_device.c new file mode 100644 index 000000000000..c777d143146b --- /dev/null +++ b/drivers/mxc/ipu/ipu_device.c @@ -0,0 +1,600 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file ipu_device.c + * + * @brief This file contains the IPU driver device interface and fops functions. + * + * @ingroup IPU + */ + +#include <linux/types.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/err.h> +#include <linux/spinlock.h> +#include <linux/delay.h> +#include <linux/clk.h> +#include <linux/poll.h> +#include <linux/sched.h> +#include <linux/time.h> +#include <linux/wait.h> +#include <asm/io.h> +#include <asm/arch/ipu.h> + +#include "ipu_prv.h" +#include "ipu_regs.h" +#include "ipu_param_mem.h" + +/* Strucutures and variables for exporting MXC IPU as device*/ + +#define MAX_Q_SIZE 10 + +static int mxc_ipu_major; +static struct class *mxc_ipu_class; + +DEFINE_SPINLOCK(queue_lock); +static DECLARE_MUTEX(user_mutex); + +static wait_queue_head_t waitq; +static int pending_events = 0; +int read_ptr = 0; +int write_ptr = 0; + +typedef struct _event_type { + int irq; + void *dev; +} event_type; + +event_type events[MAX_Q_SIZE]; + +int register_ipu_device(void); + +/* Static functions */ + +int get_events(event_type * p) +{ + unsigned long flags; + int ret = 0; + spin_lock_irqsave(&queue_lock, flags); + if (pending_events != 0) { + *p = events[read_ptr]; + read_ptr++; + pending_events--; + if (read_ptr >= MAX_Q_SIZE) + read_ptr = 0; + } else { + ret = -1; + } + + spin_unlock_irqrestore(&queue_lock, flags); + return ret; +} + +static irqreturn_t mxc_ipu_generic_handler(int irq, void *dev_id) +{ + event_type e; + e.irq = irq; + e.dev = dev_id; + events[write_ptr] = e; + write_ptr++; + if (write_ptr >= MAX_Q_SIZE) + write_ptr = 0; + pending_events++; + /* Wakeup any blocking user context */ + wake_up_interruptible(&waitq); + return IRQ_HANDLED; +} + +static int mxc_ipu_open(struct inode *inode, struct file *file) +{ + int ret = 0; + return ret; +} +static int mxc_ipu_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + int ret = 0; + + switch (cmd) { + + case IPU_INIT_CHANNEL: + { + ipu_channel_parm parm; + if (copy_from_user + (&parm, (ipu_channel_parm *) arg, + sizeof(ipu_channel_parm))) { + return -EFAULT; + } + if (!parm.flag) { + ret = + ipu_init_channel(parm.channel, + &parm.params); + } else { + ret = ipu_init_channel(parm.channel, NULL); + } + } + break; + + case IPU_UNINIT_CHANNEL: + { + ipu_channel_t ch; + int __user *argp = (void __user *)arg; + if (get_user(ch, argp)) + return -EFAULT; + ipu_uninit_channel(ch); + } + break; + + case IPU_INIT_CHANNEL_BUFFER: + { + ipu_channel_buf_parm parm; + if (copy_from_user + (&parm, (ipu_channel_buf_parm *) arg, + sizeof(ipu_channel_buf_parm))) { + return -EFAULT; + } + ret = + ipu_init_channel_buffer(parm.channel, parm.type, + parm.pixel_fmt, + parm.width, parm.height, + parm.stride, + parm.rot_mode, + parm.phyaddr_0, + parm.phyaddr_1, + parm.u_offset, + parm.v_offset); + + } + break; + + case IPU_UPDATE_CHANNEL_BUFFER: + { + ipu_channel_buf_parm parm; + if (copy_from_user + (&parm, (ipu_channel_buf_parm *) arg, + sizeof(ipu_channel_buf_parm))) { + return -EFAULT; + } + if ((parm.phyaddr_0 != (dma_addr_t) NULL) + && (parm.phyaddr_1 == (dma_addr_t) NULL)) { + ret = + ipu_update_channel_buffer(parm.channel, + parm.type, + parm.bufNum, + parm.phyaddr_0); + } else if ((parm.phyaddr_0 == (dma_addr_t) NULL) + && (parm.phyaddr_1 != (dma_addr_t) NULL)) { + ret = + ipu_update_channel_buffer(parm.channel, + parm.type, + parm.bufNum, + parm.phyaddr_1); + } else { + ret = -1; + } + + } + break; + case IPU_SELECT_CHANNEL_BUFFER: + { + ipu_channel_buf_parm parm; + if (copy_from_user + (&parm, (ipu_channel_buf_parm *) arg, + sizeof(ipu_channel_buf_parm))) { + return -EFAULT; + } + ret = + ipu_select_buffer(parm.channel, parm.type, + parm.bufNum); + + } + break; + case IPU_LINK_CHANNELS: + { + ipu_channel_link link; + if (copy_from_user + (&link, (ipu_channel_link *) arg, + sizeof(ipu_channel_link))) { + return -EFAULT; + } + ret = ipu_link_channels(link.src_ch, link.dest_ch); + + } + break; + case IPU_UNLINK_CHANNELS: + { + ipu_channel_link link; + if (copy_from_user + (&link, (ipu_channel_link *) arg, + sizeof(ipu_channel_link))) { + return -EFAULT; + } + ret = ipu_unlink_channels(link.src_ch, link.dest_ch); + + } + break; + case IPU_ENABLE_CHANNEL: + { + ipu_channel_t ch; + int __user *argp = (void __user *)arg; + if (get_user(ch, argp)) + return -EFAULT; + ipu_enable_channel(ch); + } + break; + case IPU_DISABLE_CHANNEL: + { + ipu_channel_info info; + if (copy_from_user + (&info, (ipu_channel_info *) arg, + sizeof(ipu_channel_info))) { + return -EFAULT; + } + ret = ipu_disable_channel(info.channel, info.stop); + } + break; + case IPU_ENABLE_IRQ: + { + uint32_t irq; + int __user *argp = (void __user *)arg; + if (get_user(irq, argp)) + return -EFAULT; + ipu_enable_irq(irq); + } + break; + case IPU_DISABLE_IRQ: + { + uint32_t irq; + int __user *argp = (void __user *)arg; + if (get_user(irq, argp)) + return -EFAULT; + ipu_disable_irq(irq); + } + break; + case IPU_CLEAR_IRQ: + { + uint32_t irq; + int __user *argp = (void __user *)arg; + if (get_user(irq, argp)) + return -EFAULT; + ipu_clear_irq(irq); + } + break; + case IPU_FREE_IRQ: + { + ipu_irq_info info; + if (copy_from_user + (&info, (ipu_irq_info *) arg, + sizeof(ipu_irq_info))) { + return -EFAULT; + } + ipu_free_irq(info.irq, info.dev_id); + } + break; + case IPU_REQUEST_IRQ_STATUS: + { + uint32_t irq; + int __user *argp = (void __user *)arg; + if (get_user(irq, argp)) + return -EFAULT; + ret = ipu_get_irq_status(irq); + } + break; + case IPU_SDC_INIT_PANEL: + { + ipu_sdc_panel_info sinfo; + if (copy_from_user + (&sinfo, (ipu_sdc_panel_info *) arg, + sizeof(ipu_sdc_panel_info))) { + return -EFAULT; + } + ret = + ipu_sdc_init_panel(sinfo.panel, sinfo.pixel_clk, + sinfo.width, sinfo.height, + sinfo.pixel_fmt, + sinfo.hStartWidth, + sinfo.hSyncWidth, + sinfo.hEndWidth, + sinfo.vStartWidth, + sinfo.vSyncWidth, + sinfo.vEndWidth, sinfo.signal); + } + break; + case IPU_SDC_SET_WIN_POS: + { + ipu_sdc_window_pos pos; + if (copy_from_user + (&pos, (ipu_sdc_window_pos *) arg, + sizeof(ipu_sdc_window_pos))) { + return -EFAULT; + } + ret = + ipu_sdc_set_window_pos(pos.channel, pos.x_pos, + pos.y_pos); + + } + break; + case IPU_SDC_SET_GLOBAL_ALPHA: + { + ipu_sdc_global_alpha g; + if (copy_from_user + (&g, (ipu_sdc_global_alpha *) arg, + sizeof(ipu_sdc_global_alpha))) { + return -EFAULT; + } + ret = ipu_sdc_set_global_alpha(g.enable, g.alpha); + } + break; + case IPU_SDC_SET_COLOR_KEY: + { + ipu_sdc_color_key c; + if (copy_from_user + (&c, (ipu_sdc_color_key *) arg, + sizeof(ipu_sdc_color_key))) { + return -EFAULT; + } + ret = + ipu_sdc_set_color_key(c.channel, c.enable, + c.colorKey); + } + break; + case IPU_SDC_SET_BRIGHTNESS: + { + uint8_t b; + int __user *argp = (void __user *)arg; + if (get_user(b, argp)) + return -EFAULT; + ret = ipu_sdc_set_brightness(b); + + } + break; + case IPU_REGISTER_GENERIC_ISR: + { + ipu_event_info info; + if (copy_from_user + (&info, (ipu_event_info *) arg, + sizeof(ipu_event_info))) { + return -EFAULT; + } + ret = + ipu_request_irq(info.irq, mxc_ipu_generic_handler, + 0, "video_sink", info.dev); + } + break; + case IPU_GET_EVENT: + /* User will have to allocate event_type structure and pass the pointer in arg */ + { + event_type ev; + int r = -1; + r = get_events(&ev); + if (r == -1) { + wait_event_interruptible(waitq, + (pending_events != 0)); + r = get_events(&ev); + } + ret = -1; + if (r == 0) { + if (!copy_to_user((event_type *) arg, &ev, + sizeof(event_type))) { + ret = 0; + } + } + } + break; + case IPU_ADC_WRITE_TEMPLATE: + { + ipu_adc_template temp; + if (copy_from_user + (&temp, (ipu_adc_template *) arg, sizeof(temp))) { + return -EFAULT; + } + ret = + ipu_adc_write_template(temp.disp, temp.pCmd, + temp.write); + } + break; + case IPU_ADC_UPDATE: + { + ipu_adc_update update; + if (copy_from_user + (&update, (ipu_adc_update *) arg, sizeof(update))) { + return -EFAULT; + } + ret = + ipu_adc_set_update_mode(update.channel, update.mode, + update.refresh_rate, + update.addr, update.size); + } + break; + case IPU_ADC_SNOOP: + { + ipu_adc_snoop snoop; + if (copy_from_user + (&snoop, (ipu_adc_snoop *) arg, sizeof(snoop))) { + return -EFAULT; + } + ret = + ipu_adc_get_snooping_status(snoop.statl, + snoop.stath); + } + break; + case IPU_ADC_CMD: + { + ipu_adc_cmd cmd; + if (copy_from_user + (&cmd, (ipu_adc_cmd *) arg, sizeof(cmd))) { + return -EFAULT; + } + ret = + ipu_adc_write_cmd(cmd.disp, cmd.type, cmd.cmd, + cmd.params, cmd.numParams); + } + break; + case IPU_ADC_INIT_PANEL: + { + ipu_adc_panel panel; + if (copy_from_user + (&panel, (ipu_adc_panel *) arg, sizeof(panel))) { + return -EFAULT; + } + ret = + ipu_adc_init_panel(panel.disp, panel.width, + panel.height, panel.pixel_fmt, + panel.stride, panel.signal, + panel.addr, panel.vsync_width, + panel.mode); + } + break; + case IPU_ADC_IFC_TIMING: + { + ipu_adc_ifc_timing t; + if (copy_from_user + (&t, (ipu_adc_ifc_timing *) arg, sizeof(t))) { + return -EFAULT; + } + ret = + ipu_adc_init_ifc_timing(t.disp, t.read, + t.cycle_time, t.up_time, + t.down_time, + t.read_latch_time, + t.pixel_clk); + } + break; + case IPU_CSI_INIT_INTERFACE: + { + ipu_csi_interface c; + if (copy_from_user + (&c, (ipu_csi_interface *) arg, sizeof(c))) + return -EFAULT; + ret = + ipu_csi_init_interface(c.width, c.height, + c.pixel_fmt, c.signal); + } + break; + case IPU_CSI_ENABLE_MCLK: + { + ipu_csi_mclk m; + if (copy_from_user(&m, (ipu_csi_mclk *) arg, sizeof(m))) + return -EFAULT; + ret = ipu_csi_enable_mclk(m.src, m.flag, m.wait); + } + break; + case IPU_CSI_READ_MCLK_FLAG: + { + ret = ipu_csi_read_mclk_flag(); + } + break; + case IPU_CSI_FLASH_STROBE: + { + bool strobe; + int __user *argp = (void __user *)arg; + if (get_user(strobe, argp)) + return -EFAULT; + ipu_csi_flash_strobe(strobe); + } + break; + case IPU_CSI_GET_WIN_SIZE: + { + ipu_csi_window_size w; + ipu_csi_get_window_size(&w.width, &w.height); + if (copy_to_user + ((ipu_csi_window_size *) arg, &w, sizeof(w))) + return -EFAULT; + } + break; + case IPU_CSI_SET_WIN_SIZE: + { + ipu_csi_window_size w; + if (copy_from_user + (&w, (ipu_csi_window_size *) arg, sizeof(w))) + return -EFAULT; + ipu_csi_set_window_size(w.width, w.height); + } + break; + case IPU_CSI_SET_WINDOW: + { + ipu_csi_window p; + if (copy_from_user + (&p, (ipu_csi_window *) arg, sizeof(p))) + return -EFAULT; + ipu_csi_set_window_pos(p.left, p.top); + } + break; + case IPU_PF_SET_PAUSE_ROW: + { + uint32_t p; + int __user *argp = (void __user *)arg; + if (get_user(p, argp)) + return -EFAULT; + ret = ipu_pf_set_pause_row(p); + } + break; + default: + break; + + } + return ret; +} + +static int mxc_ipu_release(struct inode *inode, struct file *file) +{ + return 0; +} + +static struct file_operations mxc_ipu_fops = { + .owner = THIS_MODULE, + .open = mxc_ipu_open, + .release = mxc_ipu_release, + .ioctl = mxc_ipu_ioctl +}; + +int register_ipu_device() +{ + int ret = 0; + struct class_device *temp; + mxc_ipu_major = register_chrdev(0, "mxc_ipu", &mxc_ipu_fops); + if (mxc_ipu_major < 0) { + printk(KERN_ERR + "Unable to register Mxc Ipu as a char device\n"); + return mxc_ipu_major; + } + + mxc_ipu_class = class_create(THIS_MODULE, "mxc_ipu"); + if (IS_ERR(mxc_ipu_class)) { + printk(KERN_ERR "Unable to create class for Mxc Ipu\n"); + ret = PTR_ERR(mxc_ipu_class); + goto err1; + } + + temp = + class_device_create(mxc_ipu_class, NULL, MKDEV(mxc_ipu_major, 0), + NULL, "mxc_ipu"); + + if (IS_ERR(temp)) { + printk(KERN_ERR "Unable to create class device for Mxc Ipu\n"); + ret = PTR_ERR(temp); + goto err2; + } + spin_lock_init(&queue_lock); + init_waitqueue_head(&waitq); + return ret; + + err2: + class_destroy(mxc_ipu_class); + err1: + unregister_chrdev(mxc_ipu_major, "mxc_ipu"); + return ret; + +} diff --git a/drivers/mxc/ipu/ipu_ic.c b/drivers/mxc/ipu/ipu_ic.c new file mode 100644 index 000000000000..83fc349deb10 --- /dev/null +++ b/drivers/mxc/ipu/ipu_ic.c @@ -0,0 +1,579 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/* + * @file ipu_ic.c + * + * @brief IPU IC functions + * + * @ingroup IPU + */ +#include <linux/types.h> +#include <linux/init.h> +#include <linux/errno.h> +#include <linux/spinlock.h> +#include <asm/io.h> +#include <asm/arch/ipu.h> + +#include "ipu_prv.h" +#include "ipu_regs.h" +#include "ipu_param_mem.h" + +enum { + IC_TASK_VIEWFINDER, + IC_TASK_ENCODER, + IC_TASK_POST_PROCESSOR +}; + +static void _init_csc(uint8_t ic_task, ipu_color_space_t in_format, + ipu_color_space_t out_format); +static bool _calc_resize_coeffs(uint32_t inSize, uint32_t outSize, + uint32_t * resizeCoeff, + uint32_t * downsizeCoeff); + +void _ipu_ic_enable_task(ipu_channel_t channel) +{ + uint32_t ic_conf; + + ic_conf = __raw_readl(IC_CONF); + switch (channel) { + case CSI_PRP_VF_ADC: + case MEM_PRP_VF_ADC: + case CSI_PRP_VF_MEM: + case MEM_PRP_VF_MEM: + ic_conf |= IC_CONF_PRPVF_EN; + break; + case MEM_ROT_VF_MEM: + ic_conf |= IC_CONF_PRPVF_ROT_EN; + break; + case CSI_PRP_ENC_MEM: + case MEM_PRP_ENC_MEM: + ic_conf |= IC_CONF_PRPENC_EN; + break; + case MEM_ROT_ENC_MEM: + ic_conf |= IC_CONF_PRPENC_ROT_EN; + break; + case MEM_PP_ADC: + case MEM_PP_MEM: + ic_conf |= IC_CONF_PP_EN; + break; + case MEM_ROT_PP_MEM: + ic_conf |= IC_CONF_PP_ROT_EN; + break; + case CSI_MEM: + // ??? + ic_conf |= IC_CONF_RWS_EN | IC_CONF_PRPENC_EN; + break; + default: + break; + } + __raw_writel(ic_conf, IC_CONF); +} + +void _ipu_ic_disable_task(ipu_channel_t channel) +{ + uint32_t ic_conf; + + ic_conf = __raw_readl(IC_CONF); + switch (channel) { + case CSI_PRP_VF_ADC: + case MEM_PRP_VF_ADC: + case CSI_PRP_VF_MEM: + case MEM_PRP_VF_MEM: + ic_conf &= ~IC_CONF_PRPVF_EN; + break; + case MEM_ROT_VF_MEM: + ic_conf &= ~IC_CONF_PRPVF_ROT_EN; + break; + case CSI_PRP_ENC_MEM: + case MEM_PRP_ENC_MEM: + ic_conf &= ~IC_CONF_PRPENC_EN; + break; + case MEM_ROT_ENC_MEM: + ic_conf &= ~IC_CONF_PRPENC_ROT_EN; + break; + case MEM_PP_ADC: + case MEM_PP_MEM: + ic_conf &= ~IC_CONF_PP_EN; + break; + case MEM_ROT_PP_MEM: + ic_conf &= ~IC_CONF_PP_ROT_EN; + break; + case CSI_MEM: + // ??? + ic_conf &= ~(IC_CONF_RWS_EN | IC_CONF_PRPENC_EN); + break; + default: + break; + } + __raw_writel(ic_conf, IC_CONF); +} + +void _ipu_ic_init_prpvf(ipu_channel_params_t * params, bool src_is_csi) +{ + uint32_t reg, ic_conf; + uint32_t downsizeCoeff, resizeCoeff; + ipu_color_space_t in_fmt, out_fmt; + + /* Setup vertical resizing */ + _calc_resize_coeffs(params->mem_prp_vf_mem.in_height, + params->mem_prp_vf_mem.out_height, + &resizeCoeff, &downsizeCoeff); + reg = (downsizeCoeff << 30) | (resizeCoeff << 16); + + /* Setup horizontal resizing */ + _calc_resize_coeffs(params->mem_prp_vf_mem.in_width, + params->mem_prp_vf_mem.out_width, + &resizeCoeff, &downsizeCoeff); + reg |= (downsizeCoeff << 14) | resizeCoeff; + + __raw_writel(reg, IC_PRP_VF_RSC); + + ic_conf = __raw_readl(IC_CONF); + + /* Setup color space conversion */ + in_fmt = format_to_colorspace(params->mem_prp_vf_mem.in_pixel_fmt); + out_fmt = format_to_colorspace(params->mem_prp_vf_mem.out_pixel_fmt); + if (in_fmt == RGB) { + if ((out_fmt == YCbCr) || (out_fmt == YUV)) { + _init_csc(IC_TASK_VIEWFINDER, RGB, out_fmt); + ic_conf |= IC_CONF_PRPVF_CSC1; /* Enable RGB->YCBCR CSC */ + } + } + if ((in_fmt == YCbCr) || (in_fmt == YUV)) { + if (out_fmt == RGB) { + _init_csc(IC_TASK_VIEWFINDER, YCbCr, RGB); + ic_conf |= IC_CONF_PRPVF_CSC1; /* Enable YCBCR->RGB CSC */ + } else { + /* TODO: Support YUV<->YCbCr conversion? */ + } + } + + if (params->mem_prp_vf_mem.graphics_combine_en) { + ic_conf |= IC_CONF_PRPVF_CMB; + + /* need transparent CSC1 conversion */ + _init_csc(IC_TASK_POST_PROCESSOR, RGB, RGB); + ic_conf |= IC_CONF_PRPVF_CSC1; /* Enable RGB->RGB CSC */ + + if (params->mem_prp_vf_mem.global_alpha_en) { + ic_conf |= IC_CONF_IC_GLB_LOC_A; + } else { + ic_conf &= ~IC_CONF_IC_GLB_LOC_A; + } + + if (params->mem_prp_vf_mem.key_color_en) { + ic_conf |= IC_CONF_KEY_COLOR_EN; + } else { + ic_conf &= ~IC_CONF_KEY_COLOR_EN; + } + } else { + ic_conf &= ~IC_CONF_PP_CMB; + } + +#ifndef CONFIG_VIRTIO_SUPPORT /* Setting RWS_EN doesn't work in Virtio */ + if (src_is_csi) { + ic_conf &= ~IC_CONF_RWS_EN; + } else { + ic_conf |= IC_CONF_RWS_EN; + } +#endif + __raw_writel(ic_conf, IC_CONF); +} + +void _ipu_ic_uninit_prpvf(void) +{ + uint32_t reg; + + reg = __raw_readl(IC_CONF); + reg &= ~(IC_CONF_PRPVF_EN | IC_CONF_PRPVF_CMB | + IC_CONF_PRPVF_CSC2 | IC_CONF_PRPVF_CSC1); + __raw_writel(reg, IC_CONF); +} + +void _ipu_ic_init_rotate_vf(ipu_channel_params_t * params) +{ +} + +void _ipu_ic_uninit_rotate_vf(void) +{ + uint32_t reg; + reg = __raw_readl(IC_CONF); + reg &= ~IC_CONF_PRPVF_ROT_EN; + __raw_writel(reg, IC_CONF); +} + +void _ipu_ic_init_csi(ipu_channel_params_t * params) +{ + uint32_t reg; + reg = __raw_readl(IC_CONF); + reg &= ~IC_CONF_CSI_MEM_WR_EN; + __raw_writel(reg, IC_CONF); +} + +void _ipu_ic_uninit_csi(void) +{ + uint32_t reg; + reg = __raw_readl(IC_CONF); + reg &= ~(IC_CONF_RWS_EN | IC_CONF_PRPENC_EN); + __raw_writel(reg, IC_CONF); +} + +void _ipu_ic_init_prpenc(ipu_channel_params_t * params, bool src_is_csi) +{ + uint32_t reg, ic_conf; + uint32_t downsizeCoeff, resizeCoeff; + ipu_color_space_t in_fmt, out_fmt; + + /* Setup vertical resizing */ + _calc_resize_coeffs(params->mem_prp_enc_mem.in_height, + params->mem_prp_enc_mem.out_height, + &resizeCoeff, &downsizeCoeff); + reg = (downsizeCoeff << 30) | (resizeCoeff << 16); + + /* Setup horizontal resizing */ + _calc_resize_coeffs(params->mem_prp_enc_mem.in_width, + params->mem_prp_enc_mem.out_width, + &resizeCoeff, &downsizeCoeff); + reg |= (downsizeCoeff << 14) | resizeCoeff; + + __raw_writel(reg, IC_PRP_ENC_RSC); + + ic_conf = __raw_readl(IC_CONF); + + /* Setup color space conversion */ + in_fmt = format_to_colorspace(params->mem_prp_enc_mem.in_pixel_fmt); + out_fmt = format_to_colorspace(params->mem_prp_enc_mem.out_pixel_fmt); + if (in_fmt == RGB) { + if ((out_fmt == YCbCr) || (out_fmt == YUV)) { + /* TODO: ERROR! */ + } + } + if ((in_fmt == YCbCr) || (in_fmt == YUV)) { + if (out_fmt == RGB) { + _init_csc(IC_TASK_ENCODER, YCbCr, RGB); + ic_conf |= IC_CONF_PRPENC_CSC1; /* Enable YCBCR->RGB CSC */ + } else { + /* TODO: Support YUV<->YCbCr conversion? */ + } + } + + if (src_is_csi) { + ic_conf &= ~IC_CONF_RWS_EN; + } else { + ic_conf |= IC_CONF_RWS_EN; + } + + __raw_writel(ic_conf, IC_CONF); +} + +void _ipu_ic_uninit_prpenc(void) +{ + uint32_t reg; + + reg = __raw_readl(IC_CONF); + reg &= ~(IC_CONF_PRPENC_EN | IC_CONF_PRPENC_CSC1); + __raw_writel(reg, IC_CONF); +} + +void _ipu_ic_init_rotate_enc(ipu_channel_params_t * params) +{ +} + +void _ipu_ic_uninit_rotate_enc(void) +{ + uint32_t reg; + + reg = __raw_readl(IC_CONF); + reg &= ~(IC_CONF_PRPENC_ROT_EN); + __raw_writel(reg, IC_CONF); +} + +void _ipu_ic_init_pp(ipu_channel_params_t * params) +{ + uint32_t reg, ic_conf; + uint32_t downsizeCoeff, resizeCoeff; + ipu_color_space_t in_fmt, out_fmt; + + /* Setup vertical resizing */ + _calc_resize_coeffs(params->mem_pp_mem.in_height, + params->mem_pp_mem.out_height, + &resizeCoeff, &downsizeCoeff); + reg = (downsizeCoeff << 30) | (resizeCoeff << 16); + + /* Setup horizontal resizing */ + _calc_resize_coeffs(params->mem_pp_mem.in_width, + params->mem_pp_mem.out_width, + &resizeCoeff, &downsizeCoeff); + reg |= (downsizeCoeff << 14) | resizeCoeff; + + __raw_writel(reg, IC_PP_RSC); + + ic_conf = __raw_readl(IC_CONF); + + /* Setup color space conversion */ + in_fmt = format_to_colorspace(params->mem_pp_mem.in_pixel_fmt); + out_fmt = format_to_colorspace(params->mem_pp_mem.out_pixel_fmt); + if (in_fmt == RGB) { + if ((out_fmt == YCbCr) || (out_fmt == YUV)) { + _init_csc(IC_TASK_POST_PROCESSOR, RGB, out_fmt); + ic_conf |= IC_CONF_PP_CSC2; /* Enable RGB->YCBCR CSC */ + } + } + if ((in_fmt == YCbCr) || (in_fmt == YUV)) { + if (out_fmt == RGB) { + _init_csc(IC_TASK_POST_PROCESSOR, YCbCr, RGB); + ic_conf |= IC_CONF_PP_CSC1; /* Enable YCBCR->RGB CSC */ + } else { + /* TODO: Support YUV<->YCbCr conversion? */ + } + } + + if (params->mem_pp_mem.graphics_combine_en) { + ic_conf |= IC_CONF_PP_CMB; + + /* need transparent CSC1 conversion */ + _init_csc(IC_TASK_POST_PROCESSOR, RGB, RGB); + ic_conf |= IC_CONF_PP_CSC1; /* Enable RGB->RGB CSC */ + + if (params->mem_pp_mem.global_alpha_en) { + ic_conf |= IC_CONF_IC_GLB_LOC_A; + } else { + ic_conf &= ~IC_CONF_IC_GLB_LOC_A; + } + + if (params->mem_pp_mem.key_color_en) { + ic_conf |= IC_CONF_KEY_COLOR_EN; + } else { + ic_conf &= ~IC_CONF_KEY_COLOR_EN; + } + } else { + ic_conf &= ~IC_CONF_PP_CMB; + } + + __raw_writel(ic_conf, IC_CONF); +} + +void _ipu_ic_uninit_pp(void) +{ + uint32_t reg; + + reg = __raw_readl(IC_CONF); + reg &= ~(IC_CONF_PP_EN | IC_CONF_PP_CSC1 | IC_CONF_PP_CSC2 | + IC_CONF_PP_CMB); + __raw_writel(reg, IC_CONF); +} + +void _ipu_ic_init_rotate_pp(ipu_channel_params_t * params) +{ +} + +void _ipu_ic_uninit_rotate_pp(void) +{ + uint32_t reg; + reg = __raw_readl(IC_CONF); + reg &= ~IC_CONF_PP_ROT_EN; + __raw_writel(reg, IC_CONF); +} + +static void _init_csc(uint8_t ic_task, ipu_color_space_t in_format, + ipu_color_space_t out_format) +{ +/* Y = R * .299 + G * .587 + B * .114; + U = R * -.169 + G * -.332 + B * .500 + 128.; + V = R * .500 + G * -.419 + B * -.0813 + 128.;*/ + static const uint32_t rgb2ycbcr_coeff[4][3] = { + {0x004D, 0x0096, 0x001D}, + {0x01D5, 0x01AB, 0x0080}, + {0x0080, 0x0195, 0x01EB}, + {0x0000, 0x0200, 0x0200}, /* A0, A1, A2 */ + }; + + /* transparent RGB->RGB matrix for combining + */ + static const uint32_t rgb2rgb_coeff[4][3] = { + {0x0080, 0x0000, 0x0000}, + {0x0000, 0x0080, 0x0000}, + {0x0000, 0x0000, 0x0080}, + {0x0000, 0x0000, 0x0000}, /* A0, A1, A2 */ + }; + +/* R = (1.164 * (Y - 16)) + (1.596 * (Cr - 128)); + G = (1.164 * (Y - 16)) - (0.392 * (Cb - 128)) - (0.813 * (Cr - 128)); + B = (1.164 * (Y - 16)) + (2.017 * (Cb - 128); */ + static const uint32_t ycbcr2rgb_coeff[4][3] = { + {149, 0, 204}, + {149, 462, 408}, + {149, 255, 0}, + {8192 - 446, 266, 8192 - 554}, /* A0, A1, A2 */ + }; + + uint32_t param[2]; + uint32_t address = 0; + + if (ic_task == IC_TASK_VIEWFINDER) { + address = 0x5A5 << 3; + } else if (ic_task == IC_TASK_ENCODER) { + address = 0x2D1 << 3; + } else if (ic_task == IC_TASK_POST_PROCESSOR) { + address = 0x87C << 3; + } else { + BUG(); + } + + if ((in_format == YCbCr) && (out_format == RGB)) { + /* Init CSC1 (YCbCr->RGB) */ + param[0] = + (ycbcr2rgb_coeff[3][0] << 27) | (ycbcr2rgb_coeff[0][0] << + 18) | + (ycbcr2rgb_coeff[1][1] << 9) | ycbcr2rgb_coeff[2][2]; + /* scale = 2, sat = 0 */ + param[1] = (ycbcr2rgb_coeff[3][0] >> 5) | (2L << (40 - 32)); + _ipu_write_param_mem(address, param, 2); + dev_dbg(g_ipu_dev, + "addr 0x%04X: word0 = 0x%08X, word1 = 0x%08X\n", + address, param[0], param[1]); + + param[0] = + (ycbcr2rgb_coeff[3][1] << 27) | (ycbcr2rgb_coeff[0][1] << + 18) | + (ycbcr2rgb_coeff[1][0] << 9) | ycbcr2rgb_coeff[2][0]; + param[1] = (ycbcr2rgb_coeff[3][1] >> 5); + address += 1L << 3; + _ipu_write_param_mem(address, param, 2); + dev_dbg(g_ipu_dev, + "addr 0x%04X: word0 = 0x%08X, word1 = 0x%08X\n", + address, param[0], param[1]); + + param[0] = + (ycbcr2rgb_coeff[3][2] << 27) | (ycbcr2rgb_coeff[0][2] << + 18) | + (ycbcr2rgb_coeff[1][2] << 9) | ycbcr2rgb_coeff[2][1]; + param[1] = (ycbcr2rgb_coeff[3][2] >> 5); + address += 1L << 3; + _ipu_write_param_mem(address, param, 2); + dev_dbg(g_ipu_dev, + "addr 0x%04X: word0 = 0x%08X, word1 = 0x%08X\n", + address, param[0], param[1]); + } else if ((in_format == RGB) && (out_format == YCbCr)) { + /* Init CSC1 (RGB->YCbCr) */ + param[0] = + (rgb2ycbcr_coeff[3][0] << 27) | (rgb2ycbcr_coeff[0][0] << + 18) | + (rgb2ycbcr_coeff[1][1] << 9) | rgb2ycbcr_coeff[2][2]; + /* scale = 1, sat = 0 */ + param[1] = (rgb2ycbcr_coeff[3][0] >> 5) | (1UL << 8); + _ipu_write_param_mem(address, param, 2); + dev_dbg(g_ipu_dev, + "addr 0x%04X: word0 = 0x%08X, word1 = 0x%08X\n", + address, param[0], param[1]); + + param[0] = + (rgb2ycbcr_coeff[3][1] << 27) | (rgb2ycbcr_coeff[0][1] << + 18) | + (rgb2ycbcr_coeff[1][0] << 9) | rgb2ycbcr_coeff[2][0]; + param[1] = (rgb2ycbcr_coeff[3][1] >> 5); + address += 1L << 3; + _ipu_write_param_mem(address, param, 2); + dev_dbg(g_ipu_dev, + "addr 0x%04X: word0 = 0x%08X, word1 = 0x%08X\n", + address, param[0], param[1]); + + param[0] = + (rgb2ycbcr_coeff[3][2] << 27) | (rgb2ycbcr_coeff[0][2] << + 18) | + (rgb2ycbcr_coeff[1][2] << 9) | rgb2ycbcr_coeff[2][1]; + param[1] = (rgb2ycbcr_coeff[3][2] >> 5); + address += 1L << 3; + _ipu_write_param_mem(address, param, 2); + dev_dbg(g_ipu_dev, + "addr 0x%04X: word0 = 0x%08X, word1 = 0x%08X\n", + address, param[0], param[1]); + } else if ((in_format == RGB) && (out_format == RGB)) { + /* Init CSC1 */ + param[0] = + (rgb2rgb_coeff[3][0] << 27) | (rgb2rgb_coeff[0][0] << 18) | + (rgb2rgb_coeff[1][1] << 9) | rgb2rgb_coeff[2][2]; + /* scale = 2, sat = 0 */ + param[1] = (rgb2rgb_coeff[3][0] >> 5) | (2UL << 8); + + _ipu_write_param_mem(address, param, 2); + + dev_dbg(g_ipu_dev, + "addr 0x%04X: word0 = 0x%08X, word1 = 0x%08X\n", + address, param[0], param[1]); + + param[0] = + (rgb2rgb_coeff[3][1] << 27) | (rgb2rgb_coeff[0][1] << 18) | + (rgb2rgb_coeff[1][0] << 9) | rgb2rgb_coeff[2][0]; + param[1] = (rgb2rgb_coeff[3][1] >> 5); + + address += 1L << 3; + _ipu_write_param_mem(address, param, 2); + + dev_dbg(g_ipu_dev, + "addr 0x%04X: word0 = 0x%08X, word1 = 0x%08X\n", + address, param[0], param[1]); + + param[0] = + (rgb2rgb_coeff[3][2] << 27) | (rgb2rgb_coeff[0][2] << 18) | + (rgb2rgb_coeff[1][2] << 9) | rgb2rgb_coeff[2][1]; + param[1] = (rgb2rgb_coeff[3][2] >> 5); + + address += 1L << 3; + _ipu_write_param_mem(address, param, 2); + + dev_dbg(g_ipu_dev, + "addr 0x%04X: word0 = 0x%08X, word1 = 0x%08X\n", + address, param[0], param[1]); + } else { + dev_err(g_ipu_dev, "Unsupported color space conversion\n"); + } +} + +static bool _calc_resize_coeffs(uint32_t inSize, uint32_t outSize, + uint32_t * resizeCoeff, + uint32_t * downsizeCoeff) +{ + uint32_t tempSize; + uint32_t tempDownsize; + + /* Cannot downsize more than 8:1 */ + if ((outSize << 3) < inSize) { + return false; + } + /* compute downsizing coefficient */ + tempDownsize = 0; + tempSize = inSize; + while ((tempSize >= outSize * 2) && (tempDownsize < 2)) { + tempSize >>= 1; + tempDownsize++; + } + *downsizeCoeff = tempDownsize; + + /* compute resizing coefficient using the following equation: + resizeCoeff = M*(SI -1)/(SO - 1) + where M = 2^13, SI - input size, SO - output size */ + *resizeCoeff = (8192L * (tempSize - 1)) / (outSize - 1); + if (*resizeCoeff >= 16384L) { + dev_err(g_ipu_dev, "Warning! Overflow on resize coeff.\n"); + *resizeCoeff = 0x3FFF; + } + + dev_dbg(g_ipu_dev, "resizing from %u -> %u pixels, " + "downsize=%u, resize=%u.%lu (reg=%u)\n", inSize, outSize, + *downsizeCoeff, (*resizeCoeff >= 8192L) ? 1 : 0, + ((*resizeCoeff & 0x1FFF) * 10000L) / 8192L, *resizeCoeff); + + return true; +} diff --git a/drivers/mxc/ipu/ipu_param_mem.h b/drivers/mxc/ipu/ipu_param_mem.h new file mode 100644 index 000000000000..07bd03a81e3f --- /dev/null +++ b/drivers/mxc/ipu/ipu_param_mem.h @@ -0,0 +1,176 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef __INCLUDE_IPU_PARAM_MEM_H__ +#define __INCLUDE_IPU_PARAM_MEM_H__ + +#include <linux/types.h> + +static __inline void _ipu_ch_param_set_size(uint32_t * params, + uint32_t pixel_fmt, uint16_t width, + uint16_t height, uint16_t stride, + uint32_t u, uint32_t v) +{ + uint32_t u_offset = 0; + uint32_t v_offset = 0; + memset(params, 0, 40); + + params[3] = + (uint32_t) ((width - 1) << 12) | ((uint32_t) (height - 1) << 24); + params[4] = (uint32_t) (height - 1) >> 8; + params[7] = (uint32_t) (stride - 1) << 3; + + switch (pixel_fmt) { + case IPU_PIX_FMT_GENERIC: + /*Represents 8-bit Generic data */ + params[7] |= 3 | (7UL << (81 - 64)) | (31L << (89 - 64)); /* BPP & PFS */ + params[8] = 2; /* SAT = use 32-bit access */ + break; + case IPU_PIX_FMT_GENERIC_32: + /*Represents 32-bit Generic data */ + params[7] |= (7UL << (81 - 64)) | (7L << (89 - 64)); /* BPP & PFS */ + params[8] = 2; /* SAT = use 32-bit access */ + break; + case IPU_PIX_FMT_RGB565: + params[7] |= 2L | (4UL << (81 - 64)) | (7L << (89 - 64)); /* BPP & PFS */ + params[8] = 2 | /* SAT = 32-bit access */ + (0UL << (99 - 96)) | /* Red bit offset */ + (5UL << (104 - 96)) | /* Green bit offset */ + (11UL << (109 - 96)) | /* Blue bit offset */ + (16UL << (114 - 96)) | /* Alpha bit offset */ + (4UL << (119 - 96)) | /* Red bit width - 1 */ + (5UL << (122 - 96)) | /* Green bit width - 1 */ + (4UL << (125 - 96)); /* Blue bit width - 1 */ + break; + case IPU_PIX_FMT_BGR24: /* 24 BPP & RGB PFS */ + params[7] |= 1 | (4UL << (81 - 64)) | (7L << (89 - 64)); + params[8] = 2 | /* SAT = 32-bit access */ + (8UL << (104 - 96)) | /* Green bit offset */ + (16UL << (109 - 96)) | /* Blue bit offset */ + (24UL << (114 - 96)) | /* Alpha bit offset */ + (7UL << (119 - 96)) | /* Red bit width - 1 */ + (7UL << (122 - 96)) | /* Green bit width - 1 */ + (uint32_t) (7UL << (125 - 96)); /* Blue bit width - 1 */ + break; + case IPU_PIX_FMT_RGB24: /* 24 BPP & RGB PFS */ + params[7] |= 1 | (4UL << (81 - 64)) | (7L << (89 - 64)); + params[8] = 2 | /* SAT = 32-bit access */ + (16UL << (99 - 96)) | /* Red bit offset */ + (8UL << (104 - 96)) | /* Green bit offset */ + (0UL << (109 - 96)) | /* Blue bit offset */ + (24UL << (114 - 96)) | /* Alpha bit offset */ + (7UL << (119 - 96)) | /* Red bit width - 1 */ + (7UL << (122 - 96)) | /* Green bit width - 1 */ + (uint32_t) (7UL << (125 - 96)); /* Blue bit width - 1 */ + break; + case IPU_PIX_FMT_BGRA32: + case IPU_PIX_FMT_BGR32: + /* BPP & pixel fmt */ + params[7] |= 0 | (4UL << (81 - 64)) | (7 << (89 - 64)); + params[8] = 2 | /* SAT = 32-bit access */ + (8UL << (99 - 96)) | /* Red bit offset */ + (16UL << (104 - 96)) | /* Green bit offset */ + (24UL << (109 - 96)) | /* Blue bit offset */ + (0UL << (114 - 96)) | /* Alpha bit offset */ + (7UL << (119 - 96)) | /* Red bit width - 1 */ + (7UL << (122 - 96)) | /* Green bit width - 1 */ + (uint32_t) (7UL << (125 - 96)); /* Blue bit width - 1 */ + params[9] = 7; /* Alpha bit width - 1 */ + break; + case IPU_PIX_FMT_RGBA32: + case IPU_PIX_FMT_RGB32: + /* BPP & pixel fmt */ + params[7] |= 0 | (4UL << (81 - 64)) | (7 << (89 - 64)); + params[8] = 2 | /* SAT = 32-bit access */ + (24UL << (99 - 96)) | /* Red bit offset */ + (16UL << (104 - 96)) | /* Green bit offset */ + (8UL << (109 - 96)) | /* Blue bit offset */ + (0UL << (114 - 96)) | /* Alpha bit offset */ + (7UL << (119 - 96)) | /* Red bit width - 1 */ + (7UL << (122 - 96)) | /* Green bit width - 1 */ + (uint32_t) (7UL << (125 - 96)); /* Blue bit width - 1 */ + params[9] = 7; /* Alpha bit width - 1 */ + break; + case IPU_PIX_FMT_ABGR32: + /* BPP & pixel fmt */ + params[7] |= 0 | (4UL << (81 - 64)) | (7 << (89 - 64)); + params[8] = 2 | /* SAT = 32-bit access */ + (0UL << (99 - 96)) | /* Alpha bit offset */ + (8UL << (104 - 96)) | /* Blue bit offset */ + (16UL << (109 - 96)) | /* Green bit offset */ + (24UL << (114 - 96)) | /* Red bit offset */ + (7UL << (119 - 96)) | /* Alpha bit width - 1 */ + (7UL << (122 - 96)) | /* Blue bit width - 1 */ + (uint32_t) (7UL << (125 - 96)); /* Green bit width - 1 */ + params[9] = 7; /* Red bit width - 1 */ + break; + case IPU_PIX_FMT_UYVY: + /* BPP & pixel format */ + params[7] |= 2 | (6UL << 17) | (7 << (89 - 64)); + params[8] = 2; /* SAT = 32-bit access */ + break; + case IPU_PIX_FMT_YUV420P2: + case IPU_PIX_FMT_YUV420P: + /* BPP & pixel format */ + params[7] |= 3 | (3UL << 17) | (7 << (89 - 64)); + params[8] = 2; /* SAT = 32-bit access */ + u_offset = (u == 0) ? stride * height : u; + v_offset = (v == 0) ? u_offset + u_offset / 4 : v; + break; + case IPU_PIX_FMT_YVU422P: + /* BPP & pixel format */ + params[7] |= 3 | (2UL << 17) | (7 << (89 - 64)); + params[8] = 2; /* SAT = 32-bit access */ + v_offset = (v == 0) ? stride * height : v; + u_offset = (u == 0) ? v_offset + v_offset / 2 : u; + break; + case IPU_PIX_FMT_YUV422P: + /* BPP & pixel format */ + params[7] |= 3 | (2UL << 17) | (7 << (89 - 64)); + params[8] = 2; /* SAT = 32-bit access */ + u_offset = (u == 0) ? stride * height : u; + v_offset = (v == 0) ? u_offset + u_offset / 2 : v; + break; + default: + dev_err(g_ipu_dev, "mxc ipu: unimplemented pixel format\n"); + break; + } + + params[1] = (1UL << (46 - 32)) | (u_offset << (53 - 32)); + params[2] = u_offset >> (64 - 53); + params[2] |= v_offset << (79 - 64); + params[3] |= v_offset >> (96 - 79); +} + +static __inline void _ipu_ch_param_set_burst_size(uint32_t * params, + uint16_t burst_pixels) +{ + params[7] &= ~(0x3FL << (89 - 64)); + params[7] |= (uint32_t) (burst_pixels - 1) << (89 - 64); +}; + +static __inline void _ipu_ch_param_set_buffer(uint32_t * params, + dma_addr_t buf0, dma_addr_t buf1) +{ + params[5] = buf0; + params[6] = buf1; +}; + +static __inline void _ipu_ch_param_set_rotation(uint32_t * params, + ipu_rotate_mode_t rot) +{ + params[7] |= (uint32_t) rot << (84 - 64); +}; + +void _ipu_write_param_mem(uint32_t addr, uint32_t * data, uint32_t numWords); + +#endif diff --git a/drivers/mxc/ipu/ipu_prv.h b/drivers/mxc/ipu/ipu_prv.h new file mode 100644 index 000000000000..1da41734f36e --- /dev/null +++ b/drivers/mxc/ipu/ipu_prv.h @@ -0,0 +1,59 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef __INCLUDE_IPU_PRV_H__ +#define __INCLUDE_IPU_PRV_H__ + +#include <linux/types.h> +#include <linux/device.h> +#include <linux/clk.h> +#include <linux/interrupt.h> +#include <asm/arch/hardware.h> + +/* Globals */ +extern struct device *g_ipu_dev; +extern spinlock_t ipu_lock; +extern struct clk *g_ipu_clk; +extern struct clk *g_ipu_csi_clk; + +int register_ipu_device(void); +ipu_color_space_t format_to_colorspace(uint32_t fmt); + +uint32_t _ipu_channel_status(ipu_channel_t channel); + +void _ipu_sdc_fg_init(ipu_channel_params_t * params); +uint32_t _ipu_sdc_fg_uninit(void); +void _ipu_sdc_bg_init(ipu_channel_params_t * params); +uint32_t _ipu_sdc_bg_uninit(void); + +void _ipu_ic_enable_task(ipu_channel_t channel); +void _ipu_ic_disable_task(ipu_channel_t channel); +void _ipu_ic_init_prpvf(ipu_channel_params_t * params, bool src_is_csi); +void _ipu_ic_uninit_prpvf(void); +void _ipu_ic_init_rotate_vf(ipu_channel_params_t * params); +void _ipu_ic_uninit_rotate_vf(void); +void _ipu_ic_init_csi(ipu_channel_params_t * params); +void _ipu_ic_uninit_csi(void); +void _ipu_ic_init_prpenc(ipu_channel_params_t * params, bool src_is_csi); +void _ipu_ic_uninit_prpenc(void); +void _ipu_ic_init_rotate_enc(ipu_channel_params_t * params); +void _ipu_ic_uninit_rotate_enc(void); +void _ipu_ic_init_pp(ipu_channel_params_t * params); +void _ipu_ic_uninit_pp(void); +void _ipu_ic_init_rotate_pp(ipu_channel_params_t * params); +void _ipu_ic_uninit_rotate_pp(void); + +int32_t _ipu_adc_init_channel(ipu_channel_t chan, display_port_t disp, + mcu_mode_t cmd, int16_t x_pos, int16_t y_pos); +int32_t _ipu_adc_uninit_channel(ipu_channel_t chan); + +#endif /* __INCLUDE_IPU_PRV_H__ */ diff --git a/drivers/mxc/ipu/ipu_regs.h b/drivers/mxc/ipu/ipu_regs.h new file mode 100644 index 000000000000..d3ac76ea7834 --- /dev/null +++ b/drivers/mxc/ipu/ipu_regs.h @@ -0,0 +1,396 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/* + * @file ipu_regs.h + * + * @brief IPU Register definitions + * + * @ingroup IPU + */ +#ifndef __IPU_REGS_INCLUDED__ +#define __IPU_REGS_INCLUDED__ + +#define IPU_REG_BASE IO_ADDRESS(IPU_CTRL_BASE_ADDR) + +/* Register addresses */ +/* IPU Common registers */ +#define IPU_CONF (IPU_REG_BASE + 0x0000) +#define IPU_CHA_BUF0_RDY (IPU_REG_BASE + 0x0004) +#define IPU_CHA_BUF1_RDY (IPU_REG_BASE + 0x0008) +#define IPU_CHA_DB_MODE_SEL (IPU_REG_BASE + 0x000C) +#define IPU_CHA_CUR_BUF (IPU_REG_BASE + 0x0010) +#define IPU_FS_PROC_FLOW (IPU_REG_BASE + 0x0014) +#define IPU_FS_DISP_FLOW (IPU_REG_BASE + 0x0018) +#define IPU_TASKS_STAT (IPU_REG_BASE + 0x001C) +#define IPU_IMA_ADDR (IPU_REG_BASE + 0x0020) +#define IPU_IMA_DATA (IPU_REG_BASE + 0x0024) +#define IPU_INT_CTRL_1 (IPU_REG_BASE + 0x0028) +#define IPU_INT_CTRL_2 (IPU_REG_BASE + 0x002C) +#define IPU_INT_CTRL_3 (IPU_REG_BASE + 0x0030) +#define IPU_INT_CTRL_4 (IPU_REG_BASE + 0x0034) +#define IPU_INT_CTRL_5 (IPU_REG_BASE + 0x0038) +#define IPU_INT_STAT_1 (IPU_REG_BASE + 0x003C) +#define IPU_INT_STAT_2 (IPU_REG_BASE + 0x0040) +#define IPU_INT_STAT_3 (IPU_REG_BASE + 0x0044) +#define IPU_INT_STAT_4 (IPU_REG_BASE + 0x0048) +#define IPU_INT_STAT_5 (IPU_REG_BASE + 0x004C) +#define IPU_BRK_CTRL_1 (IPU_REG_BASE + 0x0050) +#define IPU_BRK_CTRL_2 (IPU_REG_BASE + 0x0054) +#define IPU_BRK_STAT (IPU_REG_BASE + 0x0058) +#define IPU_DIAGB_CTRL (IPU_REG_BASE + 0x005C) +/* CMOS Sensor Interface Registers */ +#define CSI_SENS_CONF (IPU_REG_BASE + 0x0060) +#define CSI_SENS_FRM_SIZE (IPU_REG_BASE + 0x0064) +#define CSI_ACT_FRM_SIZE (IPU_REG_BASE + 0x0068) +#define CSI_OUT_FRM_CTRL (IPU_REG_BASE + 0x006C) +#define CSI_TST_CTRL (IPU_REG_BASE + 0x0070) +#define CSI_CCIR_CODE_1 (IPU_REG_BASE + 0x0074) +#define CSI_CCIR_CODE_2 (IPU_REG_BASE + 0x0078) +#define CSI_CCIR_CODE_3 (IPU_REG_BASE + 0x007C) +#define CSI_FLASH_STROBE_1 (IPU_REG_BASE + 0x0080) +#define CSI_FLASH_STROBE_2 (IPU_REG_BASE + 0x0084) +/* Image Converter Registers */ +#define IC_CONF (IPU_REG_BASE + 0x0088) +#define IC_PRP_ENC_RSC (IPU_REG_BASE + 0x008C) +#define IC_PRP_VF_RSC (IPU_REG_BASE + 0x0090) +#define IC_PP_RSC (IPU_REG_BASE + 0x0094) +#define IC_CMBP_1 (IPU_REG_BASE + 0x0098) +#define IC_CMBP_2 (IPU_REG_BASE + 0x009C) +#define PF_CONF (IPU_REG_BASE + 0x00A0) +#define IDMAC_CONF (IPU_REG_BASE + 0x00A4) +#define IDMAC_CHA_EN (IPU_REG_BASE + 0x00A8) +#define IDMAC_CHA_PRI (IPU_REG_BASE + 0x00AC) +#define IDMAC_CHA_BUSY (IPU_REG_BASE + 0x00B0) +/* SDC Registers */ +#define SDC_COM_CONF (IPU_REG_BASE + 0x00B4) +#define SDC_GW_CTRL (IPU_REG_BASE + 0x00B8) +#define SDC_FG_POS (IPU_REG_BASE + 0x00BC) +#define SDC_BG_POS (IPU_REG_BASE + 0x00C0) +#define SDC_CUR_POS (IPU_REG_BASE + 0x00C4) +#define SDC_PWM_CTRL (IPU_REG_BASE + 0x00C8) +#define SDC_CUR_MAP (IPU_REG_BASE + 0x00CC) +#define SDC_HOR_CONF (IPU_REG_BASE + 0x00D0) +#define SDC_VER_CONF (IPU_REG_BASE + 0x00D4) +#define SDC_SHARP_CONF_1 (IPU_REG_BASE + 0x00D8) +#define SDC_SHARP_CONF_2 (IPU_REG_BASE + 0x00DC) +/* ADC Registers */ +#define ADC_CONF (IPU_REG_BASE + 0x00E0) +#define ADC_SYSCHA1_SA (IPU_REG_BASE + 0x00E4) +#define ADC_SYSCHA2_SA (IPU_REG_BASE + 0x00E8) +#define ADC_PRPCHAN_SA (IPU_REG_BASE + 0x00EC) +#define ADC_PPCHAN_SA (IPU_REG_BASE + 0x00F0) +#define ADC_DISP0_CONF (IPU_REG_BASE + 0x00F4) +#define ADC_DISP0_RD_AP (IPU_REG_BASE + 0x00F8) +#define ADC_DISP0_RDM (IPU_REG_BASE + 0x00FC) +#define ADC_DISP0_SS (IPU_REG_BASE + 0x0100) +#define ADC_DISP1_CONF (IPU_REG_BASE + 0x0104) +#define ADC_DISP1_RD_AP (IPU_REG_BASE + 0x0108) +#define ADC_DISP1_RDM (IPU_REG_BASE + 0x010C) +#define ADC_DISP12_SS (IPU_REG_BASE + 0x0110) +#define ADC_DISP2_CONF (IPU_REG_BASE + 0x0114) +#define ADC_DISP2_RD_AP (IPU_REG_BASE + 0x0118) +#define ADC_DISP2_RDM (IPU_REG_BASE + 0x011C) +#define ADC_DISP_VSYNC (IPU_REG_BASE + 0x0120) +/* Display Interface re(sters */ +#define DI_DISP_IF_CONF (IPU_REG_BASE + 0x0124) +#define DI_DISP_SIG_POL (IPU_REG_BASE + 0x0128) +#define DI_SER_DISP1_CONF (IPU_REG_BASE + 0x012C) +#define DI_SER_DISP2_CONF (IPU_REG_BASE + 0x0130) +#define DI_HSP_CLK_PER (IPU_REG_BASE + 0x0134) +#define DI_DISP0_TIME_CONF_1 (IPU_REG_BASE + 0x0138) +#define DI_DISP0_TIME_CONF_2 (IPU_REG_BASE + 0x013C) +#define DI_DISP0_TIME_CONF_3 (IPU_REG_BASE + 0x0140) +#define DI_DISP1_TIME_CONF_1 (IPU_REG_BASE + 0x0144) +#define DI_DISP1_TIME_CONF_2 (IPU_REG_BASE + 0x0148) +#define DI_DISP1_TIME_CONF_3 (IPU_REG_BASE + 0x014C) +#define DI_DISP2_TIME_CONF_1 (IPU_REG_BASE + 0x0150) +#define DI_DISP2_TIME_CONF_2 (IPU_REG_BASE + 0x0154) +#define DI_DISP2_TIME_CONF_3 (IPU_REG_BASE + 0x0158) +#define DI_DISP3_TIME_CONF (IPU_REG_BASE + 0x015C) +#define DI_DISP0_DB0_MAP (IPU_REG_BASE + 0x0160) +#define DI_DISP0_DB1_MAP (IPU_REG_BASE + 0x0164) +#define DI_DISP0_DB2_MAP (IPU_REG_BASE + 0x0168) +#define DI_DISP0_CB0_MAP (IPU_REG_BASE + 0x016C) +#define DI_DISP0_CB1_MAP (IPU_REG_BASE + 0x0170) +#define DI_DISP0_CB2_MAP (IPU_REG_BASE + 0x0174) +#define DI_DISP1_DB0_MAP (IPU_REG_BASE + 0x0178) +#define DI_DISP1_DB1_MAP (IPU_REG_BASE + 0x017C) +#define DI_DISP1_DB2_MAP (IPU_REG_BASE + 0x0180) +#define DI_DISP1_CB0_MAP (IPU_REG_BASE + 0x0184) +#define DI_DISP1_CB1_MAP (IPU_REG_BASE + 0x0188) +#define DI_DISP1_CB2_MAP (IPU_REG_BASE + 0x018C) +#define DI_DISP2_DB0_MAP (IPU_REG_BASE + 0x0190) +#define DI_DISP2_DB1_MAP (IPU_REG_BASE + 0x0194) +#define DI_DISP2_DB2_MAP (IPU_REG_BASE + 0x0198) +#define DI_DISP2_CB0_MAP (IPU_REG_BASE + 0x019C) +#define DI_DISP2_CB1_MAP (IPU_REG_BASE + 0x01A0) +#define DI_DISP2_CB2_MAP (IPU_REG_BASE + 0x01A4) +#define DI_DISP3_B0_MAP (IPU_REG_BASE + 0x01A8) +#define DI_DISP3_B1_MAP (IPU_REG_BASE + 0x01AC) +#define DI_DISP3_B2_MAP (IPU_REG_BASE + 0x01B0) +#define DI_DISP_ACC_CC (IPU_REG_BASE + 0x01B4) +#define DI_DISP_LLA_CONF (IPU_REG_BASE + 0x01B8) +#define DI_DISP_LLA_DATA (IPU_REG_BASE + 0x01BC) + +#define IPUIRQ_2_STATREG(int) (IPU_INT_STAT_1 + 4*(int>>5)) +#define IPUIRQ_2_CTRLREG(int) (IPU_INT_CTRL_1 + 4*(int>>5)) +#define IPUIRQ_2_MASK(int) (1UL << (int & 0x1F)) + +enum { + IPU_CONF_CSI_EN = 0x00000001, + IPU_CONF_IC_EN = 0x00000002, + IPU_CONF_ROT_EN = 0x00000004, + IPU_CONF_PF_EN = 0x00000008, + IPU_CONF_SDC_EN = 0x00000010, + IPU_CONF_ADC_EN = 0x00000020, + IPU_CONF_DI_EN = 0x00000040, + IPU_CONF_DU_EN = 0x00000080, + IPU_CONF_PXL_ENDIAN = 0x00000100, + + FS_PRPVF_ROT_SRC_SEL = 0x00000040, + FS_PRPENC_ROT_SRC_SEL = 0x00000020, + FS_PRPENC_DEST_SEL = 0x00000010, + FS_PP_SRC_SEL_MASK = 0x00000300, + FS_PP_SRC_SEL_OFFSET = 8, + FS_PP_ROT_SRC_SEL_MASK = 0x00000C00, + FS_PP_ROT_SRC_SEL_OFFSET = 10, + FS_PF_DEST_SEL_MASK = 0x00003000, + FS_PF_DEST_SEL_OFFSET = 12, + FS_PRPVF_DEST_SEL_MASK = 0x00070000, + FS_PRPVF_DEST_SEL_OFFSET = 16, + FS_PRPVF_ROT_DEST_SEL_MASK = 0x00700000, + FS_PRPVF_ROT_DEST_SEL_OFFSET = 20, + FS_PP_DEST_SEL_MASK = 0x07000000, + FS_PP_DEST_SEL_OFFSET = 24, + FS_PP_ROT_DEST_SEL_MASK = 0x70000000, + FS_PP_ROT_DEST_SEL_OFFSET = 28, + FS_VF_IN_VALID = 0x00000002, + FS_ENC_IN_VALID = 0x00000001, + + FS_SDC_BG_SRC_SEL_MASK = 0x00000007, + FS_SDC_BG_SRC_SEL_OFFSET = 0, + FS_SDC_FG_SRC_SEL_MASK = 0x00000070, + FS_SDC_FG_SRC_SEL_OFFSET = 4, + FS_ADC1_SRC_SEL_MASK = 0x00000700, + FS_ADC1_SRC_SEL_OFFSET = 8, + FS_ADC2_SRC_SEL_MASK = 0x00007000, + FS_ADC2_SRC_SEL_OFFSET = 12, + FS_AUTO_REF_PER_MASK = 0x03FF0000, + FS_AUTO_REF_PER_OFFSET = 16, + + FS_DEST_ARM = 0, + FS_DEST_ROT = 1, + FS_DEST_PP = 1, + FS_DEST_ADC1 = 2, + FS_DEST_ADC2 = 3, + FS_DEST_SDC_BG = 4, + FS_DEST_SDC_FG = 5, + FS_DEST_ADC = 6, + + FS_SRC_ARM = 0, + FS_PP_SRC_PF = 1, + FS_PP_SRC_ROT = 2, + + FS_ROT_SRC_PP = 1, + FS_ROT_SRC_PF = 2, + + FS_PF_DEST_PP = 1, + FS_PF_DEST_ROT = 2, + + FS_SRC_ROT_VF = 1, + FS_SRC_ROT_PP = 2, + FS_SRC_VF = 3, + FS_SRC_PP = 4, + FS_SRC_SNOOP = 5, + FS_SRC_AUTOREF = 6, + FS_SRC_AUTOREF_SNOOP = 7, + + TSTAT_PF_H264_PAUSE = 0x00000001, + TSTAT_CSI2MEM_MASK = 0x0000000C, + TSTAT_CSI2MEM_OFFSET = 2, + TSTAT_VF_MASK = 0x00000600, + TSTAT_VF_OFFSET = 9, + TSTAT_VF_ROT_MASK = 0x000C0000, + TSTAT_VF_ROT_OFFSET = 18, + TSTAT_ENC_MASK = 0x00000180, + TSTAT_ENC_OFFSET = 7, + TSTAT_ENC_ROT_MASK = 0x00030000, + TSTAT_ENC_ROT_OFFSET = 16, + TSTAT_PP_MASK = 0x00001800, + TSTAT_PP_OFFSET = 11, + TSTAT_PP_ROT_MASK = 0x00300000, + TSTAT_PP_ROT_OFFSET = 20, + TSTAT_PF_MASK = 0x00C00000, + TSTAT_PF_OFFSET = 22, + TSTAT_ADCSYS1_MASK = 0x03000000, + TSTAT_ADCSYS1_OFFSET = 24, + TSTAT_ADCSYS2_MASK = 0x0C000000, + TSTAT_ADCSYS2_OFFSET = 26, + + TASK_STAT_IDLE = 0, + TASK_STAT_ACTIVE = 1, + TASK_STAT_WAIT4READY = 2, + + /* Register bits */ + SDC_COM_TFT_COLOR = 0x00000001UL, + SDC_COM_FG_EN = 0x00000010UL, + SDC_COM_GWSEL = 0x00000020UL, + SDC_COM_GLB_A = 0x00000040UL, + SDC_COM_KEY_COLOR_G = 0x00000080UL, + SDC_COM_BG_EN = 0x00000200UL, + SDC_COM_SHARP = 0x00001000UL, + + SDC_V_SYNC_WIDTH_L = 0x00000001UL, + + ADC_CONF_PRP_EN = 0x00000001L, + ADC_CONF_PP_EN = 0x00000002L, + ADC_CONF_MCU_EN = 0x00000004L, + + ADC_DISP_CONF_SL_MASK = 0x00000FFFL, + ADC_DISP_CONF_TYPE_MASK = 0x00003000L, + ADC_DISP_CONF_TYPE_XY = 0x00002000L, + + ADC_DISP_VSYNC_D0_MODE_MASK = 0x00000003L, + ADC_DISP_VSYNC_D0_WIDTH_MASK = 0x003F0000L, + ADC_DISP_VSYNC_D12_MODE_MASK = 0x0000000CL, + ADC_DISP_VSYNC_D12_WIDTH_MASK = 0x3F000000L, + + /* Image Converter Register bits */ + IC_CONF_PRPENC_EN = 0x00000001, + IC_CONF_PRPENC_CSC1 = 0x00000002, + IC_CONF_PRPENC_ROT_EN = 0x00000004, + IC_CONF_PRPVF_EN = 0x00000100, + IC_CONF_PRPVF_CSC1 = 0x00000200, + IC_CONF_PRPVF_CSC2 = 0x00000400, + IC_CONF_PRPVF_CMB = 0x00000800, + IC_CONF_PRPVF_ROT_EN = 0x00001000, + IC_CONF_PP_EN = 0x00010000, + IC_CONF_PP_CSC1 = 0x00020000, + IC_CONF_PP_CSC2 = 0x00040000, + IC_CONF_PP_CMB = 0x00080000, + IC_CONF_PP_ROT_EN = 0x00100000, + IC_CONF_IC_GLB_LOC_A = 0x10000000, + IC_CONF_KEY_COLOR_EN = 0x20000000, + IC_CONF_RWS_EN = 0x40000000, + IC_CONF_CSI_MEM_WR_EN = 0x80000000, + + IDMA_CHAN_INVALID = 0x000000FF, + IDMA_IC_0 = 0x00000001, + IDMA_IC_1 = 0x00000002, + IDMA_IC_2 = 0x00000004, + IDMA_IC_3 = 0x00000008, + IDMA_IC_4 = 0x00000010, + IDMA_IC_5 = 0x00000020, + IDMA_IC_6 = 0x00000040, + IDMA_IC_7 = 0x00000080, + IDMA_IC_8 = 0x00000100, + IDMA_IC_9 = 0x00000200, + IDMA_IC_10 = 0x00000400, + IDMA_IC_11 = 0x00000800, + IDMA_IC_12 = 0x00001000, + IDMA_IC_13 = 0x00002000, + IDMA_SDC_BG = 0x00004000, + IDMA_SDC_FG = 0x00008000, + IDMA_SDC_MASK = 0x00010000, + IDMA_SDC_PARTIAL = 0x00020000, + IDMA_ADC_SYS1_WR = 0x00040000, + IDMA_ADC_SYS2_WR = 0x00080000, + IDMA_ADC_SYS1_CMD = 0x00100000, + IDMA_ADC_SYS2_CMD = 0x00200000, + IDMA_ADC_SYS1_RD = 0x00400000, + IDMA_ADC_SYS2_RD = 0x00800000, + IDMA_PF_QP = 0x01000000, + IDMA_PF_BSP = 0x02000000, + IDMA_PF_Y_IN = 0x04000000, + IDMA_PF_U_IN = 0x08000000, + IDMA_PF_V_IN = 0x10000000, + IDMA_PF_Y_OUT = 0x20000000, + IDMA_PF_U_OUT = 0x40000000, + IDMA_PF_V_OUT = 0x80000000, + + CSI_SENS_CONF_DATA_FMT_SHIFT = 8, + CSI_SENS_CONF_DATA_FMT_RGB_YUV444 = 0x00000000L, + CSI_SENS_CONF_DATA_FMT_YUV422 = 0x00000200L, + CSI_SENS_CONF_DATA_FMT_BAYER = 0x00000300L, + + CSI_SENS_CONF_VSYNC_POL_SHIFT = 0, + CSI_SENS_CONF_HSYNC_POL_SHIFT = 1, + CSI_SENS_CONF_DATA_POL_SHIFT = 2, + CSI_SENS_CONF_PIX_CLK_POL_SHIFT = 3, + CSI_SENS_CONF_SENS_PRTCL_SHIFT = 4, + CSI_SENS_CONF_SENS_CLKSRC_SHIFT = 7, + CSI_SENS_CONF_DATA_WIDTH_SHIFT = 10, + CSI_SENS_CONF_EXT_VSYNC_SHIFT = 15, + CSI_SENS_CONF_DIVRATIO_SHIFT = 16, + + PF_CONF_TYPE_MASK = 0x00000007, + PF_CONF_TYPE_SHIFT = 0, + PF_CONF_PAUSE_EN = 0x00000010, + PF_CONF_RESET = 0x00008000, + PF_CONF_PAUSE_ROW_MASK = 0x00FF0000, + PF_CONF_PAUSE_ROW_SHIFT = 16, + + /* DI_DISP_SIG_POL bits */ + DI_D3_VSYNC_POL_SHIFT = 28, + DI_D3_HSYNC_POL_SHIFT = 27, + DI_D3_DRDY_SHARP_POL_SHIFT = 26, + DI_D3_CLK_POL_SHIFT = 25, + DI_D3_DATA_POL_SHIFT = 24, + + /* DI_DISP_IF_CONF bits */ + DI_D3_CLK_IDLE_SHIFT = 26, + DI_D3_CLK_SEL_SHIFT = 25, + DI_D3_DATAMSK_SHIFT = 24, + + DISPx_IF_CLK_DOWN_OFFSET = 22, + DISPx_IF_CLK_UP_OFFSET = 12, + DISPx_IF_CLK_PER_OFFSET = 0, + DISPx_IF_CLK_READ_EN_OFFSET = 16, + DISPx_PIX_CLK_PER_OFFSET = 0, + + DI_CONF_DISP0_EN = 0x00000001L, + DI_CONF_DISP0_IF_MODE_OFFSET = 1, + DI_CONF_DISP0_BURST_MODE_OFFSET = 3, + DI_CONF_DISP1_EN = 0x00000100L, + DI_CONF_DISP1_IF_MODE_OFFSET = 9, + DI_CONF_DISP1_BURST_MODE_OFFSET = 12, + DI_CONF_DISP2_EN = 0x00010000L, + DI_CONF_DISP2_IF_MODE_OFFSET = 17, + DI_CONF_DISP2_BURST_MODE_OFFSET = 20, + + DI_SER_DISPx_CONF_SER_BIT_NUM_OFFSET = 16, + DI_SER_DISPx_CONF_PREAMBLE_OFFSET = 8, + DI_SER_DISPx_CONF_PREAMBLE_LEN_OFFSET = 4, + DI_SER_DISPx_CONF_RW_CFG_OFFSET = 1, + DI_SER_DISPx_CONF_BURST_MODE_EN = 0x01000000L, + DI_SER_DISPx_CONF_PREAMBLE_EN = 0x00000001L, + + /* DI_DISP_ACC_CC */ + DISP0_IF_CLK_CNT_D_MASK = 0x00000003L, + DISP0_IF_CLK_CNT_D_OFFSET = 0, + DISP0_IF_CLK_CNT_C_MASK = 0x0000000CL, + DISP0_IF_CLK_CNT_C_OFFSET = 2, + DISP1_IF_CLK_CNT_D_MASK = 0x00000030L, + DISP1_IF_CLK_CNT_D_OFFSET = 4, + DISP1_IF_CLK_CNT_C_MASK = 0x000000C0L, + DISP1_IF_CLK_CNT_C_OFFSET = 6, + DISP2_IF_CLK_CNT_D_MASK = 0x00000300L, + DISP2_IF_CLK_CNT_D_OFFSET = 8, + DISP2_IF_CLK_CNT_C_MASK = 0x00000C00L, + DISP2_IF_CLK_CNT_C_OFFSET = 10, + DISP3_IF_CLK_CNT_MASK = 0x00003000L, + DISP3_IF_CLK_CNT_OFFSET = 12, +}; + +#endif diff --git a/drivers/mxc/ipu/ipu_sdc.c b/drivers/mxc/ipu/ipu_sdc.c new file mode 100644 index 000000000000..6feb24d63cfd --- /dev/null +++ b/drivers/mxc/ipu/ipu_sdc.c @@ -0,0 +1,355 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file ipu_sdc.c + * + * @brief IPU SDC submodule API functions + * + * @ingroup IPU + */ +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/spinlock.h> +#include <asm/io.h> +#include <asm/arch/ipu.h> +#include "ipu_prv.h" +#include "ipu_regs.h" +#include "ipu_param_mem.h" + +static uint32_t g_h_start_width; +static uint32_t g_v_start_width; + +static const uint32_t di_mappings[] = { + 0x1600AAAA, 0x00E05555, 0x00070000, 3, /* RGB888 */ + 0x0005000F, 0x000B000F, 0x0011000F, 1, /* RGB666 */ + 0x0011000F, 0x000B000F, 0x0005000F, 1, /* BGR666 */ + 0x0004003F, 0x000A000F, 0x000F003F, 1 /* RGB565 */ +}; + +/*! + * This function is called to initialize a synchronous LCD panel. + * + * @param panel The type of panel. + * + * @param pixel_clk Desired pixel clock frequency in Hz. + * + * @param pixel_fmt Input parameter for pixel format of buffer. Pixel + * format is a FOURCC ASCII code. + * + * @param width The width of panel in pixels. + * + * @param height The height of panel in pixels. + * + * @param hStartWidth The number of pixel clocks between the HSYNC + * signal pulse and the start of valid data. + * + * @param hSyncWidth The width of the HSYNC signal in units of pixel + * clocks. + * + * @param hEndWidth The number of pixel clocks between the end of + * valid data and the HSYNC signal for next line. + * + * @param vStartWidth The number of lines between the VSYNC + * signal pulse and the start of valid data. + * + * @param vSyncWidth The width of the VSYNC signal in units of lines + * + * @param vEndWidth The number of lines between the end of valid + * data and the VSYNC signal for next frame. + * + * @param sig Bitfield of signal polarities for LCD interface. + * + * @return This function returns 0 on success or negative error code on + * fail. + */ +int32_t ipu_sdc_init_panel(ipu_panel_t panel, + uint32_t pixel_clk, + uint16_t width, uint16_t height, + uint32_t pixel_fmt, + uint16_t h_start_width, uint16_t h_sync_width, + uint16_t h_end_width, uint16_t v_start_width, + uint16_t vSyncWidth, uint16_t v_end_width, + ipu_di_signal_cfg_t sig) +{ + unsigned long lock_flags; + uint32_t reg; + uint32_t old_conf; + uint32_t div; + + dev_dbg(g_ipu_dev, "panel size = %d x %d\n", width, height); + + if ((vSyncWidth == 0) || (h_sync_width == 0)) + return EINVAL; + + /* Init panel size and blanking periods */ + reg = + ((uint32_t) (h_sync_width - 1) << 26) | + ((uint32_t) (width + h_start_width + h_end_width - 1) << 16); + __raw_writel(reg, SDC_HOR_CONF); + + reg = ((uint32_t) (vSyncWidth - 1) << 26) | SDC_V_SYNC_WIDTH_L | + ((uint32_t) (height + v_start_width + v_end_width - 1) << 16); + __raw_writel(reg, SDC_VER_CONF); + + g_h_start_width = h_start_width; + g_v_start_width = v_start_width; + + switch (panel) { + case IPU_PANEL_SHARP_TFT: + __raw_writel(0x00FD0102L, SDC_SHARP_CONF_1); + __raw_writel(0x00F500F4L, SDC_SHARP_CONF_2); + __raw_writel(SDC_COM_SHARP | SDC_COM_TFT_COLOR, SDC_COM_CONF); + break; + case IPU_PANEL_TFT: + __raw_writel(SDC_COM_TFT_COLOR, SDC_COM_CONF); + break; + default: + return EINVAL; + } + + spin_lock_irqsave(&ipu_lock, lock_flags); + + /* Init clocking */ + + /* Calculate divider */ + /* fractional part is 4 bits so simply multiple by 2^4 to get fractional part */ + dev_dbg(g_ipu_dev, "pixel clk = %d\n", pixel_clk); + div = (clk_get_rate(g_ipu_clk) * 16) / pixel_clk; + if (div < 0x40) { /* Divider less than 4 */ + dev_dbg(g_ipu_dev, + "InitPanel() - Pixel clock divider less than 1\n"); + div = 0x40; + } + /* DISP3_IF_CLK_DOWN_WR is half the divider value and 2 less fraction bits */ + /* Subtract 1 extra from DISP3_IF_CLK_DOWN_WR based on timing debug */ + /* DISP3_IF_CLK_UP_WR is 0 */ + __raw_writel((((div / 8) - 1) << 22) | div, DI_DISP3_TIME_CONF); + + /* DI settings */ + old_conf = __raw_readl(DI_DISP_IF_CONF) & 0x78FFFFFF; + old_conf |= sig.datamask_en << DI_D3_DATAMSK_SHIFT | + sig.clksel_en << DI_D3_CLK_SEL_SHIFT | + sig.clkidle_en << DI_D3_CLK_IDLE_SHIFT; + __raw_writel(old_conf, DI_DISP_IF_CONF); + + old_conf = __raw_readl(DI_DISP_SIG_POL) & 0xE0FFFFFF; + old_conf |= sig.data_pol << DI_D3_DATA_POL_SHIFT | + sig.clk_pol << DI_D3_CLK_POL_SHIFT | + sig.enable_pol << DI_D3_DRDY_SHARP_POL_SHIFT | + sig.Hsync_pol << DI_D3_HSYNC_POL_SHIFT | + sig.Vsync_pol << DI_D3_VSYNC_POL_SHIFT; + __raw_writel(old_conf, DI_DISP_SIG_POL); + + switch (pixel_fmt) { + case IPU_PIX_FMT_RGB24: + __raw_writel(di_mappings[0], DI_DISP3_B0_MAP); + __raw_writel(di_mappings[1], DI_DISP3_B1_MAP); + __raw_writel(di_mappings[2], DI_DISP3_B2_MAP); + __raw_writel(__raw_readl(DI_DISP_ACC_CC) | + ((di_mappings[3] - 1) << 12), DI_DISP_ACC_CC); + break; + case IPU_PIX_FMT_RGB666: + __raw_writel(di_mappings[4], DI_DISP3_B0_MAP); + __raw_writel(di_mappings[5], DI_DISP3_B1_MAP); + __raw_writel(di_mappings[6], DI_DISP3_B2_MAP); + __raw_writel(__raw_readl(DI_DISP_ACC_CC) | + ((di_mappings[7] - 1) << 12), DI_DISP_ACC_CC); + break; + case IPU_PIX_FMT_BGR666: + __raw_writel(di_mappings[8], DI_DISP3_B0_MAP); + __raw_writel(di_mappings[9], DI_DISP3_B1_MAP); + __raw_writel(di_mappings[10], DI_DISP3_B2_MAP); + __raw_writel(__raw_readl(DI_DISP_ACC_CC) | + ((di_mappings[11] - 1) << 12), DI_DISP_ACC_CC); + break; + default: + __raw_writel(di_mappings[12], DI_DISP3_B0_MAP); + __raw_writel(di_mappings[13], DI_DISP3_B1_MAP); + __raw_writel(di_mappings[14], DI_DISP3_B2_MAP); + __raw_writel(__raw_readl(DI_DISP_ACC_CC) | + ((di_mappings[15] - 1) << 12), DI_DISP_ACC_CC); + break; + } + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + + dev_dbg(g_ipu_dev, "DI_DISP_IF_CONF = 0x%08X\n", + __raw_readl(DI_DISP_IF_CONF)); + dev_dbg(g_ipu_dev, "DI_DISP_SIG_POL = 0x%08X\n", + __raw_readl(DI_DISP_SIG_POL)); + dev_dbg(g_ipu_dev, "DI_DISP3_TIME_CONF = 0x%08X\n", + __raw_readl(DI_DISP3_TIME_CONF)); + + return 0; +} + +/*! + * This function sets the foreground and background plane global alpha blending + * modes. + * + * @param enable Boolean to enable or disable global alpha + * blending. If disabled, per pixel blending is used. + * + * @param alpha Global alpha value. + * + * @return This function returns 0 on success or negative error code on fail + */ +int32_t ipu_sdc_set_global_alpha(bool enable, uint8_t alpha) +{ + uint32_t reg; + unsigned long lock_flags; + + spin_lock_irqsave(&ipu_lock, lock_flags); + + if (enable) { + reg = __raw_readl(SDC_GW_CTRL) & 0x00FFFFFFL; + __raw_writel(reg | ((uint32_t) alpha << 24), SDC_GW_CTRL); + + reg = __raw_readl(SDC_COM_CONF); + __raw_writel(reg | SDC_COM_GLB_A, SDC_COM_CONF); + } else { + reg = __raw_readl(SDC_COM_CONF); + __raw_writel(reg & ~SDC_COM_GLB_A, SDC_COM_CONF); + } + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + + return 0; +} + +/*! + * This function sets the transparent color key for SDC graphic plane. + * + * @param channel Input parameter for the logical channel ID. + * + * @param enable Boolean to enable or disable color key + * + * @param colorKey 24-bit RGB color to use as transparent color key. + * + * @return This function returns 0 on success or negative error code on fail + */ +int32_t ipu_sdc_set_color_key(ipu_channel_t channel, bool enable, + uint32_t color_key) +{ + uint32_t reg, sdc_conf; + unsigned long lock_flags; + + spin_lock_irqsave(&ipu_lock, lock_flags); + + sdc_conf = __raw_readl(SDC_COM_CONF); + if (channel == MEM_SDC_BG) { + sdc_conf &= ~SDC_COM_GWSEL; + } else { + sdc_conf |= SDC_COM_GWSEL; + } + + if (enable) { + reg = __raw_readl(SDC_GW_CTRL) & 0xFF000000L; + __raw_writel(reg | (color_key & 0x00FFFFFFL), SDC_GW_CTRL); + + sdc_conf |= SDC_COM_KEY_COLOR_G; + } else { + sdc_conf &= ~SDC_COM_KEY_COLOR_G; + } + __raw_writel(sdc_conf, SDC_COM_CONF); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + + return 0; +} + +int32_t ipu_sdc_set_brightness(uint8_t value) +{ + __raw_writel(0x03000000UL | value << 16, SDC_PWM_CTRL); + return 0; +} + +/*! + * This function sets the window position of the foreground or background plane. + * modes. + * + * @param channel Input parameter for the logical channel ID. + * + * @param x_pos The X coordinate position to place window at. + * The position is relative to the top left corner. + * + * @param y_pos The Y coordinate position to place window at. + * The position is relative to the top left corner. + * + * @return This function returns 0 on success or negative error code on fail + */ +int32_t ipu_sdc_set_window_pos(ipu_channel_t channel, int16_t x_pos, + int16_t y_pos) +{ + x_pos += g_h_start_width; + y_pos += g_v_start_width; + + if (channel == MEM_SDC_BG) { + __raw_writel((x_pos << 16) | y_pos, SDC_BG_POS); + } else if (channel == MEM_SDC_FG) { + __raw_writel((x_pos << 16) | y_pos, SDC_FG_POS); + } else { + return EINVAL; + } + return 0; +} + +void _ipu_sdc_fg_init(ipu_channel_params_t * params) +{ + uint32_t reg; + (void)params; + + /* Enable FG channel */ + reg = __raw_readl(SDC_COM_CONF); + __raw_writel(reg | SDC_COM_FG_EN | SDC_COM_BG_EN, SDC_COM_CONF); +} + +uint32_t _ipu_sdc_fg_uninit(void) +{ + uint32_t reg; + + /* Disable FG channel */ + reg = __raw_readl(SDC_COM_CONF); + __raw_writel(reg & ~SDC_COM_FG_EN, SDC_COM_CONF); + + return (reg & SDC_COM_FG_EN); +} + +void _ipu_sdc_bg_init(ipu_channel_params_t * params) +{ + uint32_t reg; + (void)params; + + /* Enable FG channel */ + reg = __raw_readl(SDC_COM_CONF); + __raw_writel(reg | SDC_COM_BG_EN, SDC_COM_CONF); +} + +uint32_t _ipu_sdc_bg_uninit(void) +{ + uint32_t reg; + + /* Disable BG channel */ + reg = __raw_readl(SDC_COM_CONF); + __raw_writel(reg & ~SDC_COM_BG_EN, SDC_COM_CONF); + + return (reg & SDC_COM_BG_EN); +} + +/* Exported symbols for modules. */ +EXPORT_SYMBOL(ipu_sdc_init_panel); +EXPORT_SYMBOL(ipu_sdc_set_global_alpha); +EXPORT_SYMBOL(ipu_sdc_set_color_key); +EXPORT_SYMBOL(ipu_sdc_set_brightness); +EXPORT_SYMBOL(ipu_sdc_set_window_pos); diff --git a/drivers/mxc/ipu/pf/Kconfig b/drivers/mxc/ipu/pf/Kconfig new file mode 100644 index 000000000000..02cda917fb7a --- /dev/null +++ b/drivers/mxc/ipu/pf/Kconfig @@ -0,0 +1,7 @@ +config MXC_IPU_PF + tristate "MXC MPEG4/H.264 Post Filter Driver" + depends on MXC_IPU + default y + help + Driver for MPEG4 dering and deblock and H.264 deblock + using MXC IPU h/w diff --git a/drivers/mxc/ipu/pf/Makefile b/drivers/mxc/ipu/pf/Makefile new file mode 100644 index 000000000000..641adf4be4bd --- /dev/null +++ b/drivers/mxc/ipu/pf/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_MXC_IPU_PF) += mxc_pf.o diff --git a/drivers/mxc/ipu/pf/mxc_pf.c b/drivers/mxc/ipu/pf/mxc_pf.c new file mode 100644 index 000000000000..bb321cbcd68b --- /dev/null +++ b/drivers/mxc/ipu/pf/mxc_pf.c @@ -0,0 +1,1000 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mxc_pf.c + * + * @brief MXC IPU MPEG4/H.264 Post-filtering driver + * + * User-level API for IPU Hardware MPEG4/H.264 Post-filtering. + * + * @ingroup MXC_PF + */ + +#include <linux/pagemap.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/fs.h> +#include <linux/poll.h> +#include <linux/delay.h> +#include <linux/dma-mapping.h> +#include <asm/arch/ipu.h> +#include <asm/arch/mxc_pf.h> + +struct mxc_pf_data { + pf_operation_t mode; + u32 pf_enabled; + u32 width; + u32 height; + u32 stride; + uint32_t qp_size; + dma_addr_t qp_paddr; + void *qp_vaddr; + pf_buf buf[PF_MAX_BUFFER_CNT]; + void *buf_vaddr[PF_MAX_BUFFER_CNT]; + wait_queue_head_t pf_wait; + volatile int done_mask; + volatile int wait_mask; + volatile int busy_flag; + bool buffer_dirty; + struct semaphore busy_lock; +}; + +static struct mxc_pf_data pf_data; +static u8 open_count = 0; +static struct class *mxc_pf_class; + +/* + * Function definitions + */ + +static irqreturn_t mxc_pf_irq_handler(int irq, void *dev_id) +{ + struct mxc_pf_data *pf = dev_id; + + if (irq == IPU_IRQ_PF_Y_OUT_EOF) { + pf->done_mask |= PF_WAIT_Y; + } else if (irq == IPU_IRQ_PF_U_OUT_EOF) { + pf->done_mask |= PF_WAIT_U; + } else if (irq == IPU_IRQ_PF_V_OUT_EOF) { + pf->done_mask |= PF_WAIT_V; + } else { + return IRQ_NONE; + } + + if (pf->wait_mask && ((pf->done_mask & pf->wait_mask) == pf->wait_mask)) { + wake_up_interruptible(&pf->pf_wait); + } + return IRQ_HANDLED; +} + +/*! + * This function handles PF_IOCTL_INIT calls. It initializes the PF channels, + * interrupt handlers, and channel buffers. + * + * @return This function returns 0 on success or negative error code on + * error. + */ +static int mxc_pf_init(pf_init_params * pf_init) +{ + int err; + ipu_channel_params_t params; + u32 w; + u32 stride; + u32 h; + u32 qp_size = 0; + u32 qp_stride; + + if ((pf_init->pf_mode > 4) || (pf_init->width > 1024) || + (pf_init->height > 1024) || (pf_init->stride < pf_init->width)) { + return -EINVAL; + } + + pf_data.mode = pf_init->pf_mode; + w = pf_data.width = pf_init->width; + h = pf_data.height = pf_init->height; + stride = pf_data.stride = pf_init->stride; + pf_data.qp_size = pf_init->qp_size; + + memset(¶ms, 0, sizeof(params)); + params.mem_pf_mem.operation = pf_data.mode; + err = ipu_init_channel(MEM_PF_Y_MEM, ¶ms); + if (err < 0) { + printk(KERN_ERR "mxc_pf: error initializing channel\n"); + goto err0; + } + + err = ipu_init_channel_buffer(MEM_PF_Y_MEM, IPU_INPUT_BUFFER, + IPU_PIX_FMT_GENERIC, w, h, stride, + IPU_ROTATE_NONE, 0, 0, 0, 0); + if (err < 0) { + printk(KERN_ERR "mxc_pf: error initializing Y input buffer\n"); + goto err0; + } + + err = ipu_init_channel_buffer(MEM_PF_Y_MEM, IPU_OUTPUT_BUFFER, + IPU_PIX_FMT_GENERIC, w, h, stride, + IPU_ROTATE_NONE, 0, 0, 0, 0); + if (err < 0) { + printk(KERN_ERR "mxc_pf: error initializing Y output buffer\n"); + goto err0; + } + + w = w / 2; + h = h / 2; + stride = stride / 2; + + if (pf_data.mode != PF_MPEG4_DERING) { + err = ipu_init_channel_buffer(MEM_PF_U_MEM, IPU_INPUT_BUFFER, + IPU_PIX_FMT_GENERIC, w, h, stride, + IPU_ROTATE_NONE, 0, 0, 0, 0); + if (err < 0) { + printk(KERN_ERR + "mxc_pf: error initializing U input buffer\n"); + goto err0; + } + + err = ipu_init_channel_buffer(MEM_PF_U_MEM, IPU_OUTPUT_BUFFER, + IPU_PIX_FMT_GENERIC, w, h, stride, + IPU_ROTATE_NONE, 0, 0, 0, 0); + if (err < 0) { + printk(KERN_ERR + "mxc_pf: error initializing U output buffer\n"); + goto err0; + } + + err = ipu_init_channel_buffer(MEM_PF_V_MEM, IPU_INPUT_BUFFER, + IPU_PIX_FMT_GENERIC, w, h, stride, + IPU_ROTATE_NONE, 0, 0, 0, 0); + if (err < 0) { + printk(KERN_ERR + "mxc_pf: error initializing V input buffer\n"); + goto err0; + } + + err = ipu_init_channel_buffer(MEM_PF_V_MEM, IPU_OUTPUT_BUFFER, + IPU_PIX_FMT_GENERIC, w, h, stride, + IPU_ROTATE_NONE, 0, 0, 0, 0); + if (err < 0) { + printk(KERN_ERR + "mxc_pf: error initializing V output buffer\n"); + goto err0; + } + } + + /*Setup Channel QF and BSC Params */ + if (pf_data.mode == PF_H264_DEBLOCK) { + w = ((pf_data.width + 15) / 16); + h = (pf_data.height + 15) / 16; + qp_stride = w; + qp_size = 4 * qp_stride * h; + pr_debug("H264 QP width = %d, height = %d\n", w, h); + err = ipu_init_channel_buffer(MEM_PF_Y_MEM, + IPU_SEC_INPUT_BUFFER, + IPU_PIX_FMT_GENERIC_32, w, h, + qp_stride, IPU_ROTATE_NONE, 0, 0, + 0, 0); + if (err < 0) { + printk(KERN_ERR + "mxc_pf: error initializing H264 QP buffer\n"); + goto err0; + } +/* w = (pf_data.width + 3) / 4; */ + w *= 4; + h *= 4; + qp_stride = w; + err = ipu_init_channel_buffer(MEM_PF_U_MEM, + IPU_SEC_INPUT_BUFFER, + IPU_PIX_FMT_GENERIC, w, h, + qp_stride, IPU_ROTATE_NONE, 0, 0, + 0, 0); + if (err < 0) { + printk(KERN_ERR + "mxc_pf: error initializing H264 BSB buffer\n"); + goto err0; + } + qp_size += qp_stride * h; + } else { /* MPEG4 mode */ + + w = (pf_data.width + 15) / 16; + h = (pf_data.height + 15) / 16; + qp_stride = (w + 3) & ~0x3UL; + pr_debug("MPEG4 QP width = %d, height = %d, stride = %d\n", + w, h, qp_stride); + err = ipu_init_channel_buffer(MEM_PF_Y_MEM, + IPU_SEC_INPUT_BUFFER, + IPU_PIX_FMT_GENERIC, w, h, + qp_stride, IPU_ROTATE_NONE, 0, 0, + 0, 0); + if (err < 0) { + printk(KERN_ERR + "mxc_pf: error initializing MPEG4 QP buffer\n"); + goto err0; + } + qp_size = qp_stride * h; + } + + /* Support 2 QP buffers */ + qp_size *= 2; + + if (pf_data.qp_size > qp_size) + qp_size = pf_data.qp_size; + else + pf_data.qp_size = qp_size; + + pf_data.qp_vaddr = dma_alloc_coherent(NULL, pf_data.qp_size, + &pf_data.qp_paddr, + GFP_KERNEL | GFP_DMA); + if (!pf_data.qp_vaddr) + return -ENOMEM; + + pf_init->qp_paddr = pf_data.qp_paddr; + pf_init->qp_size = pf_data.qp_size; + + return 0; + + err0: + return err; +} + +/*! + * This function handles PF_IOCTL_UNINIT calls. It uninitializes the PF + * channels and interrupt handlers. + * + * @return This function returns 0 on success or negative error code + * on error. + */ +static int mxc_pf_uninit(void) +{ + pf_data.pf_enabled = 0; + ipu_disable_irq(IPU_IRQ_PF_Y_OUT_EOF); + ipu_disable_irq(IPU_IRQ_PF_U_OUT_EOF); + ipu_disable_irq(IPU_IRQ_PF_V_OUT_EOF); + + ipu_disable_channel(MEM_PF_Y_MEM, true); + ipu_disable_channel(MEM_PF_U_MEM, true); + ipu_disable_channel(MEM_PF_V_MEM, true); + ipu_uninit_channel(MEM_PF_Y_MEM); + ipu_uninit_channel(MEM_PF_U_MEM); + ipu_uninit_channel(MEM_PF_V_MEM); + + if (pf_data.qp_vaddr) { + dma_free_coherent(NULL, pf_data.qp_size, pf_data.qp_vaddr, + pf_data.qp_paddr); + pf_data.qp_vaddr = NULL; + } + + return 0; +} + +/*! + * This function handles PF_IOCTL_REQBUFS calls. It initializes the PF channels + * and channel buffers. + * + * @param reqbufs Input/Output Structure containing buffer mode, + * type, offset, and size. The offset and size of + * the buffer are returned for PF_MEMORY_MMAP mode. + * + * @return This function returns 0 on success or negative error code + * on error. + */ +static int mxc_pf_reqbufs(pf_reqbufs_params * reqbufs) +{ + int err; + uint32_t buf_size; + int i; + int alloc_cnt = 0; + pf_buf *buf = pf_data.buf; + if (reqbufs->count > PF_MAX_BUFFER_CNT) { + reqbufs->count = PF_MAX_BUFFER_CNT; + } + /* Deallocate mmapped buffers */ + if (reqbufs->count == 0) { + for (i = 0; i < PF_MAX_BUFFER_CNT; i++) { + if (buf[i].index != -1) { + dma_free_coherent(NULL, buf[i].size, + pf_data.buf_vaddr[i], + buf[i].offset); + pf_data.buf_vaddr[i] = NULL; + buf[i].index = -1; + buf[i].size = 0; + } + } + return 0; + } + + buf_size = (pf_data.stride * pf_data.height * 3) / 2; + if (reqbufs->req_size > buf_size) { + buf_size = reqbufs->req_size; + pr_debug("using requested buffer size of %d\n", buf_size); + } else { + reqbufs->req_size = buf_size; + pr_debug("using default buffer size of %d\n", buf_size); + } + + for (i = 0; alloc_cnt < reqbufs->count; i++) { + buf[i].index = i; + buf[i].size = buf_size; + pf_data.buf_vaddr[i] = dma_alloc_coherent(NULL, buf[i].size, + &buf[i].offset, + GFP_KERNEL | GFP_DMA); + if (!pf_data.buf_vaddr[i] || !buf[i].offset) { + printk(KERN_ERR + "mxc_pf: unable to allocate IPU buffers.\n"); + err = -ENOMEM; + goto err0; + } + pr_debug("Allocated buffer %d at paddr 0x%08X, vaddr %p\n", + i, buf[i].offset, pf_data.buf_vaddr[i]); + + alloc_cnt++; + } + + return 0; + err0: + for (i = 0; i < alloc_cnt; i++) { + dma_free_coherent(NULL, buf[i].size, pf_data.buf_vaddr[i], + buf[i].offset); + pf_data.buf_vaddr[i] = NULL; + buf[i].index = -1; + buf[i].size = 0; + } + return err; +} + +/*! + * This function handles PF_IOCTL_START calls. It sets the PF channel buffers + * addresses and starts the channels + * + * @return This function returns 0 on success or negative error code on + * error. + */ +static int mxc_pf_start(pf_buf * in, pf_buf * out, int qp_buf) +{ + int err; + dma_addr_t y_in_paddr; + dma_addr_t u_in_paddr; + dma_addr_t v_in_paddr; + dma_addr_t p1_in_paddr; + dma_addr_t p2_in_paddr; + dma_addr_t y_out_paddr; + dma_addr_t u_out_paddr; + dma_addr_t v_out_paddr; + + /* H.264 requires output buffer equal to input */ + if (pf_data.mode == PF_H264_DEBLOCK) + out = in; + + y_in_paddr = in->offset + in->y_offset; + if (in->u_offset) + u_in_paddr = in->offset + in->u_offset; + else + u_in_paddr = y_in_paddr + (pf_data.stride * pf_data.height); + if (in->v_offset) + v_in_paddr = in->offset + in->v_offset; + else + v_in_paddr = u_in_paddr + (pf_data.stride * pf_data.height) / 4; + p1_in_paddr = pf_data.qp_paddr; + if (qp_buf) + p1_in_paddr += pf_data.qp_size / 2; + + if (pf_data.mode == PF_H264_DEBLOCK) { + p2_in_paddr = p1_in_paddr + + ((pf_data.width + 15) / 16) * + ((pf_data.height + 15) / 16) * 4; + } else { + p2_in_paddr = 0; + } + + pr_debug("y_in_paddr = 0x%08X\nu_in_paddr = 0x%08X\n" + "v_in_paddr = 0x%08X\n" + "qp_paddr = 0x%08X\nbsb_paddr = 0x%08X\n", + y_in_paddr, u_in_paddr, v_in_paddr, p1_in_paddr, p2_in_paddr); + + y_out_paddr = out->offset + out->y_offset; + if (out->u_offset) + u_out_paddr = out->offset + out->u_offset; + else + u_out_paddr = y_out_paddr + (pf_data.stride * pf_data.height); + if (out->v_offset) + v_out_paddr = out->offset + out->v_offset; + else + v_out_paddr = + u_out_paddr + (pf_data.stride * pf_data.height) / 4; + + pr_debug("y_out_paddr = 0x%08X\nu_out_paddr = 0x%08X\n" + "v_out_paddr = 0x%08X\n", + y_out_paddr, u_out_paddr, v_out_paddr); + + pf_data.done_mask = 0; + + ipu_enable_irq(IPU_IRQ_PF_Y_OUT_EOF); + if (pf_data.mode != PF_MPEG4_DERING) { + ipu_enable_irq(IPU_IRQ_PF_U_OUT_EOF); + ipu_enable_irq(IPU_IRQ_PF_V_OUT_EOF); + } + + err = ipu_update_channel_buffer(MEM_PF_Y_MEM, IPU_INPUT_BUFFER, 0, + y_in_paddr); + if (err < 0) { + printk(KERN_ERR "mxc_pf: error setting Y input buffer\n"); + goto err0; + } + + err = ipu_update_channel_buffer(MEM_PF_Y_MEM, IPU_OUTPUT_BUFFER, 0, + y_out_paddr); + if (err < 0) { + printk(KERN_ERR "mxc_pf: error setting Y output buffer\n"); + goto err0; + } + + if (pf_data.mode != PF_MPEG4_DERING) { + err = + ipu_update_channel_buffer(MEM_PF_U_MEM, IPU_INPUT_BUFFER, 0, + u_in_paddr); + if (err < 0) { + printk(KERN_ERR + "mxc_pf: error setting U input buffer\n"); + goto err0; + } + + err = + ipu_update_channel_buffer(MEM_PF_U_MEM, IPU_OUTPUT_BUFFER, + 0, u_out_paddr); + if (err < 0) { + printk(KERN_ERR + "mxc_pf: error setting U output buffer\n"); + goto err0; + } + + err = + ipu_update_channel_buffer(MEM_PF_V_MEM, IPU_INPUT_BUFFER, 0, + v_in_paddr); + if (err < 0) { + printk(KERN_ERR + "mxc_pf: error setting V input buffer\n"); + goto err0; + } + + err = + ipu_update_channel_buffer(MEM_PF_V_MEM, IPU_OUTPUT_BUFFER, + 0, v_out_paddr); + if (err < 0) { + printk(KERN_ERR + "mxc_pf: error setting V output buffer\n"); + goto err0; + } + } + + err = ipu_update_channel_buffer(MEM_PF_Y_MEM, IPU_SEC_INPUT_BUFFER, 0, + p1_in_paddr); + if (err < 0) { + printk(KERN_ERR "mxc_pf: error setting QP buffer\n"); + goto err0; + } + + if (pf_data.mode == PF_H264_DEBLOCK) { + + err = ipu_update_channel_buffer(MEM_PF_U_MEM, + IPU_SEC_INPUT_BUFFER, 0, + p2_in_paddr); + if (err < 0) { + printk(KERN_ERR + "mxc_pf: error setting H264 BSB buffer\n"); + goto err0; + } + ipu_select_buffer(MEM_PF_U_MEM, IPU_SEC_INPUT_BUFFER, 0); + } + + ipu_select_buffer(MEM_PF_Y_MEM, IPU_OUTPUT_BUFFER, 0); + ipu_select_buffer(MEM_PF_Y_MEM, IPU_SEC_INPUT_BUFFER, 0); + ipu_select_buffer(MEM_PF_Y_MEM, IPU_INPUT_BUFFER, 0); + if (pf_data.mode != PF_MPEG4_DERING) { + ipu_select_buffer(MEM_PF_U_MEM, IPU_OUTPUT_BUFFER, 0); + ipu_select_buffer(MEM_PF_V_MEM, IPU_OUTPUT_BUFFER, 0); + ipu_select_buffer(MEM_PF_U_MEM, IPU_INPUT_BUFFER, 0); + ipu_select_buffer(MEM_PF_V_MEM, IPU_INPUT_BUFFER, 0); + } + + if (!pf_data.pf_enabled) { + pf_data.pf_enabled = 1; + if (pf_data.mode != PF_MPEG4_DERING) { + ipu_enable_channel(MEM_PF_V_MEM); + ipu_enable_channel(MEM_PF_U_MEM); + } + ipu_enable_channel(MEM_PF_Y_MEM); + } + + return 0; + err0: + return err; +} + +/*! + * Post Filter driver open function. This function implements the Linux + * file_operations.open() API function. + * + * @param inode struct inode * + * + * @param filp struct file * + * + * @return This function returns 0 on success or negative error code on + * error. + */ +static int mxc_pf_open(struct inode *inode, struct file *filp) +{ + int i; + + if (open_count) { + return -EBUSY; + } + + open_count++; + + memset(&pf_data, 0, sizeof(pf_data)); + for (i = 0; i < PF_MAX_BUFFER_CNT; i++) { + pf_data.buf[i].index = -1; + } + init_waitqueue_head(&pf_data.pf_wait); + init_MUTEX(&pf_data.busy_lock); + + pf_data.busy_flag = 1; + + ipu_request_irq(IPU_IRQ_PF_Y_OUT_EOF, mxc_pf_irq_handler, + 0, "mxc_ipu_pf", &pf_data); + + ipu_request_irq(IPU_IRQ_PF_U_OUT_EOF, mxc_pf_irq_handler, + 0, "mxc_ipu_pf", &pf_data); + + ipu_request_irq(IPU_IRQ_PF_V_OUT_EOF, mxc_pf_irq_handler, + 0, "mxc_ipu_pf", &pf_data); + + ipu_disable_irq(IPU_IRQ_PF_Y_OUT_EOF); + ipu_disable_irq(IPU_IRQ_PF_U_OUT_EOF); + ipu_disable_irq(IPU_IRQ_PF_V_OUT_EOF); + + return 0; +} + +/*! + * Post Filter driver release function. This function implements the Linux + * file_operations.release() API function. + * + * @param inode struct inode * + * + * @param filp struct file * + * + * @return This function returns 0 on success or negative error code on + * error. + */ +static int mxc_pf_release(struct inode *inode, struct file *filp) +{ + pf_reqbufs_params req_buf; + + if (open_count) { + mxc_pf_uninit(); + + /* Free any allocated buffers */ + req_buf.count = 0; + mxc_pf_reqbufs(&req_buf); + + ipu_free_irq(IPU_IRQ_PF_V_OUT_EOF, &pf_data); + ipu_free_irq(IPU_IRQ_PF_U_OUT_EOF, &pf_data); + ipu_free_irq(IPU_IRQ_PF_Y_OUT_EOF, &pf_data); + open_count--; + } + return 0; +} + +/*! + * Post Filter driver ioctl function. This function implements the Linux + * file_operations.ioctl() API function. + * + * @param inode struct inode * + * + * @param filp struct file * + * + * @param cmd IOCTL command to handle + * + * @param arg Pointer to arguments for IOCTL + * + * @return This function returns 0 on success or negative error code on + * error. + */ +static int mxc_pf_ioctl(struct inode *inode, struct file *filp, + unsigned int cmd, unsigned long arg) +{ + int retval = 0; + + switch (cmd) { + case PF_IOCTL_INIT: + { + pf_init_params pf_init; + + pr_debug("PF_IOCTL_INIT\n"); + if (copy_from_user(&pf_init, (void *)arg, + _IOC_SIZE(cmd))) { + retval = -EFAULT; + break; + } + + retval = mxc_pf_init(&pf_init); + if (retval < 0) + break; + pf_init.qp_paddr = pf_data.qp_paddr; + pf_init.qp_size = pf_data.qp_size; + + /* Return size of memory allocated */ + if (copy_to_user((void *)arg, &pf_init, _IOC_SIZE(cmd))) { + retval = -EFAULT; + break; + } + + pf_data.busy_flag = 0; + break; + } + case PF_IOCTL_UNINIT: + pr_debug("PF_IOCTL_UNINIT\n"); + retval = mxc_pf_uninit(); + break; + case PF_IOCTL_REQBUFS: + { + pf_reqbufs_params reqbufs; + pr_debug("PF_IOCTL_REQBUFS\n"); + + if (copy_from_user + (&reqbufs, (void *)arg, _IOC_SIZE(cmd))) { + retval = -EFAULT; + break; + } + + retval = mxc_pf_reqbufs(&reqbufs); + + /* Return size of memory allocated */ + if (copy_to_user((void *)arg, &reqbufs, _IOC_SIZE(cmd))) { + retval = -EFAULT; + break; + } + + break; + } + case PF_IOCTL_QUERYBUF: + { + pf_buf buf; + pr_debug("PF_IOCTL_QUERYBUF\n"); + + if (copy_from_user(&buf, (void *)arg, _IOC_SIZE(cmd))) { + retval = -EFAULT; + break; + } + + if ((buf.index < 0) || + (buf.index >= PF_MAX_BUFFER_CNT) || + (pf_data.buf[buf.index].index != buf.index)) { + retval = -EINVAL; + break; + } + /* Return size of memory allocated */ + if (copy_to_user((void *)arg, &pf_data.buf[buf.index], + _IOC_SIZE(cmd))) { + retval = -EFAULT; + break; + } + + break; + } + case PF_IOCTL_START: + { + int index; + pf_start_params start_params; + pr_debug("PF_IOCTL_START\n"); + + if (pf_data.busy_flag) { + retval = -EBUSY; + break; + } + + if (copy_from_user(&start_params, (void *)arg, + _IOC_SIZE(cmd))) { + retval = -EFAULT; + break; + } + if (start_params.h264_pause_row >= + ((pf_data.height + 15) / 16)) { + retval = -EINVAL; + break; + } + + pf_data.busy_flag = 1; + + index = start_params.in.index; + if ((index >= 0) && (index < PF_MAX_BUFFER_CNT)) { + if (pf_data.buf[index].offset != + start_params.in.offset) { + retval = -EINVAL; + break; + } + } + + index = start_params.out.index; + if ((index >= 0) && (index < PF_MAX_BUFFER_CNT)) { + if (pf_data.buf[index].offset != + start_params.out.offset) { + retval = -EINVAL; + break; + } + } + + ipu_pf_set_pause_row(start_params.h264_pause_row); + + /*Update y, u, v buffers in DMA Channels */ + if ((retval = + mxc_pf_start(&start_params.in, &start_params.out, + start_params.qp_buf)) + < 0) { + break; + } + + pr_debug("PF_IOCTL_START - processing started\n"); + + if (!start_params.wait) { + break; + } + + pr_debug("PF_IOCTL_START - waiting for completion\n"); + + pf_data.wait_mask = PF_WAIT_ALL; + /* Fall thru to wait */ + } + case PF_IOCTL_WAIT: + { + if (!pf_data.wait_mask) + pf_data.wait_mask = (u32) arg; + + if (pf_data.mode == PF_MPEG4_DERING) + pf_data.wait_mask &= PF_WAIT_Y; + + if (!pf_data.wait_mask) { + retval = -EINVAL; + break; + } + + if (!wait_event_interruptible_timeout(pf_data.pf_wait, + ((pf_data. + done_mask & + pf_data. + wait_mask) == + pf_data. + wait_mask), + 1 * HZ)) { + pr_debug + ("PF_IOCTL_WAIT: timeout, done_mask = %d\n", + pf_data.done_mask); + retval = -ETIME; + break; + } else if (signal_pending(current)) { + pr_debug("PF_IOCTL_WAIT: interrupt received\n"); + retval = -ERESTARTSYS; + break; + } + pf_data.busy_flag = 0; + pf_data.buffer_dirty = true; + pf_data.wait_mask = 0; + + pr_debug("PF_IOCTL_WAIT - finished\n"); + break; + } + case PF_IOCTL_RESUME: + { + int pause_row; + pr_debug("PF_IOCTL_RESUME\n"); + + if (pf_data.busy_flag == 0) { + retval = -EFAULT; + break; + } + + if (copy_from_user(&pause_row, (void *)arg, + _IOC_SIZE(cmd))) { + retval = -EFAULT; + break; + } + + if (pause_row >= ((pf_data.height + 15) / 16)) { + retval = -EINVAL; + break; + } + + ipu_pf_set_pause_row(pause_row); + break; + } + + default: + printk(KERN_ERR "ipu_pf_ioctl not supported ioctl\n"); + retval = -1; + } + + if (retval < 0) + pr_debug("return = %d\n", retval); + return retval; +} + +/*! + * Post Filter driver mmap function. This function implements the Linux + * file_operations.mmap() API function for mapping driver buffers to user space. + * + * @param file struct file * + * + * @param vma structure vm_area_struct * + * + * @return 0 Success, EINTR busy lock error, + * ENOBUFS remap_page error. + */ +static int mxc_pf_mmap(struct file *file, struct vm_area_struct *vma) +{ + unsigned long size = vma->vm_end - vma->vm_start; + int res = 0; + + pr_debug("pgoff=0x%lx, start=0x%lx, end=0x%lx\n", + vma->vm_pgoff, vma->vm_start, vma->vm_end); + + /* make this _really_ smp-safe */ + if (down_interruptible(&pf_data.busy_lock)) + return -EINTR; + + /* make buffers write-thru cacheable */ + vma->vm_page_prot = __pgprot(pgprot_val(vma->vm_page_prot) & + ~L_PTE_BUFFERABLE); + + if (remap_pfn_range(vma, vma->vm_start, + vma->vm_pgoff, size, vma->vm_page_prot)) { + printk(KERN_ERR "mxc_pf: remap_pfn_range failed\n"); + res = -ENOBUFS; + goto mmap_exit; + } + + vma->vm_flags &= ~VM_IO; /* using shared anonymous pages */ + + mmap_exit: + up(&pf_data.busy_lock); + return res; +} + +/*! + * Post Filter driver fsync function. This function implements the Linux + * file_operations.fsync() API function. + * + * The user must call fsync() before reading an output buffer. This + * call flushes the L1 and L2 caches + * + * @param filp structure file * + * + * @param dentry struct dentry * + * + * @param datasync unused + * + * @return status POLLIN | POLLRDNORM + */ +int mxc_pf_fsync(struct file *filp, struct dentry *dentry, int datasync) +{ + if (pf_data.buffer_dirty) { + flush_cache_all(); + outer_flush_all(); + } + pf_data.buffer_dirty = false; + + return 0; +} + +/*! + * Post Filter driver poll function. This function implements the Linux + * file_operations.poll() API function. + * + * @param file structure file * + * + * @param wait structure poll_table * + * + * @return status POLLIN | POLLRDNORM + */ +static unsigned int mxc_pf_poll(struct file *file, poll_table * wait) +{ + wait_queue_head_t *queue = NULL; + int res = POLLIN | POLLRDNORM; + + if (down_interruptible(&pf_data.busy_lock)) + return -EINTR; + + queue = &pf_data.pf_wait; + poll_wait(file, queue, wait); + + up(&pf_data.busy_lock); + + return res; +} + +/*! + * File operation structure functions pointers. + */ +static struct file_operations mxc_pf_fops = { + .owner = THIS_MODULE, + .open = mxc_pf_open, + .release = mxc_pf_release, + .ioctl = mxc_pf_ioctl, + .poll = mxc_pf_poll, + .mmap = mxc_pf_mmap, + .fsync = mxc_pf_fsync, +}; + +static int mxc_pf_major = 0; + +/*! + * Post Filter driver module initialization function. + */ +int mxc_pf_dev_init(void) +{ + int ret = 0; + struct class_device *temp_class; + + mxc_pf_major = register_chrdev(0, "mxc_ipu_pf", &mxc_pf_fops); + + if (mxc_pf_major < 0) { + printk(KERN_INFO "Unable to get a major for mxc_ipu_pf"); + return mxc_pf_major; + } + + mxc_pf_class = class_create(THIS_MODULE, "mxc_ipu_pf"); + if (IS_ERR(mxc_pf_class)) { + printk(KERN_ERR "Error creating mxc_ipu_pf class.\n"); + ret = PTR_ERR(mxc_pf_class); + goto err_out1; + } + + temp_class = class_device_create(mxc_pf_class, NULL, + MKDEV(mxc_pf_major, 0), NULL, + "mxc_ipu_pf"); + if (IS_ERR(temp_class)) { + printk(KERN_ERR "Error creating mxc_ipu_pf class device.\n"); + ret = PTR_ERR(temp_class); + goto err_out2; + } + + printk(KERN_INFO "IPU Post-filter loading\n"); + + return 0; + + err_out2: + class_destroy(mxc_pf_class); + err_out1: + unregister_chrdev(mxc_pf_major, "mxc_ipu_pf"); + return ret; +} + +/*! + * Post Filter driver module exit function. + */ +static void mxc_pf_exit(void) +{ + if (mxc_pf_major > 0) { + class_device_destroy(mxc_pf_class, MKDEV(mxc_pf_major, 0)); + class_destroy(mxc_pf_class); + unregister_chrdev(mxc_pf_major, "mxc_ipu_pf"); + } +} + +module_init(mxc_pf_dev_init); +module_exit(mxc_pf_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MXC MPEG4/H.264 Postfilter Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/pm/Kconfig b/drivers/mxc/pm/Kconfig new file mode 100644 index 000000000000..94715858ab51 --- /dev/null +++ b/drivers/mxc/pm/Kconfig @@ -0,0 +1,39 @@ +# +# Power Managment devices +# + +menu "Advanced Power Management devices" + +config MXC_DPTC + bool "MXC DPTC driver" + depends on (ARCH_MX3 || ARCH_MXC91321) && MXC_PMIC_MC13783 + default y + help + This selects the Freescale MXC Internal DPTC driver. + If unsure, say N. + +config MX27_DPTC + bool "MXC DPTC driver" + depends on ARCH_MX27 && MXC_PMIC_MC13783 + default y + help + This selects the Freescale MX27 Internal DPTC driver. + If unsure, say N. + +config MXC_DVFS + bool "MXC DVFS driver" + depends on ARCH_MX3 + default y + help + This selects the Freescale MXC Internal DVFS driver. + If unsure, say N. + +config MXC_DVFS_SDMA + bool "MXC DVFS SDMA support" + depends on MXC_DVFS + default n + help + This selects the Freescale MXC Internal DVFS driver SDMA support. + If unsure, say N. + +endmenu diff --git a/drivers/mxc/pm/Makefile b/drivers/mxc/pm/Makefile new file mode 100644 index 000000000000..6395fbac3e64 --- /dev/null +++ b/drivers/mxc/pm/Makefile @@ -0,0 +1,12 @@ +# +# Makefile for Power Managment devices. +# + +# Module dependencies for the MXC driver +obj-$(CONFIG_MXC_DPTC) += dptc.o + +obj-$(CONFIG_MXC_DPTC) += dvfs_dptc.o + +obj-$(CONFIG_MXC_DVFS) += dvfs_dptc.o + +obj-$(CONFIG_MX27_DPTC) += dptc_mx27.o diff --git a/drivers/mxc/pm/dptc.c b/drivers/mxc/pm/dptc.c new file mode 100644 index 000000000000..9e6bc5c3f92b --- /dev/null +++ b/drivers/mxc/pm/dptc.c @@ -0,0 +1,994 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file dptc.c + * + * @brief Driver for the Freescale Semiconductor MXC DPTC module. + * + * The DPTC driver + * driver is designed as a character driver which interacts with the MXC DPTC + * hardware. Upon initialization, the DPTC driver initializes the DPTC hardware + * sets up driver nodes attaches to the DPTC interrupt and initializes internal + * data structures. When the DPTC interrupt occurs the driver checks the cause + * of the interrupt (lower voltage, increase voltage or emergency) and changes + * the CPU voltage according to translation table that is loaded into the driver + * (the voltage changes are done by calling some routines in the mc13783 driver). + * The driver read method is used to read the currently loaded DPTC translation + * table and the write method is used in-order to update the translation table. + * Driver ioctls are used to change driver parameters and enable/disable the + * DPTC operation. + * + * @ingroup PM_MXC91321 PM_MX31 + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/fs.h> +#include <linux/interrupt.h> +#include <linux/workqueue.h> +#include <linux/proc_fs.h> +#include <linux/jiffies.h> +#include <linux/platform_device.h> +#include <asm/uaccess.h> +#include <asm/hardware.h> +#include <asm/semaphore.h> +#include <asm/arch/sdma.h> +#include <asm/arch/pmic_power.h> + +/* + * Module header file + */ +#include <asm/arch/dptc.h> + +/* + * CRM registers + */ +#ifdef CONFIG_ARCH_MX3 +#include "../../../arch/arm/mach-mx3/crm_regs.h" +#endif +#ifdef CONFIG_ARCH_MXC91321 +#include "../../../arch/arm/mach-mxc91321/crm_regs.h" +#endif + +/*! + * The dvfs_dptc_params structure holds all the internal DPTC driver parameters + * (current working point, current frequency, translation table and DPTC + * log buffer). + */ +static dvfs_dptc_params_s *dvfs_dptc_params; + +static struct delayed_work dptc_work; + +#ifndef CONFIG_MXC_DVFS_SDMA +static unsigned long ptvai; +#endif + +/*! + * Spinlock to protect CRM register accesses + */ +static DEFINE_SPINLOCK(mxc_crm_lock); + +/*! + * This function is called to read the contents of a CCM_MCU register + * + * @param reg_offset the CCM_MCU register that will read + * + * @return the register contents + */ +static unsigned long mxc_ccm_get_reg(unsigned int reg_offset) +{ + unsigned long reg; + + reg = __raw_readl(reg_offset); + return reg; +} + +/*! + * This function is called to modify the contents of a CCM_MCU register + * + * @param reg_offset the CCM_MCU register that will read + * @param mask the mask to be used to clear the bits that are to be modified + * @param data the data that should be written to the register + */ +static void mxc_ccm_modify_reg(unsigned int reg_offset, unsigned int mask, + unsigned int data) +{ + unsigned long flags; + unsigned long reg; + + spin_lock_irqsave(&mxc_crm_lock, flags); + reg = __raw_readl(reg_offset); + reg = (reg & (~mask)) | data; + __raw_writel(reg, reg_offset); + spin_unlock_irqrestore(&mxc_crm_lock, flags); +} + +/*! + * Enable DPTC hardware + */ +static void dptc_enable_dptc(void) +{ + mxc_ccm_modify_reg(MXC_CCM_PMCR0, MXC_CCM_PMCR0_DPTEN, + MXC_CCM_PMCR0_DPTEN); +} + +/*! + * Disable DPTC hardware + */ +static void dptc_disable_dptc(void) +{ + mxc_ccm_modify_reg(MXC_CCM_PMCR0, MXC_CCM_PMCR0_DPTEN, 0); +} + +/*! + * Mask DPTC interrupt + */ +static void dptc_mask_dptc_int(void) +{ + mxc_ccm_modify_reg(MXC_CCM_PMCR0, MXC_CCM_PMCR0_PTVAIM, + MXC_CCM_PMCR0_PTVAIM); +} + +/*! + * Unmask DPTC interrupt + */ +static void dptc_unmask_dptc_int(void) +{ + mxc_ccm_modify_reg(MXC_CCM_PMCR0, MXC_CCM_PMCR0_PTVAIM, 0); +} + +/*! + * Read the PTVAI bits from the CCM + * + * @return PTVAI bits value + */ +static unsigned long dptc_get_ptvai(void) +{ + return (mxc_ccm_get_reg(MXC_CCM_PMCR0) & MXC_CCM_PMCR0_PTVAI_MASK) + >> MXC_CCM_PMCR0_PTVAI_OFFSET; +} + +/*! + * Clear DCR bits of the CCM + */ +static void dptc_clear_dcr(void) +{ + if (!cpu_is_mxc91321()) { + mxc_ccm_modify_reg(MXC_CCM_PMCR0, MXC_CCM_PMCR0_DCR, 0); + } +} + +/*! + * If value equal to PTVAI bits indicates working point decrease + */ +#define DPTC_DECREASE (unsigned long)0x1 + +/*! + * If value equal to PTVAI bits indicates working point increase + */ +#define DPTC_INCREASE (unsigned long)0x2 + +/*! + * If value equal to PTVAI bits indicates working point increase to maximum + */ +#define DPTC_EMERG (unsigned long)0x3 + +#ifdef CONFIG_MXC_DVFS +#ifndef CONFIG_MXC_DVFS_SDMA +/* + * MCU will get DPTC interrupt + */ +static void dptc_set_ptvis(void) +{ + mxc_ccm_modify_reg(MXC_CCM_PMCR0, MXC_CCM_PMCR0_PTVIS, + MXC_CCM_PMCR0_PTVIS); +} +#endif +#endif + +/*! + * This function enables the DPTC reference circuits. + * + * @param params pointer to the DVFS & DPTC driver parameters structure. + * @param rc_state each high bit specifies which + * reference circuite to enable + * @return 0 on success, error code on failure + */ +int enable_ref_circuits(dvfs_dptc_params_s * params, unsigned char rc_state) +{ + int ret_val; + + if (params->dptc_is_active == FALSE) { + params->rc_state = rc_state; + + if (rc_state & 0x1) { + mxc_ccm_modify_reg(MXC_CCM_PMCR0, MXC_CCM_PMCR0_DRCE0, + MXC_CCM_PMCR0_DRCE0); + pr_debug("Ref circuit 0 enabled\n"); + } + if (rc_state & 0x2) { + mxc_ccm_modify_reg(MXC_CCM_PMCR0, MXC_CCM_PMCR0_DRCE1, + MXC_CCM_PMCR0_DRCE1); + pr_debug("Ref circuit 1 enabled\n"); + } + if (rc_state & 0x4) { + mxc_ccm_modify_reg(MXC_CCM_PMCR0, MXC_CCM_PMCR0_DRCE2, + MXC_CCM_PMCR0_DRCE2); + pr_debug("Ref circuit 2 enabled\n"); + } + if (rc_state & 0x8) { + mxc_ccm_modify_reg(MXC_CCM_PMCR0, MXC_CCM_PMCR0_DRCE3, + MXC_CCM_PMCR0_DRCE3); + pr_debug("Ref circuit 3 enabled\n"); + } + + ret_val = 0; + } else { + ret_val = -EINVAL; + } + + return ret_val; +} + +/*! + * This function disables the DPTC reference circuits. + * + * @param params pointer to the DVFS & DPTC driver parameters structure. + * @param rc_state each high bit specifies which + * reference circuite to disable + * @return 0 on success, error code on failure + */ +int disable_ref_circuits(dvfs_dptc_params_s * params, unsigned char rc_state) +{ + int ret_val; + + if (params->dptc_is_active == FALSE) { + params->rc_state &= ~rc_state; + + if (rc_state & 0x1) { + mxc_ccm_modify_reg(MXC_CCM_PMCR0, + MXC_CCM_PMCR0_DRCE0, 0); + pr_debug("Ref circuit 0 disabled\n"); + } + if (rc_state & 0x2) { + mxc_ccm_modify_reg(MXC_CCM_PMCR0, + MXC_CCM_PMCR0_DRCE1, 0); + pr_debug("Ref circuit 1 disabled\n"); + } + if (rc_state & 0x4) { + mxc_ccm_modify_reg(MXC_CCM_PMCR0, + MXC_CCM_PMCR0_DRCE2, 0); + pr_debug("Ref circuit 2 disabled\n"); + } + if (rc_state & 0x8) { + mxc_ccm_modify_reg(MXC_CCM_PMCR0, + MXC_CCM_PMCR0_DRCE3, 0); + pr_debug("Ref circuit 3 disabled\n"); + } + + ret_val = 0; + } else { + ret_val = -EINVAL; + } + + return ret_val; +} + +static void dptc_workqueue_handler(struct work_struct *work) +{ + dvfs_dptc_params_s *params; + + params = (dvfs_dptc_params_s *) work_data_bits(work); + + pr_debug("In %s: PTVAI = %lu\n", __FUNCTION__, dptc_get_ptvai()); + pr_debug("PMCR0 = 0x%lx ", mxc_ccm_get_reg(MXC_CCM_PMCR0)); + pr_debug("DCVR0 = 0x%lx ", mxc_ccm_get_reg(MXC_CCM_DCVR0)); + pr_debug("DCVR1 = 0x%lx ", mxc_ccm_get_reg(MXC_CCM_DCVR1)); + pr_debug("DCVR2 = 0x%lx ", mxc_ccm_get_reg(MXC_CCM_DCVR2)); + pr_debug("DCVR3 = 0x%lx ", mxc_ccm_get_reg(MXC_CCM_DCVR3)); + pr_debug("PTVAI = 0x%lx\n", ptvai); + + if ((params->suspended == 0 && params->turbo_mode_active == 1) + || !(cpu_is_mxc91321())) { +#ifndef CONFIG_MXC_DVFS_SDMA + switch (ptvai) { + /* Chip working point has decreased, lower working point by one */ + case DPTC_DECREASE: + set_dptc_wp(params, + params->dvfs_dptc_tables_ptr->curr_wp + 1); + break; + + /* Chip working point has increased, raise working point by one */ + case DPTC_INCREASE: + set_dptc_wp(params, + params->dvfs_dptc_tables_ptr->curr_wp - 1); + break; + + /* + * Chip working point has increased dramatically, + * raise working point to maximum */ + case DPTC_EMERG: + set_dptc_wp(params, + params->dvfs_dptc_tables_ptr->curr_wp - 1); + break; + + /* Unknown interrupt cause */ + default: + break; + } +#else + set_dptc_wp(params, params->dvfs_dptc_tables_ptr->curr_wp); + mxc_ccm_modify_reg(MXC_CCM_PMCR0, MXC_CCM_PMCR0_DPVV, + MXC_CCM_PMCR0_DPVV); +#endif + + /* + * If the DPTC module is still active, re-enable + * the DPTC hardware + */ + if (params->dptc_is_active) { + dptc_enable_dptc(); + dptc_unmask_dptc_int(); + } + } +} + +/*! + * This function is the DPTC Interrupt handler. + * This function wakes-up the dptc_workqueue_handler function that handles the + * DPTC interrupt. + */ +void dptc_irq(void) +{ +#ifndef CONFIG_MXC_DVFS_SDMA + ptvai = dptc_get_ptvai(); + + pr_debug("ptvai = 0x%lx (0x%x)!!!!!!!\n", ptvai, + __raw_readl(MXC_CCM_PMCR0)); +#ifdef CONFIG_ARCH_MXC91321 + pr_debug("REF CIRCUIT: %lu\n", mxc_ccm_get_reg(MXC_CCM_DPTCDBG)); +#endif + if (ptvai != 0) { + dptc_mask_dptc_int(); + dptc_disable_dptc(); + + schedule_delayed_work(&dptc_work, 0); + } +#else + schedule_delayed_work(&dptc_work, 0); +#endif +} + +/*! + * This function updates the CPU voltage, produced by mc13783, by calling mc13783 + * driver functions. + * + * @param dvfs_dptc_tables_ptr pointer to the DPTC translation table. + * @param wp current wp value. + * frequency. + * + */ +void set_pmic_voltage(dvfs_dptc_tables_s * dvfs_dptc_tables_ptr, int wp) +{ + /* Call mc13783 functions */ + t_regulator_voltage volt; + + /* Normal mode setting */ + if (cpu_is_mxc91321()) { + /* DVS mode setting */ + volt.sw1a = dvfs_dptc_tables_ptr->wp[wp].pmic_values[0]; + pmic_power_switcher_set_dvs(SW_SW1A, volt); + } else { + /* Normal mode setting */ + volt.sw1a = dvfs_dptc_tables_ptr->wp[wp].pmic_values[0]; + pmic_power_regulator_set_voltage(SW_SW1A, volt); + } + +#ifdef CONFIG_MXC_DVFS + volt.sw1a = dvfs_dptc_tables_ptr->wp[wp].pmic_values[1]; + pmic_power_switcher_set_dvs(SW_SW1A, volt); + + volt.sw1b = dvfs_dptc_tables_ptr->wp[wp].pmic_values[2]; + pmic_power_switcher_set_dvs(SW_SW1B, volt); + + volt.sw1b = dvfs_dptc_tables_ptr->wp[wp].pmic_values[3]; + pmic_power_switcher_set_stby(SW_SW1B, volt); +#endif + + if (cpu_is_mx31()) { + pr_debug("DPVV = 0x%lx (0x%lx)\n", + mxc_ccm_get_reg(MXC_CCM_PMCR0) & MXC_CCM_PMCR0_DPVV, + mxc_ccm_get_reg(MXC_CCM_PMCR0)); + } +} + +/*! + * This function enables the DPTC module. this function updates the DPTC + * thresholds, updates the mc13783, unmasks the DPTC interrupt and enables + * the DPTC module + * + * @param params pointer to the DVFS & DPTC driver parameters structure. + * + * @return 0 if DPTC module was enabled else returns -EINVAL. + */ +int start_dptc(dvfs_dptc_params_s * params) +{ + int freq_index = 0; + + /* Check if DPTC module isn't already active */ + if (params->dptc_is_active == FALSE) { + + enable_ref_circuits(params, params->rc_state); + disable_ref_circuits(params, ~params->rc_state); + + if (cpu_is_mxc91321() && !params->turbo_mode_active) { + params->dptc_is_active = TRUE; + return 0; + } + + /* + * Set the DPTC thresholds and mc13783 voltage to + * correspond to the current working point and frequency. + */ + set_pmic_voltage(params->dvfs_dptc_tables_ptr, + params->dvfs_dptc_tables_ptr->curr_wp); + +#ifdef CONFIG_MXC_DVFS + freq_index = dvfs_get_dvsup(); +#endif + + update_dptc_thresholds(params->dvfs_dptc_tables_ptr, + params->dvfs_dptc_tables_ptr->curr_wp, + freq_index); + + /* Mark DPCT module as active */ + params->dptc_is_active = TRUE; + + /* Enable the DPTC module and unmask the DPTC interrupt */ + dptc_enable_dptc(); + dptc_unmask_dptc_int(); + + return 0; + } + + /* DPTC module already active return error */ + return -EINVAL; +} + +/*! + * This function disables the DPTC module. + * + * @param params pointer to the DVFS & DPTC driver parameters structure. + * + * @return 0 if DPTC module was disabled else returns -EINVAL. + */ +int stop_dptc(dvfs_dptc_params_s * params) +{ + /* Check if DPTC module isn't already disabled */ + if (params->dptc_is_active != FALSE) { + + /* Disable the DPTC module and mask the DPTC interrupt */ + dptc_disable_dptc(); + dptc_mask_dptc_int(); + + /* Set working point 0 */ + set_dptc_wp(params, 0); + + /* Mark DPCT module as inactive */ + params->dptc_is_active = FALSE; + + return 0; + } + + /* DPTC module already disabled, return error */ + return -EINVAL; +} + +/*! + * This function updates the drivers current working point index. This index is + * used for access the current DTPC table entry and it corresponds to the + * current CPU working point measured by the DPTC hardware. + * + * @param params pointer to the DVFS & DPTC driver parameters structure. + * @param new_wp New working point index value to be set. + * + */ +void set_dptc_wp(dvfs_dptc_params_s * params, int new_wp) +{ + int freq_index = 0; + +#ifdef CONFIG_MXC_DVFS + freq_index = dvfs_get_dvsup(); +#endif + + /* + * Check if new index is smaller than the maximal working point + * index in the DPTC translation table and larger that 0. + */ + if ((new_wp < params->dvfs_dptc_tables_ptr->wp_num) + && (new_wp >= 0)) { + /* Set current working point index to new index */ + params->dvfs_dptc_tables_ptr->curr_wp = new_wp; + } + + /* + * Check if new index is larger than the maximal working point index in + * the DPTC translation table. + */ + if (new_wp >= params->dvfs_dptc_tables_ptr->wp_num) { + /* + * Set current working point index to maximal working point + * index in the DPTC translation table. + */ + params->dvfs_dptc_tables_ptr->curr_wp = + params->dvfs_dptc_tables_ptr->wp_num - 1; + } + + /* Check if new index is smaller than 0. */ + if (new_wp < 0) { + /* Set current working point index to 0 (minimal value) */ + params->dvfs_dptc_tables_ptr->curr_wp = 0; + } +#ifndef CONFIG_MXC_DVFS_SDMA + /* Update the DPTC hardware thresholds */ + update_dptc_thresholds(params->dvfs_dptc_tables_ptr, + params->dvfs_dptc_tables_ptr->curr_wp, + freq_index); +#else + params->prev_wp = params->dvfs_dptc_tables_ptr->curr_wp; +#endif + + /* Update the mc13783 voltage */ + set_pmic_voltage(params->dvfs_dptc_tables_ptr, + params->dvfs_dptc_tables_ptr->curr_wp); + + /* Write DPTC changes in the DPTC log buffer */ + add_dptc_log_entry(params, ¶ms->dptc_log_buffer, + params->dvfs_dptc_tables_ptr->curr_wp, freq_index); + + pr_debug("Current wp: %d\n", params->dvfs_dptc_tables_ptr->curr_wp); +} + +/*! + * This function updates the DPTC threshold registers. + * + * @param dvfs_dptc_tables_ptr pointer to the DPTC translation table. + * @param wp current wp value. + * @param freq_index translation table index of the current CPU + * frequency. + * + */ +void update_dptc_thresholds(dvfs_dptc_tables_s * dvfs_dptc_tables_ptr, + int wp, int freq_index) +{ + dcvr_state *dcvr; + +#ifdef CONFIG_MXC_DVFS_SDMA + + dcvr_state **dcvr_arr; + dcvr_state *curr_freq_dcvr; + + dcvr_arr = dvfs_dptc_tables_ptr->dcvr; + dcvr_arr = sdma_phys_to_virt((unsigned long)dcvr_arr); + curr_freq_dcvr = dcvr_arr[freq_index]; + curr_freq_dcvr = sdma_phys_to_virt((unsigned long)curr_freq_dcvr); + dcvr = &curr_freq_dcvr[wp]; +#else + /* Calculate current table entry offset in the DPTC translation table */ + dcvr = &dvfs_dptc_tables_ptr->dcvr[freq_index][wp]; +#endif + /* Update DPTC threshold registers */ + mxc_ccm_modify_reg(MXC_CCM_DCVR0, 0xffffffff, dcvr->dcvr_reg[0].AsInt); + mxc_ccm_modify_reg(MXC_CCM_DCVR1, 0xffffffff, dcvr->dcvr_reg[1].AsInt); + mxc_ccm_modify_reg(MXC_CCM_DCVR2, 0xffffffff, dcvr->dcvr_reg[2].AsInt); + mxc_ccm_modify_reg(MXC_CCM_DCVR3, 0xffffffff, dcvr->dcvr_reg[3].AsInt); +} + +/*! + * This function increments a log buffer index (head or tail) + * by the value of val. + * + * @param index pointer to the DPTC log buffer index that + * we wish to change. + * @param val the value in which the index should be incremented. + * + */ +static void inc_log_index(int *index, int val) +{ + *index = (*index + val) % LOG_ENTRIES; +} + +/*! + * This function returns the number of entries in the DPTC log buffer. + * + * @param dptc_log pointer to the DPTC log buffer structure. + * + * @return number of log buffer entries. + * + */ +static int get_entry_count(dptc_log_s * dptc_log) +{ + return ((dptc_log->head - dptc_log->tail + LOG_ENTRIES) % LOG_ENTRIES); +} + +/*! + * This function is used by the proc file system to read the DPTC log buffer. + * Each time the DPTC proc file system file is read this function is called + * and returns the data written in the log buffer. + * + * @param buf pointer to the buffer the data should be written to. + * @param start pointer to the pointer where the new data is + * written to. + * procedure should update the start pointer to point to + * where in the buffer the data was written. + * @param offset current offset in the DPTC proc file. + * @param count number of bytes to read. + * @param eof pointer to eof flag. should be set to 1 when + * reaching eof. + * @param data driver specific data pointer. + * + * @return number byte read from the log buffer. + * + */ +static int read_log(char *buf, char **start, off_t offset, int count, + int *eof, void *data) +{ + int entries_to_read; + int num_of_entries; + int entries_to_end_of_buffer, entries_left; + void *entry_ptr; + char *buf_ptr; + dvfs_dptc_params_s *params; + + params = (dvfs_dptc_params_s *) data; + + /* Calculate number of log entries to read */ + entries_to_read = count / sizeof(dptc_log_entry_s); + /* Get the number of current log buffer entries */ + num_of_entries = get_entry_count(¶ms->dptc_log_buffer); + + /* + * If number of entries to read is larger that the number of entries + * in the log buffer set number of entries to read to number of + * entries in the log buffer and set eof flag to 1 + */ + if (num_of_entries < entries_to_read) { + entries_to_read = num_of_entries; + *eof = 1; + } + + /* + * Down the log buffer mutex to exclude others from reading and + * writing to the log buffer. + */ + if (down_interruptible(¶ms->dptc_log_buffer.mutex)) { + return -EAGAIN; + } + + if (num_of_entries == 0 && offset == 0) { + inc_log_index(¶ms->dptc_log_buffer.tail, -1); + num_of_entries++; + entries_to_read++; + } + + /* get the pointer of the last (oldest) entry in the log buffer */ + entry_ptr = (void *)¶ms->dptc_log_buffer. + entries[params->dptc_log_buffer.tail]; + + /* Check if tail index wraps during current read */ + if ((params->dptc_log_buffer.tail + entries_to_read) < LOG_ENTRIES) { + /* No tail wrap around copy data from log buffer to buf */ + memcpy(buf, entry_ptr, + (entries_to_read * sizeof(dptc_log_entry_s))); + } else { + /* + * Tail wrap around. + * First copy data from current position until end of buffer, + * after that copy the rest from start of the log buffer. + */ + entries_to_end_of_buffer = LOG_ENTRIES - + params->dptc_log_buffer.tail; + memcpy(buf, entry_ptr, + (entries_to_end_of_buffer * sizeof(dptc_log_entry_s))); + + entry_ptr = (void *)¶ms->dptc_log_buffer.entries[0]; + buf_ptr = buf + + (entries_to_end_of_buffer * sizeof(dptc_log_entry_s)); + entries_left = entries_to_read - entries_to_end_of_buffer; + memcpy(buf_ptr, entry_ptr, + (entries_left * sizeof(dptc_log_entry_s))); + } + + /* Increment the tail index by the number of entries read */ + inc_log_index(¶ms->dptc_log_buffer.tail, entries_to_read); + + /* Up the log buffer mutex to allow access to the log buffer */ + up(¶ms->dptc_log_buffer.mutex); + + /* set start of data to point to buf */ + *start = buf; + + return (entries_to_read * sizeof(dptc_log_entry_s)); +} + +/*! + * This function initializes the DPTC log buffer. + * + * @param params pointer to \b dvfs_dptc_params_s. + * @param dptc_log pointer to the DPTC log buffer structure. + * + */ +void init_dptc_log(dvfs_dptc_params_s * params, dptc_log_s * dptc_log) +{ + dptc_log->tail = 0; + dptc_log->head = 0; + + /* initialize log buffer mutex used for accessing the log buffer */ + sema_init(&dptc_log->mutex, 1); + + /* add the first log buffer entry */ + add_dptc_log_entry(params, dptc_log, + params->dvfs_dptc_tables_ptr->curr_wp, + params->current_freq_index); +} + +/*! + * This function adds a new entry to the DPTC log buffer. + * + * @param params pointer to the DVFS & DPTC driver parameters structure. + * @param dptc_log pointer to the DPTC log buffer structure. + * @param wp value of the working point index written + * to the log buffer. + * @param freq_index value of the frequency index written to + * the log buffer. + * + * @return number of log buffer entries. + * + */ +void add_dptc_log_entry(dvfs_dptc_params_s * params, + dptc_log_s * dptc_log, int wp, int freq_index) +{ + /* + * Down the log buffer mutex to exclude others from reading and + * writing to the log buffer. + */ + if (down_interruptible(&dptc_log->mutex)) { + return; + } + + /* Write values to log buffer */ + dptc_log->entries[dptc_log->head].jiffies = jiffies; + dptc_log->entries[dptc_log->head].wp = wp; + + dptc_log->entries[dptc_log->head].voltage = wp; + +#ifdef CONFIG_MXC_DVFS + freq_index = dptc_get_ptvai(); +#endif + + dptc_log->entries[dptc_log->head].freq = freq_index; + + /* Increment the head index by 1 */ + inc_log_index(&dptc_log->head, 1); + /* If head index reaches the tail increment the tail by 1 */ + if (dptc_log->head == dptc_log->tail) { + inc_log_index(&dptc_log->tail, 1); + } + + /* Up the log buffer mutex to allow access to the log buffer */ + up(&dptc_log->mutex); +} + +/*! + * This function is called to put the DPTC in a low power state. + * + */ +void dptc_suspend(void) +{ +#ifdef CONFIG_MXC_DPTC + if (dvfs_dptc_params->dptc_is_active) { + if (cpu_is_mxc91321() && !dvfs_dptc_params->turbo_mode_active) { + return; + } + dptc_disable_dptc(); + set_dptc_wp(dvfs_dptc_params, + dvfs_dptc_params-> + dvfs_dptc_tables_ptr->curr_wp - 1); + } +#endif +} + +/*! + * This function is called to resume the DPTC from a low power state. + * + */ +void dptc_resume(void) +{ +#ifdef CONFIG_MXC_DPTC + if (dvfs_dptc_params->dptc_is_active) { + dptc_enable_dptc(); + dptc_unmask_dptc_int(); + } +#endif +} + +/*! + * This function initializes DPTC according to turbo mode status + * + * @param status Turbo mode disable, 1 - turbo mode enabled + * + */ +void dptc_set_turbo_mode(unsigned int status) +{ + if (cpu_is_mxc91321()) { + if (status == 1) { + dvfs_dptc_params->turbo_mode_active = 1; + set_dptc_wp(dvfs_dptc_params, + dvfs_dptc_params->dvfs_dptc_tables_ptr-> + curr_wp); + dvfs_dptc_params->suspended = 0; + } else { + dvfs_dptc_params->turbo_mode_active = 0; + if (cpu_is_mxc91321()) { + dvfs_dptc_params->suspended = + dvfs_dptc_params->turbo_mode_active; + return; + } else { + dvfs_dptc_params->suspended = 1; + } + set_pmic_voltage(dvfs_dptc_params->dvfs_dptc_tables_ptr, + dvfs_dptc_params-> + dvfs_dptc_tables_ptr->wp_num - 1); + } + } +} + +/*! + * This function initializes the DPTC hardware + * + * @param params pointer to the DPTC driver parameters structure. + * + */ +int __init init_dptc_controller(dvfs_dptc_params_s * params) +{ + dvfs_dptc_params = params; + + INIT_DELAYED_WORK(&dptc_work, dptc_workqueue_handler); + + if (create_proc_read_entry(PROC_NODE_NAME, 0, + NULL, read_log, params) == NULL) { + /* + * Error creating proc file system entry. + * Exit and return error code + */ + printk(KERN_ERR "DPTC: Unable create proc entry"); + return -EFAULT; + } + + /* Initialize the DPTC log buffer */ + init_dptc_log(params, ¶ms->dptc_log_buffer); + + set_dptc_curr_freq(params, 0); + + if (cpu_is_mxc91321()) { + dptc_set_turbo_mode(0); + } else { + set_dptc_wp(params, 0); + } + + /* By default all reference circuits are enabled */ + params->rc_state = DPTC_REF_CIRCUITS_STATUS; + + if (!cpu_is_mxc91321()) + dptc_clear_dcr(); + + /* Disable DPTC hardware and mask DPTC interrupt */ + dptc_disable_dptc(); + dptc_mask_dptc_int(); + +#ifdef CONFIG_MXC_DVFS +#ifndef CONFIG_MXC_DVFS_SDMA + dptc_set_ptvis(); +#endif +#endif + printk(KERN_INFO "DPTC controller initialized\n"); + + return 0; +} + +/*! + * This function updates the drivers current frequency index.This index is + * used for access the current DTPC table entry and it corresponds to the + * current CPU frequency (each CPU frequency has a separate index number + * according to the loaded DPTC table). + * + * @param params pointer to the DVFS & DPTC driver parameters structure. + * @param freq_index New frequency index value to be set. + * + * @return 0 if the frequency index was updated (the new index is a + * valid index and the DPTC module isn't active) else returns + * -EINVAL. + * + */ +int set_dptc_curr_freq(dvfs_dptc_params_s * params, unsigned int freq_index) +{ + /* + * Check if the new index value is a valid frequency index (smaller + * than the maximal index in the DPTC table) and if the DPTC module + * is disabled. + */ + if ((freq_index < params->dvfs_dptc_tables_ptr->dvfs_state_num) + && (params->dptc_is_active == FALSE)) { + /* + * Index is valid and DPTC module is + * disabled -> change frequency index. + */ + params->current_freq_index = freq_index; + add_dptc_log_entry(params, ¶ms->dptc_log_buffer, + params->dvfs_dptc_tables_ptr->curr_wp, + params->current_freq_index); + + return 0; + } + + /* Invalid index or DPTC module is active -> return error */ + return -EINVAL; +} + +#ifdef CONFIG_MXC_DVFS_SDMA +/* + * DPTC SDMA callback. + * Updates the mc13783 voltage + * + * @param params pointer to the DVFS & DPTC driver parameters structure. + */ +void dptc_sdma_callback(dvfs_dptc_params_s * params) +{ + printk(KERN_INFO "In %s: params->dvfs_dptc_tables_ptr->curr_wp = %d\n", + __FUNCTION__, params->dvfs_dptc_tables_ptr->curr_wp); +} +#endif + +/*! + * This function is called to put the DPTC in a low power state. + * + * @param pdev the device structure used to give information on which + * device to suspend (not relevant for DPTC) + * @param state the power state the device is entering + * + * @return The function always returns 0. + */ +int mxc_dptc_suspend(struct platform_device *pdev, pm_message_t state) +{ + dptc_suspend(); + return 0; +} + +/*! + * This function is called to resume the DPTC from a low power state. + * + * @param pdev the device structure used to give information on which + * device to suspend (not relevant for DPTC) + * + * @return The function always returns 0. + */ +int mxc_dptc_resume(struct platform_device *pdev) +{ + dptc_resume(); + return 0; +} + +EXPORT_SYMBOL(dptc_suspend); +EXPORT_SYMBOL(dptc_resume); diff --git a/drivers/mxc/pm/dptc_mx27.c b/drivers/mxc/pm/dptc_mx27.c new file mode 100644 index 000000000000..9e60294d63dd --- /dev/null +++ b/drivers/mxc/pm/dptc_mx27.c @@ -0,0 +1,1416 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file dptc_mx27.c + * + * @brief Driver for the Freescale Semiconductor MX27 DPTC module. + * + * The DPTC driver is designed as a character driver which interacts with the + * MX27 DPTC hardware. Upon initialization, the DPTC driver initializes the + * DPTC hardware sets up driver nodes attaches to the DPTC interrupt and + * initializes internal data structures. When the DPTC interrupt occurs the + * driver checks the cause of the interrupt (lower voltage, increase voltage or + * emergency) and changes the CPU voltage according to translation table that + * is loaded into the driver(the voltage changes are done by calling some + * routines in the mc13783 driver). The driver read method is used to read the + * currently loaded DPTC translation table and the write method is used + * in-order to update the translation table. Driver ioctls are used to change + * driver parameters and enable/disable the DPTC operation. + * + * @ingroup PM_MX27 + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <linux/workqueue.h> +#include <linux/proc_fs.h> +#include <linux/vmalloc.h> +#include <asm/uaccess.h> +#include <asm/semaphore.h> +#include <asm/arch/pmic_power.h> +#include <asm/arch/dvfs_dptc_struct.h> +#include "dvfs_dptc.h" + +#define MX27_PMCR_BASE_ADDR (SYSCTRL_BASE_ADDR + 0x60) +#define MX27_DCVR_BASE_ADDR (SYSCTRL_BASE_ADDR + 0x64) +#define MX27_DCVR0_OFFSET 0 +#define MX27_DCVR1_OFFSET 4 +#define MX27_DCVR2_OFFSET 8 +#define MX27_DCVR3_OFFSET 12 + +#define MX27_PMCR_MC_STATUS_OFFSET 31 +#define MX27_PMCR_MC_STATUS_MASK (1 << 31) +#define MX27_PMCR_EM_INTR_OFFSET 30 +#define MX27_PMCR_EM_INTR_MASK (1 << 30) +#define MX27_PMCR_UP_INTR_OFFSET 29 +#define MX27_PMCR_UP_INTR_MASK (1 << 29) +#define MX27_PMCR_LO_INTR_OFFSET 28 +#define MX27_PMCR_LO_INTR_MASK (1 << 28) +#define MX27_PMCR_REFCNT_OFFSET 16 +#define MX27_PMCR_REFCNT_MASK (0x7FF << 16) +#define MX27_PMCR_DCR_OFFSET 9 +#define MX27_PMCR_DCR_MASK (1 << 9) +#define MX27_PMCR_RCLKON_OFFSET 8 +#define MX27_PMCR_RCLKON_MASK (1 << 8) +#define MX27_PMCR_DRCE3_OFFSET 7 +#define MX27_PMCR_DRCE3_MASK (1 << 7) +#define MX27_PMCR_DRCE2_OFFSET 6 +#define MX27_PMCR_DRCE2_MASK (1 << 6) +#define MX27_PMCR_DRCE1_OFFSET 5 +#define MX27_PMCR_DRCE1_MASK (1 << 5) +#define MX27_PMCR_DRCE0_OFFSET 4 +#define MX27_PMCR_DRCE0_MASK (1 << 4) +#define MX27_PMCR_DIM_OFFSET 2 +#define MX27_PMCR_DIM_MASK (0x3 << 2) +#define MX27_PMCR_DIE_OFFSET 1 +#define MX27_PMCR_DIE_MASK (1 << 1) +#define MX27_PMCR_DPTEN_OFFSET 0 +#define MX27_PMCR_DPTEN_MASK (1 << 0) + +/*! + * DPTC proc file system entry name + */ +#define PROC_NODE_NAME "dptc" + +/* + * Prototypes + */ +static int dptc_mx27_open(struct inode *inode, struct file *filp); +static int dptc_mx27_release(struct inode *inode, struct file *filp); +static int dptc_mx27_ioctl(struct inode *inode, struct file *filp, + unsigned int cmd, unsigned long arg); +static irqreturn_t dptc_mx27_irq(int irq, void *dev_id); + +/* + * Global variables + */ + +/*! + * The dvfs_dptc_params structure holds all the internal DPTC driver parameters + * (current working point, current frequency, translation table and DPTC + * log buffer). + */ +static dvfs_dptc_params_s dptc_params; + +static struct work_struct dptc_work; +static volatile u32 dptc_intr_status; +static u32 dptc_initial_wp; + +/*! + * Holds the automatically selected DPTC driver major number. + */ +static int major; + +static struct class *mxc_dvfs_dptc_class; + +/* + * This mutex makes the Read,Write and IOCTL command mutual exclusive. + */ +DECLARE_MUTEX(access_mutex); + +/*! + * This structure contains pointers for device driver entry point. + * The driver register function in init module will call this + * structure. + */ +static struct file_operations fops = { + .open = dptc_mx27_open, + .release = dptc_mx27_release, + .ioctl = dptc_mx27_ioctl, +}; + +static +void mx27_pmcr_modify_reg(u32 mask, u32 val) +{ + volatile u32 reg; + reg = __raw_readl(IO_ADDRESS(MX27_PMCR_BASE_ADDR)); + reg = (reg & (~mask)) | val; + __raw_writel(reg, IO_ADDRESS(MX27_PMCR_BASE_ADDR)); +} + +static +void mx27_dcvr_modify_reg(u32 offset, u32 val) +{ + __raw_writel(val, IO_ADDRESS(MX27_DCVR_BASE_ADDR) + offset); +} + +/*! + * Enable DPTC hardware + */ +static void dptc_enable_dptc(void) +{ + mx27_pmcr_modify_reg(MX27_PMCR_DPTEN_MASK, 1); +} + +/*! + * Disable DPTC hardware + */ +static void dptc_disable_dptc(void) +{ + mx27_pmcr_modify_reg(MX27_PMCR_DPTEN_MASK, 0); +} + +/*! + * Mask DPTC interrupt + */ +static void dptc_mask_dptc_int(void) +{ + mx27_pmcr_modify_reg(MX27_PMCR_DIE_MASK, (0 << MX27_PMCR_DIE_OFFSET)); +} + +/*! + * Unmask DPTC interrupt + */ +static void dptc_unmask_dptc_int(void) +{ + mx27_pmcr_modify_reg(MX27_PMCR_DIE_MASK, (1 << MX27_PMCR_DIE_OFFSET)); +} + +/*! + * Clear DCR bits of the CCM + */ +static void dptc_clear_dcr(void) +{ + mx27_pmcr_modify_reg(MX27_PMCR_DCR_MASK, (0 << MX27_PMCR_DCR_OFFSET)); +} + +/*! + * This function enables the DPTC reference circuits. + * + * @param rc_state each high bit specifies which + * reference circuite to enable + * @return 0 on success, error code on failure + */ +static int enable_ref_circuits(unsigned char rc_state) +{ + int ret_val; + + if (dptc_params.dptc_is_active == FALSE) { + dptc_params.rc_state = rc_state; + + if (rc_state & 0x1) { + mx27_pmcr_modify_reg(MX27_PMCR_DRCE0_MASK, + (1 << MX27_PMCR_DRCE0_OFFSET)); + } + if (rc_state & 0x2) { + mx27_pmcr_modify_reg(MX27_PMCR_DRCE1_MASK, + (1 << MX27_PMCR_DRCE1_OFFSET)); + } + if (rc_state & 0x4) { + mx27_pmcr_modify_reg(MX27_PMCR_DRCE2_MASK, + (1 << MX27_PMCR_DRCE2_OFFSET)); + } + if (rc_state & 0x8) { + mx27_pmcr_modify_reg(MX27_PMCR_DRCE3_MASK, + (1 << MX27_PMCR_DRCE3_OFFSET)); + } + + ret_val = 0; + } else { + ret_val = -EINVAL; + } + + return ret_val; +} + +/*! + * This function disables the DPTC reference circuits. + * + * @param rc_state each high bit specifies which + * reference circuite to disable + * @return 0 on success, error code on failure + */ +static int disable_ref_circuits(unsigned char rc_state) +{ + int ret_val; + + if (dptc_params.dptc_is_active == FALSE) { + dptc_params.rc_state &= ~rc_state; + + if (rc_state & 0x1) { + mx27_pmcr_modify_reg(MX27_PMCR_DRCE0_MASK, + (0 << MX27_PMCR_DRCE0_OFFSET)); + } + if (rc_state & 0x2) { + mx27_pmcr_modify_reg(MX27_PMCR_DRCE1_MASK, + (0 << MX27_PMCR_DRCE1_OFFSET)); + } + if (rc_state & 0x4) { + mx27_pmcr_modify_reg(MX27_PMCR_DRCE2_MASK, + (0 << MX27_PMCR_DRCE2_OFFSET)); + } + if (rc_state & 0x8) { + mx27_pmcr_modify_reg(MX27_PMCR_DRCE3_MASK, + (0 << MX27_PMCR_DRCE3_OFFSET)); + } + + ret_val = 0; + } else { + ret_val = -EINVAL; + } + + return ret_val; +} + +/*! + * This function updates the CPU voltage, produced by MC13783, by calling + * MC13783 driver functions. + * + * @param dvfs_dptc_tables_ptr pointer to the DPTC translation table. + * @param wp current wp value. + * + */ +static void set_pmic_voltage(dvfs_dptc_tables_s * dvfs_dptc_tables_ptr, int wp) +{ + /* Call MC13783 functions */ + t_regulator_voltage volt; + + volt.sw1a = dvfs_dptc_tables_ptr->wp[wp].pmic_values[0]; + pmic_power_regulator_set_voltage(SW_SW1A, volt); +} + +/*! + * This function updates the DPTC threshold registers. + * + * @param dvfs_dptc_tables_ptr pointer to the DPTC translation table. + * @param wp current wp value. + * + */ +static void update_dptc_thresholds(dvfs_dptc_tables_s * dvfs_dptc_tables_ptr, + int wp) +{ + dcvr_state *dcvr; + + /* Calculate current table entry offset in the DPTC translation table */ + dcvr = &dvfs_dptc_tables_ptr->dcvr[0][wp]; + + /* Update DPTC threshold registers */ + mx27_dcvr_modify_reg(MX27_DCVR0_OFFSET, dcvr->dcvr_reg[0].AsInt); + mx27_dcvr_modify_reg(MX27_DCVR1_OFFSET, dcvr->dcvr_reg[1].AsInt); + mx27_dcvr_modify_reg(MX27_DCVR2_OFFSET, dcvr->dcvr_reg[2].AsInt); + mx27_dcvr_modify_reg(MX27_DCVR3_OFFSET, dcvr->dcvr_reg[3].AsInt); +} + +/*! + * This function increments a log buffer index (head or tail) + * by the value of val. + * + * @param index pointer to the DPTC log buffer index that + * we wish to change. + * @param val the value in which the index should be incremented. + * + */ +static void inc_log_index(int *index, int val) +{ + *index = (*index + val) % LOG_ENTRIES; +} + +/*! + * This function adds a new entry to the DPTC log buffer. + * + * @param dptc_log pointer to the DPTC log buffer structure. + * @param wp value of the working point index written + * to the log buffer. + * + * @return number of log buffer entries. + * + */ +static void add_dptc_log_entry(dptc_log_s * dptc_log, int wp) +{ + /* + * Down the log buffer mutex to exclude others from reading and + * writing to the log buffer. + */ + if (down_interruptible(&dptc_log->mutex)) { + return; + } + + /* Write values to log buffer */ + dptc_log->entries[dptc_log->head].jiffies = jiffies; + dptc_log->entries[dptc_log->head].wp = wp; + + dptc_log->entries[dptc_log->head].voltage = wp; + dptc_log->entries[dptc_log->head].freq = 0; + + /* Increment the head index by 1 */ + inc_log_index(&dptc_log->head, 1); + /* If head index reaches the tail increment the tail by 1 */ + if (dptc_log->head == dptc_log->tail) { + inc_log_index(&dptc_log->tail, 1); + } + + /* Up the log buffer mutex to allow access to the log buffer */ + up(&dptc_log->mutex); +} + +/*! + * This function updates the drivers current working point index. This index is + * used for access the current DTPC table entry and it corresponds to the + * current CPU working point measured by the DPTC hardware. + * + * @param new_wp New working point index value to be set. + * + */ +static void set_dptc_wp(int new_wp) +{ + /* + * Check if new index is smaller than the maximal working point + * index in the DPTC translation table and larger that 0. + */ + if ((new_wp < dptc_params.dvfs_dptc_tables_ptr->wp_num) + && (new_wp >= 0)) { + /* Set current working point index to new index */ + dptc_params.dvfs_dptc_tables_ptr->curr_wp = new_wp; + } + + /* + * Check if new index is larger than the maximal working point index in + * the DPTC translation table. + */ + if (new_wp >= dptc_params.dvfs_dptc_tables_ptr->wp_num) { + /* + * Set current working point index to maximal working point + * index in the DPTC translation table. + */ + dptc_params.dvfs_dptc_tables_ptr->curr_wp = + dptc_params.dvfs_dptc_tables_ptr->wp_num - 1; + } + + /* Check if new index is smaller than 0. */ + if (new_wp < 0) { + /* Set current working point index to 0 (minimal value) */ + dptc_params.dvfs_dptc_tables_ptr->curr_wp = 0; + } + + /* Update the DPTC hardware thresholds */ + update_dptc_thresholds(dptc_params.dvfs_dptc_tables_ptr, + dptc_params.dvfs_dptc_tables_ptr->curr_wp); + + /* Update the MC13783 voltage */ + set_pmic_voltage(dptc_params.dvfs_dptc_tables_ptr, + dptc_params.dvfs_dptc_tables_ptr->curr_wp); + + /* Write DPTC changes in the DPTC log buffer */ + add_dptc_log_entry(&dptc_params.dptc_log_buffer, + dptc_params.dvfs_dptc_tables_ptr->curr_wp); + +} + +static void dptc_workqueue_handler(struct work_struct *work) +{ + if (dptc_intr_status & 0x4) { + /* Chip working point has increased dramatically, + * raise working point to maximum */ + set_dptc_wp(dptc_params.dvfs_dptc_tables_ptr->curr_wp - 2); + } else if (dptc_intr_status & 0x2) { + /* Chip working point has increased, raise working point + * by one */ + set_dptc_wp(dptc_params.dvfs_dptc_tables_ptr->curr_wp + 1); + } else { + /* Chip working point has decreased, lower working point + * by one */ + set_dptc_wp(dptc_params.dvfs_dptc_tables_ptr->curr_wp - 1); + } + + /* + * If the DPTC module is still active, re-enable + * the DPTC hardware + */ + if (dptc_params.dptc_is_active) { + dptc_enable_dptc(); + dptc_unmask_dptc_int(); + } +} + +/*! + * This function enables the DPTC module. this function updates the DPTC + * thresholds, updates the MC13783, unmasks the DPTC interrupt and enables + * the DPTC module + * + * @return 0 if DPTC module was enabled else returns -EINVAL. + */ +static int start_dptc(void) +{ + /* Check if DPTC module isn't already active */ + if (dptc_params.dptc_is_active == FALSE) { + + enable_ref_circuits(dptc_params.rc_state); + disable_ref_circuits(~dptc_params.rc_state); + + /* + * Set the DPTC thresholds and MC13783 voltage to + * correspond to the current working point and frequency. + */ + set_pmic_voltage(dptc_params.dvfs_dptc_tables_ptr, + dptc_params.dvfs_dptc_tables_ptr->curr_wp); + + update_dptc_thresholds(dptc_params.dvfs_dptc_tables_ptr, + dptc_params.dvfs_dptc_tables_ptr-> + curr_wp); + + /* Mark DPTC module as active */ + dptc_params.dptc_is_active = TRUE; + + /* Enable the DPTC module and unmask the DPTC interrupt */ + dptc_enable_dptc(); + dptc_unmask_dptc_int(); + + return 0; + } + + /* DPTC module already active return error */ + return -EINVAL; +} + +/*! + * This function disables the DPTC module. + * + * @return 0 if DPTC module was disabled else returns -EINVAL. + */ +static int stop_dptc(void) +{ + /* Check if DPTC module isn't already disabled */ + if (dptc_params.dptc_is_active != FALSE) { + + /* Disable the DPTC module and mask the DPTC interrupt */ + dptc_disable_dptc(); + dptc_mask_dptc_int(); + + /* Set working point to default */ + set_dptc_wp(dptc_initial_wp); + + /* Mark DPCT module as inactive */ + dptc_params.dptc_is_active = FALSE; + + return 0; + } + + /* DPTC module already disabled, return error */ + return -EINVAL; +} + +static void init_dptc_wp(void) +{ + /* Call MC13783 functions */ + t_regulator_voltage volt; + int i; + + /* Normal mode setting */ + pmic_power_regulator_get_voltage(SW_SW1A, &volt); + for (i = 0; i < dptc_params.dvfs_dptc_tables_ptr->wp_num; i++) { + if (volt.sw1a == + dptc_params.dvfs_dptc_tables_ptr->wp[i].pmic_values[0]) { + break; + } + } + + /* + * Check if new index is smaller than the maximal working point + * index in the DPTC translation table and larger that 0. + */ + if ((i < dptc_params.dvfs_dptc_tables_ptr->wp_num) && (i >= 0)) { + /* Set current working point index to new index */ + dptc_params.dvfs_dptc_tables_ptr->curr_wp = i; + } + + /* + * Check if new index is larger than the maximal working point index in + * the DPTC translation table. + */ + if (i >= dptc_params.dvfs_dptc_tables_ptr->wp_num) { + /* + * Set current working point index to maximal working point + * index in the DPTC translation table. + */ + dptc_params.dvfs_dptc_tables_ptr->curr_wp = + dptc_params.dvfs_dptc_tables_ptr->wp_num - 1; + } + + dptc_initial_wp = dptc_params.dvfs_dptc_tables_ptr->curr_wp; + + /* Initialize the log buffer */ + add_dptc_log_entry(&dptc_params.dptc_log_buffer, + dptc_params.dvfs_dptc_tables_ptr->curr_wp); +} + +/*! + * This function returns the number of entries in the DPTC log buffer. + * + * @param dptc_log pointer to the DPTC log buffer structure. + * + * @return number of log buffer entries. + * + */ +static int get_entry_count(dptc_log_s * dptc_log) +{ + return ((dptc_log->head - dptc_log->tail + LOG_ENTRIES) % LOG_ENTRIES); +} + +/*! + * This function is used by the proc file system to read the DPTC log buffer. + * Each time the DPTC proc file system file is read this function is called + * and returns the data written in the log buffer. + * + * @param buf pointer to the buffer the data should be written to. + * @param start pointer to the pointer where the new data is + * written to. + * procedure should update the start pointer to point to + * where in the buffer the data was written. + * @param offset current offset in the DPTC proc file. + * @param count number of bytes to read. + * @param eof pointer to eof flag. should be set to 1 when + * reaching eof. + * @param data driver specific data pointer. + * + * @return number byte read from the log buffer. + * + */ +static int read_log(char *buf, char **start, off_t offset, int count, + int *eof, void *data) +{ + int entries_to_read; + int num_of_entries; + int entries_to_end_of_buffer, entries_left; + void *entry_ptr; + char *buf_ptr; + dvfs_dptc_params_s *params; + + params = (dvfs_dptc_params_s *) data; + + /* Calculate number of log entries to read */ + entries_to_read = count / sizeof(dptc_log_entry_s); + /* Get the number of current log buffer entries */ + num_of_entries = get_entry_count(¶ms->dptc_log_buffer); + + /* + * If number of entries to read is larger that the number of entries + * in the log buffer set number of entries to read to number of + * entries in the log buffer and set eof flag to 1 + */ + if (num_of_entries < entries_to_read) { + entries_to_read = num_of_entries; + *eof = 1; + } + + /* + * Down the log buffer mutex to exclude others from reading and + * writing to the log buffer. + */ + if (down_interruptible(¶ms->dptc_log_buffer.mutex)) { + return -EAGAIN; + } + + if (num_of_entries == 0 && offset == 0) { + inc_log_index(¶ms->dptc_log_buffer.tail, -1); + num_of_entries++; + entries_to_read++; + } + + /* get the pointer of the last (oldest) entry in the log buffer */ + entry_ptr = (void *)¶ms->dptc_log_buffer. + entries[params->dptc_log_buffer.tail]; + + /* Check if tail index wraps during current read */ + if ((params->dptc_log_buffer.tail + entries_to_read) < LOG_ENTRIES) { + /* No tail wrap around copy data from log buffer to buf */ + memcpy(buf, entry_ptr, + (entries_to_read * sizeof(dptc_log_entry_s))); + } else { + /* + * Tail wrap around. + * First copy data from current position until end of buffer, + * after that copy the rest from start of the log buffer. + */ + entries_to_end_of_buffer = LOG_ENTRIES - + params->dptc_log_buffer.tail; + memcpy(buf, entry_ptr, + (entries_to_end_of_buffer * sizeof(dptc_log_entry_s))); + + entry_ptr = (void *)¶ms->dptc_log_buffer.entries[0]; + buf_ptr = buf + + (entries_to_end_of_buffer * sizeof(dptc_log_entry_s)); + entries_left = entries_to_read - entries_to_end_of_buffer; + memcpy(buf_ptr, entry_ptr, + (entries_left * sizeof(dptc_log_entry_s))); + } + + /* Increment the tail index by the number of entries read */ + inc_log_index(¶ms->dptc_log_buffer.tail, entries_to_read); + + /* Up the log buffer mutex to allow access to the log buffer */ + up(¶ms->dptc_log_buffer.mutex); + + /* set start of data to point to buf */ + *start = buf; + + return (entries_to_read * sizeof(dptc_log_entry_s)); +} + +/*! + * This function initializes the DPTC log buffer. + * + * @param dptc_log pointer to the DPTC log buffer structure. + * + */ +static void init_dptc_log(dptc_log_s * dptc_log) +{ + dptc_log->tail = 0; + dptc_log->head = 0; + + /* initialize log buffer mutex used for accessing the log buffer */ + sema_init(&dptc_log->mutex, 1); +} + +/*! + * This function initializes the DPTC hardware + * + */ +static int init_dptc_controller(void) +{ + INIT_WORK(&dptc_work, dptc_workqueue_handler); + + if (create_proc_read_entry(PROC_NODE_NAME, 0, + NULL, read_log, &dptc_params) == NULL) { + /* + * Error creating proc file system entry. + * Exit and return error code + */ + printk(KERN_ERR "DPTC: Unable create proc entry"); + return -EFAULT; + } + + /* Initialize the DPTC log buffer */ + init_dptc_log(&dptc_params.dptc_log_buffer); + + init_dptc_wp(); + + /* By default all reference circuits are enabled */ + dptc_params.rc_state = DPTC_REF_CIRCUITS_STATUS; + + dptc_clear_dcr(); + + /* Disable DPTC hardware and mask DPTC interrupt */ + dptc_disable_dptc(); + dptc_mask_dptc_int(); + + printk(KERN_INFO "DPTC controller initialized\n"); + return 0; +} + +/*! + * This function is called to put the DPTC in a low power state. + * + * @param pdev the device structure used to give information on which + * device to suspend (not relevant for DPTC) + * @param state the power state the device is entering + * + * @return The function always returns 0. + */ +int mxc_dptc_suspend(struct platform_device *pdev, pm_message_t state) +{ + if (dptc_params.dptc_is_active) { + dptc_disable_dptc(); + set_dptc_wp(dptc_params.dvfs_dptc_tables_ptr->curr_wp - 1); + } + + dptc_params.suspended = 1; + + return 0; +} + +/*! + * This function is called to resume the DPTC from a low power state. + * + * @param pdev the device structure used to give information on which + * device to suspend (not relevant for DPTC) + * + * @return The function always returns 0. + */ +int mxc_dptc_resume(struct platform_device *pdev) +{ + dptc_params.suspended = 0; + + if (dptc_params.dptc_is_active) { + dptc_enable_dptc(); + dptc_unmask_dptc_int(); + } + + return 0; +} + +/*! + * This function frees power management table structures + */ +static void free_dvfs_dptc_table(void) +{ + int i; + + for (i = 0; i < dptc_params.dvfs_dptc_tables_ptr->dvfs_state_num; i++) { + kfree(dptc_params.dvfs_dptc_tables_ptr->dcvr[i]); + } + + kfree(dptc_params.dvfs_dptc_tables_ptr->dcvr); + kfree(dptc_params.dvfs_dptc_tables_ptr->table); + kfree(dptc_params.dvfs_dptc_tables_ptr->wp); + + kfree(dptc_params.dvfs_dptc_tables_ptr); + + dptc_params.dvfs_dptc_tables_ptr = 0; +} + +/* + * DVFS & DPTC table parsing function + * reads the next line of the table in text format + * + * @param str pointer to the previous line + * + * @return pointer to the next line + */ +static char *pm_table_get_next_line(char *str) +{ + char *line_ptr; + int flag = 0; + + if (strlen(str) == 0) + return str; + + line_ptr = strchr(str, '\n') + 1; + + while (!flag) { + if (strlen(line_ptr) == 0) { + flag = 1; + } else if (line_ptr[0] == '\n') { + line_ptr++; + } else if (line_ptr[0] == '#') { + line_ptr = pm_table_get_next_line(line_ptr); + } else { + flag = 1; + } + } + + return line_ptr; +} + +/* + * DVFS & DPTC table parsing function + * sets the values of DVFS & DPTC tables from + * table in text format + * + * @param pm_table pointer to the table in binary format + * @param pm_str pointer to the table in text format + * + * @return 0 on success, error code on failure + */ +static int dvfs_dptc_parse_table(dvfs_dptc_tables_s * pm_table, char *pm_str) +{ + char *pm_str_ptr; + int i, j, n; + dptc_wp *wp; + + pm_str_ptr = pm_str; + + n = sscanf(pm_str_ptr, "WORKING POINT %d\n", &pm_table->wp_num); + + if (n != 1) { + printk(KERN_WARNING "Failed read WORKING POINT number\n"); + return -1; + } + + pm_table->curr_wp = 0; + + pm_str_ptr = pm_table_get_next_line(pm_str_ptr); + pm_table->dvfs_state_num = 1; + + pm_table->wp = + (dptc_wp *) kmalloc(sizeof(dptc_wp) * pm_table->wp_num, GFP_KERNEL); + if (!pm_table->wp) { + printk(KERN_ERR "Failed allocating memory\n"); + return -ENOMEM; + } + + for (i = 0; i < pm_table->wp_num; i++) { + + wp = &pm_table->wp[i]; + + wp->wp_index = i; + + n = sscanf(pm_str_ptr, "WP 0x%x\n", + (unsigned int *)&wp->pmic_values[0]); + + if (n != 1) { + printk(KERN_WARNING "Failed read WP %d\n", i); + kfree(pm_table->wp); + return -1; + } + + pm_str_ptr = pm_table_get_next_line(pm_str_ptr); + + } + + pm_table->table = + (dvfs_state *) kmalloc(sizeof(dvfs_state) * + pm_table->dvfs_state_num, GFP_KERNEL); + + if (!pm_table->table) { + printk(KERN_WARNING "Failed allocating memory\n"); + kfree(pm_table->wp); + return -ENOMEM; + } + + pm_table->dcvr = + (dcvr_state **) kmalloc(sizeof(dcvr_state *) * + pm_table->dvfs_state_num, GFP_KERNEL); + + if (!pm_table->dcvr) { + printk(KERN_WARNING "Failed allocating memory\n"); + kfree(pm_table->table); + kfree(pm_table->wp); + return -ENOMEM; + } + + for (i = 0; i < pm_table->dvfs_state_num; i++) { + pm_table->dcvr[i] = + (dcvr_state *) kmalloc(sizeof(dcvr_state) * + pm_table->wp_num, GFP_KERNEL); + + if (!pm_table->dcvr[i]) { + printk(KERN_WARNING "Failed allocating memory\n"); + + for (j = i - 1; j >= 0; j--) { + kfree(pm_table->dcvr[j]); + } + + kfree(pm_table->dcvr); + return -ENOMEM; + } + + for (j = 0; j < pm_table->wp_num; j++) { + + n = sscanf(pm_str_ptr, "DCVR 0x%x 0x%x 0x%x 0x%x\n", + &pm_table->dcvr[i][j].dcvr_reg[0].AsInt, + &pm_table->dcvr[i][j].dcvr_reg[1].AsInt, + &pm_table->dcvr[i][j].dcvr_reg[2].AsInt, + &pm_table->dcvr[i][j].dcvr_reg[3].AsInt); + + if (n != 4) { + printk(KERN_WARNING "Failed read FREQ %d\n", i); + + for (j = i; j >= 0; j--) { + kfree(pm_table->dcvr[j]); + } + kfree(pm_table->dcvr); + kfree(pm_table->table); + kfree(pm_table->wp); + return -1; + } + + pm_str_ptr = pm_table_get_next_line(pm_str_ptr); + } + } + + return 0; +} + +/* + * Initializes the default values of DVFS & DPTC table + * + * @return 0 on success, error code on failure + */ +static int __init dvfs_dptc_init_default_table(void) +{ + int res = 0; + char *table_str; + + dvfs_dptc_tables_s *default_table; + + default_table = kmalloc(sizeof(dvfs_dptc_tables_s), GFP_KERNEL); + + if (!default_table) { + return -ENOMEM; + } + + table_str = default_table_str; + + memset(default_table, 0, sizeof(dvfs_dptc_tables_s)); + res = dvfs_dptc_parse_table(default_table, table_str); + + if (res == 0) { + dptc_params.dvfs_dptc_tables_ptr = default_table; + } + + return res; +} + +/*! + * This function is called when the driver is opened. This function + * checks if the user that open the device has root privileges. + * + * @param inode Pointer to device inode + * @param filp Pointer to device file structure + * + * @return The function returns 0 on success and a non-zero value on + * failure. + */ +static int dptc_mx27_open(struct inode *inode, struct file *filp) +{ + /* + * check if the program that opened the driver has root + * privileges, if not return error. + */ + if (!capable(CAP_SYS_ADMIN)) { + return -EACCES; + } + + if (dptc_params.suspended) { + return -EPERM; + } + + return 0; +} + +/*! + * This function is called when the driver is close. + * + * @param inode Pointer to device inode + * @param filp Pointer to device file structure + * + * @return The function returns 0 on success and a non-zero value on + * failure. + * + */ +static int dptc_mx27_release(struct inode *inode, struct file *filp) +{ + return 0; +} + +/*! + * This function dumps dptc translation table into string pointer + * + * @param str string pointer + */ +static void dvfs_dptc_dump_table(char *str) +{ + int i, j; + dcvr_state **dcvr_arr; + dcvr_state *dcvr_row; + + memset(str, 0, MAX_TABLE_SIZE); + + sprintf(str, "WORKING POINT %d\n", + dptc_params.dvfs_dptc_tables_ptr->wp_num); + str += strlen(str); + + for (i = 0; i < dptc_params.dvfs_dptc_tables_ptr->wp_num; i++) { + sprintf(str, "WP 0x%x\n", (unsigned int) + dptc_params.dvfs_dptc_tables_ptr->wp[i].pmic_values[0]); + str += strlen(str); + } + + dcvr_arr = dptc_params.dvfs_dptc_tables_ptr->dcvr; + for (i = 0; i < dptc_params.dvfs_dptc_tables_ptr->dvfs_state_num; i++) { + dcvr_row = dcvr_arr[i]; + + for (j = 0; j < dptc_params.dvfs_dptc_tables_ptr->wp_num; j++) { + sprintf(str, + "DCVR 0x%x 0x%x 0x%x 0x%x\n", + dcvr_row[j].dcvr_reg[0].AsInt, + dcvr_row[j].dcvr_reg[1].AsInt, + dcvr_row[j].dcvr_reg[2].AsInt, + dcvr_row[j].dcvr_reg[3].AsInt); + + str += strlen(str); + } + } +} + +/*! + * This function reads DVFS & DPTC translation table from user + * + * @param user_table pointer to user table + * @return 0 on success, error code on failure + */ +static int dvfs_dptc_set_table(char *user_table) +{ + int ret_val = -ENOIOCTLCMD; + char *tmp_str; + char *tmp_str_ptr; + dvfs_dptc_tables_s *dptc_table; + + if (dptc_params.dptc_is_active == TRUE) { + ret_val = -EINVAL; + return ret_val; + } + + tmp_str = vmalloc(MAX_TABLE_SIZE); + + if (tmp_str < 0) { + ret_val = (int)tmp_str; + } else { + memset(tmp_str, 0, MAX_TABLE_SIZE); + tmp_str_ptr = tmp_str; + + /* + * read num_of_wp and dvfs_state_num + * parameters from new table + */ + while (tmp_str_ptr - tmp_str < MAX_TABLE_SIZE && + (!copy_from_user(tmp_str_ptr, user_table, 1)) && + tmp_str_ptr[0] != 0) { + tmp_str_ptr++; + user_table++; + } + if (tmp_str_ptr == tmp_str) { + /* error reading from table */ + printk(KERN_ERR "Failed reading table from user, \ +didn't copy a character\n"); + ret_val = -EFAULT; + } else if (tmp_str_ptr - tmp_str == MAX_TABLE_SIZE) { + /* error reading from table */ + printk(KERN_ERR "Failed reading table from user, \ +read more than %d\n", MAX_TABLE_SIZE); + ret_val = -EFAULT; + } else { + /* + * copy table from user and set it as + * the current DPTC table + */ + dptc_table = kmalloc(sizeof(dvfs_dptc_tables_s), + GFP_KERNEL); + + if (!dptc_table) { + ret_val = -ENOMEM; + } else { + ret_val = + dvfs_dptc_parse_table(dptc_table, tmp_str); + + if (ret_val == 0) { + free_dvfs_dptc_table(); + dptc_params.dvfs_dptc_tables_ptr = + dptc_table; + + set_dptc_wp(dptc_initial_wp); + } + } + + } + + vfree(tmp_str); + } + + return ret_val; +} + +/*! + * This function is called when a ioctl call is made from user space. + * + * @param inode Pointer to device inode + * @param filp Pointer to device file structure + * @param cmd Ioctl command + * @param arg Ioctl argument + * + * Following are the ioctl commands for user to use:\n + * DPTC_IOCTENABLE : Enables the DPTC module.\n + * DPTC_IOCTDISABLE : Disables the DPTC module.\n + * DPTC_IOCSENABLERC : Enables DPTC reference circuits.\n + * DPTC_IOCSDISABLERC : Disables DPTC reference circuits.\n + * DPTC_IOCGETSTATE : Returns 1 if the DPTC module is enabled, + * returns 0 if the DPTC module is disabled.\n + * DPTC_IOCSWP : Sets working point.\n + * PM_IOCSTABLE : Sets translation table.\n + * PM_IOCGTABLE : Gets translation table.\n + * PM_IOCGFREQ : Returns current CPU frequency in Hz + * + * @return The function returns 0 on success and a non-zero value on + * failure. + */ +static int dptc_mx27_ioctl(struct inode *inode, struct file *filp, + unsigned int cmd, unsigned long arg) +{ + struct clk *clk; + unsigned int tmp; + int ret_val = -ENOIOCTLCMD; + char *tmp_str; + + tmp = arg; + + if (dptc_params.suspended) { + return -EPERM; + } + + down(&access_mutex); + + pr_debug("DVFS_DPTC ioctl (%d)\n", cmd); + + switch (cmd) { + /* Enable the DPTC module */ + case DPTC_IOCTENABLE: + ret_val = start_dptc(); + break; + + /* Disable the DPTC module */ + case DPTC_IOCTDISABLE: + ret_val = stop_dptc(); + break; + + case DPTC_IOCSENABLERC: + ret_val = enable_ref_circuits(tmp); + break; + + case DPTC_IOCSDISABLERC: + ret_val = disable_ref_circuits(tmp); + break; + /* + * Return the DPTC module current state. + * Returns 1 if the DPTC module is enabled, else returns 0 + */ + case DPTC_IOCGSTATE: + ret_val = dptc_params.dptc_is_active; + break; + case DPTC_IOCSWP: + if (dptc_params.dptc_is_active == FALSE) { + if (arg >= 0 && + arg < dptc_params.dvfs_dptc_tables_ptr->wp_num) { + set_dptc_wp(arg); + ret_val = 0; + } else { + ret_val = -EINVAL; + } + } else { + ret_val = -EINVAL; + } + break; + + /* Update DPTC table */ + case PM_IOCSTABLE: + ret_val = dvfs_dptc_set_table((char *)arg); + break; + + case PM_IOCGTABLE: + tmp_str = vmalloc(MAX_TABLE_SIZE); + if (tmp_str < 0) { + ret_val = (int)tmp_str; + } else { + dvfs_dptc_dump_table(tmp_str); + if (copy_to_user((char *)tmp, tmp_str, strlen(tmp_str))) { + printk(KERN_ERR + "Failed copy %d characters to 0x%x\n", + strlen(tmp_str), tmp); + ret_val = -EFAULT; + } else { + ret_val = 0; + } + vfree(tmp_str); + } + break; + + case PM_IOCGFREQ: + clk = clk_get(NULL, "cpu_clk"); + ret_val = clk_get_rate(clk); + break; + + /* Unknown ioctl command -> return error */ + default: + printk(KERN_ERR "Unknown ioctl command 0x%x\n", cmd); + ret_val = -ENOIOCTLCMD; + } + + up(&access_mutex); + + return ret_val; +} + +/*! + * This function is the DPTC Interrupt handler. + * This function wakes-up the dptc_workqueue_handler function that handles the + * DPTC interrupt. + * + * @param irq The Interrupt number + * @param dev_id Driver private data + * + * @result The function returns \b IRQ_RETVAL(1) if interrupt was handled, + * returns \b IRQ_RETVAL(0) if the interrupt was not handled. + * \b IRQ_RETVAL is defined in include/linux/interrupt.h. + */ +static irqreturn_t dptc_mx27_irq(int irq, void *dev_id) +{ + if (dptc_params.dptc_is_active == TRUE) { + dptc_intr_status = __raw_readl(IO_ADDRESS(MX27_PMCR_BASE_ADDR)); + + /* Acknowledge the interrupt */ + __raw_writel(dptc_intr_status, IO_ADDRESS(MX27_PMCR_BASE_ADDR)); + + /* Extract the interrupt cause */ + dptc_intr_status = + (dptc_intr_status >> MX27_PMCR_LO_INTR_OFFSET) & 0x7; + + if (dptc_intr_status != 0) { + dptc_mask_dptc_int(); + dptc_disable_dptc(); + schedule_work(&dptc_work); + } + } + + return IRQ_RETVAL(1); +} + +/*! + * This structure contains pointers to the power management callback functions. + */ +static struct platform_driver mxc_dptc_driver = { + .driver = { + .name = "mxc_dptc", + .bus = &platform_bus_type, + }, + .suspend = mxc_dptc_suspend, + .resume = mxc_dptc_resume, +}; + +/*! + * This is platform device structure for adding MU + */ +static struct platform_device mxc_dptc_device = { + .name = "mxc_dptc", + .id = 0, +}; + +/*! + * This function is called for module initialization. + * It initializes the driver data structures, sets up the DPTC hardware, + * registers the DPTC driver, creates a proc file system read entry and + * attaches the driver to the DPTC interrupt. + * + * @return 0 to indicate success else returns a negative number. + * + */ +static int __init dptc_mx27_init(void) +{ + int res; + struct class_device *temp_class; + + res = dvfs_dptc_init_default_table(); + + if (res < 0) { + printk(KERN_WARNING "Failed parsing default DPTC table\n"); + return res; + } + + /* Initialize DPTC hardware */ + res = init_dptc_controller(); + if (res < 0) { + free_dvfs_dptc_table(); + } + + /* Initialize internal driver structures */ + dptc_params.dptc_is_active = FALSE; + + /* + * Register DPTC driver as a char driver with an automatically allocated + * major number. + */ + major = register_chrdev(0, DEVICE_NAME, &fops); + + /* + * Return error if a negative major number is returned. + */ + if (major < 0) { + printk(KERN_ERR + "DPTC: Registering driver failed with %d\n", major); + free_dvfs_dptc_table(); + return major; + } + + mxc_dvfs_dptc_class = class_create(THIS_MODULE, DEVICE_NAME); + if (IS_ERR(mxc_dvfs_dptc_class)) { + printk(KERN_ERR "DPTC: Error creating class.\n"); + goto err_out; + } + + temp_class = + class_device_create(mxc_dvfs_dptc_class, NULL, MKDEV(major, 0), + NULL, DEVICE_NAME); + if (IS_ERR(temp_class)) { + printk(KERN_ERR "DPTC: Error creating class device.\n"); + goto err_out; + } + + /* request the DPTC interrupt */ + res = request_irq(INT_CCM, dptc_mx27_irq, 0, DEVICE_NAME, NULL); + + /* + * If res is not 0, then where was an error + * during attaching to DPTC interrupt. + * Exit and return error code. + */ + if (res) { + printk(KERN_ERR "DPTC: Unable to attach to DPTC interrupt"); + free_dvfs_dptc_table(); + goto err_out; + } + + /* Register low power modes functions */ + res = platform_driver_register(&mxc_dptc_driver); + if (res == 0) { + res = platform_device_register(&mxc_dptc_device); + if (res != 0) { + free_dvfs_dptc_table(); + goto err_out; + } + } + + dptc_params.suspended = FALSE; + + return 0; + + err_out: + printk(KERN_WARNING "MX27 DPTC driver was not initialized\n"); + class_device_destroy(mxc_dvfs_dptc_class, MKDEV(major, 0)); + class_destroy(mxc_dvfs_dptc_class); + unregister_chrdev(major, DEVICE_NAME); + return -1; +} + +/*! + * This function is called whenever the module is removed from the kernel. It + * unregisters the DVFS & DPTC driver from kernel, frees the irq number + * and removes the proc file system entry. + */ +static void __exit dptc_mx27_cleanup(void) +{ + free_dvfs_dptc_table(); + + /* Un-register the driver and remove its node */ + class_device_destroy(mxc_dvfs_dptc_class, MKDEV(major, 0)); + class_destroy(mxc_dvfs_dptc_class); + unregister_chrdev(major, DEVICE_NAME); + + /* release the DPTC interrupt */ + free_irq(INT_CCM, NULL); + + /* Unregister low power modes functions */ + platform_driver_unregister(&mxc_dptc_driver); + platform_device_unregister(&mxc_dptc_device); + + /* remove the DPTC proc file system entry */ + remove_proc_entry(PROC_NODE_NAME, NULL); +} + +module_init(dptc_mx27_init); +module_exit(dptc_mx27_cleanup); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MX27 DPTC driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/pm/dvfs_dptc.c b/drivers/mxc/pm/dvfs_dptc.c new file mode 100644 index 000000000000..d78cec974dae --- /dev/null +++ b/drivers/mxc/pm/dvfs_dptc.c @@ -0,0 +1,1248 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file dvfs_dptc.c + * + * @brief Driver for the Freescale Semiconductor MXC DVFS & DPTC module. + * + * The DVFS & DPTC driver + * driver is designed as a character driver which interacts with the MXC + * DVFS & DPTC hardware. Upon initialization, the DVFS & DPTC driver initializes + * the DVFS & DPTC hardware sets up driver nodes attaches to the DVFS & DPTC + * interrupts and initializes internal data structures. When the DVFS or DPTC + * interrupt occurs the driver checks the cause of the interrupt + * (lower voltage/frequency, increase voltage/frequency or emergency) and changes + * the CPU voltage and/or frequency according to translation table that is loaded + * into the driver (the voltage changes are done by calling some routines + * of the mc13783 driver). + * + * @ingroup PM_MX31 PM_MXC91321 + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/fs.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <linux/workqueue.h> +#include <linux/proc_fs.h> +#include <linux/jiffies.h> +#include <linux/vmalloc.h> +#include <asm/uaccess.h> +#include <asm/semaphore.h> +#include <asm/arch/hardware.h> +#include <asm/arch/pmic_external.h> + +/* + * Module header files + */ +#include "dvfs_dptc.h" +#include <asm/arch/dptc.h> + +#ifdef CONFIG_MXC_DVFS +#include <asm/arch/dvfs.h> +#endif + +/* + * Prototypes + */ +static int dvfs_dptc_open(struct inode *inode, struct file *filp); +static int dvfs_dptc_release(struct inode *inode, struct file *filp); +static int dvfs_dptc_ioctl(struct inode *inode, struct file *filp, + unsigned int cmd, unsigned long arg); +#ifdef CONFIG_MXC_DVFS_SDMA +static ssize_t dvfs_dptc_read(struct file *filp, char __user * buf, + size_t count, loff_t * ppos); +#endif + +#ifndef CONFIG_MXC_DVFS_SDMA +static irqreturn_t dvfs_dptc_irq(int irq, void *dev_id); +#else +static void dvfs_dptc_sdma_callback(dvfs_dptc_params_s * params); +#endif + +#ifdef CONFIG_MXC_DPTC +/*! + * This structure contains pointers to the power management callback functions. + */ +static struct platform_driver mxc_dptc_driver = { + .driver = { + .name = "mxc_dptc", + }, + .suspend = mxc_dptc_suspend, + .resume = mxc_dptc_resume, +}; + +/*! + * This is platform device structure for adding MU + */ +static struct platform_device mxc_dptc_device = { + .name = "mxc_dptc", + .id = 0, +}; +#endif + +/* + * Global variables + */ + +/*! + * The dvfs_dptc_params structure holds all the internal DPTC driver parameters + * (current working point, current frequency, translation table and DPTC + * log buffer). + */ +static dvfs_dptc_params_s dvfs_dptc_params; + +/*! + * Holds the automatically selected DPTC driver major number. + */ +static int major; + +static struct class *mxc_dvfs_dptc_class; + +/* + * This mutex makes the Read,Write and IOCTL command mutual exclusive. + */ +DECLARE_MUTEX(access_mutex); + +/*! + * This structure contains pointers for device driver entry point. + * The driver register function in init module will call this + * structure. + */ +static struct file_operations fops = { + .open = dvfs_dptc_open, + .release = dvfs_dptc_release, + .ioctl = dvfs_dptc_ioctl, +#ifdef CONFIG_MXC_DVFS_SDMA + .read = dvfs_dptc_read, +#endif +}; + +#ifdef CONFIG_MXC_DVFS_SDMA +/* + * Update pointers to physical addresses of DVFS & DPTC table + * for SDMA usage + * + * @param dvfs_dptc_tables_ptr pointer to the DVFS & + * DPTC translation table. + */ +static void dvfs_dptc_virt_2_phys(dvfs_dptc_tables_s * dvfs_dptc_table) +{ + int i; + + /* Update DCVR pointers */ + for (i = 0; i < dvfs_dptc_table->dvfs_state_num; i++) { + dvfs_dptc_table->dcvr[i] = (dcvr_state *) + sdma_virt_to_phys(dvfs_dptc_table->dcvr[i]); + } + dvfs_dptc_table->dcvr = (dcvr_state **) + sdma_virt_to_phys(dvfs_dptc_table->dcvr); + dvfs_dptc_table->table = (dvfs_state *) + sdma_virt_to_phys(dvfs_dptc_table->table); +} + +/* + * Update pointers to virtual addresses of DVFS & DPTC table + * for ARM usage + * + * @param dvfs_dptc_tables_ptr pointer to the DVFS & + * DPTC translation table. + */ +static void dvfs_dptc_phys_2_virt(dvfs_dptc_tables_s * dvfs_dptc_table) +{ + int i; + + dvfs_dptc_table->table = sdma_phys_to_virt + ((unsigned long)dvfs_dptc_table->table); + dvfs_dptc_table->dcvr = sdma_phys_to_virt + ((unsigned long)dvfs_dptc_table->dcvr); + + /* Update DCVR pointers */ + for (i = 0; i < dvfs_dptc_table->dvfs_state_num; i++) { + dvfs_dptc_table->dcvr[i] = + sdma_phys_to_virt((unsigned long)dvfs_dptc_table->dcvr[i]); + } +} +#endif + +/*! + * This function frees power management table structures + */ +static void free_dvfs_dptc_table(void) +{ + int i; + +#ifdef CONFIG_MXC_DVFS_SDMA + dvfs_dptc_phys_2_virt(dvfs_dptc_params.dvfs_dptc_tables_ptr); +#endif + + for (i = 0; + i < dvfs_dptc_params.dvfs_dptc_tables_ptr->dvfs_state_num; i++) { + sdma_free(dvfs_dptc_params.dvfs_dptc_tables_ptr->dcvr[i]); + } + + sdma_free(dvfs_dptc_params.dvfs_dptc_tables_ptr->dcvr); + sdma_free(dvfs_dptc_params.dvfs_dptc_tables_ptr->table); + sdma_free(dvfs_dptc_params.dvfs_dptc_tables_ptr->wp); + + sdma_free(dvfs_dptc_params.dvfs_dptc_tables_ptr); + + dvfs_dptc_params.dvfs_dptc_tables_ptr = 0; +} + +/* + * DVFS & DPTC table parsing function + * reads the next line of the table in text format + * + * @param str pointer to the previous line + * + * @return pointer to the next line + */ +static char *pm_table_get_next_line(char *str) +{ + char *line_ptr; + int flag = 0; + + if (strlen(str) == 0) + return str; + + line_ptr = strchr(str, '\n') + 1; + + while (!flag) { + if (strlen(line_ptr) == 0) { + flag = 1; + } else if (line_ptr[0] == '\n') { + line_ptr++; + } else if (line_ptr[0] == '#') { + line_ptr = pm_table_get_next_line(line_ptr); + } else { + flag = 1; + } + } + + return line_ptr; +} + +/* + * DVFS & DPTC table parsing function + * sets the values of DVFS & DPTC tables from + * table in text format + * + * @param pm_table pointer to the table in binary format + * @param pm_str pointer to the table in text format + * + * @return 0 on success, error code on failure + */ +static int dvfs_dptc_parse_table(dvfs_dptc_tables_s * pm_table, char *pm_str) +{ + char *pm_str_ptr; + int i, j, n; + dptc_wp *wp; + dvfs_state *table; + + pm_str_ptr = pm_str; + + n = sscanf(pm_str_ptr, "WORKING POINT %d\n", &pm_table->wp_num); + + if (n != 1) { + printk(KERN_WARNING "Failed read WORKING POINT number\n"); + return -1; + } + + pm_table->curr_wp = 0; + + pm_str_ptr = pm_table_get_next_line(pm_str_ptr); + + if (cpu_is_mx31() || cpu_is_mx32()) { + pm_table->dvfs_state_num = 4; + pm_table->use_four_freq = 1; + } else { + pm_table->dvfs_state_num = 1; + } + + pm_table->wp = + (dptc_wp *) sdma_malloc(sizeof(dptc_wp) * pm_table->wp_num); + if (!pm_table->wp) { + printk(KERN_ERR "Failed allocating memory\n"); + return -ENOMEM; + } + + for (i = 0; i < pm_table->wp_num; i++) { + + wp = &pm_table->wp[i]; + + wp->wp_index = i; + + if (cpu_is_mx31() || cpu_is_mx32()) { + n = sscanf(pm_str_ptr, "WP 0x%x 0x%x 0x%x 0x%x\n", + (unsigned int *)&wp->pmic_values[0], + (unsigned int *)&wp->pmic_values[1], + (unsigned int *)&wp->pmic_values[2], + (unsigned int *)&wp->pmic_values[3]); + + if (n != 4) { + printk(KERN_WARNING "Failed read WP %d\n", i); + sdma_free(pm_table->wp); + return -1; + } + } else { + n = sscanf(pm_str_ptr, "WP 0x%x\n", + (unsigned int *)&wp->pmic_values[0]); + + if (n != 1) { + printk(KERN_WARNING "Failed read WP %d\n", i); + sdma_free(pm_table->wp); + return -1; + } + } + + pm_str_ptr = pm_table_get_next_line(pm_str_ptr); + + } + + pm_table->table = + (dvfs_state *) sdma_malloc(sizeof(dvfs_state) * + pm_table->dvfs_state_num); + + if (!pm_table->table) { + printk(KERN_WARNING "Failed allocating memory\n"); + sdma_free(pm_table->wp); + return -ENOMEM; + } + + if (cpu_is_mx31() || cpu_is_mx32()) { + for (i = 0; i < pm_table->dvfs_state_num; i++) { + table = &pm_table->table[i]; + + n = sscanf(pm_str_ptr, + "FREQ %d %d 0x%x 0x%x 0x%x 0x%x %d\n", + (unsigned int *)&table->pll_sw_up, + (unsigned int *)&table->pll_sw_down, + (unsigned int *)&table->pdr0_up, + (unsigned int *)&table->pdr0_down, + (unsigned int *)&table->pll_up, + (unsigned int *)&table->pll_down, + (unsigned int *)&table->vscnt); + + if (n != 7) { + printk(KERN_WARNING "Failed read FREQ %d\n", i); + sdma_free(pm_table->table); + sdma_free(pm_table->wp); + return -1; + } + + pm_str_ptr = pm_table_get_next_line(pm_str_ptr); + } + } + + pm_table->dcvr = + (dcvr_state **) sdma_malloc(sizeof(dcvr_state *) * + pm_table->dvfs_state_num); + + if (!pm_table->dcvr) { + printk(KERN_WARNING "Failed allocating memory\n"); + sdma_free(pm_table->table); + sdma_free(pm_table->wp); + return -ENOMEM; + } + + for (i = 0; i < pm_table->dvfs_state_num; i++) { + pm_table->dcvr[i] = + (dcvr_state *) sdma_malloc(sizeof(dcvr_state) * + pm_table->wp_num); + + if (!pm_table->dcvr[i]) { + printk(KERN_WARNING "Failed allocating memory\n"); + + for (j = i - 1; j >= 0; j--) { + sdma_free(pm_table->dcvr[j]); + } + + sdma_free(pm_table->dcvr); + return -ENOMEM; + } + + for (j = 0; j < pm_table->wp_num; j++) { + + n = sscanf(pm_str_ptr, "DCVR 0x%x 0x%x 0x%x 0x%x\n", + &pm_table->dcvr[i][j].dcvr_reg[0].AsInt, + &pm_table->dcvr[i][j].dcvr_reg[1].AsInt, + &pm_table->dcvr[i][j].dcvr_reg[2].AsInt, + &pm_table->dcvr[i][j].dcvr_reg[3].AsInt); + + if (n != 4) { + printk(KERN_WARNING "Failed read FREQ %d\n", i); + + for (j = i; j >= 0; j--) { + sdma_free(pm_table->dcvr[j]); + } + sdma_free(pm_table->dcvr); + sdma_free(pm_table->table); + sdma_free(pm_table->wp); + return -1; + } + + pm_str_ptr = pm_table_get_next_line(pm_str_ptr); + } + } + + return 0; +} + +/* + * Initializes the default values of DVFS & DPTC table + * + * @return 0 on success, error code on failure + */ +static int __init dvfs_dptc_init_default_table(void) +{ + int res = 0; + char *table_str; + struct clk *clk; + + dvfs_dptc_tables_s *default_table; + + default_table = sdma_malloc(sizeof(dvfs_dptc_tables_s)); + + if (!default_table) { + return -ENOMEM; + } + + table_str = default_table_str; + if (cpu_is_mx31() || cpu_is_mx32()) { + if (cpu_is_mx31_rev(CHIP_REV_2_0) < 0) { + clk = clk_get(NULL, "ckih"); + if (clk_get_rate(clk) == 27000000) { + printk(KERN_INFO + "DVFS & DPTC: using 27MHz CKIH table\n"); +#ifdef CONFIG_ARCH_MX3 + table_str = default_table_str_27ckih; +#endif + } + } else { +#ifdef CONFIG_ARCH_MX3 + table_str = default_table_str_rev2; +#endif + } + clk_put(clk); + } + + memset(default_table, 0, sizeof(dvfs_dptc_tables_s)); + res = dvfs_dptc_parse_table(default_table, table_str); + + if (res == 0) { + dvfs_dptc_params.dvfs_dptc_tables_ptr = default_table; + } + + return res; +} + +#ifdef CONFIG_MXC_DVFS_SDMA +/*! + * This function is called for SDMA channel initialization. + * + * @param params pointer to the DPTC driver parameters structure. + * + * @return 0 to indicate success else returns a negative number. + */ +static int init_sdma_channel(dvfs_dptc_params_s * params) +{ + dma_channel_params sdma_params; + dma_request_t sdma_request; + int i; + int res = 0; + + params->sdma_channel = 0; + res = mxc_request_dma(¶ms->sdma_channel, "DVFS_DPTC"); + if (res < 0) { + printk(KERN_ERR "Failed allocate SDMA channel for DVFS_DPTC\n"); + return res; + } + + memset(&sdma_params, 0, sizeof(dma_channel_params)); + sdma_params.peripheral_type = CCM; + sdma_params.transfer_type = per_2_emi; + sdma_params.event_id = DMA_REQ_CCM; + sdma_params.callback = (dma_callback_t) dvfs_dptc_sdma_callback; + sdma_params.arg = params; + sdma_params.per_address = CCM_BASE_ADDR; + sdma_params.watermark_level = + sdma_virt_to_phys(params->dvfs_dptc_tables_ptr); + sdma_params.bd_number = 2; + + res = mxc_dma_setup_channel(params->sdma_channel, &sdma_params); + + if (res == 0) { + memset(&sdma_request, 0, sizeof(dma_request_t)); + + for (i = 0; i < DVFS_LB_SDMA_BD; i++) { + sdma_request.destAddr = (__u8 *) + (params->dvfs_log_buffer_phys + + i * (DVFS_LB_SIZE * DVFS_LB_SAMPLE_SIZE / 8) / + DVFS_LB_SDMA_BD); + sdma_request.count = DVFS_LB_SIZE / DVFS_LB_SDMA_BD; + sdma_request.bd_cont = 1; + + mxc_dma_set_config(params->sdma_channel, &sdma_request, + i); + } + + mxc_dma_start(params->sdma_channel); + } + + return res; +} +#endif + +/*! + * This function is called for module initialization. + * It initializes the driver data structures, sets up the DPTC hardware, + * registers the DPTC driver, creates a proc file system read entry and + * attaches the driver to the DPTC interrupt. + * + * @return 0 to indicate success else returns a negative number. + * + */ +static int __init dvfs_dptc_init(void) +{ + int res = 0; + struct class_device *temp_class; + + res = dvfs_dptc_init_default_table(); + + if (res < 0) { + printk(KERN_WARNING "Failed parsing default DPTC table\n"); + return res; + } +#ifdef CONFIG_MXC_DPTC + /* Initialize DPTC hardware */ + res = init_dptc_controller(&dvfs_dptc_params); + if (res < 0) { + free_dvfs_dptc_table(); + return res; + } +#endif + +#ifdef CONFIG_MXC_DVFS + /* Initialize DVFS hardware */ + res = init_dvfs_controller(&dvfs_dptc_params); + if (res < 0) { + free_dvfs_dptc_table(); + return res; + } + + /* Enable 4 mc13783 output voltages */ + pmic_write_reg(REG_ARBITRATION_SWITCHERS, 1, (1 << 5)); + + /* Enable mc13783 voltage ready signal */ + pmic_write_reg(REG_INTERRUPT_MASK_1, 0, (1 << 11)); + + /* Set mc13783 DVS speed 25mV each 4us */ + pmic_write_reg(REG_SWITCHERS_4, 1, (1 << 6)); + pmic_write_reg(REG_SWITCHERS_4, 0, (1 << 7)); + + dvfs_update_freqs_table(dvfs_dptc_params.dvfs_dptc_tables_ptr); +#endif + +#ifdef CONFIG_MXC_DVFS_SDMA + /* Update addresses to physical */ + if (res == 0) { + dvfs_dptc_virt_2_phys(dvfs_dptc_params.dvfs_dptc_tables_ptr); + } + + res = init_sdma_channel(&dvfs_dptc_params); + if (res < 0) { + free_dvfs_dptc_table(); + return res; + } +#endif + + /* Initialize internal driver structures */ + dvfs_dptc_params.dptc_is_active = FALSE; + +#ifdef CONFIG_MXC_DVFS + dvfs_dptc_params.dvfs_is_active = FALSE; +#endif + + /* + * Register DPTC driver as a char driver with an automatically allocated + * major number. + */ + major = register_chrdev(0, DEVICE_NAME, &fops); + + /* + * Return error if a negative major number is returned. + */ + if (major < 0) { + printk(KERN_ERR + "DPTC: Registering driver failed with %d\n", major); + free_dvfs_dptc_table(); + return major; + } + + mxc_dvfs_dptc_class = class_create(THIS_MODULE, DEVICE_NAME); + if (IS_ERR(mxc_dvfs_dptc_class)) { + printk(KERN_ERR "DPTC: Error creating class.\n"); + res = PTR_ERR(mxc_dvfs_dptc_class); + goto err_out1; + } + + temp_class = + class_device_create(mxc_dvfs_dptc_class, NULL, MKDEV(major, 0), + NULL, DEVICE_NAME); + if (IS_ERR(temp_class)) { + printk(KERN_ERR "DPTC: Error creating class device.\n"); + res = PTR_ERR(temp_class); + goto err_out2; + } +#ifndef CONFIG_MXC_DVFS_SDMA + /* request the DPTC interrupt */ + res = request_irq(INT_CCM, dvfs_dptc_irq, 0, DEVICE_NAME, NULL); + /* + * If res is not 0, then where was an error + * during attaching to DPTC interrupt. + * Exit and return error code. + */ + if (res) { + printk(KERN_ERR "DPTC: Unable to attach to DPTC interrupt"); + goto err_out3; + } + /* request the DVFS interrupt */ + res = request_irq(INT_DVFS, dvfs_dptc_irq, 0, DEVICE_NAME, NULL); + if (res) { + printk(KERN_ERR "DVFS: Unable to attach to DVFS interrupt"); + goto err_out4; + } +#endif + +#ifdef CONFIG_MXC_DPTC + /* Register low power modes functions */ + res = platform_driver_register(&mxc_dptc_driver); + if (res == 0) { + res = platform_device_register(&mxc_dptc_device); + if (res != 0) { + goto err_out5; + } + } +#endif + dvfs_dptc_params.suspended = FALSE; + + return res; + + err_out5: + free_irq(INT_DVFS, NULL); +#ifndef CONFIG_MXC_DVFS_SDMA + err_out4: +#endif + free_irq(INT_CCM, NULL); +#ifndef CONFIG_MXC_DVFS_SDMA + err_out3: +#endif + class_device_destroy(mxc_dvfs_dptc_class, MKDEV(major, 0)); + err_out2: + class_destroy(mxc_dvfs_dptc_class); + err_out1: + unregister_chrdev(major, DEVICE_NAME); + free_dvfs_dptc_table(); + printk(KERN_ERR "DVFS&DPTC driver was not initialized\n"); + return res; +} + +/*! + * This function is called whenever the module is removed from the kernel. It + * unregisters the DVFS & DPTC driver from kernel, frees the irq number + * and removes the proc file system entry. + */ +static void __exit dvfs_dptc_cleanup(void) +{ +#ifdef CONFIG_MXC_DPTC + /* Unregister low power modes functions */ + platform_driver_unregister(&mxc_dptc_driver); + platform_device_unregister(&mxc_dptc_device); +#endif + + free_dvfs_dptc_table(); + + /* Un-register the driver and remove its node */ + class_device_destroy(mxc_dvfs_dptc_class, MKDEV(major, 0)); + class_destroy(mxc_dvfs_dptc_class); + unregister_chrdev(major, DEVICE_NAME); + + /* release the DPTC interrupt */ + free_irq(INT_CCM, NULL); + /* release the DVFS interrupt */ + free_irq(INT_DVFS, NULL); + + /* remove the DPTC proc file system entry */ + remove_proc_entry(PROC_NODE_NAME, NULL); +} + +/*! + * This function is called when the driver is opened. This function + * checks if the user that open the device has root privileges. + * + * @param inode Pointer to device inode + * @param filp Pointer to device file structure + * + * @return The function returns 0 on success and a non-zero value on + * failure. + */ +static int dvfs_dptc_open(struct inode *inode, struct file *filp) +{ + /* + * check if the program that opened the driver has root + * privileges, if not return error. + */ + if (!capable(CAP_SYS_ADMIN)) { + return -EACCES; + } + + if (dvfs_dptc_params.suspended) { + return -EPERM; + } + + return 0; +} + +/*! + * This function is called when the driver is close. + * + * @param inode Pointer to device inode + * @param filp Pointer to device file structure + * + * @return The function returns 0 on success and a non-zero value on + * failure. + * + */ +static int dvfs_dptc_release(struct inode *inode, struct file *filp) +{ + return 0; +} + +/*! + * This function dumps dptc translation table into string pointer + * + * @param str string pointer + */ +static void dvfs_dptc_dump_table(char *str) +{ + int i, j; + dcvr_state **dcvr_arr; + dcvr_state *dcvr_row; + dvfs_state *table; + + memset(str, 0, MAX_TABLE_SIZE); + + sprintf(str, "WORKING POINT %d\n", + dvfs_dptc_params.dvfs_dptc_tables_ptr->wp_num); + str += strlen(str); + + for (i = 0; i < dvfs_dptc_params.dvfs_dptc_tables_ptr->wp_num; i++) { + if (cpu_is_mx31() || cpu_is_mx32()) { + sprintf(str, "WP 0x%x 0x%x 0x%x 0x%x\n", (unsigned int) + dvfs_dptc_params.dvfs_dptc_tables_ptr->wp[i]. + pmic_values[0], (unsigned int) + dvfs_dptc_params.dvfs_dptc_tables_ptr->wp[i]. + pmic_values[1], (unsigned int) + dvfs_dptc_params.dvfs_dptc_tables_ptr->wp[i]. + pmic_values[2], (unsigned int) + dvfs_dptc_params.dvfs_dptc_tables_ptr->wp[i]. + pmic_values[3]); + } else { + sprintf(str, "WP 0x%x\n", (unsigned int) + dvfs_dptc_params.dvfs_dptc_tables_ptr->wp[i]. + pmic_values[0]); + } + + str += strlen(str); + } + + if (cpu_is_mx31() || cpu_is_mx32()) { + for (i = 0; + i < dvfs_dptc_params.dvfs_dptc_tables_ptr->dvfs_state_num; + i++) { + table = dvfs_dptc_params.dvfs_dptc_tables_ptr->table; +#ifdef CONFIG_MXC_DVFS_SDMA + table = sdma_phys_to_virt((unsigned long)table); +#endif + sprintf(str, + "FREQ %d %d 0x%x 0x%x 0x%x 0x%x %d\n", + (unsigned int)table[i].pll_sw_up, + (unsigned int)table[i].pll_sw_down, + (unsigned int)table[i].pdr0_up, + (unsigned int)table[i].pdr0_down, + (unsigned int)table[i].pll_up, + (unsigned int)table[i].pll_down, + (unsigned int)table[i].vscnt); + + str += strlen(str); + } + } + + for (i = 0; + i < dvfs_dptc_params.dvfs_dptc_tables_ptr->dvfs_state_num; i++) { + dcvr_arr = dvfs_dptc_params.dvfs_dptc_tables_ptr->dcvr; +#ifdef CONFIG_MXC_DVFS_SDMA + dcvr_arr = sdma_phys_to_virt((unsigned long)dcvr_arr); +#endif + dcvr_row = dcvr_arr[i]; +#ifdef CONFIG_MXC_DVFS_SDMA + dcvr_row = sdma_phys_to_virt((unsigned long)dcvr_row); +#endif + + for (j = 0; + j < dvfs_dptc_params.dvfs_dptc_tables_ptr->wp_num; j++) { + sprintf(str, + "DCVR 0x%x 0x%x 0x%x 0x%x\n", + dcvr_row[j].dcvr_reg[0].AsInt, + dcvr_row[j].dcvr_reg[1].AsInt, + dcvr_row[j].dcvr_reg[2].AsInt, + dcvr_row[j].dcvr_reg[3].AsInt); + + str += strlen(str); + } + } +} + +/*! + * This function reads DVFS & DPTC translation table from user + * + * @param user_table pointer to user table + * @return 0 on success, error code on failure + */ +int dvfs_dptc_set_table(char *user_table) +{ + int ret_val = -ENOIOCTLCMD; + char *tmp_str; + char *tmp_str_ptr; + dvfs_dptc_tables_s *dptc_table; + + if ((cpu_is_mx31() || cpu_is_mx32()) && + (dvfs_dptc_params.dptc_is_active == TRUE || + dvfs_dptc_params.dvfs_is_active == TRUE)) { + ret_val = -EINVAL; + return ret_val; + } else if (dvfs_dptc_params.dptc_is_active == TRUE) { + ret_val = -EINVAL; + return ret_val; + } + + tmp_str = vmalloc(MAX_TABLE_SIZE); + + if (tmp_str < 0) { + ret_val = (int)tmp_str; + } else { + memset(tmp_str, 0, MAX_TABLE_SIZE); + tmp_str_ptr = tmp_str; + + /* + * read num_of_wp and dvfs_state_num + * parameters from new table + */ + while (tmp_str_ptr - tmp_str < MAX_TABLE_SIZE && + (!copy_from_user(tmp_str_ptr, user_table, 1)) && + tmp_str_ptr[0] != 0) { + tmp_str_ptr++; + user_table++; + } + if (tmp_str_ptr == tmp_str) { + /* error reading from table */ + printk(KERN_ERR "Failed reading table from user, \ +didn't copy a character\n"); + ret_val = -EFAULT; + } else if (tmp_str_ptr - tmp_str == MAX_TABLE_SIZE) { + /* error reading from table */ + printk(KERN_ERR "Failed reading table from user, \ +read more than %d\n", MAX_TABLE_SIZE); + ret_val = -EFAULT; + } else { + /* + * copy table from user and set it as + * the current DPTC table + */ + dptc_table = sdma_malloc(sizeof(dvfs_dptc_tables_s)); + + if (!dptc_table) { + ret_val = -ENOMEM; + } else { + ret_val = + dvfs_dptc_parse_table(dptc_table, tmp_str); + + if (ret_val == 0) { + free_dvfs_dptc_table(); + dvfs_dptc_params.dvfs_dptc_tables_ptr = + dptc_table; + +#ifdef CONFIG_MXC_DVFS + dvfs_update_freqs_table + (dvfs_dptc_params. + dvfs_dptc_tables_ptr); +#endif + +#ifdef CONFIG_MXC_DVFS_SDMA + /* Update addresses to physical */ + dvfs_dptc_virt_2_phys(dvfs_dptc_params. + dvfs_dptc_tables_ptr); + mxc_free_dma(dvfs_dptc_params. + sdma_channel); + init_sdma_channel(&dvfs_dptc_params); +#endif +#ifdef CONFIG_MXC_DPTC + set_dptc_curr_freq(&dvfs_dptc_params, + 0); + set_dptc_wp(&dvfs_dptc_params, 0); +#endif + } + } + + } + + vfree(tmp_str); + } + + return ret_val; +} + +#ifdef CONFIG_MXC_DVFS_SDMA +static ssize_t dvfs_dptc_read(struct file *filp, char __user * buf, + size_t count, loff_t * ppos) +{ + size_t count0, count1; + + while (dvfs_dptc_params.chars_in_buffer < count) { + //count = dvfs_dptc_params.chars_in_buffer; + waitqueue_active(&dvfs_dptc_params.dvfs_pred_wait); + wake_up(&dvfs_dptc_params.dvfs_pred_wait); + schedule(); + } + + if (dvfs_dptc_params.read_ptr + count < + dvfs_dptc_params.dvfs_log_buffer + + DVFS_LB_SIZE * DVFS_LB_SAMPLE_SIZE / 8) { + count0 = count; + count1 = 0; + } else { + count0 = + dvfs_dptc_params.dvfs_log_buffer + + DVFS_LB_SIZE * DVFS_LB_SAMPLE_SIZE / 8 - + dvfs_dptc_params.read_ptr; + count1 = count - count0; + } + + copy_to_user(buf, dvfs_dptc_params.read_ptr, count0); + copy_to_user(buf + count0, dvfs_dptc_params.dvfs_log_buffer, count1); + + if (count1 == 0) { + dvfs_dptc_params.read_ptr += count; + } else { + dvfs_dptc_params.read_ptr = + dvfs_dptc_params.dvfs_log_buffer + count1; + } + + if (dvfs_dptc_params.read_ptr == + dvfs_dptc_params.dvfs_log_buffer + + DVFS_LB_SIZE * DVFS_LB_SAMPLE_SIZE / 8) { + dvfs_dptc_params.read_ptr = dvfs_dptc_params.dvfs_log_buffer; + } + + dvfs_dptc_params.chars_in_buffer -= count; + + return count; +} +#endif + +/*! + * This function is called when a ioctl call is made from user space. + * + * @param inode Pointer to device inode + * @param filp Pointer to device file structure + * @param cmd Ioctl command + * @param arg Ioctl argument + * + * Following are the ioctl commands for user to use:\n + * DPTC_IOCTENABLE : Enables the DPTC module.\n + * DPTC_IOCTDISABLE : Disables the DPTC module.\n + * DPTC_IOCSENABLERC : Enables DPTC reference circuits.\n + * DPTC_IOCSDISABLERC : Disables DPTC reference circuits.\n + * DPTC_IOCGETSTATE : Returns 1 if the DPTC module is enabled, + * returns 0 if the DPTC module is disabled.\n + * DPTC_IOCSWP : Sets working point.\n + * PM_IOCSTABLE : Sets translation table.\n + * PM_IOCGTABLE : Gets translation table.\n + * DVFS_IOCTENABLE : Enables DVFS + * DVFS_IOCTDISABLE : Disables DVFS + * DVFS_IOCGSTATE : Returns 1 if the DVFS module is enabled, + * returns 0 if the DVFS module is disabled.\n + * DVFS_IOCSSWGP : Sets the value of DVFS SW general + * purpose bits.\n + * DVFS_IOCSWFI : Sets the status of WFI monitoring.\n + * PM_IOCGFREQ : Returns current CPU frequency in Hz + * DVFS_IOCSFREQ : Sets DVFS frequency when DVFS\n + * HW is disabled.\n + * + * @return The function returns 0 on success and a non-zero value on + * failure. + */ +static int dvfs_dptc_ioctl(struct inode *inode, struct file *filp, + unsigned int cmd, unsigned long arg) +{ + struct clk *clk; + unsigned int tmp; + int ret_val = -ENOIOCTLCMD; + char *tmp_str; + + tmp = arg; + + if (dvfs_dptc_params.suspended) { + return -EPERM; + } + + down(&access_mutex); + + pr_debug("DVFS_DPTC ioctl (%d)\n", cmd); + + switch (cmd) { +#ifdef CONFIG_MXC_DPTC + /* Enable the DPTC module */ + case DPTC_IOCTENABLE: + ret_val = start_dptc(&dvfs_dptc_params); + break; + + /* Disable the DPTC module */ + case DPTC_IOCTDISABLE: + ret_val = stop_dptc(&dvfs_dptc_params); + break; + + case DPTC_IOCSENABLERC: + ret_val = enable_ref_circuits(&dvfs_dptc_params, tmp); + break; + + case DPTC_IOCSDISABLERC: + ret_val = disable_ref_circuits(&dvfs_dptc_params, tmp); + break; + /* + * Return the DPTC module current state. + * Returns 1 if the DPTC module is enabled, else returns 0 + */ + case DPTC_IOCGSTATE: + ret_val = dvfs_dptc_params.dptc_is_active; + break; + case DPTC_IOCSWP: + if (dvfs_dptc_params.dptc_is_active == FALSE) { + if (arg >= 0 && + arg < + dvfs_dptc_params.dvfs_dptc_tables_ptr->wp_num) { + set_dptc_wp(&dvfs_dptc_params, arg); + ret_val = 0; + } else { + ret_val = -EINVAL; + } + } else { + ret_val = -EINVAL; + } + break; + +#endif /* CONFIG_MXC_DPTC */ + + /* Update DPTC table */ + case PM_IOCSTABLE: + ret_val = dvfs_dptc_set_table((char *)arg); + break; + + case PM_IOCGTABLE: + tmp_str = vmalloc(MAX_TABLE_SIZE); + if (tmp_str < 0) { + ret_val = (int)tmp_str; + } else { + dvfs_dptc_dump_table(tmp_str); + if (copy_to_user((char *)tmp, tmp_str, strlen(tmp_str))) { + printk(KERN_ERR + "Failed copy %d characters to 0x%x\n", + strlen(tmp_str), tmp); + ret_val = -EFAULT; + } else { + ret_val = 0; + } + vfree(tmp_str); + } + break; + +#ifdef CONFIG_MXC_DVFS + /* Enable the DVFS module */ + case DVFS_IOCTENABLE: + ret_val = start_dvfs(&dvfs_dptc_params); + break; + + /* Disable the DVFS module */ + case DVFS_IOCTDISABLE: + ret_val = stop_dvfs(&dvfs_dptc_params); + break; + /* + * Return the DVFS module current state. + * Returns 1 if the DPTC module is enabled, else returns 0 + */ + case DVFS_IOCGSTATE: + ret_val = dvfs_dptc_params.dvfs_is_active; + break; + case DVFS_IOCSSWGP: + ret_val = set_sw_gp((unsigned char)arg); + break; + case DVFS_IOCSWFI: + ret_val = set_wfi((unsigned char)arg); + break; + case DVFS_IOCSFREQ: + if (dvfs_dptc_params.dvfs_is_active == FALSE || + dvfs_dptc_params.dvfs_mode == DVFS_PRED_MODE) { + if (arg >= 0 && + arg < + dvfs_dptc_params.dvfs_dptc_tables_ptr-> + dvfs_state_num) { + ret_val = dvfs_set_state(arg); + } else { + ret_val = -EINVAL; + } + } else { + ret_val = -EINVAL; + } + break; + case DVFS_IOCSMODE: +#ifdef CONFIG_MXC_DVFS_SDMA + if (dvfs_dptc_params.dvfs_is_active == FALSE) { + if ((unsigned int)arg == DVFS_HW_MODE || + (unsigned int)arg == DVFS_PRED_MODE) { + dvfs_dptc_params.dvfs_mode = (unsigned int)arg; + ret_val = 0; + } else { + ret_val = -EINVAL; + } + } else { + ret_val = -EINVAL; + } +#else + /* Predictive mode is supported only in SDMA mode */ + ret_val = -EINVAL; +#endif + break; +#endif /* CONFIG_MXC_DVFS */ + case PM_IOCGFREQ: + clk = clk_get(NULL, "cpu_clk"); + ret_val = clk_get_rate(clk); + break; + + /* Unknown ioctl command -> return error */ + default: + printk(KERN_ERR "Unknown ioctl command 0x%x\n", cmd); + ret_val = -ENOIOCTLCMD; + } + + up(&access_mutex); + + return ret_val; +} + +#ifndef CONFIG_MXC_DVFS_SDMA +/*! + * This function is the DPTC & DVFS Interrupt handler. + * This function wakes-up the dvfs_dptc_workqueue_handler function that handles the + * DPTC interrupt. + * + * @param irq The Interrupt number + * @param dev_id Driver private data + * + * @result The function returns \b IRQ_RETVAL(1) if interrupt was handled, + * returns \b IRQ_RETVAL(0) if the interrupt was not handled. + * \b IRQ_RETVAL is defined in include/linux/interrupt.h. + */ +static irqreturn_t dvfs_dptc_irq(int irq, void *dev_id) +{ + +#ifdef CONFIG_MXC_DPTC + if (dvfs_dptc_params.dptc_is_active == TRUE) { + dptc_irq(); + } +#endif + +#ifdef CONFIG_MXC_DVFS + if (dvfs_dptc_params.dvfs_is_active == TRUE) { + dvfs_irq(&dvfs_dptc_params); + } +#endif + + return IRQ_RETVAL(1); +} +#else +/*! + * This function is the DPTC & DVFS SDMA callback. + * + * @param params pointer to the DVFS & DPTC driver parameters structure. + */ +static void dvfs_dptc_sdma_callback(dvfs_dptc_params_s * params) +{ + dma_request_t sdma_request_params; + int i; + + for (i = 0; i < DVFS_LB_SDMA_BD; i++) { + mxc_dma_get_config(params->sdma_channel, + &sdma_request_params, i); + + if (sdma_request_params.bd_error == 1) { + printk(KERN_WARNING + "Error in DVFS-DPTC buffer descriptor\n"); + } + + if (sdma_request_params.bd_done == 0) { + params->chars_in_buffer += + (DVFS_LB_SIZE * DVFS_LB_SAMPLE_SIZE / 8) / + DVFS_LB_SDMA_BD; + + if (params->chars_in_buffer > + (DVFS_LB_SIZE * DVFS_LB_SAMPLE_SIZE / 8)) { + params->chars_in_buffer = + DVFS_LB_SIZE * DVFS_LB_SAMPLE_SIZE / 8; + params->read_ptr = params->dvfs_log_buffer; + } + + sdma_request_params.destAddr = + (__u8 *) (params->dvfs_log_buffer_phys + + i * (DVFS_LB_SIZE * DVFS_LB_SAMPLE_SIZE / + 8) / DVFS_LB_SDMA_BD); + sdma_request_params.count = + DVFS_LB_SIZE / DVFS_LB_SDMA_BD; + sdma_request_params.bd_cont = 1; + mxc_dma_set_config(params->sdma_channel, + &sdma_request_params, i); + + if (params->dvfs_mode == DVFS_PRED_MODE) { + wake_up_interruptible(¶ms->dvfs_pred_wait); + } + } + } + +#ifdef CONFIG_MXC_DPTC + if (params->prev_wp != params->dvfs_dptc_tables_ptr->curr_wp) { + dptc_irq(); + } +#endif +} +#endif + +module_init(dvfs_dptc_init); +module_exit(dvfs_dptc_cleanup); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("DVFS & DPTC driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/pm/dvfs_dptc.h b/drivers/mxc/pm/dvfs_dptc.h new file mode 100644 index 000000000000..8f8d5fae3087 --- /dev/null +++ b/drivers/mxc/pm/dvfs_dptc.h @@ -0,0 +1,83 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file dvfs_dptc.h + * + * @brief MXC dvfs & dptc header file. + * + * @ingroup PM_MX27 PM_MX31 PM_MXC91321 + */ +#ifndef __DVFS_DPTC_H__ +#define __DVFS_DPTC_H__ + +#include <asm/arch/pm_api.h> +#include <asm/hardware.h> +#include <asm/arch/dvfs_dptc_struct.h> + +#ifdef CONFIG_ARCH_MX27 +#include <asm/arch/dma.h> +#else +#include <asm/arch/sdma.h> +#endif + +#ifdef CONFIG_ARCH_MX3 +#include "dvfs_dptc_table_mx31.h" +#include "dvfs_dptc_table_mx31_27ckih.h" +#include "dvfs_dptc_table_mx31_rev2.h" +#endif +#ifdef CONFIG_ARCH_MXC91321 +#include "dvfs_dptc_table_mxc91321.h" +#endif +#ifdef CONFIG_ARCH_MX27 +#include "dvfs_dptc_table_mx27.h" +#endif + +#ifdef CONFIG_MXC_DVFS +#ifndef CONFIG_MXC_DPTC +/*! + * DPTC Module Name + */ +#define DEVICE_NAME "dvfs" + +/*! + * DPTC driver node Name + */ +#define NODE_NAME "dvfs" +#endif /* ifndef CONFIG_MXC_DPTC */ +#ifdef CONFIG_MXC_DPTC +/*! + * DPTC Module Name + */ +#define DEVICE_NAME "dvfs_dptc" + +/*! + * DPTC driver node Name + */ +#define NODE_NAME "dvfs_dptc" +#endif /* ifdef CONFIG_MXC_DPTC */ +#else /* ifdef CONFIG_MXC_DVFS */ +/*! + * DPTC Module Name + */ +#define DEVICE_NAME "dptc" + +/*! + * DPTC driver node Name + */ +#define NODE_NAME "dptc" +#endif /* ifdef CONFIG_MXC_DVFS */ + +#define MAX_TABLE_SIZE 8192 + +#endif /* __DPTC_H__ */ diff --git a/drivers/mxc/pm/dvfs_dptc_table_mx27.h b/drivers/mxc/pm/dvfs_dptc_table_mx27.h new file mode 100644 index 000000000000..422db4b2c76c --- /dev/null +++ b/drivers/mxc/pm/dvfs_dptc_table_mx27.h @@ -0,0 +1,69 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file dptc.h + * + * @brief i.MX27 dptc table file. + * + * @ingroup PM_MX27 + */ +#ifndef __DVFS_DPTC_TABLE_MX27_H__ +#define __DVFS_DPTC_TABLE_MX27_H__ + +/*! + * Default DPTC table definition + */ +#define NUM_OF_FREQS 1 +#define NUM_OF_WP 17 + +static char *default_table_str = "WORKING POINT 17\n\ +\n\ +WP 0x1c\n\ +WP 0x1b\n\ +WP 0x1a\n\ +WP 0x19\n\ +WP 0x18\n\ +WP 0x17\n\ +WP 0x16\n\ +WP 0x15\n\ +WP 0x14\n\ +WP 0x13\n\ +WP 0x12\n\ +WP 0x11\n\ +WP 0x10\n\ +WP 0xf\n\ +WP 0xe\n\ +WP 0xd\n\ +WP 0xc\n\ +\n\ +DCVR 0xffe00000 0x18e2e85b 0xffe00000 0x25c4688a \n\ +DCVR 0xffe00000 0x18e2e85b 0xffe00000 0x25c4688a \n\ +DCVR 0xffe00000 0x1902e85b 0xffe00000 0x25e4688a \n\ +DCVR 0xffe00000 0x1922e85b 0xffe00000 0x25e4688a \n\ +DCVR 0xffe00000 0x1942ec5b 0xffe00000 0x2604688a \n\ +DCVR 0xffe00000 0x1942ec5b 0xffe00000 0x26646c8a \n\ +DCVR 0xffe00000 0x1962ec5b 0xffe00000 0x26c4708b \n\ +DCVR 0xffe00000 0x1962ec5b 0xffe00000 0x26e4708b \n\ +DCVR 0xffe00000 0x1982f05c 0xffe00000 0x2704748b \n\ +DCVR 0xffe00000 0x19c2f05c 0xffe00000 0x2744748b \n\ +DCVR 0xffe00000 0x1a02f45c 0xffe00000 0x2784788b \n\ +DCVR 0xffe00000 0x1a42f45c 0xffe00000 0x27c47c8b \n\ +DCVR 0xffe00000 0x1a82f85c 0xffe00000 0x2824808c \n\ +DCVR 0xffe00000 0x1aa2f85c 0xffe00000 0x2884848c \n\ +DCVR 0xffe00000 0x1ac2fc5c 0xffe00000 0x28e4888c \n\ +DCVR 0xffe00000 0x1ae2fc5c 0xffe00000 0x2924888c \n\ +DCVR 0xffe00000 0x1b23005d 0xffe00000 0x29648c8c \n\ +"; + +#endif diff --git a/drivers/mxc/pm/dvfs_dptc_table_mx31.h b/drivers/mxc/pm/dvfs_dptc_table_mx31.h new file mode 100644 index 000000000000..81fe80f28af8 --- /dev/null +++ b/drivers/mxc/pm/dvfs_dptc_table_mx31.h @@ -0,0 +1,157 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file dvfs_dptc_table_mx31.h + * + * @brief MX31 dvfs & dptc table file for CKIH clock 26MHz. + * + * @ingroup PM_MX31 + */ +#ifndef __DVFS_DPTC_TABLE_MX31_H__ +#define __DVFS_DPTC_TABLE_MX31_H__ + +#define NUM_OF_FREQS 4 +#define NUM_OF_WP 17 + +/*! + * Default DPTC table definition. + * The table doesn't use PLL switch, because on DDR boards + * PLL switch is not possible due to HW issue. + * For SDR boards new table can be loaded. + * + * The table keeps the same voltage of 3.5V for frequencies lower than 399MHz. + * Theoretically we don't need DPTC for these frequencies, + * but we have to keep DPTC enabled for fluent DVFS switching + * back to high frequency. + */ +static char *default_table_str = "WORKING POINT 17\n\ +\n\ +# mc13783 switcher SW values for each working point\n\ +# The first line is for WP of highest voltage\n\ +# The first column is for highest frequency\n\ +# SW1A SW1A DVS SW1B DVS SW1B STANDBY\n\ +WP 0x1d 0x12 0x12 0x12\n\ +WP 0x1c 0x12 0x12 0x12\n\ +WP 0x1b 0x12 0x12 0x12\n\ +WP 0x1a 0x12 0x12 0x12\n\ +WP 0x19 0x12 0x12 0x12\n\ +WP 0x18 0x12 0x12 0x12\n\ +WP 0x17 0x12 0x12 0x12\n\ +WP 0x16 0x12 0x12 0x12\n\ +WP 0x15 0x12 0x12 0x12\n\ +WP 0x14 0x12 0x12 0x12\n\ +WP 0x13 0x12 0x12 0x12\n\ +WP 0x12 0x12 0x12 0x12\n\ +WP 0x12 0x12 0x12 0x12\n\ +WP 0x12 0x12 0x12 0x12\n\ +WP 0x12 0x12 0x12 0x12\n\ +WP 0x12 0x12 0x12 0x12\n\ +WP 0x12 0x12 0x12 0x12\n\ +\n\ +# pll_sw_up pll_sw_down pdr_up pdr_down pll_up pll_down vscnt\n\ +# 532MHz\n\ +FREQ 0 0 0xff871e58 0xff871e59 0x33280c 0x33280c 7\n\ +# 266MHz\n\ +FREQ 0 0 0xff871e58 0xff871e5b 0x33280c 0x33280c 7\n\ +# 133MHz\n\ +FREQ 0 0 0xff871e58 0xff871e5b 0x33280c 0x33280c 7\n\ +# 133MHz\n\ +FREQ 0 0 0xff871e58 0xff871e5b 0x33280c 0x33280c 7\n\ +# 532MHz\n\ +DCVR 0xffc00000 0x95c00000 0xffc00000 0xe5800000\n\ +DCVR 0xffc00000 0x95e3e8e4 0xffc00000 0xe5b6fda0\n\ +DCVR 0xffc00000 0x95e3e8e4 0xffc00000 0xe5b6fda0\n\ +DCVR 0xffc00000 0x95e3e8e8 0xffc00000 0xe5f70da4\n\ +DCVR 0xffc00000 0x9623f8e8 0xffc00000 0xe6371da8\n\ +DCVR 0xffc00000 0x966408f0 0xffc00000 0xe6b73db0\n\ +DCVR 0xffc00000 0x96e428f4 0xffc00000 0xe7776dbc\n\ +DCVR 0xffc00000 0x976448fc 0xffc00000 0xe8379dc8\n\ +DCVR 0xffc00000 0x97e46904 0xffc00000 0xe977ddd8\n\ +DCVR 0xffc00000 0x98a48910 0xffc00000 0xeab81de8\n\ +DCVR 0xffc00000 0x9964b918 0xffc00000 0xebf86df8\n\ +DCVR 0xffc00000 0xffe4e924 0xffc00000 0xfff8ae08\n\ +DCVR 0xffc00000 0xffe5192c 0xffc00000 0xfff8fe1c\n\ +DCVR 0xffc00000 0xffe54938 0xffc00000 0xfff95e2c\n\ +DCVR 0xffc00000 0xffe57944 0xffc00000 0xfff9ae44\n\ +DCVR 0xffc00000 0xffe5b954 0xffc00000 0xfffa0e58\n\ +DCVR 0xffc00000 0xffe5e960 0xffc00000 0xfffa6e70\n\ +\n\ +# 266MHz\n\ +DCVR 0xffc00000 0x95c00000 0xffc00000 0xe5800000\n\ +DCVR 0xffc00000 0x95c8f238 0xffc00000 0xe58dc368\n\ +DCVR 0xffc00000 0x95c8f238 0xffc00000 0xe58dc368\n\ +DCVR 0xffc00000 0x95c8f238 0xffc00000 0xe5cdc368\n\ +DCVR 0xffc00000 0x9609023c 0xffc00000 0xe60dc36c\n\ +DCVR 0xffc00000 0x9649023c 0xffc00000 0xe68dd36c\n\ +DCVR 0xffc00000 0x96c9023c 0xffc00000 0xe74dd370\n\ +DCVR 0xffc00000 0x97491240 0xffc00000 0xe80de374\n\ +DCVR 0xffc00000 0x97c92240 0xffc00000 0xe94df374\n\ +DCVR 0xffc00000 0x98892244 0xffc00000 0xea8e0378\n\ +DCVR 0xffc00000 0x99493248 0xffc00000 0xebce137c\n\ +DCVR 0xffc00000 0xffc93248 0xffc00000 0xffce3384\n\ +DCVR 0xffc00000 0xffc9424c 0xffc00000 0xffce4388\n\ +DCVR 0xffc00000 0xffc95250 0xffc00000 0xffce538c\n\ +DCVR 0xffc00000 0xffc96250 0xffc00000 0xffce7390\n\ +DCVR 0xffc00000 0xffc97254 0xffc00000 0xffce8394\n\ +DCVR 0xffc00000 0xffc98258 0xffc00000 0xffcea39c\n\ +\n\ +# 133MHz\n\ +DCVR 0xffc00000 0x95c00000 0xffc00000 0xe5800000\n\ +DCVR 0xffc00000 0x95c8f238 0xffc00000 0xe58dc368\n\ +DCVR 0xffc00000 0x95c8f238 0xffc00000 0xe58dc368\n\ +DCVR 0xffc00000 0x95c8f238 0xffc00000 0xe5cdc368\n\ +DCVR 0xffc00000 0x9609023c 0xffc00000 0xe60dc36c\n\ +DCVR 0xffc00000 0x9649023c 0xffc00000 0xe68dd36c\n\ +DCVR 0xffc00000 0x96c9023c 0xffc00000 0xe74dd370\n\ +DCVR 0xffc00000 0x97491240 0xffc00000 0xe80de374\n\ +DCVR 0xffc00000 0x97c92240 0xffc00000 0xe94df374\n\ +DCVR 0xffc00000 0x98892244 0xffc00000 0xea8e0378\n\ +DCVR 0xffc00000 0x99493248 0xffc00000 0xebce137c\n\ +DCVR 0xffc00000 0xffc93248 0xffc00000 0xffce3384\n\ +DCVR 0xffc00000 0xffc9424c 0xffc00000 0xffce4388\n\ +DCVR 0xffc00000 0xffc95250 0xffc00000 0xffce538c\n\ +DCVR 0xffc00000 0xffc96250 0xffc00000 0xffce7390\n\ +DCVR 0xffc00000 0xffc97254 0xffc00000 0xffce8394\n\ +DCVR 0xffc00000 0xffc98258 0xffc00000 0xffcea39c\n\ +\n\ +# 133MHz\n\ +DCVR 0xffc00000 0x95c00000 0xffc00000 0xe5800000\n\ +DCVR 0xffc00000 0x95c8f238 0xffc00000 0xe58dc368\n\ +DCVR 0xffc00000 0x95c8f238 0xffc00000 0xe58dc368\n\ +DCVR 0xffc00000 0x95c8f238 0xffc00000 0xe5cdc368\n\ +DCVR 0xffc00000 0x9609023c 0xffc00000 0xe60dc36c\n\ +DCVR 0xffc00000 0x9649023c 0xffc00000 0xe68dd36c\n\ +DCVR 0xffc00000 0x96c9023c 0xffc00000 0xe74dd370\n\ +DCVR 0xffc00000 0x97491240 0xffc00000 0xe80de374\n\ +DCVR 0xffc00000 0x97c92240 0xffc00000 0xe94df374\n\ +DCVR 0xffc00000 0x98892244 0xffc00000 0xea8e0378\n\ +DCVR 0xffc00000 0x99493248 0xffc00000 0xebce137c\n\ +DCVR 0xffc00000 0xffc93248 0xffc00000 0xffce3384\n\ +DCVR 0xffc00000 0xffc9424c 0xffc00000 0xffce4388\n\ +DCVR 0xffc00000 0xffc95250 0xffc00000 0xffce538c\n\ +DCVR 0xffc00000 0xffc96250 0xffc00000 0xffce7390\n\ +DCVR 0xffc00000 0xffc97254 0xffc00000 0xffce8394\n\ +DCVR 0xffc00000 0xffc98258 0xffc00000 0xffcea39c\n\ +"; + +#endif diff --git a/drivers/mxc/pm/dvfs_dptc_table_mx31_27ckih.h b/drivers/mxc/pm/dvfs_dptc_table_mx31_27ckih.h new file mode 100644 index 000000000000..ff8b520776c0 --- /dev/null +++ b/drivers/mxc/pm/dvfs_dptc_table_mx31_27ckih.h @@ -0,0 +1,152 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file dptc.h + * + * @brief MX31 dvfs & dptc table file for CKIH clock 27MHz. + * + * @ingroup PM_MX31 + */ +#ifndef __DVFS_DPTC_TABLE_MX31_27CKIH_H__ +#define __DVFS_DPTC_TABLE_MX31_27CKIH_H__ + +#define NUM_OF_FREQS 4 +#define NUM_OF_WP 17 + +/*! + * Default DPTC table definition. + * The table doesn't use PLL switch, because on DDR boards + * PLL switch is not possible due to HW issue. + * For SDR boards new table can be loaded. + * + * The table keeps the same voltage of 1.35V for frequencies lower than 399MHz. + * Theoretically we don't need DPTC for these frequencies, + * but we have to keep DPTC enabled for fluent DVFS switching + * back to high frequency. + */ +static char *default_table_str_27ckih = "WORKING POINT 17\n\ +WP 0x1d 0x12 0x12 0x12\n\ +WP 0x1c 0x12 0x12 0x12\n\ +WP 0x1b 0x12 0x12 0x12\n\ +WP 0x1a 0x12 0x12 0x12\n\ +WP 0x19 0x12 0x12 0x12\n\ +WP 0x18 0x12 0x12 0x12\n\ +WP 0x17 0x12 0x12 0x12\n\ +WP 0x16 0x12 0x12 0x12\n\ +WP 0x15 0x12 0x12 0x12\n\ +WP 0x14 0x12 0x12 0x12\n\ +WP 0x13 0x12 0x12 0x12\n\ +WP 0x12 0x12 0x12 0x12\n\ +WP 0x11 0x12 0x12 0x12\n\ +WP 0x10 0x12 0x12 0x12\n\ +WP 0xf 0x12 0x12 0x12\n\ +WP 0xe 0x12 0x12 0x12\n\ +WP 0xd 0x12 0x12 0x12\n\ +\n\ +# pll_sw_up pll_sw_down pdr_up pdr_down pll_up pll_down vscnt\n\ +# 532MHz\n\ +FREQ 0 0 0xff871e58 0xff871e59 0xe240d 0xe240d 7\n\ +# 266MHz\n\ +FREQ 0 0 0xff871e58 0xff871e5b 0xe240d 0xe240d 7\n\ +# 133MHz\n\ +FREQ 0 0 0xff871e58 0xff871e5b 0xe240d 0xe240d 7\n\ +# 133MHz\n\ +FREQ 0 0 0xff871e58 0xff871e5b 0xe240d 0xe240d 7\n\ +# 532MHz\n\ +DCVR 0xffc00000 0x90400000 0xffc00000 0xdd000000\n\ +DCVR 0xffc00000 0x90629890 0xffc00000 0xdd34ed20\n\ +DCVR 0xffc00000 0x90629890 0xffc00000 0xdd34ed20\n\ +DCVR 0xffc00000 0x90629894 0xffc00000 0xdd74fd24\n\ +DCVR 0xffc00000 0x90a2a894 0xffc00000 0xddb50d28\n\ +DCVR 0xffc00000 0x90e2b89c 0xffc00000 0xde352d30\n\ +DCVR 0xffc00000 0x9162d8a0 0xffc00000 0xdef55d38\n\ +DCVR 0xffc00000 0x91e2f8a8 0xffc00000 0xdfb58d44\n\ +DCVR 0xffc00000 0x926308b0 0xffc00000 0xe0b5cd54\n\ +DCVR 0xffc00000 0x92e328bc 0xffc00000 0xe1f60d64\n\ +DCVR 0xffc00000 0x93a358c0 0xffc00000 0xe3365d74\n\ +DCVR 0xffc00000 0xf66388cc 0xffc00000 0xf6768d84\n\ +DCVR 0xffc00000 0xf663b8d4 0xffc00000 0xf676dd98\n\ +DCVR 0xffc00000 0xf663e8e0 0xffc00000 0xf6773da4\n\ +DCVR 0xffc00000 0xf66418ec 0xffc00000 0xf6778dbc\n\ +DCVR 0xffc00000 0xf66458fc 0xffc00000 0xf677edd0\n\ +DCVR 0xffc00000 0xf6648908 0xffc00000 0xf6783de8\n\ +\n\ +# 266MHz\n\ +DCVR 0xffc00000 0x90400000 0xffc00000 0xdd000000\n\ +DCVR 0xffc00000 0x9048a224 0xffc00000 0xdd0d4348\n\ +DCVR 0xffc00000 0x9048a224 0xffc00000 0xdd0d4348\n\ +DCVR 0xffc00000 0x9048a224 0xffc00000 0xdd4d4348\n\ +DCVR 0xffc00000 0x9088b228 0xffc00000 0xdd8d434c\n\ +DCVR 0xffc00000 0x90c8b228 0xffc00000 0xde0d534c\n\ +DCVR 0xffc00000 0x9148b228 0xffc00000 0xdecd5350\n\ +DCVR 0xffc00000 0x91c8c22c 0xffc00000 0xdf8d6354\n\ +DCVR 0xffc00000 0x9248d22c 0xffc00000 0xe08d7354\n\ +DCVR 0xffc00000 0x92c8d230 0xffc00000 0xe1cd8358\n\ +DCVR 0xffc00000 0x9388e234 0xffc00000 0xe30d935c\n\ +DCVR 0xffc00000 0xf648e234 0xffc00000 0xf64db364\n\ +DCVR 0xffc00000 0xf648f238 0xffc00000 0xf64dc368\n\ +DCVR 0xffc00000 0xf648f23c 0xffc00000 0xf64dd36c\n\ +DCVR 0xffc00000 0xf649023c 0xffc00000 0xf64de370\n\ +DCVR 0xffc00000 0xf649123c 0xffc00000 0xf64df374\n\ +DCVR 0xffc00000 0xf6492240 0xffc00000 0xf64e1378\n\ +\n\ +# 133MHz\n\ +DCVR 0xffc00000 0x90400000 0xffc00000 0xdd000000\n\ +DCVR 0xffc00000 0x9048a224 0xffc00000 0xdd0d4348\n\ +DCVR 0xffc00000 0x9048a224 0xffc00000 0xdd0d4348\n\ +DCVR 0xffc00000 0x9048a224 0xffc00000 0xdd4d4348\n\ +DCVR 0xffc00000 0x9088b228 0xffc00000 0xdd8d434c\n\ +DCVR 0xffc00000 0x90c8b228 0xffc00000 0xde0d534c\n\ +DCVR 0xffc00000 0x9148b228 0xffc00000 0xdecd5350\n\ +DCVR 0xffc00000 0x91c8c22c 0xffc00000 0xdf8d6354\n\ +DCVR 0xffc00000 0x9248d22c 0xffc00000 0xe08d7354\n\ +DCVR 0xffc00000 0x92c8d230 0xffc00000 0xe1cd8358\n\ +DCVR 0xffc00000 0x9388e234 0xffc00000 0xe30d935c\n\ +DCVR 0xffc00000 0xf648e234 0xffc00000 0xf64db364\n\ +DCVR 0xffc00000 0xf648f238 0xffc00000 0xf64dc368\n\ +DCVR 0xffc00000 0xf648f23c 0xffc00000 0xf64dd36c\n\ +DCVR 0xffc00000 0xf649023c 0xffc00000 0xf64de370\n\ +DCVR 0xffc00000 0xf649123c 0xffc00000 0xf64df374\n\ +DCVR 0xffc00000 0xf6492240 0xffc00000 0xf64e1378\n\ +\n\ +# 133MHz\n\ +DCVR 0xffc00000 0x90400000 0xffc00000 0xdd000000\n\ +DCVR 0xffc00000 0x9048a224 0xffc00000 0xdd0d4348\n\ +DCVR 0xffc00000 0x9048a224 0xffc00000 0xdd0d4348\n\ +DCVR 0xffc00000 0x9048a224 0xffc00000 0xdd4d4348\n\ +DCVR 0xffc00000 0x9088b228 0xffc00000 0xdd8d434c\n\ +DCVR 0xffc00000 0x90c8b228 0xffc00000 0xde0d534c\n\ +DCVR 0xffc00000 0x9148b228 0xffc00000 0xdecd5350\n\ +DCVR 0xffc00000 0x91c8c22c 0xffc00000 0xdf8d6354\n\ +DCVR 0xffc00000 0x9248d22c 0xffc00000 0xe08d7354\n\ +DCVR 0xffc00000 0x92c8d230 0xffc00000 0xe1cd8358\n\ +DCVR 0xffc00000 0x9388e234 0xffc00000 0xe30d935c\n\ +DCVR 0xffc00000 0xf648e234 0xffc00000 0xf64db364\n\ +DCVR 0xffc00000 0xf648f238 0xffc00000 0xf64dc368\n\ +DCVR 0xffc00000 0xf648f23c 0xffc00000 0xf64dd36c\n\ +DCVR 0xffc00000 0xf649023c 0xffc00000 0xf64de370\n\ +DCVR 0xffc00000 0xf649123c 0xffc00000 0xf64df374\n\ +DCVR 0xffc00000 0xf6492240 0xffc00000 0xf64e1378\n\ +"; + +#endif diff --git a/drivers/mxc/pm/dvfs_dptc_table_mx31_rev2.h b/drivers/mxc/pm/dvfs_dptc_table_mx31_rev2.h new file mode 100644 index 000000000000..cb0f64c98749 --- /dev/null +++ b/drivers/mxc/pm/dvfs_dptc_table_mx31_rev2.h @@ -0,0 +1,158 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file dvfs_dptc_table_mx31_rev2.h + * + * @brief MX31 dvfs & dptc table file for MX31 2.0 + * + * @ingroup PM_MX31 + */ +#ifndef __DVFS_DPTC_TABLE_MX3_REV2_H__ +#define __DVFS_DPTC_TABLE_MX3_REV2_H__ + +#define NUM_OF_FREQS 4 +#define NUM_OF_WP 17 + +/*! + * Default DPTC table definition. + * The table doesn't use PLL switch, because on DDR boards + * PLL switch is not possible due to HW issue. + * For SDR boards new table can be loaded. + * + * The table keeps the same voltage of 3.5V for frequencies lower than 399MHz. + * Theoretically we don't need DPTC for these frequencies, + * but we have to keep DPTC enabled for fluent DVFS switching + * back to high frequency. + */ +static char *default_table_str_rev2 = "WORKING POINT 17\n\ +\n\ +# mc13783 switcher SW values for each working point\n\ +# The first line is for WP of highest voltage\n\ +# The first column is for highest frequency\n\ +# SW1A SW1A DVS SW1B DVS SW1B STANDBY\n\ +WP 0x1d 0xc 0xc 0xc\n\ +WP 0x1c 0xc 0xc 0xc\n\ +WP 0x1b 0xc 0xc 0xc\n\ +WP 0x1a 0xc 0xc 0xc\n\ +WP 0x19 0xc 0xc 0xc\n\ +WP 0x18 0xc 0xc 0xc\n\ +WP 0x17 0xc 0xc 0xc\n\ +WP 0x16 0xc 0xc 0xc\n\ +WP 0x15 0xc 0xc 0xc\n\ +WP 0x14 0xc 0xc 0xc\n\ +WP 0x13 0xc 0xc 0xc\n\ +WP 0x12 0xc 0xc 0xc\n\ +WP 0x11 0xc 0xc 0xc\n\ +WP 0x10 0xc 0xc 0xc\n\ +WP 0xf 0xc 0xc 0xc\n\ +WP 0xe 0xc 0xc 0xc\n\ +WP 0xd 0xc 0xc 0xc\n\ +\n\ +# pll_sw_up pll_sw_down pdr_up pdr_down pll_up pll_down vscnt\n\ +# 532MHz\n\ +FREQ 0 1 0xff871e58 0xff871650 0x0033280c 0x00331c23 1\n\ +# 399MHz\n\ +FREQ 1 1 0xff871e58 0xff871e59 0x0033280c 0x0033280c 4\n\ +# 266MHz\n\ +FREQ 0 0 0xff871e58 0xff871e5b 0x0033280c 0x0033280c 4\n\ +# 133MHz\n\ +FREQ 0 0 0xff871e58 0xff871e5b 0x0033280c 0x0033280c 4\n\ +\n\ +# 532MHz\n\ +DCVR 0xffc00000 0x95c00000 0xffc00000 0xe5800000\n\ +DCVR 0xffc00000 0x95e3e8e4 0xffc00000 0xe5b6fda0\n\ +DCVR 0xffc00000 0x95e3e8e4 0xffc00000 0xe5b6fda0\n\ +DCVR 0xffc00000 0x95e3e8e8 0xffc00000 0xe5f70da4\n\ +DCVR 0xffc00000 0x9623f8e8 0xffc00000 0xe6371da8\n\ +DCVR 0xffc00000 0x966408f0 0xffc00000 0xe6b73db0\n\ +DCVR 0xffc00000 0x96e428f4 0xffc00000 0xe7776dbc\n\ +DCVR 0xffc00000 0x976448fc 0xffc00000 0xe8379dc8\n\ +DCVR 0xffc00000 0x97e46904 0xffc00000 0xe977ddd8\n\ +DCVR 0xffc00000 0x98a48910 0xffc00000 0xeab81de8\n\ +DCVR 0xffc00000 0x9964b918 0xffc00000 0xebf86df8\n\ +DCVR 0xffc00000 0xffe4e924 0xffc00000 0xfff8ae08\n\ +DCVR 0xffc00000 0xffe5192c 0xffc00000 0xfff8fe1c\n\ +DCVR 0xffc00000 0xffe54938 0xffc00000 0xfff95e2c\n\ +DCVR 0xffc00000 0xffe57944 0xffc00000 0xfff9ae44\n\ +DCVR 0xffc00000 0xffe5b954 0xffc00000 0xfffa0e58\n\ +DCVR 0xffc00000 0xffe5e960 0xffc00000 0xfffa6e70\n\ +\n\ +# 266MHz\n\ +DCVR 0xffc00000 0x95c00000 0xffc00000 0xe5800000\n\ +DCVR 0xffc00000 0x95c8f238 0xffc00000 0xe58dc368\n\ +DCVR 0xffc00000 0x95c8f238 0xffc00000 0xe58dc368\n\ +DCVR 0xffc00000 0x95c8f238 0xffc00000 0xe5cdc368\n\ +DCVR 0xffc00000 0x9609023c 0xffc00000 0xe60dc36c\n\ +DCVR 0xffc00000 0x9649023c 0xffc00000 0xe68dd36c\n\ +DCVR 0xffc00000 0x96c9023c 0xffc00000 0xe74dd370\n\ +DCVR 0xffc00000 0x97491240 0xffc00000 0xe80de374\n\ +DCVR 0xffc00000 0x97c92240 0xffc00000 0xe94df374\n\ +DCVR 0xffc00000 0x98892244 0xffc00000 0xea8e0378\n\ +DCVR 0xffc00000 0x99493248 0xffc00000 0xebce137c\n\ +DCVR 0xffc00000 0xffc93248 0xffc00000 0xffce3384\n\ +DCVR 0xffc00000 0xffc9424c 0xffc00000 0xffce4388\n\ +DCVR 0xffc00000 0xffc95250 0xffc00000 0xffce538c\n\ +DCVR 0xffc00000 0xffc96250 0xffc00000 0xffce7390\n\ +DCVR 0xffc00000 0xffc97254 0xffc00000 0xffce8394\n\ +DCVR 0xffc00000 0xffc98258 0xffc00000 0xffcea39c\n\ +\n\ +# 133MHz\n\ +DCVR 0xffc00000 0x95c00000 0xffc00000 0xe5800000\n\ +DCVR 0xffc00000 0x95c8f238 0xffc00000 0xe58dc368\n\ +DCVR 0xffc00000 0x95c8f238 0xffc00000 0xe58dc368\n\ +DCVR 0xffc00000 0x95c8f238 0xffc00000 0xe5cdc368\n\ +DCVR 0xffc00000 0x9609023c 0xffc00000 0xe60dc36c\n\ +DCVR 0xffc00000 0x9649023c 0xffc00000 0xe68dd36c\n\ +DCVR 0xffc00000 0x96c9023c 0xffc00000 0xe74dd370\n\ +DCVR 0xffc00000 0x97491240 0xffc00000 0xe80de374\n\ +DCVR 0xffc00000 0x97c92240 0xffc00000 0xe94df374\n\ +DCVR 0xffc00000 0x98892244 0xffc00000 0xea8e0378\n\ +DCVR 0xffc00000 0x99493248 0xffc00000 0xebce137c\n\ +DCVR 0xffc00000 0xffc93248 0xffc00000 0xffce3384\n\ +DCVR 0xffc00000 0xffc9424c 0xffc00000 0xffce4388\n\ +DCVR 0xffc00000 0xffc95250 0xffc00000 0xffce538c\n\ +DCVR 0xffc00000 0xffc96250 0xffc00000 0xffce7390\n\ +DCVR 0xffc00000 0xffc97254 0xffc00000 0xffce8394\n\ +DCVR 0xffc00000 0xffc98258 0xffc00000 0xffcea39c\n\ +\n\ +# 133MHz\n\ +DCVR 0xffc00000 0x95c00000 0xffc00000 0xe5800000\n\ +DCVR 0xffc00000 0x95c8f238 0xffc00000 0xe58dc368\n\ +DCVR 0xffc00000 0x95c8f238 0xffc00000 0xe58dc368\n\ +DCVR 0xffc00000 0x95c8f238 0xffc00000 0xe5cdc368\n\ +DCVR 0xffc00000 0x9609023c 0xffc00000 0xe60dc36c\n\ +DCVR 0xffc00000 0x9649023c 0xffc00000 0xe68dd36c\n\ +DCVR 0xffc00000 0x96c9023c 0xffc00000 0xe74dd370\n\ +DCVR 0xffc00000 0x97491240 0xffc00000 0xe80de374\n\ +DCVR 0xffc00000 0x97c92240 0xffc00000 0xe94df374\n\ +DCVR 0xffc00000 0x98892244 0xffc00000 0xea8e0378\n\ +DCVR 0xffc00000 0x99493248 0xffc00000 0xebce137c\n\ +DCVR 0xffc00000 0xffc93248 0xffc00000 0xffce3384\n\ +DCVR 0xffc00000 0xffc9424c 0xffc00000 0xffce4388\n\ +DCVR 0xffc00000 0xffc95250 0xffc00000 0xffce538c\n\ +DCVR 0xffc00000 0xffc96250 0xffc00000 0xffce7390\n\ +DCVR 0xffc00000 0xffc97254 0xffc00000 0xffce8394\n\ +DCVR 0xffc00000 0xffc98258 0xffc00000 0xffcea39c\n\ +"; + +#endif diff --git a/drivers/mxc/pm/dvfs_dptc_table_mxc91321.h b/drivers/mxc/pm/dvfs_dptc_table_mxc91321.h new file mode 100644 index 000000000000..35b8dce893c5 --- /dev/null +++ b/drivers/mxc/pm/dvfs_dptc_table_mxc91321.h @@ -0,0 +1,68 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file dptc.h + * + * @brief MXC91321 dvfs & dptc table file. + * + * @ingroup PM_MXC91321 + */ +#ifndef __DVFS_DPTC_TABLE_MXC91321_H__ +#define __DVFS_DPTC_TABLE_MXC91321_H__ + +/*! + * Default DPTC table definition + */ +#define NUM_OF_FREQS 1 +#define NUM_OF_WP 17 + +static char *default_table_str = "WORKING POINT 17\n\ +\n\ +WP 0x1c\n\ +WP 0x1b\n\ +WP 0x1a\n\ +WP 0x19\n\ +WP 0x18\n\ +WP 0x17\n\ +WP 0x16\n\ +WP 0x15\n\ +WP 0x14\n\ +WP 0x13\n\ +WP 0x12\n\ +WP 0x11\n\ +WP 0x10\n\ +WP 0xf\n\ +WP 0xe\n\ +WP 0xd\n\ +WP 0xc\n\ +\n\ +DCVR 0x7fe00000 0x82E10870 0xF53DCFD0 0xCA32ED04\n\ +DCVR 0x7fe00000 0x83211874 0xF5BE0FDC 0xCAB32D10\n\ +DCVR 0x7fe00000 0x8361287C 0xF5FE2FEC 0xCAB33D1C\n\ +DCVR 0x7fe00000 0x8361388C 0xF5FE3004 0xCAF34D34\n\ +DCVR 0x7fe00000 0x83613890 0xF63E4010 0xCAF35D3C\n\ +DCVR 0x7fe00000 0x83614894 0xF63E5018 0xCB335D44\n\ +DCVR 0x7fe00000 0x83A1489C 0xF67E6024 0xCB337D4C\n\ +DCVR 0x7fe00000 0x83A158A0 0xF67E7030 0xCB337D54\n\ +DCVR 0x7fe00000 0x83A158A4 0xF6BE8038 0xCB738D60\n\ +DCVR 0x7fe00000 0x83E168A8 0xF6BEA044 0xCBB39D68\n\ +DCVR 0x7fe00000 0x83E168B0 0xF6FEB050 0xCBB3BD74\n\ +DCVR 0x7fe00000 0x83E178B8 0xF73EC05C 0xCBF3BD80\n\ +DCVR 0x7fe00000 0x842188BC 0xF77EE06C 0xCBF3DD8C\n\ +DCVR 0x7fe00000 0x842198C8 0xF77EF07C 0xCC33FD9C\n\ +DCVR 0x7fe00000 0x8421A8CC 0xF7BF108C 0xCC73FDA8\n\ +DCVR 0x7fe00000 0x8461B8D8 0xF7FF309C 0xCCB42DB8\n\ +DCVR 0x7fe00000 0x84A1C8E0 0xF83F50B0 0xCCF44DC8\n\ +"; +#endif diff --git a/drivers/mxc/pmic/Kconfig b/drivers/mxc/pmic/Kconfig new file mode 100644 index 000000000000..4a99b56202fa --- /dev/null +++ b/drivers/mxc/pmic/Kconfig @@ -0,0 +1,62 @@ +# +# PMIC device driver configuration +# + +menu "MXC PMIC support" + +config MXC_SPI_PMIC_CORE + tristate "PMIC Protocol support (SPI interface)" + depends on ARCH_MXC && SPI_MXC + default n + ---help--- + This is the PMIC core/protocol driver for the Freescale MXC application. + SPI should be providing the interface between the PMIC and the MCU. + You must select the SPI driver support to enable this option. + +config MXC_PMIC + boolean + default MXC_SPI_PMIC_CORE + +config MXC_PMIC_CHARDEV + tristate "MXC PMIC device interface" + depends on MXC_PMIC + help + Say Y here to use "pmic" device files, found in the /dev directory + on the system. They make it possible to have user-space programs + use or controll PMIC. Mainly its useful for notifying PMIC events + to user-space programs. + +comment "MXC PMIC Client Drivers" + depends on MXC_PMIC + +config MXC_PMIC_MC13783 + tristate "MC13783 Client Drivers" + depends on MXC_SPI_PMIC_CORE + default n + ---help--- + This is the MXC MC13783(PMIC) client drivers support. It include + ADC, Audio, Battery, Connectivity, Light, Power and RTC. + +source "drivers/mxc/pmic/mc13783/Kconfig" + +config MXC_PMIC_SC55112 + tristate "SC55112 Client Drivers" + depends on MXC_SPI_PMIC_CORE && ARCH_MXC91131 + default n + ---help--- + This is the MXC MC13783(PMIC) client drivers support. It include + ADC, Audio, Battery, Connectivity, Light, Power and RTC. + +config MXC_PMIC_FIXARB + bool "Use Arbitration Bit Workaround" + depends on MXC_PMIC_SC55112 + default n + ---help--- + Enable this option to include the code that will automatically + toggle the sc55112 arbitration bits to allow register write + access to the secondary processor. Note that this also requires + a hardware modification to the RF Deck that connects the Sphinx + card (with the sc55112 PMIC) to the MXC91131 EVB. + + +endmenu diff --git a/drivers/mxc/pmic/Makefile b/drivers/mxc/pmic/Makefile new file mode 100644 index 000000000000..d937cf72d69d --- /dev/null +++ b/drivers/mxc/pmic/Makefile @@ -0,0 +1,7 @@ +# +# Makefile for the MXC PMIC drivers. +# + +obj-y += core/ +obj-$(CONFIG_MXC_PMIC_MC13783) += mc13783/ +obj-$(CONFIG_MXC_PMIC_SC55112) += sc55112/ diff --git a/drivers/mxc/pmic/core/Makefile b/drivers/mxc/pmic/core/Makefile new file mode 100644 index 000000000000..2fe13f764c61 --- /dev/null +++ b/drivers/mxc/pmic/core/Makefile @@ -0,0 +1,15 @@ +# +# Makefile for the PMIC core drivers. +# +obj-$(CONFIG_MXC_SPI_PMIC_CORE) += pmic_core_spi_mod.o +obj-$(CONFIG_MXC_PMIC_CHARDEV) += pmic-dev.o + +pmic_core_spi_mod-objs := pmic_external.o pmic_event.o pmic_core_spi.o + +ifeq ($(CONFIG_MXC_PMIC_MC13783),y) +pmic_core_spi_mod-objs += mc13783.o +endif + +ifeq ($(CONFIG_MXC_PMIC_SC55112),y) +pmic_core_spi_mod-objs += sc55112.o +endif diff --git a/drivers/mxc/pmic/core/mc13783.c b/drivers/mxc/pmic/core/mc13783.c new file mode 100644 index 000000000000..0c81f0d5dc56 --- /dev/null +++ b/drivers/mxc/pmic/core/mc13783.c @@ -0,0 +1,368 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file pmic/core/mc13783.c + * @brief This file contains MC13783 specific PMIC code. This implementaion + * may differ for each PMIC chip. + * + * @ingroup PMIC_CORE + */ + +/* + * Includes + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/device.h> +#include <linux/spi/spi.h> + +#include <asm/uaccess.h> +#include <asm/arch/gpio.h> + +#include <asm/arch/pmic_external.h> +#include <asm/arch/pmic_status.h> +#include "pmic_config.h" +#include "pmic.h" + +/* + * Defines + */ +#define EVENT_MASK_0 0x697fdf +#define EVENT_MASK_1 0x3efffb +#define MXC_PMIC_FRAME_MASK 0x00FFFFFF +#define MXC_PMIC_MAX_REG_NUM 0x3F +#define MXC_PMIC_REG_NUM_SHIFT 0x19 +#define MXC_PMIC_WRITE_BIT_SHIFT 31 + +static unsigned int events_enabled0 = 0; +static unsigned int events_enabled1 = 0; +static struct mxc_pmic pmic_drv_data; + +/*! + * This function is called to read a register on PMIC. + * + * @param reg_num number of the pmic register to be read + * @param reg_val return value of register + * + * @return Returns 0 on success -1 on failure. + */ +int pmic_read(unsigned int reg_num, unsigned int *reg_val) +{ + unsigned int frame = 0; + int ret = 0; + + if (reg_num > MXC_PMIC_MAX_REG_NUM) + return PMIC_ERROR; + + frame |= reg_num << MXC_PMIC_REG_NUM_SHIFT; + + ret = spi_rw(pmic_drv_data.spi, (u8 *) & frame, 1); + + *reg_val = frame & MXC_PMIC_FRAME_MASK; + + return ret; +} + +/*! + * This function is called to write a value to the register on PMIC. + * + * @param reg_num number of the pmic register to be written + * @param reg_val value to be written + * + * @return Returns 0 on success -1 on failure. + */ +int pmic_write(int reg_num, const unsigned int reg_val) +{ + unsigned int frame = 0; + int ret = 0; + + if (reg_num > MXC_PMIC_MAX_REG_NUM) + return PMIC_ERROR; + + frame |= (1 << MXC_PMIC_WRITE_BIT_SHIFT); + + frame |= reg_num << MXC_PMIC_REG_NUM_SHIFT; + + frame |= reg_val & MXC_PMIC_FRAME_MASK; + + ret = spi_rw(pmic_drv_data.spi, (u8 *) & frame, 1); + + return ret; +} + +/*! + * This function initializes the SPI device parameters for this PMIC. + * + * @param spi the SPI slave device(PMIC) + * + * @return None + */ +int pmic_spi_setup(struct spi_device *spi) +{ + /* Setup the SPI slave i.e.PMIC */ + pmic_drv_data.spi = spi; + + spi->mode = SPI_MODE_2 | SPI_CS_HIGH; + spi->bits_per_word = 32; + + return spi_setup(spi); +} + +/*! + * This function initializes the PMIC registers. + * + * @return None + */ +int pmic_init_registers(void) +{ + CHECK_ERROR(pmic_write(REG_INTERRUPT_MASK_0, MXC_PMIC_FRAME_MASK)); + CHECK_ERROR(pmic_write(REG_INTERRUPT_MASK_1, MXC_PMIC_FRAME_MASK)); + CHECK_ERROR(pmic_write(REG_INTERRUPT_STATUS_0, MXC_PMIC_FRAME_MASK)); + CHECK_ERROR(pmic_write(REG_INTERRUPT_STATUS_1, MXC_PMIC_FRAME_MASK)); + return PMIC_SUCCESS; +} + +/*! + * This function returns the PMIC version in system. + * + * @param ver pointer to the pmic_version_t structure + * + * @return This function returns PMIC version. + */ +void pmic_get_revision(pmic_version_t * ver) +{ + int rev_id = 0; + int rev1 = 0; + int rev2 = 0; + int finid = 0; + int icid = 0; + + ver->id = PMIC_MC13783; + pmic_read(REG_REVISION, &rev_id); + + rev1 = (rev_id & 0x018) >> 3; + rev2 = (rev_id & 0x007); + icid = (rev_id & 0x01C0) >> 6; + finid = (rev_id & 0x01E00) >> 9; + + /* Ver 0.2 is actually 3.2a. Report as 3.2 */ + if ((rev1 == 0) && (rev2 == 2)) { + rev1 = 3; + } + + if (rev1 == 0 || icid != 2) { + ver->revision = -1; + printk(KERN_NOTICE + "mc13783: Not detected.\tAccess failed\t!!!\n"); + } else { + ver->revision = ((rev1 * 10) + rev2); + printk(KERN_INFO "mc13783 Rev %d.%d FinVer %x detected\n", rev1, + rev2, finid); + } + + return; + +} + +/*! + * This function reads the interrupt status registers of PMIC + * and determine the current active events. + * + * @param active_events array pointer to be used to return active + * event numbers. + * + * @return This function returns PMIC version. + */ +unsigned int pmic_get_active_events(unsigned int *active_events) +{ + unsigned int count = 0; + unsigned int status0, status1; + int bit_set; + + pmic_read(REG_INTERRUPT_STATUS_0, &status0); + pmic_read(REG_INTERRUPT_STATUS_1, &status1); + pmic_write(REG_INTERRUPT_STATUS_0, status0); + pmic_write(REG_INTERRUPT_STATUS_1, status1); + status0 &= events_enabled0; + status1 &= events_enabled1; + + while (status0) { + bit_set = ffs(status0) - 1; + *(active_events + count) = bit_set; + count++; + status0 ^= (1 << bit_set); + } + while (status1) { + bit_set = ffs(status1) - 1; + *(active_events + count) = bit_set + 24; + count++; + status1 ^= (1 << bit_set); + } + + return count; +} + +/*! + * This function unsets a bit in mask register of pmic to unmask an event IT. + * + * @param event the event to be unmasked + * + * @return This function returns PMIC_SUCCESS on SUCCESS, error on FAILURE. + */ +int pmic_event_unmask(type_event event) +{ + unsigned int event_mask = 0; + unsigned int mask_reg = 0; + unsigned int event_bit = 0; + int ret; + + if (event < EVENT_E1HZI) { + mask_reg = REG_INTERRUPT_MASK_0; + event_mask = EVENT_MASK_0; + event_bit = (1 << event); + events_enabled0 |= event_bit; + } else { + event -= 24; + mask_reg = REG_INTERRUPT_MASK_1; + event_mask = EVENT_MASK_1; + event_bit = (1 << event); + events_enabled1 |= event_bit; + } + + if ((event_bit & event_mask) == 0) { + pr_debug("Error: unmasking a reserved/unused event\n"); + return PMIC_ERROR; + } + + ret = pmic_write_reg(mask_reg, 0, event_bit); + + pr_debug("Enable Event : %d\n", event); + + return ret; +} + +/*! + * This function sets a bit in mask register of pmic to disable an event IT. + * + * @param event the event to be masked + * + * @return This function returns PMIC_SUCCESS on SUCCESS, error on FAILURE. + */ +int pmic_event_mask(type_event event) +{ + unsigned int event_mask = 0; + unsigned int mask_reg = 0; + unsigned int event_bit = 0; + int ret; + + if (event < EVENT_E1HZI) { + mask_reg = REG_INTERRUPT_MASK_0; + event_mask = EVENT_MASK_0; + event_bit = (1 << event); + events_enabled0 &= ~event_bit; + } else { + event -= 24; + mask_reg = REG_INTERRUPT_MASK_1; + event_mask = EVENT_MASK_1; + event_bit = (1 << event); + events_enabled1 &= ~event_bit; + } + + if ((event_bit & event_mask) == 0) { + pr_debug("Error: masking a reserved/unused event\n"); + return PMIC_ERROR; + } + + ret = pmic_write_reg(mask_reg, event_bit, event_bit); + + pr_debug("Disable Event : %d\n", event); + + return ret; +} + +/*! + * This function is called to read all sensor bits of PMIC. + * + * @param sensor Sensor to be checked. + * + * @return This function returns true if the sensor bit is high; + * or returns false if the sensor bit is low. + */ +bool pmic_check_sensor(t_sensor sensor) +{ + unsigned int reg_val = 0; + + CHECK_ERROR(pmic_read_reg + (REG_INTERRUPT_SENSE_0, ®_val, PMIC_ALL_BITS)); + + if ((1 << sensor) & reg_val) + return true; + else + return false; +} + +/*! + * This function checks one sensor of PMIC. + * + * @param sensor_bits structure of all sensor bits. + * + * @return This function returns PMIC_SUCCESS on SUCCESS, error on FAILURE. + */ + +PMIC_STATUS pmic_get_sensors(t_sensor_bits * sensor_bits) +{ + int sense_0 = 0; + int sense_1 = 0; + + memset(sensor_bits, 0, sizeof(t_sensor_bits)); + + pmic_read_reg(REG_INTERRUPT_SENSE_0, &sense_0, 0xffffff); + pmic_read_reg(REG_INTERRUPT_SENSE_1, &sense_1, 0xffffff); + + sensor_bits->sense_chgdets = (sense_0 & (1 << 6)) ? true : false; + sensor_bits->sense_chgovs = (sense_0 & (1 << 7)) ? true : false; + sensor_bits->sense_chgrevs = (sense_0 & (1 << 8)) ? true : false; + sensor_bits->sense_chgshorts = (sense_0 & (1 << 9)) ? true : false; + sensor_bits->sense_cccvs = (sense_0 & (1 << 10)) ? true : false; + sensor_bits->sense_chgcurrs = (sense_0 & (1 << 11)) ? true : false; + sensor_bits->sense_bpons = (sense_0 & (1 << 12)) ? true : false; + sensor_bits->sense_lobatls = (sense_0 & (1 << 13)) ? true : false; + sensor_bits->sense_lobaths = (sense_0 & (1 << 14)) ? true : false; + sensor_bits->sense_usb4v4s = (sense_0 & (1 << 16)) ? true : false; + sensor_bits->sense_usb2v0s = (sense_0 & (1 << 17)) ? true : false; + sensor_bits->sense_usb0v8s = (sense_0 & (1 << 18)) ? true : false; + sensor_bits->sense_id_floats = (sense_0 & (1 << 19)) ? true : false; + sensor_bits->sense_id_gnds = (sense_0 & (1 << 20)) ? true : false; + sensor_bits->sense_se1s = (sense_0 & (1 << 21)) ? true : false; + sensor_bits->sense_ckdets = (sense_0 & (1 << 22)) ? true : false; + + sensor_bits->sense_onofd1s = (sense_1 & (1 << 3)) ? true : false; + sensor_bits->sense_onofd2s = (sense_1 & (1 << 4)) ? true : false; + sensor_bits->sense_onofd3s = (sense_1 & (1 << 5)) ? true : false; + sensor_bits->sense_pwrrdys = (sense_1 & (1 << 11)) ? true : false; + sensor_bits->sense_thwarnhs = (sense_1 & (1 << 12)) ? true : false; + sensor_bits->sense_thwarnls = (sense_1 & (1 << 13)) ? true : false; + sensor_bits->sense_clks = (sense_1 & (1 << 14)) ? true : false; + sensor_bits->sense_mc2bs = (sense_1 & (1 << 17)) ? true : false; + sensor_bits->sense_hsdets = (sense_1 & (1 << 18)) ? true : false; + sensor_bits->sense_hsls = (sense_1 & (1 << 19)) ? true : false; + sensor_bits->sense_alspths = (sense_1 & (1 << 20)) ? true : false; + sensor_bits->sense_ahsshorts = (sense_1 & (1 << 21)) ? true : false; + return PMIC_SUCCESS; +} + +EXPORT_SYMBOL(pmic_check_sensor); +EXPORT_SYMBOL(pmic_get_sensors); diff --git a/drivers/mxc/pmic/core/pmic-dev.c b/drivers/mxc/pmic/core/pmic-dev.c new file mode 100644 index 000000000000..ffdeb68ce2eb --- /dev/null +++ b/drivers/mxc/pmic/core/pmic-dev.c @@ -0,0 +1,318 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All rights reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file pmic-dev.c + * @brief This provides /dev interface to the user program. They make it + * possible to have user-space programs use or control PMIC. Mainly its + * useful for notifying PMIC events to user-space programs. + * + * @ingroup PMIC_CORE + */ + +/* + * Includes + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/kdev_t.h> +#include <linux/circ_buf.h> +#include <linux/major.h> +#include <linux/init.h> +#include <linux/fs.h> + +#include <asm/uaccess.h> +#include <asm/arch/pmic_external.h> + +#define PMIC_NAME "pmic" +#define CIRC_BUF_MAX 16 +#define CIRC_ADD(elem,cir_buf,size) \ + down(&event_mutex); \ + if(CIRC_SPACE(cir_buf.head, cir_buf.tail, size)){ \ + cir_buf.buf[cir_buf.head] = (char)elem; \ + cir_buf.head = (cir_buf.head + 1) & (size - 1); \ + } else { \ + pr_info("Failed to notify event to the user\n");\ + } \ + up(&event_mutex); + +#define CIRC_REMOVE(elem,cir_buf,size) \ + down(&event_mutex); \ + if(CIRC_CNT(cir_buf.head, cir_buf.tail, size)){ \ + elem = (int)cir_buf.buf[cir_buf.tail]; \ + cir_buf.tail = (cir_buf.tail + 1) & (size - 1); \ + } else { \ + elem = -1; \ + pr_info("No valid notified event\n"); \ + } \ + up(&event_mutex); + +static int pmic_major; +static struct class *pmic_class; +static struct fasync_struct *pmic_dev_queue; + +static DECLARE_MUTEX(event_mutex); +static struct circ_buf pmic_events; + +static void callbackfn(void *event) +{ + printk(KERN_INFO "\n\n DETECTED PMIC EVENT : %d\n\n", + (unsigned int)event); +} + +static void user_notify_callback(void *event) +{ + CIRC_ADD((int)event, pmic_events, CIRC_BUF_MAX); + kill_fasync(&pmic_dev_queue, SIGIO, POLL_IN); +} + +/*! + * This function implements IOCTL controls on a PMIC device. + * + * @param inode pointer on the node + * @param file pointer on the file + * @param cmd the command + * @param arg the parameter + * @return This function returns 0 if successful. + */ +static int pmic_dev_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + register_info reg_info; + pmic_event_callback_t event_sub; + type_event event; + int ret = 0; + + if (_IOC_TYPE(cmd) != 'P') + return -ENOTTY; + + switch (cmd) { + case PMIC_READ_REG: + if (copy_from_user(®_info, (register_info *) arg, + sizeof(register_info))) { + return -EFAULT; + } + ret = + pmic_read_reg(reg_info.reg, &(reg_info.reg_value), + 0x00ffffff); + pr_debug("read reg %d %x\n", reg_info.reg, reg_info.reg_value); + if (copy_to_user((register_info *) arg, ®_info, + sizeof(register_info))) { + return -EFAULT; + } + break; + + case PMIC_WRITE_REG: + if (copy_from_user(®_info, (register_info *) arg, + sizeof(register_info))) { + return -EFAULT; + } + ret = + pmic_write_reg(reg_info.reg, reg_info.reg_value, + 0x00ffffff); + pr_debug("write reg %d %x\n", reg_info.reg, reg_info.reg_value); + if (copy_to_user((register_info *) arg, ®_info, + sizeof(register_info))) { + return -EFAULT; + } + break; + + case PMIC_SUBSCRIBE: + if (get_user(event, (int __user *)arg)) { + return -EFAULT; + } + event_sub.func = callbackfn; + event_sub.param = (void *)event; + ret = pmic_event_subscribe(event, event_sub); + pr_debug("subscribe done\n"); + break; + + case PMIC_UNSUBSCRIBE: + if (get_user(event, (int __user *)arg)) { + return -EFAULT; + } + event_sub.func = callbackfn; + event_sub.param = (void *)event; + ret = pmic_event_unsubscribe(event, event_sub); + pr_debug("unsubscribe done\n"); + break; + + case PMIC_NOTIFY_USER: + if (get_user(event, (int __user *)arg)) { + return -EFAULT; + } + event_sub.func = user_notify_callback; + event_sub.param = (void *)event; + ret = pmic_event_subscribe(event, event_sub); + break; + + case PMIC_GET_NOTIFY: + CIRC_REMOVE(event, pmic_events, CIRC_BUF_MAX); + if (put_user(event, (int __user *)arg)) { + return -EFAULT; + } + break; + + default: + printk(KERN_ERR "%d unsupported ioctl command\n", (int)cmd); + return -EINVAL; + } + + return ret; +} + +/*! + * This function implements the open method on a PMIC device. + * + * @param inode pointer on the node + * @param file pointer on the file + * @return This function returns 0. + */ +static int pmic_dev_open(struct inode *inode, struct file *file) +{ + pr_debug("open\n"); + return PMIC_SUCCESS; +} + +/*! + * This function implements the release method on a PMIC device. + * + * @param inode pointer on the node + * @param file pointer on the file + * + * @return This function returns 0. + */ +static int pmic_dev_free(struct inode *inode, struct file *file) +{ + pr_debug("free\n"); + return PMIC_SUCCESS; +} + +static int pmic_dev_fasync(int fd, struct file *filp, int mode) +{ + return fasync_helper(fd, filp, mode, &pmic_dev_queue); +} + +/*! + * This structure defines file operations for a PMIC device. + */ +static struct file_operations pmic_fops = { + /*! + * the owner + */ + .owner = THIS_MODULE, + /*! + * the ioctl operation + */ + .ioctl = pmic_dev_ioctl, + /*! + * the open operation + */ + .open = pmic_dev_open, + /*! + * the release operation + */ + .release = pmic_dev_free, + /*! + * the release operation + */ + .fasync = pmic_dev_fasync, +}; + +/*! + * This function implements the init function of the PMIC char device. + * This function is called when the module is loaded. It registers + * the character device for PMIC to be used by user-space programs. + * + * @return This function returns 0. + */ +static int __init pmic_dev_init(void) +{ + int ret = 0; + struct class_device *pmic_device; + pmic_version_t pmic_ver; + + pmic_ver = pmic_get_version(); + if (pmic_ver.revision < 0) { + printk(KERN_ERR "No PMIC device found\n"); + return -ENODEV; + } + + pmic_major = register_chrdev(0, PMIC_NAME, &pmic_fops); + if (pmic_major < 0) { + printk(KERN_ERR "unable to get a major for pmic\n"); + return pmic_major; + } + + pmic_class = class_create(THIS_MODULE, PMIC_NAME); + if (IS_ERR(pmic_class)) { + printk(KERN_ERR "Error creating pmic class.\n"); + ret = PMIC_ERROR; + goto err; + } + + pmic_device = + class_device_create(pmic_class, NULL, MKDEV(pmic_major, 0), + NULL, PMIC_NAME); + if (IS_ERR(pmic_device)) { + printk(KERN_ERR "Error creating pmic class device.\n"); + ret = PMIC_ERROR; + goto err1; + } + + pmic_events.buf = kmalloc(CIRC_BUF_MAX * sizeof(char), GFP_KERNEL); + if (NULL == pmic_events.buf) { + ret = -ENOMEM; + goto err2; + } + pmic_events.head = pmic_events.tail = 0; + + printk(KERN_INFO "PMIC Character device: successfully loaded\n"); + return ret; + err2: + class_device_destroy(pmic_class, MKDEV(pmic_major, 0)); + err1: + class_destroy(pmic_class); + err: + unregister_chrdev(pmic_major, PMIC_NAME); + return ret; + +} + +/*! + * This function implements the exit function of the PMIC character device. + * This function is called when the module is unloaded. It unregisters + * the PMIC character device. + * + */ +static void __exit pmic_dev_exit(void) +{ + class_device_destroy(pmic_class, MKDEV(pmic_major, 0)); + class_destroy(pmic_class); + + unregister_chrdev(pmic_major, PMIC_NAME); + + printk(KERN_INFO "PMIC Character device: successfully unloaded\n"); +} + +/* + * Module entry points + */ + +module_init(pmic_dev_init); +module_exit(pmic_dev_exit); + +MODULE_DESCRIPTION("PMIC Protocol /dev entries driver"); +MODULE_AUTHOR("FreeScale"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/pmic/core/pmic.h b/drivers/mxc/pmic/core/pmic.h new file mode 100644 index 000000000000..2456600112fa --- /dev/null +++ b/drivers/mxc/pmic/core/pmic.h @@ -0,0 +1,132 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef __PMIC_H__ +#define __PMIC_H__ + + /*! + * @file pmic.h + * @brief This file contains prototypes of all the functions to be + * defined for each PMIC chip. The implementation of these may differ + * from PMIC chip to PMIC chip. + * + * @ingroup PMIC_CORE + */ + +#include <linux/spi/spi.h> + +#define MAX_ACTIVE_EVENTS 10 + +/*! + * This structure is a way for the PMIC core driver to define their own + * \b spi_device structure. This structure includes the core \b spi_device + * structure that is provided by Linux SPI Framework/driver as an + * element and may contain other elements that are required by core driver. + */ +struct mxc_pmic { + /*! + * Master side proxy for an SPI slave device(PMIC) + */ + struct spi_device *spi; +}; + +/*! + * This function is called to transfer data to PMIC on SPI. + * + * @param spi the SPI slave device(PMIC) + * @param buf the pointer to the data buffer + * @param len the length of the data to be transferred + * + * @return Returns 0 on success -1 on failure. + */ +static inline int spi_rw(struct spi_device *spi, u8 * buf, size_t len) +{ + struct spi_transfer t = { + .tx_buf = (const void *)buf, + .rx_buf = buf, + .len = len, + .cs_change = 0, + .delay_usecs = 0, + }; + struct spi_message m; + + spi_message_init(&m); + spi_message_add_tail(&t, &m); + if (spi_sync(spi, &m) != 0 || m.status != 0) + return PMIC_ERROR; + return (len - m.actual_length); +} + +/*! + * This function returns the PMIC version in system. + * + * @param ver pointer to the pmic_version_t structure + * + * @return This function returns PMIC version. + */ +void pmic_get_revision(pmic_version_t * ver); + +/*! + * This function initializes the SPI device parameters for this PMIC. + * + * @param spi the SPI slave device(PMIC) + * + * @return None + */ +int pmic_spi_setup(struct spi_device *spi); + +/*! + * This function initializes the PMIC registers. + * + * @return None + */ +int pmic_init_registers(void); + +/*! + * This function reads the interrupt status registers of PMIC + * and determine the current active events. + * + * @param active_events array pointer to be used to return active + * event numbers. + * + * @return This function returns PMIC version. + */ +unsigned int pmic_get_active_events(unsigned int *active_events); + +/*! + * This function sets a bit in mask register of pmic to disable an event IT. + * + * @param event the event to be masked + * + * @return This function returns PMIC_SUCCESS on SUCCESS, error on FAILURE. + */ +int pmic_event_mask(type_event event); + +/*! + * This function unsets a bit in mask register of pmic to unmask an event IT. + * + * @param event the event to be unmasked + * + * @return This function returns PMIC_SUCCESS on SUCCESS, error on FAILURE. + */ +int pmic_event_unmask(type_event event); + +#ifdef CONFIG_MXC_PMIC_FIXARB +extern PMIC_STATUS pmic_fix_arbitration(struct spi_device *spi); +#else +static inline PMIC_STATUS pmic_fix_arbitration(struct spi_device *spi) +{ + return PMIC_SUCCESS; +} +#endif + +#endif /* __PMIC_H__ */ diff --git a/drivers/mxc/pmic/core/pmic_config.h b/drivers/mxc/pmic/core/pmic_config.h new file mode 100644 index 000000000000..0dae58db044b --- /dev/null +++ b/drivers/mxc/pmic/core/pmic_config.h @@ -0,0 +1,63 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef __PMIC_CONFIG_H__ +#define __PMIC_CONFIG_H__ + + /*! + * @file pmic_config.h + * @brief This file contains configuration define used by PMIC Client drivers. + * + * @ingroup PMIC_CORE + */ + +/* + * Includes + */ +#include <linux/kernel.h> /* printk() */ +#include <linux/module.h> /* modules */ +#include <linux/init.h> /* module_{init,exit}() */ +#include <linux/slab.h> /* kmalloc()/kfree() */ +#include <linux/stddef.h> + +#include <asm/uaccess.h> /* copy_{from,to}_user() */ +#include <asm/arch/pmic_status.h> +#include <asm/arch/pmic_external.h> + +/* + * Bitfield macros that use rely on bitfield width/shift information. + */ +#define BITFMASK(field) (((1U << (field ## _WID)) - 1) << (field ## _LSH)) +#define BITFVAL(field, val) ((val) << (field ## _LSH)) +#define BITFEXT(var, bit) ((var & BITFMASK(bit)) >> (bit ## _LSH)) + +/* + * Macros implementing error handling + */ +#define CHECK_ERROR(a) \ +do { \ + int ret = (a); \ + if(ret != PMIC_SUCCESS) \ + return ret; \ +} while (0) + +#define CHECK_ERROR_KFREE(func, freeptrs) \ +do { \ + int ret = (func); \ + if (ret != PMIC_SUCCESS) \ + { \ + freeptrs; \ + return ret; \ + }\ +} while(0); + +#endif /* __PMIC_CONFIG_H__ */ diff --git a/drivers/mxc/pmic/core/pmic_core_spi.c b/drivers/mxc/pmic/core/pmic_core_spi.c new file mode 100644 index 000000000000..8b28b49ed3cd --- /dev/null +++ b/drivers/mxc/pmic/core/pmic_core_spi.c @@ -0,0 +1,326 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file pmic_core_spi.c + * @brief This is the main file for the PMIC Core/Protocol driver. SPI + * should be providing the interface between the PMIC and the MCU. + * + * @ingroup PMIC_CORE + */ + +/* + * Includes + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/spi/spi.h> + +#include <asm/uaccess.h> +#include <asm/arch/gpio.h> + +#include <asm/arch/pmic_external.h> +#include <asm/arch/pmic_status.h> +#include "pmic.h" + +/* + * Global variables + */ +static pmic_version_t mxc_pmic_version; +unsigned int active_events[MAX_ACTIVE_EVENTS]; + +/* + * Static functions + */ +static void pmic_pdev_register(void); +static void pmic_pdev_unregister(void); +void pmic_bh_handler(struct work_struct *work); + +/* + * Platform device structure for PMIC client drivers + */ +static struct platform_device adc_ldm = { + .name = "pmic_adc", + .id = 1, +}; +static struct platform_device battery_ldm = { + .name = "pmic_battery", + .id = 1, +}; +static struct platform_device power_ldm = { + .name = "pmic_power", + .id = 1, +}; +static struct platform_device rtc_ldm = { + .name = "pmic_rtc", + .id = 1, +}; +static struct platform_device light_ldm = { + .name = "pmic_light", + .id = 1, +}; + +/* + * External functions + */ +extern void pmic_event_list_init(void); +extern void pmic_event_callback(type_event event); +extern void gpio_pmic_active(void); + +/*! + * Bottom half handler of PMIC event handling. + */ +DECLARE_WORK(pmic_ws, pmic_bh_handler); + +/*! + * This function registers platform device structures for + * PMIC client drivers. + */ +static void pmic_pdev_register(void) +{ + platform_device_register(&adc_ldm); + platform_device_register(&battery_ldm); + platform_device_register(&rtc_ldm); + platform_device_register(&power_ldm); + platform_device_register(&light_ldm); +} + +/*! + * This function unregisters platform device structures for + * PMIC client drivers. + */ +static void pmic_pdev_unregister(void) +{ + platform_device_unregister(&adc_ldm); + platform_device_unregister(&battery_ldm); + platform_device_unregister(&rtc_ldm); + platform_device_unregister(&power_ldm); + platform_device_unregister(&light_ldm); +} + +/*! + * This function is called when pmic interrupt occurs on the processor. + * It is the interrupt handler for the pmic module. + * + * @param irq the irq number + * @param dev_id the pointer on the device + * + * @return The function returns IRQ_HANDLED when handled. + */ +static irqreturn_t pmic_irq_handler(int irq, void *dev_id) +{ + /* prepare a task */ + schedule_work(&pmic_ws); + + return IRQ_HANDLED; +} + +/*! + * This function is the bottom half handler of the PMIC interrupt. + * It checks for active events and launches callback for the + * active events. + */ +void pmic_bh_handler(struct work_struct *work) +{ + unsigned int loop; + unsigned int count = 0; + + count = pmic_get_active_events(active_events); + + for (loop = 0; loop < count; loop++) { + pmic_event_callback(active_events[loop]); + } + + return; +} + +/*! + * This function is used to determine the PMIC type and its revision. + * + * @return Returns the PMIC type and its revision. + */ + +pmic_version_t pmic_get_version(void) +{ + return mxc_pmic_version; +} + +EXPORT_SYMBOL(pmic_get_version); + +/*! + * This function puts the SPI slave device in low-power mode/state. + * + * @param spi the SPI slave device + * @param message the power state to enter + * + * @return Returns 0 on SUCCESS and error on FAILURE. + */ +static int pmic_suspend(struct spi_device *spi, pm_message_t message) +{ + return PMIC_SUCCESS; +} + +/*! + * This function brings the SPI slave device back from low-power mode/state. + * + * @param spi the SPI slave device + * + * @return Returns 0 on SUCCESS and error on FAILURE. + */ +static int pmic_resume(struct spi_device *spi) +{ + return PMIC_SUCCESS; +} + +/*! + * This function is called whenever the SPI slave device is detected. + * + * @param spi the SPI slave device + * + * @return Returns 0 on SUCCESS and error on FAILURE. + */ +static int __devinit pmic_probe(struct spi_device *spi) +{ + int ret = 0; + + if (!strcmp(spi->dev.bus_id, PMIC_ARBITRATION)) { + if (PMIC_SUCCESS != pmic_fix_arbitration(spi)) { + dev_err((struct device *)spi, + "Unable to fix arbitration!! Access Failed\n"); + return -EACCES; + } + return PMIC_SUCCESS; + } + + /* Initialize the PMIC parameters */ + ret = pmic_spi_setup(spi); + if (ret != PMIC_SUCCESS) { + return PMIC_ERROR; + } + + /* Initialize the PMIC event handling */ + pmic_event_list_init(); + + /* Initialize GPIO for PMIC Interrupt */ + gpio_pmic_active(); + + /* Get the PMIC Version */ + pmic_get_revision(&mxc_pmic_version); + if (mxc_pmic_version.revision < 0) { + dev_err((struct device *)spi, + "PMIC not detected!!! Access Failed\n"); + return -ENODEV; + } else { + dev_dbg((struct device *)spi, + "Detected pmic core IC version number is %d\n", + mxc_pmic_version.revision); + } + + /* Initialize the PMIC parameters */ + ret = pmic_init_registers(); + if (ret != PMIC_SUCCESS) { + return PMIC_ERROR; + } + + /* Set and install PMIC IRQ handler */ + set_irq_type(spi->irq, IRQF_TRIGGER_RISING); + ret = request_irq(spi->irq, pmic_irq_handler, 0, "PMIC_IRQ", 0); + if (ret) { + dev_err((struct device *)spi, "gpio1: irq%d error.", spi->irq); + return ret; + } + + pmic_pdev_register(); + + printk(KERN_INFO "Device %s probed\n", spi->dev.bus_id); + + return PMIC_SUCCESS; +} + +/*! + * This function is called whenever the SPI slave device is removed. + * + * @param spi the SPI slave device + * + * @return Returns 0 on SUCCESS and error on FAILURE. + */ +static int __devexit pmic_remove(struct spi_device *spi) +{ + free_irq(spi->irq, 0); + + pmic_pdev_unregister(); + + printk(KERN_INFO "Device %s removed\n", spi->dev.bus_id); + + return PMIC_SUCCESS; +} + +/*! + * This structure contains pointers to the power management callback functions. + */ +static struct spi_driver pmic_driver = { + .driver = { + .name = "pmic_spi", + .bus = &spi_bus_type, + .owner = THIS_MODULE, + }, + .probe = pmic_probe, + .remove = __devexit_p(pmic_remove), + .suspend = pmic_suspend, + .resume = pmic_resume, +}; + +/* + * Initialization and Exit + */ + +/*! + * This function implements the init function of the PMIC device. + * This function is called when the module is loaded. It registers + * the PMIC Protocol driver. + * + * @return This function returns 0. + */ +static int __init pmic_init(void) +{ + pr_debug("Registering the PMIC Protocol Driver\n"); + return spi_register_driver(&pmic_driver); +} + +/*! + * This function implements the exit function of the PMIC device. + * This function is called when the module is unloaded. It unregisters + * the PMIC Protocol driver. + * + */ +static void __exit pmic_exit(void) +{ + pr_debug("Unregistering the PMIC Protocol Driver\n"); + spi_unregister_driver(&pmic_driver); +} + +/* + * Module entry points + */ +subsys_initcall(pmic_init); +module_exit(pmic_exit); + +MODULE_DESCRIPTION("Core/Protocol driver for PMIC"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/pmic/core/pmic_event.c b/drivers/mxc/pmic/core/pmic_event.c new file mode 100644 index 000000000000..e224b4c6c269 --- /dev/null +++ b/drivers/mxc/pmic/core/pmic_event.c @@ -0,0 +1,237 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file pmic_event.c + * @brief This file manage all event of PMIC component. + * + * It contains event subscription, unsubscription and callback + * launch methods implemeted. + * + * @ingroup PMIC_CORE + */ + +/* + * Includes + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/slab.h> +#include <linux/errno.h> +#include <linux/interrupt.h> +#include <linux/list.h> + +#include <asm/uaccess.h> +#include <asm/arch/pmic_external.h> +#include <asm/arch/pmic_status.h> +#include "pmic.h" + +/*! + * This structure is used to keep a list of subscribed + * callbacks for an event. + */ +typedef struct { + /*! + * Keeps a list of subscribed clients to an event. + */ + struct list_head list; + + /*! + * Callback function with parameter, called when event occurs + */ + pmic_event_callback_t callback; +} pmic_event_callback_list_t; + +/* Create a mutex to be used to prevent concurrent access to the event list */ +static DECLARE_MUTEX(event_mutex); + +/* This is a pointer to the event handler array. It defines the currently + * active set of events and user-defined callback functions. + */ +static struct list_head pmic_events[PMIC_MAX_EVENTS]; + +/*! + * This function initializes event list for PMIC event handling. + * + */ +void pmic_event_list_init(void) +{ + int i; + + for (i = 0; i < PMIC_MAX_EVENTS; i++) { + INIT_LIST_HEAD(&pmic_events[i]); + } + + sema_init(&event_mutex, 1); + return; +} + +/*! + * This function is used to subscribe on an event. + * + * @param event the event number to be subscribed + * @param callback the callback funtion to be subscribed + * + * @return This function returns 0 on SUCCESS, error on FAILURE. + */ +PMIC_STATUS pmic_event_subscribe(type_event event, + pmic_event_callback_t callback) +{ + pmic_event_callback_list_t *new = NULL; + + pr_debug("Event:%d Subscribe\n", event); + + /* Check whether the event & callback are valid? */ + if (event >= PMIC_MAX_EVENTS) { + pr_debug("Invalid Event:%d\n", event); + return -EINVAL; + } + if (NULL == callback.func) { + pr_debug("Null or Invalid Callback\n"); + return -EINVAL; + } + + /* Create a new linked list entry */ + new = kmalloc(sizeof(pmic_event_callback_list_t), GFP_KERNEL); + if (NULL == new) { + return -ENOMEM; + } + /* Initialize the list node fields */ + new->callback.func = callback.func; + new->callback.param = callback.param; + INIT_LIST_HEAD(&new->list); + + /* Obtain the lock to access the list */ + if (down_interruptible(&event_mutex)) { + kfree(new); + return PMIC_SYSTEM_ERROR_EINTR; + } + + /* Unmask the requested event */ + if (list_empty(&pmic_events[event])) { + if (pmic_event_unmask(event) != PMIC_SUCCESS) { + kfree(new); + up(&event_mutex); + return PMIC_ERROR; + } + } + + /* Add this entry to the event list */ + list_add_tail(&new->list, &pmic_events[event]); + + /* Release the lock */ + up(&event_mutex); + + return PMIC_SUCCESS; +} + +/*! + * This function is used to unsubscribe on an event. + * + * @param event the event number to be unsubscribed + * @param callback the callback funtion to be unsubscribed + * + * @return This function returns 0 on SUCCESS, error on FAILURE. + */ +PMIC_STATUS pmic_event_unsubscribe(type_event event, + pmic_event_callback_t callback) +{ + struct list_head *p; + struct list_head *n; + pmic_event_callback_list_t *temp = NULL; + int ret = PMIC_EVENT_NOT_SUBSCRIBED; + + pr_debug("Event:%d Unsubscribe\n", event); + + /* Check whether the event & callback are valid? */ + if (event >= PMIC_MAX_EVENTS) { + pr_debug("Invalid Event:%d\n", event); + return -EINVAL; + } + + if (NULL == callback.func) { + pr_debug("Null or Invalid Callback\n"); + return -EINVAL; + } + + /* Obtain the lock to access the list */ + if (down_interruptible(&event_mutex)) { + return PMIC_SYSTEM_ERROR_EINTR; + } + + /* Find the entry in the list */ + list_for_each_safe(p, n, &pmic_events[event]) { + temp = list_entry(p, pmic_event_callback_list_t, list); + if (temp->callback.func == callback.func + && temp->callback.param == callback.param) { + /* Remove the entry from the list */ + list_del(p); + kfree(temp); + ret = PMIC_SUCCESS; + break; + } + } + + /* Unmask the requested event */ + if (list_empty(&pmic_events[event])) { + if (pmic_event_mask(event) != PMIC_SUCCESS) { + ret = PMIC_UNSUBSCRIBE_ERROR; + } + } + + /* Release the lock */ + up(&event_mutex); + + return ret; +} + +/*! + * This function calls all callback of a specific event. + * + * @param event the active event number + * + * @return None + */ +void pmic_event_callback(type_event event) +{ + struct list_head *p; + pmic_event_callback_list_t *temp = NULL; + + /* Obtain the lock to access the list */ + if (down_interruptible(&event_mutex)) { + return; + } + + if (list_empty(&pmic_events[event])) { + pr_debug("PMIC Event:%d detected. No callback subscribed\n", + event); + up(&event_mutex); + return; + } + + list_for_each(p, &pmic_events[event]) { + temp = list_entry(p, pmic_event_callback_list_t, list); + temp->callback.func(temp->callback.param); + } + + /* Release the lock */ + up(&event_mutex); + + return; + +} + +EXPORT_SYMBOL(pmic_event_subscribe); +EXPORT_SYMBOL(pmic_event_unsubscribe); diff --git a/drivers/mxc/pmic/core/pmic_external.c b/drivers/mxc/pmic/core/pmic_external.c new file mode 100644 index 000000000000..9e092eb0907c --- /dev/null +++ b/drivers/mxc/pmic/core/pmic_external.c @@ -0,0 +1,105 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file pmic_external.c + * @brief This file contains all external functions of PMIC drivers. + * + * @ingroup PMIC_CORE + */ + +/* + * Includes + */ +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/wait.h> +#include <linux/init.h> +#include <linux/errno.h> +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/uaccess.h> + +#include <asm/arch/pmic_external.h> +#include <asm/arch/pmic_status.h> + +#include "pmic_config.h" + +/* + * External Functions + */ +extern int pmic_read(int reg_num, unsigned int *reg_val); +extern int pmic_write(int reg_num, const unsigned int reg_val); + +/*! + * This function is called by PMIC clients to read a register on PMIC. + * + * @param reg number of register + * @param reg_value return value of register + * @param reg_mask Bitmap mask indicating which bits to modify + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_read_reg(int reg, unsigned int *reg_value, + unsigned int reg_mask) +{ + int ret = 0; + unsigned int temp = 0; + + ret = pmic_read(reg, &temp); + if (ret != PMIC_SUCCESS) { + return PMIC_ERROR; + } + *reg_value = (temp & reg_mask); + + pr_debug("Read REG[ %d ] = 0x%x\n", reg, *reg_value); + + return ret; +} + +/*! + * This function is called by PMIC clients to write a register on PMIC. + * + * @param reg number of register + * @param reg_value New value of register + * @param reg_mask Bitmap mask indicating which bits to modify + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_write_reg(int reg, unsigned int reg_value, + unsigned int reg_mask) +{ + int ret = 0; + unsigned int temp = 0; + + ret = pmic_read(reg, &temp); + if (ret != PMIC_SUCCESS) { + return PMIC_ERROR; + } + temp = (temp & (~reg_mask)) | reg_value; +#ifdef CONFIG_MXC_PMIC_MC13783 + if (reg == REG_POWER_MISCELLANEOUS) + temp &= 0xFFFE7FFF; +#endif + ret = pmic_write(reg, temp); + if (ret != PMIC_SUCCESS) { + return PMIC_ERROR; + } + + pr_debug("Write REG[ %d ] = 0x%x\n", reg, reg_value); + + return ret; +} + +EXPORT_SYMBOL(pmic_read_reg); +EXPORT_SYMBOL(pmic_write_reg); diff --git a/drivers/mxc/pmic/mc13783/Kconfig b/drivers/mxc/pmic/mc13783/Kconfig new file mode 100644 index 000000000000..02496c624e2e --- /dev/null +++ b/drivers/mxc/pmic/mc13783/Kconfig @@ -0,0 +1,55 @@ +# +# PMIC Modules configuration +# + +config MXC_MC13783_ADC + tristate "MC13783 ADC support" + depends on MXC_PMIC_MC13783 + ---help--- + This is the MC13783 ADC module driver. This module provides kernel API + for the ADC system of MC13783. + It controls also the touch screen interface. + If you want MC13783 ADC support, you should say Y here + +config MXC_MC13783_AUDIO + tristate "MC13783 Audio support" + depends on MXC_PMIC_MC13783 + ---help--- + This is the MC13783 audio module driver. This module provides kernel API + for audio part of MC13783. + If you want MC13783 audio support, you should say Y here +config MXC_MC13783_RTC + tristate "MC13783 Real Time Clock (RTC) support" + depends on MXC_PMIC_MC13783 + ---help--- + This is the MC13783 RTC module driver. This module provides kernel API + for RTC part of MC13783. + If you want MC13783 RTC support, you should say Y here +config MXC_MC13783_LIGHT + tristate "MC13783 Light and Backlight support" + depends on MXC_PMIC_MC13783 + ---help--- + This is the MC13783 Light module driver. This module provides kernel API + for led and backlight control part of MC13783. + If you want MC13783 Light support, you should say Y here +config MXC_MC13783_BATTERY + tristate "MC13783 Battery API support" + depends on MXC_PMIC_MC13783 + ---help--- + This is the MC13783 battery module driver. This module provides kernel API + for battery control part of MC13783. + If you want MC13783 battery support, you should say Y here +config MXC_MC13783_CONNECTIVITY + tristate "MC13783 Connectivity API support" + depends on MXC_PMIC_MC13783 + ---help--- + This is the MC13783 connectivity module driver. This module provides kernel API + for USB/RS232 connectivity control part of MC13783. + If you want MC13783 connectivity support, you should say Y here +config MXC_MC13783_POWER + tristate "MC13783 Power API support" + depends on MXC_PMIC_MC13783 + ---help--- + This is the MC13783 power and supplies module driver. This module provides kernel API + for power and regulator control part of MC13783. + If you want MC13783 power support, you should say Y here diff --git a/drivers/mxc/pmic/mc13783/Makefile b/drivers/mxc/pmic/mc13783/Makefile new file mode 100644 index 000000000000..7bbba23f5ab9 --- /dev/null +++ b/drivers/mxc/pmic/mc13783/Makefile @@ -0,0 +1,18 @@ +# +# Makefile for the mc13783 pmic drivers. +# + +obj-$(CONFIG_MXC_MC13783_ADC) += pmic_adc-mod.o +obj-$(CONFIG_MXC_MC13783_AUDIO) += pmic_audio-mod.o +obj-$(CONFIG_MXC_MC13783_RTC) += pmic_rtc-mod.o +obj-$(CONFIG_MXC_MC13783_LIGHT) += pmic_light-mod.o +obj-$(CONFIG_MXC_MC13783_BATTERY) += pmic_battery-mod.o +obj-$(CONFIG_MXC_MC13783_CONNECTIVITY) += pmic_convity-mod.o +obj-$(CONFIG_MXC_MC13783_POWER) += pmic_power-mod.o +pmic_adc-mod-objs := pmic_adc.o +pmic_audio-mod-objs := pmic_audio.o +pmic_rtc-mod-objs := pmic_rtc.o +pmic_light-mod-objs := pmic_light.o +pmic_battery-mod-objs := pmic_battery.o +pmic_convity-mod-objs := pmic_convity.o +pmic_power-mod-objs := pmic_power.o diff --git a/drivers/mxc/pmic/mc13783/pmic_adc.c b/drivers/mxc/pmic/mc13783/pmic_adc.c new file mode 100644 index 000000000000..5f72f0d43d2a --- /dev/null +++ b/drivers/mxc/pmic/mc13783/pmic_adc.c @@ -0,0 +1,1504 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mc13783/pmic_adc.c + * @brief This is the main file of PMIC(mc13783) ADC driver. + * + * @ingroup PMIC_ADC + */ + +/* + * Includes + */ + +#include <linux/platform_device.h> +#include <linux/poll.h> +#include <linux/sched.h> +#include <linux/time.h> +#include <linux/delay.h> +#include <linux/wait.h> + +#include <asm/arch/pmic_adc.h> +#include <asm/arch/pmic_power.h> + +#include "../core/pmic_config.h" +#include "../core/pmic.h" +#include "pmic_adc_defs.h" + +#define NB_ADC_REG 5 + +static int pmic_adc_major; + +/* internal function */ +static void callback_tsi(void *); +static void callback_adcdone(void *); +static void callback_adcbisdone(void *); +static void callback_adc_comp_high(void *); + +/*! + * Number of users waiting in suspendq + */ +static int swait = 0; + +/*! + * To indicate whether any of the adc devices are suspending + */ +static int suspend_flag = 0; + +/*! + * The suspendq is used by blocking application calls + */ +static wait_queue_head_t suspendq; + +static struct class *pmic_adc_class; + +/* + * ADC mc13783 API + */ +/* EXPORTED FUNCTIONS */ +EXPORT_SYMBOL(pmic_adc_init); +EXPORT_SYMBOL(pmic_adc_deinit); +EXPORT_SYMBOL(pmic_adc_convert); +EXPORT_SYMBOL(pmic_adc_convert_8x); +EXPORT_SYMBOL(pmic_adc_convert_multichnnel); +EXPORT_SYMBOL(pmic_adc_set_touch_mode); +EXPORT_SYMBOL(pmic_adc_get_touch_mode); +EXPORT_SYMBOL(pmic_adc_get_touch_sample); +EXPORT_SYMBOL(pmic_adc_get_battery_current); +EXPORT_SYMBOL(pmic_adc_active_comparator); +EXPORT_SYMBOL(pmic_adc_deactive_comparator); + +static DECLARE_COMPLETION(adcdone_it); +static DECLARE_COMPLETION(adcbisdone_it); +static DECLARE_COMPLETION(adc_tsi); +static pmic_event_callback_t tsi_event; +static pmic_event_callback_t event_adc; +static pmic_event_callback_t event_adc_bis; +static pmic_event_callback_t adc_comp_h; +static bool data_ready_adc_1; +static bool data_ready_adc_2; +static bool adc_ts; +static bool wait_ts; +static bool monitor_en; +static bool monitor_adc; +static t_check_mode wcomp_mode; +void (*monitoring_cb) (void); /*call back to be called when event is detected. */ + +static DECLARE_WAIT_QUEUE_HEAD(queue_adc_busy); +static t_adc_state adc_dev[2]; + +static unsigned channel_num[] = { + 0, + 1, + 3, + 4, + 2, + 12, + 13, + 14, + 15, + -1, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 7, + 6, + -1, + -1, + -1, + -1, + 5, + 7 +}; + +/*! + * This is the suspend of power management for the mc13783 ADC API. + * It supports SAVE and POWER_DOWN state. + * + * @param pdev the device + * @param state the state + * + * @return This function returns 0 if successful. + */ +static int pmic_adc_suspend(struct platform_device *pdev, pm_message_t state) +{ + unsigned int reg_value = 0; + suspend_flag = 1; + CHECK_ERROR(pmic_write_reg(REG_ADC_0, DEF_ADC_0, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(REG_ADC_1, reg_value, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(REG_ADC_2, reg_value, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(REG_ADC_3, DEF_ADC_3, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(REG_ADC_4, reg_value, PMIC_ALL_BITS)); + + return 0; +}; + +/*! + * This is the resume of power management for the mc13783 adc API. + * It supports RESTORE state. + * + * @param pdev the device + * + * @return This function returns 0 if successful. + */ +static int pmic_adc_resume(struct platform_device *pdev) +{ + /* nothing for mc13783 adc */ + suspend_flag = 0; + while (swait > 0) { + swait--; + wake_up_interruptible(&suspendq); + } + + return 0; +}; + +/* + * Call back functions + */ + +/*! + * This is the callback function called on TSI mc13783 event, used in synchronous call. + */ +static void callback_tsi(void *unused) +{ + pr_debug("*** TSI IT mc13783 PMIC_ADC_GET_TOUCH_SAMPLE ***\n"); + if (wait_ts) { + complete(&adc_tsi); + pmic_event_mask(EVENT_TSI); + } +} + +/*! + * This is the callback function called on ADCDone mc13783 event. + */ +static void callback_adcdone(void *unused) +{ + if (data_ready_adc_1) { + complete(&adcdone_it); + } +} + +/*! + * This is the callback function called on ADCDone mc13783 event. + */ +static void callback_adcbisdone(void *unused) +{ + pr_debug("* adcdone bis it callback *\n"); + if (data_ready_adc_2) { + complete(&adcbisdone_it); + } +} + +/*! + * This is the callback function called on mc13783 event. + */ +static void callback_adc_comp_high(void *unused) +{ + pr_debug("* adc comp it high *\n"); + if (wcomp_mode == CHECK_HIGH || wcomp_mode == CHECK_LOW_OR_HIGH) { + /* launch callback */ + if (monitoring_cb != NULL) { + monitoring_cb(); + } + } +} + +/*! + * This function performs filtering and rejection of excessive noise prone + * samples. + * + * @param ts_curr Touch screen value + * + * @return This function returns 0 on success, -1 otherwise. + */ +static int pmic_adc_filter(t_touch_screen * ts_curr) +{ + unsigned int ydiff1, ydiff2, ydiff3, xdiff1, xdiff2, xdiff3; + unsigned int sample_sumx, sample_sumy; + static unsigned int prev_x[FILTLEN], prev_y[FILTLEN]; + int index = 0; + unsigned int y_curr, x_curr; + static int filt_count = 0; + /* Added a variable filt_type to decide filtering at run-time */ + unsigned int filt_type = 0; + + if (ts_curr->contact_resistance == 0) { + ts_curr->x_position = 0; + ts_curr->y_position = 0; + filt_count = 0; + return 0; + } + + ydiff1 = abs(ts_curr->y_position1 - ts_curr->y_position2); + ydiff2 = abs(ts_curr->y_position2 - ts_curr->y_position3); + ydiff3 = abs(ts_curr->y_position1 - ts_curr->y_position3); + if ((ydiff1 > DELTA_Y_MAX) || + (ydiff2 > DELTA_Y_MAX) || (ydiff3 > DELTA_Y_MAX)) { + pr_debug("pmic_adc_filter: Ret pos 1\n"); + return -1; + } + + xdiff1 = abs(ts_curr->x_position1 - ts_curr->x_position2); + xdiff2 = abs(ts_curr->x_position2 - ts_curr->x_position3); + xdiff3 = abs(ts_curr->x_position1 - ts_curr->x_position3); + + if ((xdiff1 > DELTA_X_MAX) || + (xdiff2 > DELTA_X_MAX) || (xdiff3 > DELTA_X_MAX)) { + pr_debug("mc13783_adc_filter: Ret pos 2\n"); + return -1; + } + /* Compute two closer values among the three available Y readouts */ + + if (ydiff1 < ydiff2) { + if (ydiff1 < ydiff3) { + // Sample 0 & 1 closest together + sample_sumy = ts_curr->y_position1 + + ts_curr->y_position2; + } else { + // Sample 0 & 2 closest together + sample_sumy = ts_curr->y_position1 + + ts_curr->y_position3; + } + } else { + if (ydiff2 < ydiff3) { + // Sample 1 & 2 closest together + sample_sumy = ts_curr->y_position2 + + ts_curr->y_position3; + } else { + // Sample 0 & 2 closest together + sample_sumy = ts_curr->y_position1 + + ts_curr->y_position3; + } + } + + /* + * Compute two closer values among the three available X + * readouts + */ + if (xdiff1 < xdiff2) { + if (xdiff1 < xdiff3) { + // Sample 0 & 1 closest together + sample_sumx = ts_curr->x_position1 + + ts_curr->x_position2; + } else { + // Sample 0 & 2 closest together + sample_sumx = ts_curr->x_position1 + + ts_curr->x_position3; + } + } else { + if (xdiff2 < xdiff3) { + // Sample 1 & 2 closest together + sample_sumx = ts_curr->x_position2 + + ts_curr->x_position3; + } else { + // Sample 0 & 2 closest together + sample_sumx = ts_curr->x_position1 + + ts_curr->x_position3; + } + } + /* + * Wait FILTER_MIN_DELAY number of samples to restart + * filtering + */ + if (filt_count < FILTER_MIN_DELAY) { + /* + * Current output is the average of the two closer + * values and no filtering is used + */ + y_curr = (sample_sumy / 2); + x_curr = (sample_sumx / 2); + ts_curr->y_position = y_curr; + ts_curr->x_position = x_curr; + filt_count++; + } else { + if (abs(sample_sumx - (prev_x[0] + prev_x[1])) > + (DELTA_X_MAX * 16)) { + pr_debug("pmic_adc_filter: : Ret pos 3\n"); + return -1; + } + if (abs(sample_sumy - (prev_y[0] + prev_y[1])) > + (DELTA_Y_MAX * 16)) { + return -1; + } + sample_sumy /= 2; + sample_sumx /= 2; + /* Use hard filtering if the sample difference < 10 */ + if ((abs(sample_sumy - prev_y[0]) > 10) || + (abs(sample_sumx - prev_x[0]) > 10)) { + filt_type = 1; + } + + /* + * Current outputs are the average of three previous + * values and the present readout + */ + y_curr = sample_sumy; + for (index = 0; index < FILTLEN; index++) { + if (filt_type == 0) { + y_curr = y_curr + (prev_y[index]); + } else { + y_curr = y_curr + (prev_y[index] / 3); + } + } + if (filt_type == 0) { + y_curr = y_curr >> 2; + } else { + y_curr = y_curr >> 1; + } + ts_curr->y_position = y_curr; + + x_curr = sample_sumx; + for (index = 0; index < FILTLEN; index++) { + if (filt_type == 0) { + x_curr = x_curr + (prev_x[index]); + } else { + x_curr = x_curr + (prev_x[index] / 3); + } + } + if (filt_type == 0) { + x_curr = x_curr >> 2; + } else { + x_curr = x_curr >> 1; + } + ts_curr->x_position = x_curr; + + } + + /* Update previous X and Y values */ + for (index = (FILTLEN - 1); index > 0; index--) { + prev_x[index] = prev_x[index - 1]; + prev_y[index] = prev_y[index - 1]; + } + + /* + * Current output will be the most recent past for the + * next sample + */ + prev_y[0] = y_curr; + prev_x[0] = x_curr; + + return 0; +} + +/*! + * This function implements the open method on a MC13783 ADC device. + * + * @param inode pointer on the node + * @param file pointer on the file + * @return This function returns 0. + */ +static int pmic_adc_open(struct inode *inode, struct file *file) +{ + while (suspend_flag == 1) { + swait++; + /* Block if the device is suspended */ + if (wait_event_interruptible(suspendq, (suspend_flag == 0))) { + return -ERESTARTSYS; + } + } + pr_debug("mc13783_adc : mc13783_adc_open()\n"); + return 0; +} + +/*! + * This function implements the release method on a MC13783 ADC device. + * + * @param inode pointer on the node + * @param file pointer on the file + * @return This function returns 0. + */ +static int pmic_adc_free(struct inode *inode, struct file *file) +{ + pr_debug("mc13783_adc : mc13783_adc_free()\n"); + return 0; +} + +/*! + * This function initializes all ADC registers with default values. This + * function also registers the interrupt events. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +int pmic_adc_init(void) +{ + unsigned int reg_value = 0, i = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + for (i = 0; i < ADC_NB_AVAILABLE; i++) { + adc_dev[i] = ADC_FREE; + } + CHECK_ERROR(pmic_write_reg(REG_ADC_0, DEF_ADC_0, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(REG_ADC_1, reg_value, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(REG_ADC_2, reg_value, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(REG_ADC_3, DEF_ADC_3, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(REG_ADC_4, reg_value, PMIC_ALL_BITS)); + + data_ready_adc_1 = false; + data_ready_adc_2 = false; + adc_ts = false; + wait_ts = false; + monitor_en = false; + monitor_adc = false; + wcomp_mode = CHECK_LOW; + monitoring_cb = NULL; + /* sub to ADCDone IT */ + event_adc.param = NULL; + event_adc.func = callback_adcdone; + CHECK_ERROR(pmic_event_subscribe(EVENT_ADCDONEI, event_adc)); + + /* sub to ADCDoneBis IT */ + event_adc_bis.param = NULL; + event_adc_bis.func = callback_adcbisdone; + CHECK_ERROR(pmic_event_subscribe(EVENT_ADCBISDONEI, event_adc_bis)); + + /* sub to Touch Screen IT */ + tsi_event.param = NULL; + tsi_event.func = callback_tsi; + CHECK_ERROR(pmic_event_subscribe(EVENT_TSI, tsi_event)); + + /* ADC reading above high limit */ + adc_comp_h.param = NULL; + adc_comp_h.func = callback_adc_comp_high; + CHECK_ERROR(pmic_event_subscribe(EVENT_WHIGHI, adc_comp_h)); + + return PMIC_SUCCESS; +} + +/*! + * This function disables the ADC, de-registers the interrupt events. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_adc_deinit(void) +{ + CHECK_ERROR(pmic_event_unsubscribe(EVENT_ADCDONEI, event_adc)); + CHECK_ERROR(pmic_event_unsubscribe(EVENT_ADCBISDONEI, event_adc_bis)); + CHECK_ERROR(pmic_event_unsubscribe(EVENT_TSI, tsi_event)); + CHECK_ERROR(pmic_event_unsubscribe(EVENT_WHIGHI, adc_comp_h)); + + return PMIC_SUCCESS; +} + +/*! + * This function initializes adc_param structure. + * + * @param adc_param Structure to be initialized. + * + * @return This function returns 0 if successful. + */ +int mc13783_adc_init_param(t_adc_param * adc_param) +{ + int i = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + adc_param->delay = 0; + adc_param->conv_delay = false; + adc_param->single_channel = false; + adc_param->group = false; + adc_param->channel_0 = BATTERY_VOLTAGE; + adc_param->channel_1 = BATTERY_VOLTAGE; + adc_param->read_mode = 0; + adc_param->wait_tsi = 0; + adc_param->chrgraw_devide_5 = true; + adc_param->read_ts = false; + adc_param->ts_value.x_position = 0; + adc_param->ts_value.y_position = 0; + adc_param->ts_value.contact_resistance = 0; + for (i = 0; i <= MAX_CHANNEL; i++) { + adc_param->value[i] = 0; + } + return 0; +} + +/*! + * This function starts the convert. + * + * @param adc_param contains all adc configuration and return value. + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS mc13783_adc_convert(t_adc_param * adc_param) +{ + bool use_bis = false; + unsigned int adc_0_reg = 0, adc_1_reg = 0, reg_1 = 0, result_reg = + 0, i = 0; + unsigned int result = 0, temp = 0; + pmic_version_t mc13783_ver; + pr_debug("mc13783 ADC - mc13783_adc_convert ....\n"); + if (suspend_flag == 1) { + return -EBUSY; + } + + if (adc_param->wait_tsi) { + /* we need to set ADCEN 1 for TSI interrupt on mc13783 1.x */ + /* configure adc to wait tsi interrupt */ + INIT_COMPLETION(adc_tsi); + pr_debug("mc13783 ADC - pmic_write_reg ....\n"); + adc_0_reg = 0x001c00 | (ADC_BIS * use_bis); + pmic_event_unmask(EVENT_TSI); + CHECK_ERROR(pmic_write_reg + (REG_ADC_0, adc_0_reg, PMIC_ALL_BITS)); + adc_1_reg = 0x200001 | (ADC_BIS * adc_ts); + CHECK_ERROR(pmic_write_reg + (REG_ADC_1, adc_1_reg, PMIC_ALL_BITS)); + pr_debug("wait tsi ....\n"); + wait_ts = true; + wait_for_completion_interruptible(&adc_tsi); + wait_ts = false; + } + use_bis = mc13783_adc_request(); + if (use_bis < 0) { + pr_debug("process has received a signal and got interrupted\n"); + return -EINTR; + } + + /* CONFIGURE ADC REG 0 */ + adc_0_reg = 0; + adc_1_reg = 0; + if (adc_param->read_ts == false) { + adc_0_reg = adc_param->read_mode & 0x00003F; + /* add auto inc */ + adc_0_reg |= ADC_INC; + if (use_bis) { + /* add adc bis */ + adc_0_reg |= ADC_BIS; + } + mc13783_ver = pmic_get_version(); + if (mc13783_ver.revision >= 20) { + if (adc_param->chrgraw_devide_5) { + adc_0_reg |= ADC_CHRGRAW_D5; + } + } + if (adc_param->single_channel) { + adc_1_reg |= ADC_SGL_CH; + } + + if (adc_param->conv_delay) { + adc_1_reg |= ADC_ATO; + } + + if (adc_param->group) { + adc_1_reg |= ADC_ADSEL; + } + + if (adc_param->single_channel) { + adc_1_reg |= ADC_SGL_CH; + } + + adc_1_reg |= (adc_param->channel_0 << ADC_CH_0_POS) & + ADC_CH_0_MASK; + adc_1_reg |= (adc_param->channel_1 << ADC_CH_1_POS) & + ADC_CH_1_MASK; + } else { + adc_0_reg = 0x003c00 | (ADC_BIS * use_bis) | ADC_INC; + } + pr_debug("Write Reg %i = %x\n", REG_ADC_0, adc_0_reg); + /*Change has been made here */ + CHECK_ERROR(pmic_write_reg(REG_ADC_0, adc_0_reg, + ADC_INC | ADC_BIS | ADC_CHRGRAW_D5 | + 0xfff00ff)); + /* CONFIGURE ADC REG 1 */ + if (adc_param->read_ts == false) { + adc_1_reg |= ADC_NO_ADTRIG; + adc_1_reg |= ADC_EN; + adc_1_reg |= (adc_param->delay << ADC_DELAY_POS) & + ADC_DELAY_MASK; + if (use_bis) { + adc_1_reg |= ADC_BIS; + } + } else { + /* configure and start convert to read x and y position */ + /* configure to read 2 value in channel selection 1 & 2 */ + adc_1_reg = 0x100409 | (ADC_BIS * use_bis) | ADC_NO_ADTRIG; + } + reg_1 = adc_1_reg; + if (use_bis == 0) { + data_ready_adc_1 = false; + adc_1_reg |= ASC_ADC; + data_ready_adc_1 = true; + pr_debug("Write Reg %i = %x\n", REG_ADC_1, adc_1_reg); + INIT_COMPLETION(adcdone_it); + CHECK_ERROR(pmic_write_reg(REG_ADC_1, adc_1_reg, + ADC_SGL_CH | ADC_ATO | ADC_ADSEL + | ADC_CH_0_MASK | ADC_CH_1_MASK | + ADC_NO_ADTRIG | ADC_EN | + ADC_DELAY_MASK | ASC_ADC | ADC_BIS)); + pr_debug("wait adc done \n"); + wait_for_completion_interruptible(&adcdone_it); + data_ready_adc_1 = false; + } else { + data_ready_adc_2 = false; + adc_1_reg |= ASC_ADC; + data_ready_adc_2 = true; + INIT_COMPLETION(adcbisdone_it); + CHECK_ERROR(pmic_write_reg(REG_ADC_1, adc_1_reg, 0xFFFFFF)); + temp = 0x800000; + CHECK_ERROR(pmic_write_reg(REG_ADC_3, temp, 0xFFFFFF)); + temp = 0x001000; + pmic_write_reg(REG_ARBITRATION_PERIPHERAL_AUDIO, temp, + 0xFFFFFF); + pr_debug("wait adc done bis\n"); + wait_for_completion_interruptible(&adcbisdone_it); + data_ready_adc_2 = false; + } + /* read result and store in adc_param */ + result = 0; + if (use_bis == 0) { + result_reg = REG_ADC_2; + } else { + result_reg = REG_ADC_4; + } + CHECK_ERROR(pmic_write_reg(REG_ADC_1, 4 << ADC_CH_1_POS, + ADC_CH_0_MASK | ADC_CH_1_MASK)); + + for (i = 0; i <= 3; i++) { + CHECK_ERROR(pmic_read_reg(result_reg, &result, PMIC_ALL_BITS)); + pr_debug("result %i = %x\n", result_reg, result); + adc_param->value[i] = ((result & ADD1_RESULT_MASK) >> 2); + adc_param->value[i + 4] = ((result & ADD2_RESULT_MASK) >> 14); + } + if (adc_param->read_ts) { + adc_param->ts_value.x_position = adc_param->value[2]; + adc_param->ts_value.x_position1 = adc_param->value[0]; + adc_param->ts_value.x_position2 = adc_param->value[1]; + adc_param->ts_value.x_position3 = adc_param->value[2]; + adc_param->ts_value.y_position1 = adc_param->value[3]; + adc_param->ts_value.y_position2 = adc_param->value[4]; + adc_param->ts_value.y_position3 = adc_param->value[5]; + adc_param->ts_value.y_position = adc_param->value[5]; + adc_param->ts_value.contact_resistance = adc_param->value[6]; + + } + + /*if (adc_param->read_ts) { + adc_param->ts_value.x_position = adc_param->value[2]; + adc_param->ts_value.y_position = adc_param->value[5]; + adc_param->ts_value.contact_resistance = adc_param->value[6]; + } */ + mc13783_adc_release(use_bis); + return PMIC_SUCCESS; +} + +/*! + * This function select the required read_mode for a specific channel. + * + * @param channel The channel to be sampled + * + * @return This function returns the requires read_mode + */ +t_reading_mode mc13783_set_read_mode(t_channel channel) +{ + t_reading_mode read_mode = 0; + + switch (channel) { + case LICELL: + read_mode = M_LITHIUM_CELL; + break; + case CHARGE_CURRENT: + read_mode = M_CHARGE_CURRENT; + break; + case BATTERY_CURRENT: + read_mode = M_BATTERY_CURRENT; + break; + case THERMISTOR: + read_mode = M_THERMISTOR; + break; + case DIE_TEMP: + read_mode = M_DIE_TEMPERATURE; + break; + case USB_ID: + read_mode = M_UID; + break; + default: + read_mode = 0; + } + + return read_mode; +} + +/*! + * This function triggers a conversion and returns one sampling result of one + * channel. + * + * @param channel The channel to be sampled + * @param result The pointer to the conversion result. The memory + * should be allocated by the caller of this function. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_adc_convert(t_channel channel, unsigned short *result) +{ + t_adc_param adc_param; + PMIC_STATUS ret; + + if (suspend_flag == 1) { + return -EBUSY; + } + + channel = channel_num[channel]; + if (channel == -1) { + pr_debug("Wrong channel ID\n"); + return PMIC_PARAMETER_ERROR; + } + mc13783_adc_init_param(&adc_param); + pr_debug("pmic_adc_convert\n"); + adc_param.read_ts = false; + adc_param.read_mode = mc13783_set_read_mode(channel); + + adc_param.single_channel = true; + /* Find the group */ + if ((channel >= 0) && (channel <= 7)) { + adc_param.channel_0 = channel; + adc_param.group = false; + } else if ((channel >= 8) && (channel <= 15)) { + adc_param.channel_0 = channel & 0x07; + adc_param.group = true; + } else { + return PMIC_PARAMETER_ERROR; + } + ret = mc13783_adc_convert(&adc_param); + *result = adc_param.value[0]; + return ret; +} + +/*! + * This function triggers a conversion and returns eight sampling results of + * one channel. + * + * @param channel The channel to be sampled + * @param result The pointer to array to store eight sampling results. + * The memory should be allocated by the caller of this + * function. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_adc_convert_8x(t_channel channel, unsigned short *result) +{ + t_adc_param adc_param; + int i; + PMIC_STATUS ret; + if (suspend_flag == 1) { + return -EBUSY; + } + + channel = channel_num[channel]; + + if (channel == -1) { + pr_debug("Wrong channel ID\n"); + return PMIC_PARAMETER_ERROR; + } + mc13783_adc_init_param(&adc_param); + pr_debug("pmic_adc_convert_8x\n"); + adc_param.read_ts = false; + adc_param.single_channel = true; + adc_param.read_mode = mc13783_set_read_mode(channel); + if ((channel >= 0) && (channel <= 7)) { + adc_param.channel_0 = channel; + adc_param.channel_1 = channel; + adc_param.group = false; + } else if ((channel >= 8) && (channel <= 15)) { + adc_param.channel_0 = channel & 0x07; + adc_param.channel_1 = channel & 0x07; + adc_param.group = true; + } else { + return PMIC_PARAMETER_ERROR; + } + + ret = mc13783_adc_convert(&adc_param); + for (i = 0; i <= 7; i++) { + result[i] = adc_param.value[i]; + } + return ret; +} + +/*! + * This function triggers a conversion and returns sampling results of each + * specified channel. + * + * @param channels This input parameter is bitmap to specify channels + * to be sampled. + * @param result The pointer to array to store sampling results. + * The memory should be allocated by the caller of this + * function. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_adc_convert_multichnnel(t_channel channels, + unsigned short *result) +{ + t_adc_param adc_param; + int i; + PMIC_STATUS ret; + if (suspend_flag == 1) { + return -EBUSY; + } + mc13783_adc_init_param(&adc_param); + pr_debug("pmic_adc_convert_multichnnel\n"); + + channels = channel_num[channels]; + + if (channels == -1) { + pr_debug("Wrong channel ID\n"); + return PMIC_PARAMETER_ERROR; + } + + adc_param.read_ts = false; + adc_param.single_channel = false; + if ((channels >= 0) && (channels <= 7)) { + adc_param.channel_0 = channels; + adc_param.channel_1 = ((channels + 4) % 4) + 4; + adc_param.group = false; + } else if ((channels >= 8) && (channels <= 15)) { + channels = channels & 0x07; + adc_param.channel_0 = channels; + adc_param.channel_1 = ((channels + 4) % 4) + 4; + adc_param.group = true; + } else { + return PMIC_PARAMETER_ERROR; + } + adc_param.read_mode = 0x00003f; + adc_param.read_ts = false; + ret = mc13783_adc_convert(&adc_param); + + for (i = 0; i <= 7; i++) { + result[i] = adc_param.value[i]; + } + return ret; +} + +/*! + * This function sets touch screen operation mode. + * + * @param touch_mode Touch screen operation mode. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_adc_set_touch_mode(t_touch_mode touch_mode) +{ + if (suspend_flag == 1) { + return -EBUSY; + } + CHECK_ERROR(pmic_write_reg(REG_ADC_0, + BITFVAL(MC13783_ADC0_TS_M, touch_mode), + BITFMASK(MC13783_ADC0_TS_M))); + return PMIC_SUCCESS; +} + +/*! + * This function retrieves the current touch screen operation mode. + * + * @param touch_mode Pointer to the retrieved touch screen operation + * mode. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_adc_get_touch_mode(t_touch_mode * touch_mode) +{ + unsigned int value; + if (suspend_flag == 1) { + return -EBUSY; + } + CHECK_ERROR(pmic_read_reg(REG_ADC_0, &value, PMIC_ALL_BITS)); + + *touch_mode = BITFEXT(value, MC13783_ADC0_TS_M); + + return PMIC_SUCCESS; +} + +/*! + * This function retrieves the current touch screen (X,Y) coordinates. + * + * @param touch_sample Pointer to touch sample. + * @param wait indicates whether this call must block or not. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_adc_get_touch_sample(t_touch_screen * touch_sample, int wait) +{ + mc13783_adc_read_ts(touch_sample, wait); + pmic_adc_filter(touch_sample); + return PMIC_SUCCESS; +} + +/*! + * This function read the touch screen value. + * + * @param ts_value return value of touch screen + * @param wait_tsi if true, this function is synchronous (wait in TSI event). + * + * @return This function returns 0. + */ +PMIC_STATUS mc13783_adc_read_ts(t_touch_screen * ts_value, int wait_tsi) +{ + t_adc_param param; + pr_debug("mc13783_adc : mc13783_adc_read_ts\n"); + if (suspend_flag == 1) { + return -EBUSY; + } + if (wait_ts) { + pr_debug("mc13783_adc : error TS busy \n"); + return PMIC_ERROR; + } + mc13783_adc_init_param(¶m); + param.wait_tsi = wait_tsi; + param.read_ts = true; + mc13783_adc_convert(¶m); + /* check if x-y is ok */ + if ((param.ts_value.x_position1 < TS_X_MAX) && + (param.ts_value.x_position1 >= TS_X_MIN) && + (param.ts_value.y_position1 < TS_Y_MAX) && + (param.ts_value.y_position1 >= TS_Y_MIN) && + (param.ts_value.x_position2 < TS_X_MAX) && + (param.ts_value.x_position2 >= TS_X_MIN) && + (param.ts_value.y_position2 < TS_Y_MAX) && + (param.ts_value.y_position2 >= TS_Y_MIN) && + (param.ts_value.x_position3 < TS_X_MAX) && + (param.ts_value.x_position3 >= TS_X_MIN) && + (param.ts_value.y_position3 < TS_Y_MAX) && + (param.ts_value.y_position3 >= TS_Y_MIN)) { + ts_value->x_position = param.ts_value.x_position; + ts_value->x_position1 = param.ts_value.x_position1; + ts_value->x_position2 = param.ts_value.x_position2; + ts_value->x_position3 = param.ts_value.x_position3; + ts_value->y_position = param.ts_value.y_position; + ts_value->y_position1 = param.ts_value.y_position1; + ts_value->y_position2 = param.ts_value.y_position2; + ts_value->y_position3 = param.ts_value.y_position3; + ts_value->contact_resistance = + param.ts_value.contact_resistance + 1; + + } else { + ts_value->x_position = 0; + ts_value->y_position = 0; + ts_value->contact_resistance = 0; + + } + return PMIC_SUCCESS; +} + +/*! + * This function starts a Battery Current mode conversion. + * + * @param mode Conversion mode. + * @param result Battery Current measurement result. + * if \a mode = ADC_8CHAN_1X, the result is \n + * result[0] = (BATTP - BATT_I) \n + * if \a mode = ADC_1CHAN_8X, the result is \n + * result[0] = BATTP \n + * result[1] = BATT_I \n + * result[2] = BATTP \n + * result[3] = BATT_I \n + * result[4] = BATTP \n + * result[5] = BATT_I \n + * result[6] = BATTP \n + * result[7] = BATT_I + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_adc_get_battery_current(t_conversion_mode mode, + unsigned short *result) +{ + PMIC_STATUS ret; + t_channel channel; + if (suspend_flag == 1) { + return -EBUSY; + } + channel = BATTERY_CURRENT; + if (mode == ADC_8CHAN_1X) { + ret = pmic_adc_convert(channel, result); + } else { + ret = pmic_adc_convert_8x(channel, result); + } + return ret; +} + +/*! + * This function request a ADC. + * + * @return This function returns index of ADC to be used (0 or 1) if successful. + return -1 if error. + */ +int mc13783_adc_request(void) +{ + int adc_index = -1; + if (((adc_dev[0] == ADC_USED) && (adc_dev[1] == ADC_USED))) { + /* all ADC is used wait... */ + wait_event(queue_adc_busy, 0); + } else if (adc_dev[0] == ADC_FREE) { + adc_dev[0] = ADC_USED; + adc_index = 0; + } else if (adc_dev[1] == ADC_FREE) { + adc_dev[1] = ADC_USED; + adc_index = 1; + } + pr_debug("mc13783_adc : request ADC %d\n", adc_index); + return adc_index; +} + +/*! + * This function release an ADC. + * + * @param adc_index index of ADC to be released. + * + * @return This function returns 0 if successful. + */ +int mc13783_adc_release(int adc_index) +{ + while (suspend_flag == 1) { + swait++; + /* Block if the device is suspended */ + if (wait_event_interruptible(suspendq, (suspend_flag == 0))) { + return -ERESTARTSYS; + } + } + + pr_debug("mc13783_adc : release ADC %d\n", adc_index); + if ((adc_dev[adc_index] == ADC_MONITORING) || + (adc_dev[adc_index] == ADC_USED)) { + adc_dev[adc_index] = ADC_FREE; + wake_up(&queue_adc_busy); + return 0; + } + return -1; +} + +/*! + * This function initializes monitoring structure. + * + * @param monitor Structure to be initialized. + * + * @return This function returns 0 if successful. + */ +int mc13783_adc_init_monitor_param(t_monitoring_param * monitor) +{ + pr_debug("mc13783_adc : init monitor\n"); + monitor->delay = 0; + monitor->conv_delay = false; + monitor->channel = BATTERY_VOLTAGE; + monitor->read_mode = 0; + monitor->comp_low = 0; + monitor->comp_high = 0; + monitor->group = 0; + monitor->check_mode = CHECK_LOW_OR_HIGH; + monitor->callback = NULL; + return 0; +} + +/*! + * This function actives the comparator. When comparator is active and ADC + * is enabled, the 8th converted value will be digitally compared against the + * window defined by WLOW and WHIGH registers. + * + * @param low Comparison window low threshold (WLOW). + * @param high Comparison window high threshold (WHIGH). + * @param channel The channel to be sampled + * @param callback Callback function to be called when the converted + * value is beyond the comparison window. The callback + * function will pass a parameter of type + * \b t_comp_expection to indicate the reason of + * comparator exception. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_adc_active_comparator(unsigned char low, + unsigned char high, + t_channel channel, + t_comparator_cb callback) +{ + bool use_bis = false; + unsigned int adc_0_reg = 0, adc_1_reg = 0, adc_3_reg = 0; + t_monitoring_param monitoring; + + if (suspend_flag == 1) { + return -EBUSY; + } + if (monitor_en) { + pr_debug("mc13783_adc : monitoring already configured\n"); + return PMIC_ERROR; + } + monitor_en = true; + mc13783_adc_init_monitor_param(&monitoring); + monitoring.comp_low = low; + monitoring.comp_high = high; + monitoring.channel = channel; + monitoring.callback = (void *)callback; + + use_bis = mc13783_adc_request(); + if (use_bis < 0) { + pr_debug("mc13783_adc : request error\n"); + return PMIC_ERROR; + } + monitor_adc = use_bis; + + adc_0_reg = 0; + + /* TO DO ADOUT CONFIGURE */ + adc_0_reg = monitoring.read_mode & ADC_MODE_MASK; + if (use_bis) { + /* add adc bis */ + adc_0_reg |= ADC_BIS; + } + adc_0_reg |= ADC_WCOMP; + + /* CONFIGURE ADC REG 1 */ + adc_1_reg = 0; + adc_1_reg |= ADC_EN; + if (monitoring.conv_delay) { + adc_1_reg |= ADC_ATO; + } + if (monitoring.group) { + adc_1_reg |= ADC_ADSEL; + } + adc_1_reg |= (monitoring.channel << ADC_CH_0_POS) & ADC_CH_0_MASK; + adc_1_reg |= (monitoring.delay << ADC_DELAY_POS) & ADC_DELAY_MASK; + if (use_bis) { + adc_1_reg |= ADC_BIS; + } + + adc_3_reg |= (monitoring.comp_high << ADC_WCOMP_H_POS) & + ADC_WCOMP_H_MASK; + adc_3_reg |= (monitoring.comp_low << ADC_WCOMP_L_POS) & + ADC_WCOMP_L_MASK; + if (use_bis) { + adc_3_reg |= ADC_BIS; + } + + wcomp_mode = monitoring.check_mode; + /* call back to be called when event is detected. */ + monitoring_cb = monitoring.callback; + + CHECK_ERROR(pmic_write_reg(REG_ADC_0, adc_0_reg, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(REG_ADC_1, adc_1_reg, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(REG_ADC_3, adc_3_reg, PMIC_ALL_BITS)); + return PMIC_SUCCESS; +} + +/*! + * This function deactivates the comparator. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_adc_deactive_comparator(void) +{ + unsigned int reg_value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + if (!monitor_en) { + pr_debug("mc13783_adc : adc monitoring free\n"); + return PMIC_ERROR; + } + + if (monitor_en) { + reg_value = ADC_BIS; + } + + /* clear all reg value */ + CHECK_ERROR(pmic_write_reg(REG_ADC_0, reg_value, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(REG_ADC_1, reg_value, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(REG_ADC_3, reg_value, PMIC_ALL_BITS)); + + reg_value = 0; + + if (monitor_adc) { + CHECK_ERROR(pmic_write_reg + (REG_ADC_4, reg_value, PMIC_ALL_BITS)); + } else { + CHECK_ERROR(pmic_write_reg + (REG_ADC_2, reg_value, PMIC_ALL_BITS)); + } + + mc13783_adc_release(monitor_adc); + monitor_en = false; + return PMIC_SUCCESS; +} + +/*! + * This function implements IOCTL controls on a MC13783 ADC device. + * + * @param inode pointer on the node + * @param file pointer on the file + * @param cmd the command + * @param arg the parameter + * @return This function returns 0 if successful. + */ +static int pmic_adc_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + t_adc_convert_param *convert_param; + t_touch_mode touch_mode; + t_touch_screen touch_sample; + unsigned short b_current; + t_adc_comp_param *comp_param; + if ((_IOC_TYPE(cmd) != 'p') && (_IOC_TYPE(cmd) != 'D')) + return -ENOTTY; + + while (suspend_flag == 1) { + swait++; + /* Block if the device is suspended */ + if (wait_event_interruptible(suspendq, (suspend_flag == 0))) { + return -ERESTARTSYS; + } + } + + switch (cmd) { + case PMIC_ADC_INIT: + pr_debug("init adc\n"); + CHECK_ERROR(pmic_adc_init()); + break; + + case PMIC_ADC_DEINIT: + pr_debug("deinit adc\n"); + CHECK_ERROR(pmic_adc_deinit()); + break; + + case PMIC_ADC_CONVERT: + if ((convert_param = kmalloc(sizeof(t_adc_convert_param), + GFP_KERNEL)) == NULL) { + return -ENOMEM; + } + if (copy_from_user(convert_param, (t_adc_convert_param *) arg, + sizeof(t_adc_convert_param))) { + kfree(convert_param); + return -EFAULT; + } + CHECK_ERROR_KFREE(pmic_adc_convert(convert_param->channel, + convert_param->result), + (kfree(convert_param))); + + if (copy_to_user((t_adc_convert_param *) arg, convert_param, + sizeof(t_adc_convert_param))) { + kfree(convert_param); + return -EFAULT; + } + kfree(convert_param); + break; + + case PMIC_ADC_CONVERT_8X: + if ((convert_param = kmalloc(sizeof(t_adc_convert_param), + GFP_KERNEL)) == NULL) { + return -ENOMEM; + } + if (copy_from_user(convert_param, (t_adc_convert_param *) arg, + sizeof(t_adc_convert_param))) { + kfree(convert_param); + return -EFAULT; + } + CHECK_ERROR_KFREE(pmic_adc_convert_8x(convert_param->channel, + convert_param->result), + (kfree(convert_param))); + + if (copy_to_user((t_adc_convert_param *) arg, convert_param, + sizeof(t_adc_convert_param))) { + kfree(convert_param); + return -EFAULT; + } + kfree(convert_param); + break; + + case PMIC_ADC_CONVERT_MULTICHANNEL: + if ((convert_param = kmalloc(sizeof(t_adc_convert_param), + GFP_KERNEL)) == NULL) { + return -ENOMEM; + } + if (copy_from_user(convert_param, (t_adc_convert_param *) arg, + sizeof(t_adc_convert_param))) { + kfree(convert_param); + return -EFAULT; + } + + CHECK_ERROR_KFREE(pmic_adc_convert_multichnnel + (convert_param->channel, + convert_param->result), + (kfree(convert_param))); + + if (copy_to_user((t_adc_convert_param *) arg, convert_param, + sizeof(t_adc_convert_param))) { + kfree(convert_param); + return -EFAULT; + } + kfree(convert_param); + break; + + case PMIC_ADC_SET_TOUCH_MODE: + CHECK_ERROR(pmic_adc_set_touch_mode((t_touch_mode) arg)); + break; + + case PMIC_ADC_GET_TOUCH_MODE: + CHECK_ERROR(pmic_adc_get_touch_mode(&touch_mode)); + if (copy_to_user((t_touch_mode *) arg, &touch_mode, + sizeof(t_touch_mode))) { + return -EFAULT; + } + break; + + case PMIC_ADC_GET_TOUCH_SAMPLE: + pr_debug("pmic_adc_ioctl: " "PMIC_ADC_GET_TOUCH_SAMPLE\n"); + CHECK_ERROR(pmic_adc_get_touch_sample(&touch_sample, 1)); + if (copy_to_user((t_touch_screen *) arg, &touch_sample, + sizeof(t_touch_screen))) { + return -EFAULT; + } + break; + + case PMIC_ADC_GET_BATTERY_CURRENT: + CHECK_ERROR(pmic_adc_get_battery_current(ADC_8CHAN_1X, + &b_current)); + if (copy_to_user((unsigned short *)arg, &b_current, + sizeof(unsigned short))) { + + return -EFAULT; + } + break; + + case PMIC_ADC_ACTIVATE_COMPARATOR: + if ((comp_param = kmalloc(sizeof(t_adc_comp_param), GFP_KERNEL)) + == NULL) { + return -ENOMEM; + } + if (copy_from_user(comp_param, (t_adc_comp_param *) arg, + sizeof(t_adc_comp_param))) { + kfree(comp_param); + return -EFAULT; + } + CHECK_ERROR_KFREE(pmic_adc_active_comparator(comp_param->wlow, + comp_param->whigh, + comp_param-> + channel, + comp_param-> + callback), + (kfree(comp_param))); + break; + + case PMIC_ADC_DEACTIVE_COMPARATOR: + CHECK_ERROR(pmic_adc_deactive_comparator()); + break; + + default: + pr_debug("pmic_adc_ioctl: unsupported ioctl command 0x%x\n", + cmd); + return -EINVAL; + } + return 0; +} + +static struct file_operations mc13783_adc_fops = { + .owner = THIS_MODULE, + .ioctl = pmic_adc_ioctl, + .open = pmic_adc_open, + .release = pmic_adc_free, +}; + +static int pmic_adc_module_probe(struct platform_device *pdev) +{ + int ret = 0; + struct class_device *temp_class; + + pmic_adc_major = register_chrdev(0, "pmic_adc", &mc13783_adc_fops); + + if (pmic_adc_major < 0) { + pr_debug(KERN_ERR "Unable to get a major for pmic_adc\n"); + return pmic_adc_major; + } + init_waitqueue_head(&suspendq); + + pmic_adc_class = class_create(THIS_MODULE, "pmic_adc"); + if (IS_ERR(pmic_adc_class)) { + pr_debug(KERN_ERR "Error creating pmic_adc class.\n"); + ret = PTR_ERR(pmic_adc_class); + goto err_out1; + } + + temp_class = class_device_create(pmic_adc_class, NULL, + MKDEV(pmic_adc_major, 0), + NULL, "pmic_adc"); + if (IS_ERR(temp_class)) { + pr_debug(KERN_ERR "Error creating pmic_adc class device.\n"); + ret = PTR_ERR(temp_class); + goto err_out2; + } + + ret = pmic_adc_init(); + if (ret != PMIC_SUCCESS) { + pr_debug(KERN_ERR "Error in pmic_adc_init.\n"); + goto err_out4; + } + + pr_debug(KERN_INFO "PMIC ADC successfully probed\n"); + return ret; + + err_out4: + class_device_destroy(pmic_adc_class, MKDEV(pmic_adc_major, 0)); + err_out2: + class_destroy(pmic_adc_class); + err_out1: + unregister_chrdev(pmic_adc_major, "pmic_adc"); + return ret; +} + +static int pmic_adc_module_remove(struct platform_device *pdev) +{ + pmic_adc_deinit(); + class_device_destroy(pmic_adc_class, MKDEV(pmic_adc_major, 0)); + class_destroy(pmic_adc_class); + unregister_chrdev(pmic_adc_major, "pmic_adc"); + pr_debug(KERN_INFO "PMIC ADC successfully removed\n"); + return 0; +} + +static struct platform_driver pmic_adc_driver_ldm = { + .driver = { + .name = "pmic_adc", + }, + .suspend = pmic_adc_suspend, + .resume = pmic_adc_resume, + .probe = pmic_adc_module_probe, + .remove = pmic_adc_module_remove, +}; + +/* + * Initialization and Exit + */ +static int __init pmic_adc_module_init(void) +{ + pr_debug("PMIC ADC driver loading...\n"); + return platform_driver_register(&pmic_adc_driver_ldm); +} + +static void __exit pmic_adc_module_exit(void) +{ + platform_driver_unregister(&pmic_adc_driver_ldm); + pr_debug("PMIC ADC driver successfully unloaded\n"); +} + +/* + * Module entry points + */ + +module_init(pmic_adc_module_init); +module_exit(pmic_adc_module_exit); + +MODULE_DESCRIPTION("PMIC ADC device driver"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/pmic/mc13783/pmic_adc_defs.h b/drivers/mxc/pmic/mc13783/pmic_adc_defs.h new file mode 100644 index 000000000000..c2a282c829ca --- /dev/null +++ b/drivers/mxc/pmic/mc13783/pmic_adc_defs.h @@ -0,0 +1,321 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mc13783/pmic_adc_defs.h + * @brief This header contains all defines for PMIC(mc13783) ADC driver. + * + * @ingroup PMIC_ADC + */ + +#ifndef __MC13783_ADC__DEFS_H__ +#define __MC13783_ADC__DEFS_H__ + +#define MC13783_ADC_DEVICE "/dev/mc13783_adc" + +#define DEF_ADC_0 0x008000 +#define DEF_ADC_3 0x000080 + +#define ADC_NB_AVAILABLE 2 + +#define MAX_CHANNEL 7 + +/* + * Maximun allowed variation in the three X/Y co-ordinates acquired from + * touch-screen + */ +#define DELTA_Y_MAX 50 +#define DELTA_X_MAX 50 + +/* Upon clearing the filter, this is the delay in restarting the filter */ +#define FILTER_MIN_DELAY 4 + +/* Length of X and Y Touch screen filters */ +#define FILTLEN 3 + +#define TS_X_MAX 1000 +#define TS_Y_MAX 1000 + +#define TS_X_MIN 80 +#define TS_Y_MIN 80 + +#define MC13783_ADC0_TS_M_LSH 14 +#define MC13783_ADC0_TS_M_WID 3 +/* + * ADC 0 + */ +#define ADC_WAIT_TSI_0 0x001C00 + +/* + * ADC 1 + */ + +#define ADC_EN 0x000001 +#define ADC_SGL_CH 0x000002 +#define ADC_ADSEL 0x000008 +#define ADC_CH_0_POS 5 +#define ADC_CH_0_MASK 0x0000E0 +#define ADC_CH_1_POS 8 +#define ADC_CH_1_MASK 0x000700 +#define ADC_DELAY_POS 11 +#define ADC_DELAY_MASK 0x07F800 +#define ADC_ATO 0x080000 +#define ASC_ADC 0x100000 +#define ADC_WAIT_TSI_1 0x300001 +#define ADC_CHRGRAW_D5 0x008000 + +/* + * ADC 2 - 4 + */ +#define ADD1_RESULT_MASK 0x00000FFC +#define ADD2_RESULT_MASK 0x00FFC000 +#define ADC_TS_MASK 0x00FFCFFC + +/* + * ADC 3 + */ +#define ADC_INC 0x030000 +#define ADC_BIS 0x800000 + +/* + * ADC 3 + */ +#define ADC_NO_ADTRIG 0x200000 +#define ADC_WCOMP 0x040000 +#define ADC_WCOMP_H_POS 0 +#define ADC_WCOMP_L_POS 9 +#define ADC_WCOMP_H_MASK 0x00003F +#define ADC_WCOMP_L_MASK 0x007E00 + +#define ADC_MODE_MASK 0x00003F + +/* + * Interrupt Status 0 + */ +#define ADC_INT_BISDONEI 0x02 + +/*! + * Define state mode of ADC. + */ +typedef enum adc_state { + /*! + * Free. + */ + ADC_FREE, + /*! + * Used. + */ + ADC_USED, + /*! + * Monitoring + */ + ADC_MONITORING, +} t_adc_state; + +/*! + * This enumeration, is used to configure the mode of ADC. + */ +typedef enum reading_mode { + /*! + * Enables lithium cell reading + */ + M_LITHIUM_CELL = 0x000001, + /*! + * Enables charge current reading + */ + M_CHARGE_CURRENT = 0x000002, + /*! + * Enables battery current reading + */ + M_BATTERY_CURRENT = 0x000004, + /*! + * Enables thermistor reading + */ + M_THERMISTOR = 0x000008, + /*! + * Enables die temperature reading + */ + M_DIE_TEMPERATURE = 0x000010, + /*! + * Enables UID reading + */ + M_UID = 0x000020, +} t_reading_mode; + +/*! + * This enumeration, is used to configure the monitoring mode. + */ +typedef enum check_mode { + /*! + * Comparator low level + */ + CHECK_LOW, + /*! + * Comparator high level + */ + CHECK_HIGH, + /*! + * Comparator low or high level + */ + CHECK_LOW_OR_HIGH, +} t_check_mode; + +/*! + * This structure is used to configure and report adc value. + */ +typedef struct { + /*! + * Delay before first conversion + */ + unsigned int delay; + /*! + * sets the ATX bit for delay on all conversion + */ + bool conv_delay; + /*! + * Sets the single channel mode + */ + bool single_channel; + /*! + * Selects the set of inputs + */ + bool group; + /*! + * Channel selection 1 + */ + t_channel channel_0; + /*! + * Channel selection 2 + */ + t_channel channel_1; + /*! + * Used to configure ADC mode with t_reading_mode + */ + t_reading_mode read_mode; + /*! + * Sets the Touch screen mode + */ + bool read_ts; + /*! + * Wait TSI event before touch screen reading + */ + bool wait_tsi; + /*! + * Sets CHRGRAW scaling to divide by 5 + * Only supported on 2.0 and higher + */ + bool chrgraw_devide_5; + /*! + * Return ADC values + */ + unsigned int value[8]; + /*! + * Return touch screen values + */ + t_touch_screen ts_value; +} t_adc_param; + +/*! + * This structure is used to configure the monitoring mode of ADC. + */ +typedef struct { + /*! + * Delay before first conversion + */ + unsigned int delay; + /*! + * sets the ATX bit for delay on all conversion + */ + bool conv_delay; + /*! + * Channel selection 1 + */ + t_channel channel; + /*! + * Selects the set of inputs + */ + bool group; + /*! + * Used to configure ADC mode with t_reading_mode + */ + unsigned int read_mode; + /*! + * Comparator low level in WCOMP mode + */ + unsigned int comp_low; + /*! + * Comparator high level in WCOMP mode + */ + unsigned int comp_high; + /*! + * Sets type of monitoring (low, high or both) + */ + t_check_mode check_mode; + /*! + * Callback to be launched when event is detected + */ + void (*callback) (void); +} t_monitoring_param; + +/*! + * This function performs filtering and rejection of excessive noise prone + * samples. + * + * @param ts_curr Touch screen value + * + * @return This function returns 0 on success, -1 otherwise. + */ +static int pmic_adc_filter(t_touch_screen * ts_curr); + +/*! + * This function request a ADC. + * + * @return This function returns index of ADC to be used (0 or 1) if successful. + return -1 if error. + */ +int mc13783_adc_request(void); + +/*! + * This function is used to update buffer of touch screen value in read mode. + */ +void update_buffer(void); + +/*! + * This function release an ADC. + * + * @param adc_index index of ADC to be released. + * + * @return This function returns 0 if successful. + */ +int mc13783_adc_release(int adc_index); + +/*! + * This function select the required read_mode for a specific channel. + * + * @param channel The channel to be sampled + * + * @return This function returns the requires read_mode + */ +t_reading_mode mc13783_set_read_mode(t_channel channel); + +/*! + * This function read the touch screen value. + * + * @param touch_sample return value of touch screen + * @param wait_tsi if true, this function is synchronous (wait in TSI event). + * + * @return This function returns 0. + */ +PMIC_STATUS mc13783_adc_read_ts(t_touch_screen * touch_sample, int wait_tsi); + +#endif /* __MC13783_ADC__DEFS_H__ */ diff --git a/drivers/mxc/pmic/mc13783/pmic_audio.c b/drivers/mxc/pmic/mc13783/pmic_audio.c new file mode 100644 index 000000000000..f73ffb47cbf7 --- /dev/null +++ b/drivers/mxc/pmic/mc13783/pmic_audio.c @@ -0,0 +1,5819 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mc13783/pmic_audio.c + * @brief Implementation of the PMIC(mc13783) Audio driver APIs. + * + * The PMIC Audio driver and this API were developed to support the + * audio playback, recording, and mixing capabilities of the power + * management ICs that are available from Freescale Semiconductor, Inc. + * + * The following operating modes are supported: + * + * @verbatim + Operating Mode mc13783 + ---------------------------- ------- + Stereo DAC Playback Yes + Stereo DAC Input Mixing Yes + Voice CODEC Playback Yes + Voice CODEC Input Mixing Yes + Voice CODEC Mono Recording Yes + Voice CODEC Stereo Recording Yes + Microphone Bias Control Yes + Output Amplifier Control Yes + Output Mixing Control Yes + Input Amplifier Control Yes + Master/Slave Mode Select Yes + Anti Pop Bias Circuit Control Yes + @endverbatim + * + * Note that the Voice CODEC may also be referred to as the Telephone + * CODEC in the PMIC DTS documentation. Also note that, while the power + * management ICs do provide similar audio capabilities, each PMIC may + * support additional configuration settings and features. Therefore, it + * is highly recommended that the appropriate power management IC DTS + * documents be used in conjunction with this API interface. + * + * @ingroup PMIC_AUDIO + */ + +#include <linux/module.h> +#include <linux/interrupt.h> /* For tasklet interface. */ +#include <linux/platform_device.h> /* For kernel module interface. */ +#include <linux/init.h> +#include <linux/spinlock.h> /* For spinlock interface. */ +#include <asm/arch/pmic_audio.h> /* For PMIC Audio driver interface. */ +#include <asm/arch/pmic_adc.h> /* For PMIC ADC driver interface. */ + +#include "../core/pmic_config.h" + +/* + * mc13783 PMIC Audio API + */ + +/* EXPORTED FUNCTIONS */ +EXPORT_SYMBOL(MIN_STDAC_SAMPLING_RATE_HZ); +EXPORT_SYMBOL(MAX_STDAC_SAMPLING_RATE_HZ); +EXPORT_SYMBOL(pmic_audio_open); +EXPORT_SYMBOL(pmic_audio_close); +EXPORT_SYMBOL(pmic_audio_set_protocol); +EXPORT_SYMBOL(pmic_audio_get_protocol); +EXPORT_SYMBOL(pmic_audio_enable); +EXPORT_SYMBOL(pmic_audio_disable); +EXPORT_SYMBOL(pmic_audio_reset); +EXPORT_SYMBOL(pmic_audio_reset_all); +EXPORT_SYMBOL(pmic_audio_set_callback); +EXPORT_SYMBOL(pmic_audio_clear_callback); +EXPORT_SYMBOL(pmic_audio_get_callback); +EXPORT_SYMBOL(pmic_audio_antipop_enable); +EXPORT_SYMBOL(pmic_audio_antipop_disable); +EXPORT_SYMBOL(pmic_audio_digital_filter_reset); +EXPORT_SYMBOL(pmic_audio_vcodec_set_clock); +EXPORT_SYMBOL(pmic_audio_vcodec_get_clock); +EXPORT_SYMBOL(pmic_audio_vcodec_set_rxtx_timeslot); +EXPORT_SYMBOL(pmic_audio_vcodec_get_rxtx_timeslot); +EXPORT_SYMBOL(pmic_audio_vcodec_set_secondary_txslot); +EXPORT_SYMBOL(pmic_audio_vcodec_get_secondary_txslot); +EXPORT_SYMBOL(pmic_audio_vcodec_set_config); +EXPORT_SYMBOL(pmic_audio_vcodec_clear_config); +EXPORT_SYMBOL(pmic_audio_vcodec_get_config); +EXPORT_SYMBOL(pmic_audio_vcodec_enable_bypass); +EXPORT_SYMBOL(pmic_audio_vcodec_disable_bypass); +EXPORT_SYMBOL(pmic_audio_stdac_set_clock); +EXPORT_SYMBOL(pmic_audio_stdac_get_clock); +EXPORT_SYMBOL(pmic_audio_stdac_set_rxtx_timeslot); +EXPORT_SYMBOL(pmic_audio_stdac_get_rxtx_timeslot); +EXPORT_SYMBOL(pmic_audio_stdac_set_config); +EXPORT_SYMBOL(pmic_audio_stdac_clear_config); +EXPORT_SYMBOL(pmic_audio_stdac_get_config); +EXPORT_SYMBOL(pmic_audio_input_set_config); +EXPORT_SYMBOL(pmic_audio_input_clear_config); +EXPORT_SYMBOL(pmic_audio_input_get_config); +EXPORT_SYMBOL(pmic_audio_vcodec_set_mic); +EXPORT_SYMBOL(pmic_audio_vcodec_get_mic); +EXPORT_SYMBOL(pmic_audio_vcodec_set_mic_on_off); +EXPORT_SYMBOL(pmic_audio_vcodec_get_mic_on_off); +EXPORT_SYMBOL(pmic_audio_vcodec_set_record_gain); +EXPORT_SYMBOL(pmic_audio_vcodec_get_record_gain); +EXPORT_SYMBOL(pmic_audio_vcodec_enable_micbias); +EXPORT_SYMBOL(pmic_audio_vcodec_disable_micbias); +EXPORT_SYMBOL(pmic_audio_vcodec_enable_mixer); +EXPORT_SYMBOL(pmic_audio_vcodec_disable_mixer); +EXPORT_SYMBOL(pmic_audio_stdac_enable_mixer); +EXPORT_SYMBOL(pmic_audio_stdac_disable_mixer); +EXPORT_SYMBOL(pmic_audio_output_set_port); +EXPORT_SYMBOL(pmic_audio_output_get_port); +EXPORT_SYMBOL(pmic_audio_output_set_stereo_in_gain); +EXPORT_SYMBOL(pmic_audio_output_get_stereo_in_gain); +EXPORT_SYMBOL(pmic_audio_output_set_pgaGain); +EXPORT_SYMBOL(pmic_audio_output_get_pgaGain); +EXPORT_SYMBOL(pmic_audio_output_enable_mixer); +EXPORT_SYMBOL(pmic_audio_output_disable_mixer); +EXPORT_SYMBOL(pmic_audio_output_set_balance); +EXPORT_SYMBOL(pmic_audio_output_get_balance); +EXPORT_SYMBOL(pmic_audio_output_enable_mono_adder); +EXPORT_SYMBOL(pmic_audio_output_disable_mono_adder); +EXPORT_SYMBOL(pmic_audio_output_set_mono_adder_gain); +EXPORT_SYMBOL(pmic_audio_output_get_mono_adder_gain); +EXPORT_SYMBOL(pmic_audio_output_set_config); +EXPORT_SYMBOL(pmic_audio_output_clear_config); +EXPORT_SYMBOL(pmic_audio_output_get_config); +EXPORT_SYMBOL(pmic_audio_output_enable_phantom_ground); +EXPORT_SYMBOL(pmic_audio_output_disable_phantom_ground); +EXPORT_SYMBOL(pmic_audio_set_autodetect); + +#ifdef DEBUG_AUDIO +EXPORT_SYMBOL(pmic_audio_dump_registers); +#endif /* DEBUG_AUDIO */ +/*! + * Define the minimum sampling rate (in Hz) that is supported by the + * Stereo DAC. + */ +const unsigned MIN_STDAC_SAMPLING_RATE_HZ = 8000; + +/*! + * Define the maximum sampling rate (in Hz) that is supported by the + * Stereo DAC. + */ +const unsigned MAX_STDAC_SAMPLING_RATE_HZ = 96000; + +/*! @def SET_BITS + * Set a register field to a given value. + */ +#define SET_BITS(reg, field, value) (((value) << reg.field.offset) & \ + reg.field.mask) +/*! @def GET_BITS + * Get the current value of a given register field. + */ +#define GET_BITS(reg, field, value) (((value) & reg.field.mask) >> \ + reg.field.offset) + +/*! + * @brief Define the possible states for a device handle. + * + * This enumeration is used to track the current state of each device handle. + */ +typedef enum { + HANDLE_FREE, /*!< Handle is available for use. */ + HANDLE_IN_USE /*!< Handle is currently in use. */ +} HANDLE_STATE; + +/*! + * @brief Identifies the hardware interrupt source. + * + * This enumeration identifies which of the possible hardware interrupt + * sources actually caused the current interrupt handler to be called. + */ +typedef enum { + CORE_EVENT_MC2BI, /*!< Microphone Bias 2 detect. */ + CORE_EVENT_HSDETI, /*!< Detect Headset attach */ + CORE_EVENT_HSLI, /*!< Detect Stereo Headset */ + CORE_EVENT_ALSPTHI, /*!< Detect Thermal shutdown of ALSP */ + CORE_EVENT_AHSSHORTI /*!< Detect Short circuit on AHS outputs */ +} PMIC_CORE_EVENT; + +/*! + * @brief This structure is used to track the state of a microphone input. + */ +typedef struct { + PMIC_AUDIO_INPUT_PORT mic; /*!< Microphone input port. */ + PMIC_AUDIO_INPUT_MIC_STATE micOnOff; /*!< Microphone On/Off state. */ + PMIC_AUDIO_MIC_AMP_MODE ampMode; /*!< Input amplifier mode. */ + PMIC_AUDIO_MIC_GAIN gain; /*!< Input amplifier gain level. */ +} PMIC_MICROPHONE_STATE; + +/*! + * @brief Tracks whether a headset is currently attached or not. + */ +typedef enum { + NO_HEADSET, /*!< No headset currently attached. */ + HEADSET_ON /*!< Headset has been attached. */ +} HEADSET_STATUS; + +/*! + * @brief mc13783 only enum that indicates the path to output taken + * by the voice codec output + */ +typedef enum { + VCODEC_DIRECT_OUT, /*!< Vcodec signal out direct */ + VCODEC_MIXER_OUT /*!< Output via the mixer */ +} PMIC_AUDIO_VCODEC_OUTPUT_PATH; + +/*! + * @brief This structure is used to define a specific hardware register field. + * + * All hardware register fields are defined using an offset to the LSB + * and a mask. The offset is used to right shift a register value before + * applying the mask to actually obtain the value of the field. + */ +typedef struct { + const unsigned char offset; /*!< Offset of LSB of register field. */ + const unsigned int mask; /*!< Mask value used to isolate register field. */ +} REGFIELD; + +/*! + * @brief This structure lists all fields of the AUD_CODEC hardware register. + */ +typedef struct { + REGFIELD CDCSSISEL; /*!< codec SSI bus select */ + REGFIELD CDCCLKSEL; /*!< Codec clock input select */ + REGFIELD CDCSM; /*!< Codec slave / master select */ + REGFIELD CDCBCLINV; /*!< Codec bitclock inversion */ + REGFIELD CDCFSINV; /*!< Codec framesync inversion */ + REGFIELD CDCFS; /*!< Bus protocol selection - 2 bits */ + REGFIELD CDCCLK; /*!< Codec clock setting - 3 bits */ + REGFIELD CDCFS8K16K; /*!< Codec framesync select */ + REGFIELD CDCEN; /*!< Codec enable */ + REGFIELD CDCCLKEN; /*!< Codec clocking enable */ + REGFIELD CDCTS; /*!< Codec SSI tristate */ + REGFIELD CDCDITH; /*!< Codec dithering */ + REGFIELD CDCRESET; /*!< Codec filter reset */ + REGFIELD CDCBYP; /*!< Codec bypass */ + REGFIELD CDCALM; /*!< Codec analog loopback */ + REGFIELD CDCDLM; /*!< Codec digital loopback */ + REGFIELD AUDIHPF; /*!< Transmit high pass filter enable */ + REGFIELD AUDOHPF; /*!< Receive high pass filter enable */ +} REGISTER_AUD_CODEC; + +/*! + * @brief This variable is used to access the AUD_CODEC hardware register. + * + * This variable defines how to access all of the fields within the + * AUD_CODEC hardware register. The initial values consist of the offset + * and mask values needed to access each of the register fields. + */ +static const REGISTER_AUD_CODEC regAUD_CODEC = { + {0, 0x000001}, /* CDCSSISEL */ + {1, 0x000002}, /* CDCCLKSEL */ + {2, 0x000004}, /* CDCSM */ + {3, 0x000008}, /* CDCBCLINV */ + {4, 0x000010}, /* CDCFSINV */ + {5, 0x000060}, /* CDCFS */ + {7, 0x000380}, /* CDCCLK */ + {10, 0x000400}, /* CDCFS8K16K */ + {11, 0x000800}, /* CDCEN */ + {12, 0x001000}, /* CDCCLKEN */ + {13, 0x002000}, /* CDCTS */ + {14, 0x004000}, /* CDCDITH */ + {15, 0x008000}, /* CDCRESET */ + {16, 0x010000}, /* CDCBYP */ + {17, 0x020000}, /* CDCALM */ + {18, 0x040000}, /* CDCDLM */ + {19, 0x080000}, /* AUDIHPF */ + {20, 0x100000} /* AUDOHPF */ + /* Unused */ + /* Unused */ + /* Unused */ + +}; + +/*! + * @brief This structure lists all fields of the ST_DAC hardware register. + */ + /* VVV */ +typedef struct { + REGFIELD STDCSSISEL; /*!< Stereo DAC SSI bus select */ + REGFIELD STDCCLKSEL; /*!< Stereo DAC clock input select */ + REGFIELD STDCSM; /*!< Stereo DAC slave / master select */ + REGFIELD STDCBCLINV; /*!< Stereo DAC bitclock inversion */ + REGFIELD STDCFSINV; /*!< Stereo DAC framesync inversion */ + REGFIELD STDCFS; /*!< Bus protocol selection - 2 bits */ + REGFIELD STDCCLK; /*!< Stereo DAC clock setting - 3 bits */ + REGFIELD STDCFSDLYB; /*!< Stereo DAC framesync delay bar */ + REGFIELD STDCEN; /*!< Stereo DAC enable */ + REGFIELD STDCCLKEN; /*!< Stereo DAC clocking enable */ + REGFIELD STDCRESET; /*!< Stereo DAC filter reset */ + REGFIELD SPDIF; /*!< Stereo DAC SSI SPDIF mode. Mode no longer available. */ + REGFIELD SR; /*!< Stereo DAC sample rate - 4 bits */ +} REGISTER_ST_DAC; + +/*! + * @brief This variable is used to access the ST_DAC hardware register. + * + * This variable defines how to access all of the fields within the + * ST_DAC hardware register. The initial values consist of the offset + * and mask values needed to access each of the register fields. + */ +static const REGISTER_ST_DAC regST_DAC = { + {0, 0x000001}, /* STDCSSISEL */ + {1, 0x000002}, /* STDCCLKSEL */ + {2, 0x000004}, /* STDCSM */ + {3, 0x000008}, /* STDCBCLINV */ + {4, 0x000010}, /* STDCFSINV */ + {5, 0x000060}, /* STDCFS */ + {7, 0x000380}, /* STDCCLK */ + {10, 0x000400}, /* STDCFSDLYB */ + {11, 0x000800}, /* STDCEN */ + {12, 0x001000}, /* STDCCLKEN */ + {15, 0x008000}, /* STDCRESET */ + {16, 0x010000}, /* SPDIF */ + {17, 0x1E0000} /* SR */ +}; + +/*! + * @brief This structure lists all of the fields in the SSI_NETWORK hardware register. + */ +typedef struct { + REGFIELD CDCTXRXSLOT; /*!< Codec timeslot assignment - 2 bits */ + REGFIELD CDCTXSECSLOT; /*!< Codec secondary transmit timeslot - 2 bits */ + REGFIELD CDCRXSECSLOT; /*!< Codec secondary receive timeslot - 2 bits */ + REGFIELD CDCRXSECGAIN; /*!< Codec secondary receive channel gain setting - 2 bits */ + REGFIELD CDCSUMGAIN; /*!< Codec summed receive signal gain setting */ + REGFIELD CDCFSDLY; /*!< Codec framesync delay */ + REGFIELD STDCSLOTS; /*!< Stereo DAC number of timeslots select - 2 bits */ + REGFIELD STDCRXSLOT; /*!< Stereo DAC timeslot assignment - 2 bits */ + REGFIELD STDCRXSECSLOT; /*!< Stereo DAC secondary receive timeslot - 2 bits */ + REGFIELD STDCRXSECGAIN; /*!< Stereo DAC secondary receive channel gain setting - 2 bits */ + REGFIELD STDCSUMGAIN; /*!< Stereo DAC summed receive signal gain setting */ +} REGISTER_SSI_NETWORK; + +/*! + * @brief This variable is used to access the SSI_NETWORK hardware register. + * + * This variable defines how to access all of the fields within the + * SSI_NETWORK hardware register. The initial values consist of the offset + * and mask values needed to access each of the register fields. + */ +static const REGISTER_SSI_NETWORK regSSI_NETWORK = { + {2, 0x00000c}, /* CDCTXRXSLOT */ + {4, 0x000030}, /* CDCTXSECSLOT */ + {6, 0x0000c0}, /* CDCRXSECSLOT */ + {8, 0x000300}, /* CDCRXSECGAIN */ + {10, 0x000400}, /* CDCSUMGAIN */ + {11, 0x000800}, /* CDCFSDLY */ + {12, 0x003000}, /* STDCSLOTS */ + {14, 0x00c000}, /* STDCRXSLOT */ + {16, 0x030000}, /* STDCRXSECSLOT */ + {18, 0x0c0000}, /* STDCRXSECGAIN */ + {20, 0x100000} /* STDCSUMGAIN */ +}; + +/*! + * @brief This structure lists all fields of the AUDIO_TX hardware register. + * + * + */ +typedef struct { + REGFIELD MC1BEN; /*!< Microphone bias 1 enable */ + REGFIELD MC2BEN; /*!< Microphone bias 2 enable */ + REGFIELD MC2BDETDBNC; /*!< Microphone bias detect debounce setting */ + REGFIELD MC2BDETEN; /*!< Microphone bias 2 detect enable */ + REGFIELD AMC1REN; /*!< Amplifier Amc1R enable */ + REGFIELD AMC1RITOV; /*!< Amplifier Amc1R current to voltage mode enable */ + REGFIELD AMC1LEN; /*!< Amplifier Amc1L enable */ + REGFIELD AMC1LITOV; /*!< Amplifier Amc1L current to voltage mode enable */ + REGFIELD AMC2EN; /*!< Amplifier Amc2 enable */ + REGFIELD AMC2ITOV; /*!< Amplifier Amc2 current to voltage mode enable */ + REGFIELD ATXINEN; /*!< Amplifier Atxin enable */ + REGFIELD ATXOUTEN; /*!< Reserved for output TXOUT enable, currently not used */ + REGFIELD RXINREC; /*!< RXINR/RXINL to voice CODEC ADC routing enable */ + REGFIELD PGATXR; /*!< Transmit gain setting right - 5 bits */ + REGFIELD PGATXL; /*!< Transmit gain setting left - 5 bits */ +} REGISTER_AUDIO_TX; + +/*! + * @brief This variable is used to access the AUDIO_TX hardware register. + * + * This variable defines how to access all of the fields within the + * AUDIO_TX hardware register. The initial values consist of the offset + * and mask values needed to access each of the register fields. + */ +static const REGISTER_AUDIO_TX regAUDIO_TX = { + {0, 0x000001}, /* MC1BEN */ + {1, 0x000002}, /* MC2BEN */ + {2, 0x000004}, /* MC2BDETDBNC */ + {3, 0x000008}, /* MC2BDETEN */ + {5, 0x000020}, /* AMC1REN */ + {6, 0x000040}, /* AMC1RITOV */ + {7, 0x000080}, /* AMC1LEN */ + {8, 0x000100}, /* AMC1LITOV */ + {9, 0x000200}, /* AMC2EN */ + {10, 0x000400}, /* AMC2ITOV */ + {11, 0x000800}, /* ATXINEN */ + {12, 0x001000}, /* ATXOUTEN */ + {13, 0x002000}, /* RXINREC */ + {14, 0x07c000}, /* PGATXR */ + {19, 0xf80000} /* PGATXL */ +}; + +/*! + * @brief This structure lists all fields of the AUDIO_RX_0 hardware register. + */ +typedef struct { + REGFIELD VAUDIOON; /*!< Forces VAUDIO in active on mode */ + REGFIELD BIASEN; /*!< Audio bias enable */ + REGFIELD BIASSPEED; /*!< Turn on ramp speed of the audio bias */ + REGFIELD ASPEN; /*!< Amplifier Asp enable */ + REGFIELD ASPSEL; /*!< Asp input selector */ + REGFIELD ALSPEN; /*!< Amplifier Alsp enable */ + REGFIELD ALSPREF; /*!< Bias Alsp at common audio reference */ + REGFIELD ALSPSEL; /*!< Alsp input selector */ + REGFIELD LSPLEN; /*!< Output LSPL enable */ + REGFIELD AHSREN; /*!< Amplifier AhsR enable */ + REGFIELD AHSLEN; /*!< Amplifier AhsL enable */ + REGFIELD AHSSEL; /*!< Ahsr and Ahsl input selector */ + REGFIELD HSPGDIS; /*!< Phantom ground disable */ + REGFIELD HSDETEN; /*!< Headset detect enable */ + REGFIELD HSDETAUTOB; /*!< Amplifier state determined by headset detect */ + REGFIELD ARXOUTREN; /*!< Output RXOUTR enable */ + REGFIELD ARXOUTLEN; /*!< Output RXOUTL enable */ + REGFIELD ARXOUTSEL; /*!< Arxout input selector */ + REGFIELD CDCOUTEN; /*!< Output CDCOUT enable */ + REGFIELD HSLDETEN; /*!< Headset left channel detect enable */ + REGFIELD ADDCDC; /*!< Adder channel codec selection */ + REGFIELD ADDSTDC; /*!< Adder channel stereo DAC selection */ + REGFIELD ADDRXIN; /*!< Adder channel line in selection */ +} REGISTER_AUDIO_RX_0; + +/*! + * @brief This variable is used to access the AUDIO_RX_0 hardware register. + * + * This variable defines how to access all of the fields within the + * AUDIO_RX_0 hardware register. The initial values consist of the offset + * and mask values needed to access each of the register fields. + */ +static const REGISTER_AUDIO_RX_0 regAUDIO_RX_0 = { + {0, 0x000001}, /* VAUDIOON */ + {1, 0x000002}, /* BIASEN */ + {2, 0x000004}, /* BIASSPEED */ + {3, 0x000008}, /* ASPEN */ + {4, 0x000010}, /* ASPSEL */ + {5, 0x000020}, /* ALSPEN */ + {6, 0x000040}, /* ALSPREF */ + {7, 0x000080}, /* ALSPSEL */ + {8, 0x000100}, /* LSPLEN */ + {9, 0x000200}, /* AHSREN */ + {10, 0x000400}, /* AHSLEN */ + {11, 0x000800}, /* AHSSEL */ + {12, 0x001000}, /* HSPGDIS */ + {13, 0x002000}, /* HSDETEN */ + {14, 0x004000}, /* HSDETAUTOB */ + {15, 0x008000}, /* ARXOUTREN */ + {16, 0x010000}, /* ARXOUTLEN */ + {17, 0x020000}, /* ARXOUTSEL */ + {18, 0x040000}, /* CDCOUTEN */ + {19, 0x080000}, /* HSLDETEN */ + {21, 0x200000}, /* ADDCDC */ + {22, 0x400000}, /* ADDSTDC */ + {23, 0x800000} /* ADDRXIN */ +}; + +/*! + * @brief This structure lists all fields of the AUDIO_RX_1 hardware register. + */ +typedef struct { + REGFIELD PGARXEN; /*!< Codec receive PGA enable */ + REGFIELD PGARX; /*!< Codec receive gain setting - 4 bits */ + REGFIELD PGASTEN; /*!< Stereo DAC PGA enable */ + REGFIELD PGAST; /*!< Stereo DAC gain setting - 4 bits */ + REGFIELD ARXINEN; /*!< Amplifier Arx enable */ + REGFIELD ARXIN; /*!< Amplifier Arx additional gain setting */ + REGFIELD PGARXIN; /*!< PGArxin gain setting - 4 bits */ + REGFIELD MONO; /*!< Mono adder setting - 2 bits */ + REGFIELD BAL; /*!< Balance control - 3 bits */ + REGFIELD BALLR; /*!< Left / right balance */ +} REGISTER_AUDIO_RX_1; + +/*! + * @brief This variable is used to access the AUDIO_RX_1 hardware register. + * + * This variable defines how to access all of the fields within the + * AUDIO_RX_1 hardware register. The initial values consist of the offset + * and mask values needed to access each of the register fields. + */ +static const REGISTER_AUDIO_RX_1 regAUDIO_RX_1 = { + {0, 0x000001}, /* PGARXEN */ + {1, 0x00001e}, /* PGARX */ + {5, 0x000020}, /* PGASTEN */ + {6, 0x0003c0}, /* PGAST */ + {10, 0x000400}, /* ARXINEN */ + {11, 0x000800}, /* ARXIN */ + {12, 0x00f000}, /* PGARXIN */ + {16, 0x030000}, /* MONO */ + {18, 0x1c0000}, /* BAL */ + {21, 0x200000} /* BALLR */ +}; + +/*! Define a mask to access the entire hardware register. */ +static const unsigned int REG_FULLMASK = 0xffffff; + +/*! Reset value for the AUD_CODEC register. */ +static const unsigned int RESET_AUD_CODEC = 0x180027; + +/*! Reset value for the ST_DAC register. + * + * Note that we avoid resetting any of the arbitration bits. + */ +static const unsigned int RESET_ST_DAC = 0x0E0004; + +/*! Reset value for the SSI_NETWORK register. */ +static const unsigned int RESET_SSI_NETWORK = 0x013060; + +/*! Reset value for the AUDIO_TX register. + * + * Note that we avoid resetting any of the arbitration bits. + */ +static const unsigned int RESET_AUDIO_TX = 0x420000; + +/*! Reset value for the AUDIO_RX_0 register. */ +static const unsigned int RESET_AUDIO_RX_0 = 0x001000; + +/*! Reset value for the AUDIO_RX_1 register. */ +static const unsigned int RESET_AUDIO_RX_1 = 0x00D35A; + +/*! Reset mask for the SSI network Vcodec part. first 12 bits + * 0 - 11 */ +static const unsigned int REG_SSI_VCODEC_MASK = 0x000fff; + +/*! Reset mask for the SSI network STDAC part. last 12 bits + * 12 - 24 */ +static const unsigned int REG_SSI_STDAC_MASK = 0xfff000; + +/*! Constant NULL value for initializing/reseting the audio handles. */ +static const PMIC_AUDIO_HANDLE AUDIO_HANDLE_NULL = (PMIC_AUDIO_HANDLE) NULL; + +/*! + * @brief This structure maintains the current state of the Stereo DAC. + */ +typedef struct { + PMIC_AUDIO_HANDLE handle; /*!< Handle used to access + the Stereo DAC. */ + HANDLE_STATE handleState; /*!< Current handle state. */ + PMIC_AUDIO_DATA_BUS busID; /*!< Data bus used to access + the Stereo DAC. */ + bool protocol_set; + PMIC_AUDIO_BUS_PROTOCOL protocol; /*!< Data bus protocol. */ + PMIC_AUDIO_BUS_MODE masterSlave; /*!< Master/Slave mode + select. */ + PMIC_AUDIO_NUMSLOTS numSlots; /*!< Number of timeslots + used. */ + PMIC_AUDIO_CALLBACK callback; /*!< Event notification + callback function + pointer. */ + PMIC_AUDIO_EVENTS eventMask; /*!< Event notification mask. */ + PMIC_AUDIO_CLOCK_IN_SOURCE clockIn; /*!< Stereo DAC clock input + source select. */ + PMIC_AUDIO_STDAC_SAMPLING_RATE samplingRate; /*!< Stereo DAC sampling rate + select. */ + PMIC_AUDIO_STDAC_CLOCK_IN_FREQ clockFreq; /*!< Stereo DAC clock input + frequency. */ + PMIC_AUDIO_CLOCK_INVERT invert; /*!< Stereo DAC clock signal + invert select. */ + PMIC_AUDIO_STDAC_TIMESLOTS timeslot; /*!< Stereo DAC data + timeslots select. */ + PMIC_AUDIO_STDAC_CONFIG config; /*!< Stereo DAC configuration + options. */ +} PMIC_AUDIO_STDAC_STATE; + +/*! + * @brief This variable maintains the current state of the Stereo DAC. + * + * This variable tracks the current state of the Stereo DAC audio hardware + * along with any information that is required by the device driver to + * manage the hardware (e.g., callback functions and event notification + * masks). + * + * The initial values represent the reset/power on state of the Stereo DAC. + */ +static PMIC_AUDIO_STDAC_STATE stDAC = { + (PMIC_AUDIO_HANDLE) NULL, /* handle */ + HANDLE_FREE, /* handleState */ + AUDIO_DATA_BUS_1, /* busID */ + false, + NORMAL_MSB_JUSTIFIED_MODE, /* protocol */ + BUS_MASTER_MODE, /* masterSlave */ + USE_2_TIMESLOTS, /* numSlots */ + (PMIC_AUDIO_CALLBACK) NULL, /* callback */ + (PMIC_AUDIO_EVENTS) NULL, /* eventMask */ + CLOCK_IN_CLIA, /* clockIn */ + STDAC_RATE_44_1_KHZ, /* samplingRate */ + STDAC_CLI_13MHZ, /* clockFreq */ + NO_INVERT, /* invert */ + USE_TS0_TS1, /* timeslot */ + (PMIC_AUDIO_STDAC_CONFIG) 0 /* config */ +}; + +/*! + * @brief This structure maintains the current state of the Voice CODEC. + */ +typedef struct { + PMIC_AUDIO_HANDLE handle; /*!< Handle used to access + the Voice CODEC. */ + HANDLE_STATE handleState; /*!< Current handle state. */ + PMIC_AUDIO_DATA_BUS busID; /*!< Data bus used to access + the Voice CODEC. */ + bool protocol_set; + PMIC_AUDIO_BUS_PROTOCOL protocol; /*!< Data bus protocol. */ + PMIC_AUDIO_BUS_MODE masterSlave; /*!< Master/Slave mode + select. */ + PMIC_AUDIO_NUMSLOTS numSlots; /*!< Number of timeslots + used. */ + PMIC_AUDIO_CALLBACK callback; /*!< Event notification + callback function + pointer. */ + PMIC_AUDIO_EVENTS eventMask; /*!< Event notification + mask. */ + PMIC_AUDIO_CLOCK_IN_SOURCE clockIn; /*!< Voice CODEC clock input + source select. */ + PMIC_AUDIO_VCODEC_SAMPLING_RATE samplingRate; /*!< Voice CODEC sampling + rate select. */ + PMIC_AUDIO_VCODEC_CLOCK_IN_FREQ clockFreq; /*!< Voice CODEC clock input + frequency. */ + PMIC_AUDIO_CLOCK_INVERT invert; /*!< Voice CODEC clock + signal invert select. */ + PMIC_AUDIO_VCODEC_TIMESLOT timeslot; /*!< Voice CODEC data + timeslot select. */ + PMIC_AUDIO_VCODEC_TIMESLOT secondaryTXtimeslot; + + PMIC_AUDIO_VCODEC_CONFIG config; /*!< Voice CODEC + configuration + options. */ + PMIC_MICROPHONE_STATE leftChannelMic; /*!< Left channel + microphone + configuration. */ + PMIC_MICROPHONE_STATE rightChannelMic; /*!< Right channel + microphone + configuration. */ +} PMIC_AUDIO_VCODEC_STATE; + +/*! + * @brief This variable maintains the current state of the Voice CODEC. + * + * This variable tracks the current state of the Voice CODEC audio hardware + * along with any information that is required by the device driver to + * manage the hardware (e.g., callback functions and event notification + * masks). + * + * The initial values represent the reset/power on state of the Voice CODEC. + */ +static PMIC_AUDIO_VCODEC_STATE vCodec = { + (PMIC_AUDIO_HANDLE) NULL, /* handle */ + HANDLE_FREE, /* handleState */ + AUDIO_DATA_BUS_2, /* busID */ + false, + NETWORK_MODE, /* protocol */ + BUS_SLAVE_MODE, /* masterSlave */ + USE_4_TIMESLOTS, /* numSlots */ + (PMIC_AUDIO_CALLBACK) NULL, /* callback */ + (PMIC_AUDIO_EVENTS) NULL, /* eventMask */ + CLOCK_IN_CLIB, /* clockIn */ + VCODEC_RATE_8_KHZ, /* samplingRate */ + VCODEC_CLI_13MHZ, /* clockFreq */ + NO_INVERT, /* invert */ + USE_TS0, /* timeslot pri */ + USE_TS2, /* timeslot sec TX */ + INPUT_HIGHPASS_FILTER | OUTPUT_HIGHPASS_FILTER, /* config */ + /* leftChannelMic */ + {NO_MIC, /* mic */ + MICROPHONE_OFF, /* micOnOff */ + AMP_OFF, /* ampMode */ + MIC_GAIN_0DB /* gain */ + }, + /* rightChannelMic */ + {NO_MIC, /* mic */ + MICROPHONE_OFF, /* micOnOff */ + AMP_OFF, /* ampMode */ + MIC_GAIN_0DB /* gain */ + } +}; + +/*! + * @brief This maintains the current state of the External Stereo Input. + */ +typedef struct { + PMIC_AUDIO_HANDLE handle; /*!< Handle used to access the + External Stereo Inputs. */ + HANDLE_STATE handleState; /*!< Current handle state. */ + PMIC_AUDIO_CALLBACK callback; /*!< Event notification callback + function pointer. */ + PMIC_AUDIO_EVENTS eventMask; /*!< Event notification mask. */ + PMIC_AUDIO_STEREO_IN_GAIN inputGain; /*!< External Stereo Input + amplifier gain level. */ +} PMIC_AUDIO_EXT_STEREO_IN_STATE; + +/*! + * @brief This maintains the current state of the External Stereo Input. + * + * This variable tracks the current state of the External Stereo Input audio + * hardware along with any information that is required by the device driver + * to manage the hardware (e.g., callback functions and event notification + * masks). + * + * The initial values represent the reset/power on state of the External + * Stereo Input. + */ +static PMIC_AUDIO_EXT_STEREO_IN_STATE extStereoIn = { + (PMIC_AUDIO_HANDLE) NULL, /* handle */ + HANDLE_FREE, /* handleState */ + (PMIC_AUDIO_CALLBACK) NULL, /* callback */ + (PMIC_AUDIO_EVENTS) NULL, /* eventMask */ + STEREO_IN_GAIN_0DB /* inputGain */ +}; + +/*! + * @brief This maintains the current state of the callback & Eventmask. + */ +typedef struct { + PMIC_AUDIO_CALLBACK callback; /*!< Event notification callback + function pointer. */ + PMIC_AUDIO_EVENTS eventMask; /*!< Event notification mask. */ +} PMIC_AUDIO_EVENT_STATE; + +static PMIC_AUDIO_EVENT_STATE event_state = { + (PMIC_AUDIO_CALLBACK) NULL, /*Callback */ + (PMIC_AUDIO_EVENTS) NULL, /* EventMask */ + +}; + +/*! + * @brief This maintains the current state of the Audio Output Section. + */ +typedef struct { + PMIC_AUDIO_OUTPUT_PORT outputPort; /*!< Current audio + output port. */ + PMIC_AUDIO_OUTPUT_PGA_GAIN vCodecoutputPGAGain; /*!< Output PGA gain + level codec */ + PMIC_AUDIO_OUTPUT_PGA_GAIN stDacoutputPGAGain; /*!< Output PGA gain + level stDAC */ + PMIC_AUDIO_OUTPUT_PGA_GAIN extStereooutputPGAGain; /*!< Output PGA gain + level stereo ext */ + PMIC_AUDIO_OUTPUT_BALANCE_GAIN balanceLeftGain; /*!< Left channel + balance gain + level. */ + PMIC_AUDIO_OUTPUT_BALANCE_GAIN balanceRightGain; /*!< Right channel + balance gain + level. */ + PMIC_AUDIO_MONO_ADDER_OUTPUT_GAIN monoAdderGain; /*!< Mono adder gain + level. */ + PMIC_AUDIO_OUTPUT_CONFIG config; /*!< Audio output + section config + options. */ + PMIC_AUDIO_VCODEC_OUTPUT_PATH vCodecOut; + +} PMIC_AUDIO_AUDIO_OUTPUT_STATE; + +/*! + * @brief This variable maintains the current state of the Audio Output Section. + * + * This variable tracks the current state of the Audio Output Section. + * + * The initial values represent the reset/power on state of the Audio + * Output Section. + */ +static PMIC_AUDIO_AUDIO_OUTPUT_STATE audioOutput = { + (PMIC_AUDIO_OUTPUT_PORT) NULL, /* outputPort */ + OUTPGA_GAIN_0DB, /* outputPGAGain */ + OUTPGA_GAIN_0DB, /* outputPGAGain */ + OUTPGA_GAIN_0DB, /* outputPGAGain */ + BAL_GAIN_0DB, /* balanceLeftGain */ + BAL_GAIN_0DB, /* balanceRightGain */ + MONOADD_GAIN_0DB, /* monoAdderGain */ + (PMIC_AUDIO_OUTPUT_CONFIG) 0, /* config */ + VCODEC_DIRECT_OUT +}; + +/*! The current headset status. */ +static HEADSET_STATUS headsetState = NO_HEADSET; + +/* Removed PTT variable */ +/*! Define a 1 ms wait interval that is needed to ensure that certain + * hardware operations are successfully completed. + */ +static const unsigned long delay_1ms = (HZ / 1000); + +/*! + * @brief This spinlock is used to provide mutual exclusion. + * + * Create a spinlock that can be used to provide mutually exclusive + * read/write access to the globally accessible data structures + * that were defined above. Mutually exclusive access is required to + * ensure that the audio data structures are consistent at all times + * when possibly accessed by multiple threads of execution (for example, + * while simultaneously handling a user request and an interrupt event). + * + * We need to use a spinlock whenever we do need to provide mutual + * exclusion while possibly executing in a hardware interrupt context. + * Spinlocks should be held for the minimum time that is necessary + * because hardware interrupts are disabled while a spinlock is held. + * + */ + +static spinlock_t lock = SPIN_LOCK_UNLOCKED; +/*! + * @brief This mutex is used to provide mutual exclusion. + * + * Create a mutex that can be used to provide mutually exclusive + * read/write access to the globally accessible data structures + * that were defined above. Mutually exclusive access is required to + * ensure that the audio data structures are consistent at all times + * when possibly accessed by multiple threads of execution. + * + * Note that we use a mutex instead of the spinlock whenever disabling + * interrupts while in the critical section is not required. This helps + * to minimize kernel interrupt handling latency. + */ +static DECLARE_MUTEX(mutex); + +/*! + * @brief Global variable to track currently active interrupt events. + * + * This global variable is used to keep track of all of the currently + * active interrupt events for the audio driver. Note that access to this + * variable may occur while within an interrupt context and, therefore, + * must be guarded by using a spinlock. + */ +/* static PMIC_CORE_EVENT eventID = 0; */ + +/* Prototypes for all static audio driver functions. */ +/* +static PMIC_STATUS pmic_audio_mic_boost_enable(void); +static PMIC_STATUS pmic_audio_mic_boost_disable(void);*/ +static PMIC_STATUS pmic_audio_close_handle(const PMIC_AUDIO_HANDLE handle); +static PMIC_STATUS pmic_audio_reset_device(const PMIC_AUDIO_HANDLE handle); + +static PMIC_STATUS pmic_audio_deregister(void *callback, + PMIC_AUDIO_EVENTS * const eventMask); + +/************************************************************************* + * Audio device access APIs. + ************************************************************************* + */ + +/*! + * @name General Setup and Configuration APIs + * Functions for general setup and configuration of the PMIC Audio + * hardware. + */ +/*@{*/ + +PMIC_STATUS pmic_audio_set_autodetect(int val) +{ + PMIC_STATUS status; + unsigned int reg_mask = 0, reg_write = 0; + reg_mask = SET_BITS(regAUDIO_RX_0, VAUDIOON, 1); + status = pmic_write_reg(REG_AUDIO_RX_0, reg_mask, reg_mask); + if (status != PMIC_SUCCESS) + return status; + reg_mask = 0; + if (val == 1) { + reg_write = SET_BITS(regAUDIO_RX_0, HSDETEN, 1); + } else { + reg_write = 0; + } + reg_mask = + SET_BITS(regAUDIO_RX_0, HSDETEN, 1) | SET_BITS(regAUDIO_RX_0, + HSDETAUTOB, 1); + status = pmic_write_reg(REG_AUDIO_RX_0, reg_write, reg_mask); + + return status; +} + +/*! + * @brief Request exclusive access to the PMIC Audio hardware. + * + * Attempt to open and gain exclusive access to a key PMIC audio hardware + * component (e.g., the Stereo DAC or the Voice CODEC). Depending upon the + * type of audio operation that is desired and the nature of the audio data + * stream, the Stereo DAC and/or the Voice CODEC will be a required hardware + * component and needs to be acquired by calling this function. + * + * If the open request is successful, then a numeric handle is returned + * and this handle must be used in all subsequent function calls to complete + * the configuration of either the Stereo DAC or the Voice CODEC and along + * with any other associated audio hardware components that will be needed. + * + * The same handle must also be used in the close call when use of the PMIC + * audio hardware is no longer required. + * + * The open request will fail if the requested audio hardware component has + * already been acquired by a previous open call but not yet closed. + * + * @param handle Device handle to be used for subsequent PMIC + * audio API calls. + * @param device The required PMIC audio hardware component. + * + * @retval PMIC_SUCCESS If the open request was successful + * @retval PMIC_PARAMETER_ERROR If the handle argument is NULL. + * @retval PMIC_ERROR If the audio hardware component is + * unavailable. + */ +PMIC_STATUS pmic_audio_open(PMIC_AUDIO_HANDLE * const handle, + const PMIC_AUDIO_SOURCE device) +{ + PMIC_STATUS rc = PMIC_ERROR; + + if (handle == (PMIC_AUDIO_HANDLE *) NULL) { + /* Do not dereference a NULL pointer. */ + return PMIC_PARAMETER_ERROR; + } + + /* We only need to acquire a mutex here because the interrupt handler + * never modifies the device handle or device handle state. Therefore, + * we don't need to worry about conflicts with the interrupt handler + * or the need to execute in an interrupt context. + * + * But we do need a critical section here to avoid problems in case + * multiple calls to pmic_audio_open() are made since we can only allow + * one of them to succeed. + */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + /* Check the current device handle state and acquire the handle if + * it is available. + */ + + if ((device == STEREO_DAC) && (stDAC.handleState == HANDLE_FREE)) { + stDAC.handle = (PMIC_AUDIO_HANDLE) (&stDAC); + stDAC.handleState = HANDLE_IN_USE; + *handle = stDAC.handle; + rc = PMIC_SUCCESS; + } else if ((device == VOICE_CODEC) + && (vCodec.handleState == HANDLE_FREE)) { + vCodec.handle = (PMIC_AUDIO_HANDLE) (&vCodec); + vCodec.handleState = HANDLE_IN_USE; + *handle = vCodec.handle; + rc = PMIC_SUCCESS; + } else if ((device == EXTERNAL_STEREO_IN) && + (extStereoIn.handleState == HANDLE_FREE)) { + extStereoIn.handle = (PMIC_AUDIO_HANDLE) (&extStereoIn); + extStereoIn.handleState = HANDLE_IN_USE; + *handle = extStereoIn.handle; + rc = PMIC_SUCCESS; + } else { + *handle = AUDIO_HANDLE_NULL; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Terminate further access to the PMIC audio hardware. + * + * Terminate further access to the PMIC audio hardware that was previously + * acquired by calling pmic_audio_open(). This now allows another thread to + * successfully call pmic_audio_open() to gain access. + * + * Note that we will shutdown/reset the Voice CODEC or Stereo DAC as well as + * any associated audio input/output components that are no longer required. + * + * @param handle Device handle from pmic_audio_open() call. + * + * @retval PMIC_SUCCESS If the close request was successful. + * @retval PMIC_PARAMETER_ERROR If the handle is invalid. + */ +PMIC_STATUS pmic_audio_close(const PMIC_AUDIO_HANDLE handle) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + /* We need a critical section here to maintain a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + /* We can now call pmic_audio_close_handle() to actually do the work. */ + rc = pmic_audio_close_handle(handle); + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Configure the data bus protocol to be used. + * + * Provide the parameters needed to properly configure the audio data bus + * protocol so that data can be read/written to either the Stereo DAC or + * the Voice CODEC. + * + * @param handle Device handle from pmic_audio_open() call. + * @param busID Select data bus to be used. + * @param protocol Select the data bus protocol. + * @param masterSlave Select the data bus timing mode. + * @param numSlots Define the number of timeslots (only if in + * master mode). + * + * @retval PMIC_SUCCESS If the protocol was successful configured. + * @retval PMIC_PARAMETER_ERROR If the handle or the protocol parameters + * are invalid. + */ +PMIC_STATUS pmic_audio_set_protocol(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_DATA_BUS busID, + const PMIC_AUDIO_BUS_PROTOCOL protocol, + const PMIC_AUDIO_BUS_MODE masterSlave, + const PMIC_AUDIO_NUMSLOTS numSlots) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + const unsigned int ST_DAC_MASK = SET_BITS(regST_DAC, STDCSSISEL, 1) | + SET_BITS(regST_DAC, STDCFS, 3) | SET_BITS(regST_DAC, STDCSM, 1); + + unsigned int reg_mask; + /*unsigned int VCODEC_MASK = SET_BITS(regAUD_CODEC, CDCSSISEL, 1) | + SET_BITS(regAUD_CODEC, CDCFS, 3) | SET_BITS(regAUD_CODEC, CDCSM, 1); */ + + unsigned int SSI_NW_MASK = SET_BITS(regSSI_NETWORK, STDCSLOTS, 1); + unsigned int reg_value = 0; + unsigned int ssi_nw_value = 0; + + /* Enter a critical section so that we can ensure only one + * state change request is completed at a time. + */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if (handle == (PMIC_AUDIO_HANDLE) NULL) { + rc = PMIC_PARAMETER_ERROR; + } else { + if ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE)) { + if ((stDAC.handleState == HANDLE_IN_USE) && + (stDAC.busID == busID) && (stDAC.protocol_set)) { + pr_debug("The requested bus already in USE\n"); + rc = PMIC_PARAMETER_ERROR; + } else if ((masterSlave == BUS_MASTER_MODE) + && (numSlots != USE_4_TIMESLOTS)) { + pr_debug + ("mc13783 supports only 4 slots in Master mode\n"); + rc = PMIC_NOT_SUPPORTED; + } else if ((masterSlave == BUS_SLAVE_MODE) + && (numSlots != USE_4_TIMESLOTS)) { + pr_debug + ("Driver currently supports only 4 slots in Slave mode\n"); + rc = PMIC_NOT_SUPPORTED; + } else if (!((protocol == NETWORK_MODE) || + (protocol == I2S_MODE))) { + pr_debug + ("mc13783 Voice codec works only in Network and I2S modes\n"); + rc = PMIC_NOT_SUPPORTED; + } else { + pr_debug + ("Proceeding to configure Voice Codec\n"); + if (busID == AUDIO_DATA_BUS_1) { + reg_value = + SET_BITS(regAUD_CODEC, CDCSSISEL, + 0); + } else { + reg_value = + SET_BITS(regAUD_CODEC, CDCSSISEL, + 1); + } + reg_mask = SET_BITS(regAUD_CODEC, CDCSSISEL, 1); + if (PMIC_SUCCESS != + pmic_write_reg(REG_AUDIO_CODEC, + reg_value, reg_mask)) + return PMIC_ERROR; + + if (masterSlave == BUS_MASTER_MODE) { + reg_value = + SET_BITS(regAUD_CODEC, CDCSM, 0); + } else { + reg_value = + SET_BITS(regAUD_CODEC, CDCSM, 1); + } + reg_mask = SET_BITS(regAUD_CODEC, CDCSM, 1); + if (PMIC_SUCCESS != + pmic_write_reg(REG_AUDIO_CODEC, + reg_value, reg_mask)) + return PMIC_ERROR; + + if (protocol == NETWORK_MODE) { + reg_value = + SET_BITS(regAUD_CODEC, CDCFS, 1); + } else { /* protocol == I2S, other options have been already eliminated */ + reg_value = + SET_BITS(regAUD_CODEC, CDCFS, 2); + } + reg_mask = SET_BITS(regAUD_CODEC, CDCFS, 3); + if (PMIC_SUCCESS != + pmic_write_reg(REG_AUDIO_CODEC, + reg_value, reg_mask)) + return PMIC_ERROR; + + ssi_nw_value = + SET_BITS(regSSI_NETWORK, CDCFSDLY, 1); + /*if (pmic_write_reg + (REG_AUDIO_CODEC, reg_value, + VCODEC_MASK) != PMIC_SUCCESS) { + rc = PMIC_ERROR; + } else { */ + vCodec.busID = busID; + vCodec.protocol = protocol; + vCodec.masterSlave = masterSlave; + vCodec.numSlots = numSlots; + vCodec.protocol_set = true; + //pmic_write_reg(REG_AUDIO_SSI_NETWORK, ssi_nw_value, ssi_nw_value); + + pr_debug + ("mc13783 Voice codec successfully configured\n"); + rc = PMIC_SUCCESS; + //} + + } + + } else if ((handle == stDAC.handle) && + (stDAC.handleState == HANDLE_IN_USE)) { + if ((vCodec.handleState == HANDLE_IN_USE) && + (vCodec.busID == busID) && (vCodec.protocol_set)) { + pr_debug("The requested bus already in USE\n"); + rc = PMIC_PARAMETER_ERROR; + } else if (((protocol == NORMAL_MSB_JUSTIFIED_MODE) || + (protocol == I2S_MODE)) + && (numSlots != USE_2_TIMESLOTS)) { + pr_debug + ("STDAC uses only 2 slots in Normal and I2S modes\n"); + rc = PMIC_PARAMETER_ERROR; + } else if ((protocol == NETWORK_MODE) && + !((numSlots == USE_2_TIMESLOTS) || + (numSlots == USE_4_TIMESLOTS) || + (numSlots == USE_8_TIMESLOTS))) { + pr_debug + ("STDAC uses only 2,4 or 8 slots in Network mode\n"); + rc = PMIC_PARAMETER_ERROR; + } else if (protocol == SPD_IF_MODE) { + pr_debug + ("STDAC driver currently does not support SPD IF mode\n"); + rc = PMIC_NOT_SUPPORTED; + } else { + pr_debug + ("Proceeding to configure Stereo DAC\n"); + if (busID == AUDIO_DATA_BUS_1) { + reg_value = + SET_BITS(regST_DAC, STDCSSISEL, 0); + } else { + reg_value = + SET_BITS(regST_DAC, STDCSSISEL, 1); + } + if (masterSlave == BUS_MASTER_MODE) { + reg_value |= + SET_BITS(regST_DAC, STDCSM, 0); + } else { + reg_value |= + SET_BITS(regST_DAC, STDCSM, 1); + } + if (protocol == NETWORK_MODE) { + reg_value |= + SET_BITS(regST_DAC, STDCFS, 1); + } else if (protocol == + NORMAL_MSB_JUSTIFIED_MODE) { + reg_value |= + SET_BITS(regST_DAC, STDCFS, 0); + } else { /* I2S mode as the other option has already been eliminated */ + reg_value |= + SET_BITS(regST_DAC, STDCFS, 2); + } + + if (pmic_write_reg + (REG_AUDIO_STEREO_DAC, + reg_value, ST_DAC_MASK) != PMIC_SUCCESS) { + rc = PMIC_ERROR; + } else { + if (numSlots == USE_2_TIMESLOTS) { + reg_value = + SET_BITS(regSSI_NETWORK, + STDCSLOTS, 3); + } else if (numSlots == USE_4_TIMESLOTS) { + reg_value = + SET_BITS(regSSI_NETWORK, + STDCSLOTS, 2); + } else { /* Use 8 timeslots - L , R and 6 other */ + reg_value = + SET_BITS(regSSI_NETWORK, + STDCSLOTS, 1); + } + if (pmic_write_reg + (REG_AUDIO_SSI_NETWORK, + reg_value, + SSI_NW_MASK) != PMIC_SUCCESS) { + rc = PMIC_ERROR; + } else { + stDAC.busID = busID; + stDAC.protocol = protocol; + stDAC.protocol_set = true; + stDAC.masterSlave = masterSlave; + stDAC.numSlots = numSlots; + pr_debug + ("mc13783 Stereo DAC successfully configured\n"); + rc = PMIC_SUCCESS; + } + } + + } + } else { + rc = PMIC_PARAMETER_ERROR; + /* Handle can only be Voice Codec or Stereo DAC */ + pr_debug("Handles only STDAC and VCODEC\n"); + } + + } + /* Exit critical section. */ + up(&mutex); + return rc; +} + +/*! + * @brief Retrieve the current data bus protocol configuration. + * + * Retrieve the parameters that define the current audio data bus protocol. + * + * @param handle Device handle from pmic_audio_open() call. + * @param busID The data bus being used. + * @param protocol The data bus protocol being used. + * @param masterSlave The data bus timing mode being used. + * @param numSlots The number of timeslots being used (if in + * master mode). + * + * @retval PMIC_SUCCESS If the protocol was successful retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle is invalid. + */ +PMIC_STATUS pmic_audio_get_protocol(const PMIC_AUDIO_HANDLE handle, + PMIC_AUDIO_DATA_BUS * const busID, + PMIC_AUDIO_BUS_PROTOCOL * const protocol, + PMIC_AUDIO_BUS_MODE * const masterSlave, + PMIC_AUDIO_NUMSLOTS * const numSlots) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + if ((busID != (PMIC_AUDIO_DATA_BUS *) NULL) && + (protocol != (PMIC_AUDIO_BUS_PROTOCOL *) NULL) && + (masterSlave != (PMIC_AUDIO_BUS_MODE *) NULL) && + (numSlots != (PMIC_AUDIO_NUMSLOTS *) NULL)) { + /* Enter a critical section so that we return a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == stDAC.handle) && + (stDAC.handleState == HANDLE_IN_USE)) { + *busID = stDAC.busID; + *protocol = stDAC.protocol; + *masterSlave = stDAC.masterSlave; + *numSlots = stDAC.numSlots; + rc = PMIC_SUCCESS; + } else if ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE)) { + *busID = vCodec.busID; + *protocol = vCodec.protocol; + *masterSlave = vCodec.masterSlave; + *numSlots = vCodec.numSlots; + rc = PMIC_SUCCESS; + } + + /* Exit critical section. */ + up(&mutex); + } + + return rc; +} + +/*! + * @brief Enable the Stereo DAC or the Voice CODEC. + * + * Explicitly enable the Stereo DAC or the Voice CODEC to begin audio + * playback or recording as required. This should only be done after + * successfully configuring all of the associated audio components (e.g., + * microphones, amplifiers, etc.). + * + * Note that the timed delays used in this function are necessary to + * ensure reliable operation of the Voice CODEC and Stereo DAC. The + * Stereo DAC seems to be particularly sensitive and it has been observed + * to fail to generate the required master mode clock signals if it is + * not allowed enough time to initialize properly. + * + * @param handle Device handle from pmic_audio_open() call. + * + * @retval PMIC_SUCCESS If the device was successful enabled. + * @retval PMIC_PARAMETER_ERROR If the handle is invalid. + * @retval PMIC_ERROR If the device could not be enabled. + */ +PMIC_STATUS pmic_audio_enable(const PMIC_AUDIO_HANDLE handle) +{ + const unsigned int AUDIO_BIAS_ENABLE = SET_BITS(regAUDIO_RX_0, + VAUDIOON, 1); + const unsigned int STDAC_ENABLE = SET_BITS(regST_DAC, STDCEN, 1); + const unsigned int VCODEC_ENABLE = SET_BITS(regAUD_CODEC, CDCEN, 1); + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == stDAC.handle) && (stDAC.handleState == HANDLE_IN_USE)) { + /* We can enable the Stereo DAC. */ + rc = pmic_write_reg(REG_AUDIO_STEREO_DAC, + STDAC_ENABLE, STDAC_ENABLE); + /*pmic_read_reg(REG_AUDIO_STEREO_DAC, ®_value); */ + if (rc != PMIC_SUCCESS) { + pr_debug("Failed to enable the Stereo DAC\n"); + rc = PMIC_ERROR; + } + } else if ((handle == vCodec.handle) + && (vCodec.handleState == HANDLE_IN_USE)) { + /* Must first set the audio bias bit to power up the audio circuits. */ + pmic_write_reg(REG_AUDIO_RX_0, AUDIO_BIAS_ENABLE, + AUDIO_BIAS_ENABLE); + /* Then we can enable the Voice CODEC. */ + rc = pmic_write_reg(REG_AUDIO_CODEC, VCODEC_ENABLE, + VCODEC_ENABLE); + + /* pmic_read_reg(REG_AUDIO_CODEC, ®_value); */ + if (rc != PMIC_SUCCESS) { + pr_debug("Failed to enable the Voice codec\n"); + rc = PMIC_ERROR; + } + } + /* Exit critical section. */ + up(&mutex); + return rc; +} + +/*! + * @brief Disable the Stereo DAC or the Voice CODEC. + * + * Explicitly disable the Stereo DAC or the Voice CODEC to end audio + * playback or recording as required. + * + * @param handle Device handle from pmic_audio_open() call. + * + * @retval PMIC_SUCCESS If the device was successful disabled. + * @retval PMIC_PARAMETER_ERROR If the handle is invalid. + * @retval PMIC_ERROR If the device could not be disabled. + */ +PMIC_STATUS pmic_audio_disable(const PMIC_AUDIO_HANDLE handle) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + const unsigned int STDAC_DISABLE = SET_BITS(regST_DAC, STDCEN, 1); + const unsigned int VCODEC_DISABLE = SET_BITS(regAUD_CODEC, CDCEN, 1); + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + if ((handle == stDAC.handle) && (stDAC.handleState == HANDLE_IN_USE)) { + rc = pmic_write_reg(REG_AUDIO_STEREO_DAC, 0, STDAC_DISABLE); + } else if ((handle == vCodec.handle) + && (vCodec.handleState == HANDLE_IN_USE)) { + rc = pmic_write_reg(REG_AUDIO_CODEC, 0, VCODEC_DISABLE); + } + if (rc == PMIC_SUCCESS) { + pr_debug("Disabled successfully\n"); + } + /* Exit critical section. */ + up(&mutex); + return rc; +} + +/*! + * @brief Reset the selected audio hardware control registers to their + * power on state. + * + * This resets all of the audio hardware control registers currently + * associated with the device handle back to their power on states. For + * example, if the handle is associated with the Stereo DAC and a + * specific output port and output amplifiers, then this function will + * reset all of those components to their initial power on state. + * + * @param handle Device handle from pmic_audio_open() call. + * + * @retval PMIC_SUCCESS If the reset operation was successful. + * @retval PMIC_PARAMETER_ERROR If the handle is invalid. + * @retval PMIC_ERROR If the reset was unsuccessful. + */ +PMIC_STATUS pmic_audio_reset(const PMIC_AUDIO_HANDLE handle) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + rc = pmic_audio_reset_device(handle); + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Reset all audio hardware control registers to their power on state. + * + * This resets all of the audio hardware control registers back to their + * power on states. Use this function with care since it also invalidates + * (i.e., automatically closes) all currently opened device handles. + * + * @retval PMIC_SUCCESS If the reset operation was successful. + * @retval PMIC_ERROR If the reset was unsuccessful. + */ +PMIC_STATUS pmic_audio_reset_all(void) +{ + PMIC_STATUS rc = PMIC_SUCCESS; + unsigned int audio_ssi_reset = 0; + unsigned int audio_rx1_reset = 0; + /* We need a critical section here to maintain a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + /* First close all opened device handles, also deregisters callbacks. */ + pmic_audio_close_handle(stDAC.handle); + pmic_audio_close_handle(vCodec.handle); + pmic_audio_close_handle(extStereoIn.handle); + + if (pmic_write_reg(REG_AUDIO_RX_1, RESET_AUDIO_RX_1, + PMIC_ALL_BITS) != PMIC_SUCCESS) { + rc = PMIC_ERROR; + } else { + audio_rx1_reset = 1; + } + if (pmic_write_reg(REG_AUDIO_SSI_NETWORK, RESET_SSI_NETWORK, + PMIC_ALL_BITS) != PMIC_SUCCESS) { + rc = PMIC_ERROR; + } else { + audio_ssi_reset = 1; + } + if (pmic_write_reg + (REG_AUDIO_STEREO_DAC, RESET_ST_DAC, + PMIC_ALL_BITS) != PMIC_SUCCESS) { + rc = PMIC_ERROR; + } else { + /* Also reset the driver state information to match. Note that we + * keep the device handle and event callback settings unchanged + * since these don't affect the actual hardware and we rely on + * the user to explicitly close the handle or deregister callbacks + */ + if (audio_ssi_reset) { + /* better to check if SSI is also reset as some fields are represennted in SSI reg */ + stDAC.busID = AUDIO_DATA_BUS_1; + stDAC.protocol = NORMAL_MSB_JUSTIFIED_MODE; + stDAC.masterSlave = BUS_MASTER_MODE; + stDAC.protocol_set = false; + stDAC.numSlots = USE_2_TIMESLOTS; + stDAC.clockIn = CLOCK_IN_CLIA; + stDAC.samplingRate = STDAC_RATE_44_1_KHZ; + stDAC.clockFreq = STDAC_CLI_13MHZ; + stDAC.invert = NO_INVERT; + stDAC.timeslot = USE_TS0_TS1; + stDAC.config = (PMIC_AUDIO_STDAC_CONFIG) 0; + } + } + + if (pmic_write_reg(REG_AUDIO_CODEC, RESET_AUD_CODEC, + PMIC_ALL_BITS) != PMIC_SUCCESS) { + rc = PMIC_ERROR; + } else { + /* Also reset the driver state information to match. Note that we + * keep the device handle and event callback settings unchanged + * since these don't affect the actual hardware and we rely on + * the user to explicitly close the handle or deregister callbacks + */ + if (audio_ssi_reset) { + vCodec.busID = AUDIO_DATA_BUS_2; + vCodec.protocol = NETWORK_MODE; + vCodec.masterSlave = BUS_SLAVE_MODE; + vCodec.protocol_set = false; + vCodec.numSlots = USE_4_TIMESLOTS; + vCodec.clockIn = CLOCK_IN_CLIB; + vCodec.samplingRate = VCODEC_RATE_8_KHZ; + vCodec.clockFreq = VCODEC_CLI_13MHZ; + vCodec.invert = NO_INVERT; + vCodec.timeslot = USE_TS0; + vCodec.config = + INPUT_HIGHPASS_FILTER | OUTPUT_HIGHPASS_FILTER; + } + } + + if (pmic_write_reg(REG_AUDIO_RX_0, RESET_AUDIO_RX_0, + PMIC_ALL_BITS) != PMIC_SUCCESS) { + rc = PMIC_ERROR; + } else { + /* Also reset the driver state information to match. */ + audioOutput.outputPort = (PMIC_AUDIO_OUTPUT_PORT) NULL; + audioOutput.vCodecoutputPGAGain = OUTPGA_GAIN_0DB; + audioOutput.stDacoutputPGAGain = OUTPGA_GAIN_0DB; + audioOutput.extStereooutputPGAGain = OUTPGA_GAIN_0DB; + audioOutput.balanceLeftGain = BAL_GAIN_0DB; + audioOutput.balanceRightGain = BAL_GAIN_0DB; + audioOutput.monoAdderGain = MONOADD_GAIN_0DB; + audioOutput.config = (PMIC_AUDIO_OUTPUT_CONFIG) 0; + audioOutput.vCodecOut = VCODEC_DIRECT_OUT; + } + + if (pmic_write_reg(REG_AUDIO_TX, RESET_AUDIO_TX, + PMIC_ALL_BITS) != PMIC_SUCCESS) { + rc = PMIC_ERROR; + } else { + /* Also reset the driver state information to match. Note that we + * reset the vCodec fields since all of the input/recording + * devices are only connected to the Voice CODEC and are managed + * as part of the Voice CODEC state. + */ + if (audio_rx1_reset) { + vCodec.leftChannelMic.mic = NO_MIC; + vCodec.leftChannelMic.micOnOff = MICROPHONE_OFF; + vCodec.leftChannelMic.ampMode = CURRENT_TO_VOLTAGE; + vCodec.leftChannelMic.gain = MIC_GAIN_0DB; + vCodec.rightChannelMic.mic = NO_MIC; + vCodec.rightChannelMic.micOnOff = MICROPHONE_OFF; + vCodec.rightChannelMic.ampMode = AMP_OFF; + vCodec.rightChannelMic.gain = MIC_GAIN_0DB; + } + } + /* Finally, also reset any global state variables. */ + headsetState = NO_HEADSET; + /* Exit the critical section. */ + up(&mutex); + return rc; +} + +/*! + * @brief Set the Audio callback function. + * + * Register a callback function that will be used to signal PMIC audio + * events. For example, the OSS audio driver should register a callback + * function in order to be notified of headset connect/disconnect events. + * + * @param func A pointer to the callback function. + * @param eventMask A mask selecting events to be notified. + * @param hs_state To know the headset state. + * + * + * + * @retval PMIC_SUCCESS If the callback was successfully + * registered. + * @retval PMIC_PARAMETER_ERROR If the handle or the eventMask is invalid. + */ +PMIC_STATUS pmic_audio_set_callback(void *func, + const PMIC_AUDIO_EVENTS eventMask, + PMIC_HS_STATE * hs_state) +{ + unsigned long flags; + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + pmic_event_callback_t eventNotify; + + /* We need to start a critical section here to ensure a consistent state + * in case simultaneous calls to pmic_audio_set_callback() are made. In + * that case, we must serialize the calls to ensure that the "callback" + * and "eventMask" state variables are always consistent. + * + * Note that we don't actually need to acquire the spinlock until later + * when we are finally ready to update the "callback" and "eventMask" + * state variables which are shared with the interrupt handler. + */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + rc = PMIC_ERROR; + /* Register for PMIC events from the core protocol driver. */ + if (eventMask & MICROPHONE_DETECTED) { + /* We need to register for the A1 amplifier interrupt. */ + eventNotify.func = func; + eventNotify.param = (void *)(CORE_EVENT_MC2BI); + rc = pmic_event_subscribe(EVENT_MC2BI, eventNotify); + + if (rc != PMIC_SUCCESS) { + pr_debug + ("%s: pmic_event_subscribe() for EVENT_HSDETI " + "failed\n", __FILE__); + goto End; + } + } + + if (eventMask & HEADSET_DETECTED) { + /* We need to register for the A1 amplifier interrupt. */ + eventNotify.func = func; + eventNotify.param = (void *)(CORE_EVENT_HSDETI); + rc = pmic_event_subscribe(EVENT_HSDETI, eventNotify); + + if (rc != PMIC_SUCCESS) { + pr_debug + ("%s: pmic_event_subscribe() for EVENT_HSDETI " + "failed\n", __FILE__); + goto Cleanup_HDT; + } + + } + if (eventMask & HEADSET_STEREO) { + /* We need to register for the A1 amplifier interrupt. */ + eventNotify.func = func; + eventNotify.param = (void *)(CORE_EVENT_HSLI); + rc = pmic_event_subscribe(EVENT_HSLI, eventNotify); + + if (rc != PMIC_SUCCESS) { + pr_debug + ("%s: pmic_event_subscribe() for EVENT_HSLI " + "failed\n", __FILE__); + goto Cleanup_HST; + } + } + if (eventMask & HEADSET_THERMAL_SHUTDOWN) { + /* We need to register for the A1 amplifier interrupt. */ + eventNotify.func = func; + eventNotify.param = (void *)(CORE_EVENT_ALSPTHI); + rc = pmic_event_subscribe(EVENT_ALSPTHI, eventNotify); + + if (rc != PMIC_SUCCESS) { + pr_debug + ("%s: pmic_event_subscribe() for EVENT_ALSPTHI " + "failed\n", __FILE__); + goto Cleanup_TSD; + } + pr_debug("Registered for EVENT_ALSPTHI\n"); + } + if (eventMask & HEADSET_SHORT_CIRCUIT) { + /* We need to register for the A1 amplifier interrupt. */ + eventNotify.func = func; + eventNotify.param = (void *)(CORE_EVENT_AHSSHORTI); + rc = pmic_event_subscribe(EVENT_AHSSHORTI, eventNotify); + + if (rc != PMIC_SUCCESS) { + pr_debug + ("%s: pmic_event_subscribe() for EVENT_AHSSHORTI " + "failed\n", __FILE__); + goto Cleanup_HShort; + } + pr_debug("Registered for EVENT_AHSSHORTI\n"); + } + + /* We also need the spinlock here to avoid possible problems + * with the interrupt handler when we update the + * "callback" and "eventMask" state variables. + */ + spin_lock_irqsave(&lock, flags); + + /* Successfully registered for all events. */ + event_state.callback = func; + event_state.eventMask = eventMask; + + /* The spinlock is no longer needed now that we've finished + * updating the "callback" and "eventMask" state variables. + */ + spin_unlock_irqrestore(&lock, flags); + + goto End; + + /* This section unregisters any already registered events if we should + * encounter an error partway through the registration process. Note + * that we don't check the return status here since it is already set + * to PMIC_ERROR before we get here. + */ + Cleanup_HShort: + + if (eventMask & HEADSET_SHORT_CIRCUIT) { + eventNotify.func = func; + eventNotify.param = (void *)(CORE_EVENT_AHSSHORTI); + pmic_event_unsubscribe(EVENT_AHSSHORTI, eventNotify); + } + + Cleanup_TSD: + + if (eventMask & HEADSET_THERMAL_SHUTDOWN) { + eventNotify.func = func; + eventNotify.param = (void *)(CORE_EVENT_ALSPTHI); + pmic_event_unsubscribe(EVENT_ALSPTHI, eventNotify); + } + + Cleanup_HST: + + if (eventMask & HEADSET_STEREO) { + eventNotify.func = func; + eventNotify.param = (void *)(CORE_EVENT_HSLI); + pmic_event_unsubscribe(EVENT_HSLI, eventNotify); + } + + Cleanup_HDT: + + if (eventMask & HEADSET_DETECTED) { + eventNotify.func = func; + eventNotify.param = (void *)(CORE_EVENT_HSDETI); + pmic_event_unsubscribe(EVENT_HSDETI, eventNotify); + } + + End: + /* Exit the critical section. */ + up(&mutex); + return rc; +} + +/*! + * @brief Deregisters the existing audio callback function. + * + * Deregister the callback function that was previously registered by calling + * pmic_audio_set_callback(). + * + * + * @retval PMIC_SUCCESS If the callback was successfully + * deregistered. + * @retval PMIC_PARAMETER_ERROR If the handle is invalid. + */ +PMIC_STATUS pmic_audio_clear_callback(void) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + /* We need a critical section to maintain a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if (event_state.callback != (PMIC_AUDIO_CALLBACK) NULL) { + rc = pmic_audio_deregister(&(event_state.callback), + &(event_state.eventMask)); + } + + /* Exit the critical section. */ + up(&mutex); + return rc; +} + +/*! + * @brief Get the current audio callback function settings. + * + * Get the current callback function and event mask. + * + * @param func The current callback function. + * @param eventMask The current event selection mask. + * + * @retval PMIC_SUCCESS If the callback information was + * successfully retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle is invalid. + */ +PMIC_STATUS pmic_audio_get_callback(PMIC_AUDIO_CALLBACK * const func, + PMIC_AUDIO_EVENTS * const eventMask) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + /* We only need to acquire the mutex here because we will not be updating + * anything that may affect the interrupt handler. We just need to ensure + * that the callback fields are not changed while we are in the critical + * section by calling either pmic_audio_set_callback() or + * pmic_audio_clear_callback(). + */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((func != (PMIC_AUDIO_CALLBACK *) NULL) && + (eventMask != (PMIC_AUDIO_EVENTS *) NULL)) { + + *func = event_state.callback; + *eventMask = event_state.eventMask; + + rc = PMIC_SUCCESS; + } + + /* Exit the critical section. */ + up(&mutex); + return rc; +} + +/*! + * @brief Enable the anti-pop circuitry to avoid extra noise when inserting + * or removing a external device (e.g., a headset). + * + * Enable the use of the built-in anti-pop circuitry to prevent noise from + * being generated when an external audio device is inserted or removed + * from an audio plug. A slow ramp speed may be needed to avoid extra noise. + * + * @param rampSpeed The desired anti-pop circuitry ramp speed. + * + * @retval PMIC_SUCCESS If the anti-pop circuitry was successfully + * enabled. + * @retval PMIC_ERROR If the anti-pop circuitry could not be + * enabled. + */ +PMIC_STATUS pmic_audio_antipop_enable(const PMIC_AUDIO_ANTI_POP_RAMP_SPEED + rampSpeed) +{ + PMIC_STATUS rc = PMIC_ERROR; + unsigned int reg_value = 0; + const unsigned int reg_mask = SET_BITS(regAUDIO_RX_0, BIASEN, 1) | + SET_BITS(regAUDIO_RX_0, BIASSPEED, 1); + + /* No critical section required here since we are not updating any + * global data. + */ + + /* + * Antipop is enabled by enabling the BIAS (BIASEN) and setting the + * BIASSPEED . + * BIASEN is just to make sure that BIAS is enabled + */ + reg_value = SET_BITS(regAUDIO_RX_0, BIASEN, 1) + | SET_BITS(regAUDIO_RX_0, BIASSPEED, 0) | SET_BITS(regAUDIO_RX_0, + HSLDETEN, 1); + rc = pmic_write_reg(REG_AUDIO_RX_0, reg_value, reg_mask); + return rc; +} + +/*! + * @brief Disable the anti-pop circuitry. + * + * Disable the use of the built-in anti-pop circuitry to prevent noise from + * being generated when an external audio device is inserted or removed + * from an audio plug. + * + * @retval PMIC_SUCCESS If the anti-pop circuitry was successfully + * disabled. + * @retval PMIC_ERROR If the anti-pop circuitry could not be + * disabled. + */ +PMIC_STATUS pmic_audio_antipop_disable(void) +{ + PMIC_STATUS rc = PMIC_ERROR; + const unsigned int reg_mask = SET_BITS(regAUDIO_RX_0, BIASSPEED, 1) | + SET_BITS(regAUDIO_RX_0, BIASEN, 1); + const unsigned int reg_write = SET_BITS(regAUDIO_RX_0, BIASSPEED, 1) | + SET_BITS(regAUDIO_RX_0, BIASEN, 0); + + /* No critical section required here since we are not updating any + * global data. + */ + + /* + * Antipop is disabled by setting BIASSPEED = 0. BIASEN bit remains set + * as only antipop needs to be disabled + */ + rc = pmic_write_reg(REG_AUDIO_RX_0, reg_write, reg_mask); + + return rc; +} + +/*! + * @brief Performs a reset of the Voice CODEC/Stereo DAC digital filter. + * + * The digital filter should be reset whenever the clock or sampling rate + * configuration has been changed. + * + * @param handle Device handle from pmic_audio_open() call. + * + * @retval PMIC_SUCCESS If the digital filter was successfully + * reset. + * @retval PMIC_ERROR If the digital filter could not be reset. + */ +PMIC_STATUS pmic_audio_digital_filter_reset(const PMIC_AUDIO_HANDLE handle) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_mask = 0; + + /* No critical section required here since we are not updating any + * global data. + */ + + if ((handle == stDAC.handle) && (stDAC.handleState == HANDLE_IN_USE)) { + reg_mask = SET_BITS(regST_DAC, STDCRESET, 1); + if (pmic_write_reg(REG_AUDIO_STEREO_DAC, reg_mask, + reg_mask) != PMIC_SUCCESS) { + rc = PMIC_ERROR; + } else { + pr_debug("STDAC filter reset\n"); + } + + } else if ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE)) { + reg_mask = SET_BITS(regAUD_CODEC, CDCRESET, 1); + if (pmic_write_reg(REG_AUDIO_CODEC, reg_mask, + reg_mask) != PMIC_SUCCESS) { + rc = PMIC_ERROR; + } else { + pr_debug("CODEC filter reset\n"); + } + } + return rc; +} + +/*! + * @brief Get the most recent PTT button voltage reading. + * + * This feature is not supported by mc13783 + * @param level PTT button level. + * + * @retval PMIC_SUCCESS If the most recent PTT button voltage was + * returned. + * @retval PMIC_PARAMETER_ERROR If a NULL pointer argument was given. + */ +PMIC_STATUS pmic_audio_get_ptt_button_level(unsigned int *const level) +{ + PMIC_STATUS rc = PMIC_NOT_SUPPORTED; + return rc; +} + +#ifdef DEBUG_AUDIO + +/*! + * @brief Provide a hexadecimal dump of all PMIC audio registers (DEBUG only) + * + * This function is intended strictly for debugging purposes only and will + * print the current values of the following PMIC registers: + * + * - AUD_CODEC + * - ST_DAC + * - AUDIO_RX_0 + * - AUDIO_RX_1 + * - AUDIO_TX + * - AUDIO_SSI_NW + * + * The register fields will not be decoded. + * + * Note that we don't dump any of the arbitration bits because we cannot + * access the true arbitration bit settings when reading the registers + * from the secondary SPI bus. + * + * Also note that we must not call this function with interrupts disabled, + * for example, while holding a spinlock, because calls to pmic_read_reg() + * eventually end up in the SPI driver which will want to perform a + * schedule() operation. If schedule() is called with interrupts disabled, + * then you will see messages like the following: + * + * BUG: scheduling while atomic: ... + * + */ +void pmic_audio_dump_registers(void) +{ + unsigned int reg_value = 0; + + /* Dump the AUD_CODEC (Voice CODEC) register. */ + if (pmic_read_reg(REG_AUDIO_CODEC, ®_value, REG_FULLMASK) + == PMIC_SUCCESS) { + pr_debug("Audio Codec = 0x%x\n", reg_value); + } else { + pr_debug("Failed to read audio codec\n"); + } + + /* Dump the ST DAC (Stereo DAC) register. */ + if (pmic_read_reg + (REG_AUDIO_STEREO_DAC, ®_value, REG_FULLMASK) == PMIC_SUCCESS) { + pr_debug("Stereo DAC = 0x%x\n", reg_value); + } else { + pr_debug("Failed to read Stereo DAC\n"); + } + + /* Dump the SSI NW register. */ + if (pmic_read_reg + (REG_AUDIO_SSI_NETWORK, ®_value, REG_FULLMASK) == PMIC_SUCCESS) { + pr_debug("SSI Network = 0x%x\n", reg_value); + } else { + pr_debug("Failed to read SSI network\n"); + } + + /* Dump the Audio RX 0 register. */ + if (pmic_read_reg(REG_AUDIO_RX_0, ®_value, REG_FULLMASK) + == PMIC_SUCCESS) { + pr_debug("Audio RX 0 = 0x%x\n", reg_value); + } else { + pr_debug("Failed to read audio RX 0\n"); + } + + /* Dump the Audio RX 1 register. */ + if (pmic_read_reg(REG_AUDIO_RX_1, ®_value, REG_FULLMASK) + == PMIC_SUCCESS) { + pr_debug("Audio RX 1 = 0x%x\n", reg_value); + } else { + pr_debug("Failed to read audio RX 1\n"); + } + /* Dump the Audio TX register. */ + if (pmic_read_reg(REG_AUDIO_TX, ®_value, REG_FULLMASK) == + PMIC_SUCCESS) { + pr_debug("Audio Tx = 0x%x\n", reg_value); + } else { + pr_debug("Failed to read audio TX\n"); + } + +} + +#endif /* DEBUG_AUDIO */ + +/*@}*/ + +/************************************************************************* + * General Voice CODEC configuration. + ************************************************************************* + */ + +/*! + * @name General Voice CODEC Setup and Configuration APIs + * Functions for general setup and configuration of the PMIC Voice + * CODEC hardware. + */ +/*@{*/ + +/*! + * @brief Set the Voice CODEC clock source and operating characteristics. + * + * Define the Voice CODEC clock source and operating characteristics. This + * must be done before the Voice CODEC is enabled. + * + * + * + * @param handle Device handle from pmic_audio_open() call. + * @param clockIn Select the clock signal source. + * @param clockFreq Select the clock signal frequency. + * @param samplingRate Select the audio data sampling rate. + * @param invert Enable inversion of the frame sync and/or + * bit clock inputs. + * + * @retval PMIC_SUCCESS If the Voice CODEC clock settings were + * successfully configured. + * @retval PMIC_PARAMETER_ERROR If the handle or clock configuration was + * invalid. + * @retval PMIC_ERROR If the Voice CODEC clock configuration + * could not be set. + */ +PMIC_STATUS pmic_audio_vcodec_set_clock(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_CLOCK_IN_SOURCE + clockIn, + const PMIC_AUDIO_VCODEC_CLOCK_IN_FREQ + clockFreq, + const PMIC_AUDIO_VCODEC_SAMPLING_RATE + samplingRate, + const PMIC_AUDIO_CLOCK_INVERT invert) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_value = 0; + unsigned int reg_mask = 0; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + /* Validate all of the calling parameters. */ + if (handle == (PMIC_AUDIO_HANDLE) NULL) { + rc = PMIC_PARAMETER_ERROR; + } else if ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE)) { + if ((clockIn != CLOCK_IN_CLIA) && (clockIn != CLOCK_IN_CLIB)) { + rc = PMIC_PARAMETER_ERROR; + } else if (!((clockFreq >= VCODEC_CLI_13MHZ) + && (clockFreq <= VCODEC_CLI_33_6MHZ))) { + rc = PMIC_PARAMETER_ERROR; + } else if ((samplingRate != VCODEC_RATE_8_KHZ) + && (samplingRate != VCODEC_RATE_16_KHZ)) { + rc = PMIC_PARAMETER_ERROR; + } else if (!((invert >= NO_INVERT) + && (invert <= INVERT_FRAMESYNC))) { + rc = PMIC_PARAMETER_ERROR; + } else { + /*reg_mask = SET_BITS(regAUD_CODEC, CDCCLK, 7) | + SET_BITS(regAUD_CODEC, CDCCLKSEL, 1) | + SET_BITS(regAUD_CODEC, CDCFS8K16K, 1) | + SET_BITS(regAUD_CODEC, CDCBCLINV, 1) | + SET_BITS(regAUD_CODEC, CDCFSINV, 1); */ + if (clockIn == CLOCK_IN_CLIA) { + reg_value = + SET_BITS(regAUD_CODEC, CDCCLKSEL, 0); + } else { + reg_value = + SET_BITS(regAUD_CODEC, CDCCLKSEL, 1); + } + reg_mask = SET_BITS(regAUD_CODEC, CDCCLKSEL, 1); + if (PMIC_SUCCESS != + pmic_write_reg(REG_AUDIO_CODEC, + reg_value, reg_mask)) + return PMIC_ERROR; + + reg_value = 0; + if (clockFreq == VCODEC_CLI_13MHZ) { + reg_value |= SET_BITS(regAUD_CODEC, CDCCLK, 0); + } else if (clockFreq == VCODEC_CLI_15_36MHZ) { + reg_value |= SET_BITS(regAUD_CODEC, CDCCLK, 1); + } else if (clockFreq == VCODEC_CLI_16_8MHZ) { + reg_value |= SET_BITS(regAUD_CODEC, CDCCLK, 2); + } else if (clockFreq == VCODEC_CLI_26MHZ) { + reg_value |= SET_BITS(regAUD_CODEC, CDCCLK, 4); + } else { + reg_value |= SET_BITS(regAUD_CODEC, CDCCLK, 7); + } + reg_mask = SET_BITS(regAUD_CODEC, CDCCLK, 7); + if (PMIC_SUCCESS != + pmic_write_reg(REG_AUDIO_CODEC, + reg_value, reg_mask)) + return PMIC_ERROR; + + reg_value = 0; + reg_mask = 0; + + if (samplingRate == VCODEC_RATE_8_KHZ) { + reg_value |= + SET_BITS(regAUD_CODEC, CDCFS8K16K, 0); + } else { + reg_value |= + SET_BITS(regAUD_CODEC, CDCFS8K16K, 1); + } + reg_mask = SET_BITS(regAUD_CODEC, CDCFS8K16K, 1); + if (PMIC_SUCCESS != + pmic_write_reg(REG_AUDIO_CODEC, + reg_value, reg_mask)) + return PMIC_ERROR; + reg_value = 0; + reg_mask = + SET_BITS(regAUD_CODEC, CDCBCLINV, + 1) | SET_BITS(regAUD_CODEC, CDCFSINV, 1); + + if (invert & INVERT_BITCLOCK) { + reg_value |= + SET_BITS(regAUD_CODEC, CDCBCLINV, 1); + } + if (invert & INVERT_FRAMESYNC) { + reg_value |= + SET_BITS(regAUD_CODEC, CDCFSINV, 1); + } + if (invert & NO_INVERT) { + reg_value |= + SET_BITS(regAUD_CODEC, CDCBCLINV, 0); + reg_value |= + SET_BITS(regAUD_CODEC, CDCFSINV, 0); + } + if (pmic_write_reg + (REG_AUDIO_CODEC, reg_value, + reg_mask) != PMIC_SUCCESS) { + rc = PMIC_ERROR; + } else { + pr_debug("CODEC clock set\n"); + vCodec.clockIn = clockIn; + vCodec.clockFreq = clockFreq; + vCodec.samplingRate = samplingRate; + vCodec.invert = invert; + } + + } + + } else { + rc = PMIC_PARAMETER_ERROR; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Get the Voice CODEC clock source and operating characteristics. + * + * Get the current Voice CODEC clock source and operating characteristics. + * + * @param handle Device handle from pmic_audio_open() call. + * @param clockIn The clock signal source. + * @param clockFreq The clock signal frequency. + * @param samplingRate The audio data sampling rate. + * @param invert Inversion of the frame sync and/or + * bit clock inputs is enabled/disabled. + * + * @retval PMIC_SUCCESS If the Voice CODEC clock settings were + * successfully retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle invalid. + * @retval PMIC_ERROR If the Voice CODEC clock configuration + * could not be retrieved. + */ +PMIC_STATUS pmic_audio_vcodec_get_clock(const PMIC_AUDIO_HANDLE handle, + PMIC_AUDIO_CLOCK_IN_SOURCE * + const clockIn, + PMIC_AUDIO_VCODEC_CLOCK_IN_FREQ * + const clockFreq, + PMIC_AUDIO_VCODEC_SAMPLING_RATE * + const samplingRate, + PMIC_AUDIO_CLOCK_INVERT * const invert) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + /* Use a critical section to ensure that we return a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE) && + (clockIn != (PMIC_AUDIO_CLOCK_IN_SOURCE *) NULL) && + (clockFreq != (PMIC_AUDIO_VCODEC_CLOCK_IN_FREQ *) NULL) && + (samplingRate != (PMIC_AUDIO_VCODEC_SAMPLING_RATE *) NULL) && + (invert != (PMIC_AUDIO_CLOCK_INVERT *) NULL)) { + *clockIn = vCodec.clockIn; + *clockFreq = vCodec.clockFreq; + *samplingRate = vCodec.samplingRate; + *invert = vCodec.invert; + + rc = PMIC_SUCCESS; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Set the Voice CODEC primary audio channel timeslot. + * + * Set the Voice CODEC primary audio channel timeslot. This function must be + * used if the default timeslot for the primary audio channel is to be changed. + * + * @param handle Device handle from pmic_audio_open() call. + * @param timeslot Select the primary audio channel timeslot. + * + * @retval PMIC_SUCCESS If the Voice CODEC primary audio channel + * timeslot was successfully configured. + * @retval PMIC_PARAMETER_ERROR If the handle or audio channel timeslot + * was invalid. + * @retval PMIC_ERROR If the Voice CODEC primary audio channel + * timeslot could not be set. + */ +PMIC_STATUS pmic_audio_vcodec_set_rxtx_timeslot(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_VCODEC_TIMESLOT + timeslot) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_write = 0; + const unsigned int reg_mask = SET_BITS(regSSI_NETWORK, CDCTXRXSLOT, 3); + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE) && + ((timeslot == USE_TS0) || (timeslot == USE_TS1) || + (timeslot == USE_TS2) || (timeslot == USE_TS3))) { + reg_write = SET_BITS(regSSI_NETWORK, CDCTXRXSLOT, timeslot); + + rc = pmic_write_reg(REG_AUDIO_SSI_NETWORK, reg_write, reg_mask); + + if (rc == PMIC_SUCCESS) { + vCodec.timeslot = timeslot; + } + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Get the current Voice CODEC primary audio channel timeslot. + * + * Get the current Voice CODEC primary audio channel timeslot. + * + * @param handle Device handle from pmic_audio_open() call. + * @param timeslot The primary audio channel timeslot. + * + * @retval PMIC_SUCCESS If the Voice CODEC primary audio channel + * timeslot was successfully retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the Voice CODEC primary audio channel + * timeslot could not be retrieved. + */ +PMIC_STATUS pmic_audio_vcodec_get_rxtx_timeslot(const PMIC_AUDIO_HANDLE handle, + PMIC_AUDIO_VCODEC_TIMESLOT * + const timeslot) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE) && + (timeslot != (PMIC_AUDIO_VCODEC_TIMESLOT *) NULL)) { + *timeslot = vCodec.timeslot; + + rc = PMIC_SUCCESS; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Set the Voice CODEC secondary recording audio channel timeslot. + * + * Set the Voice CODEC secondary audio channel timeslot. This function must be + * used if the default timeslot for the secondary audio channel is to be + * changed. The secondary audio channel timeslot is used to transmit the audio + * data that was recorded by the Voice CODEC from the secondary audio input + * channel. + * + * @param handle Device handle from pmic_audio_open() call. + * @param timeslot Select the secondary audio channel timeslot. + * + * @retval PMIC_SUCCESS If the Voice CODEC secondary audio channel + * timeslot was successfully configured. + * @retval PMIC_PARAMETER_ERROR If the handle or audio channel timeslot + * was invalid. + * @retval PMIC_ERROR If the Voice CODEC secondary audio channel + * timeslot could not be set. + */ +PMIC_STATUS pmic_audio_vcodec_set_secondary_txslot(const PMIC_AUDIO_HANDLE + handle, + const + PMIC_AUDIO_VCODEC_TIMESLOT + timeslot) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_mask = SET_BITS(regSSI_NETWORK, CDCTXSECSLOT, 3); + unsigned int reg_write = 0; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == vCodec.handle) && (vCodec.handleState == HANDLE_IN_USE)) { + /* How to handle primary slot and secondary slot being the same */ + if ((timeslot >= USE_TS0) && (timeslot <= USE_TS3) + && (timeslot != vCodec.timeslot)) { + reg_write = + SET_BITS(regSSI_NETWORK, CDCTXSECSLOT, timeslot); + + rc = pmic_write_reg(REG_AUDIO_SSI_NETWORK, + reg_write, reg_mask); + + if (rc == PMIC_SUCCESS) { + vCodec.secondaryTXtimeslot = timeslot; + } + } + + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Get the Voice CODEC secondary recording audio channel timeslot. + * + * Get the Voice CODEC secondary audio channel timeslot. + * + * @param handle Device handle from pmic_audio_open() call. + * @param timeslot The secondary audio channel timeslot. + * + * @retval PMIC_SUCCESS If the Voice CODEC secondary audio channel + * timeslot was successfully retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the Voice CODEC secondary audio channel + * timeslot could not be retrieved. + */ +PMIC_STATUS pmic_audio_vcodec_get_secondary_txslot(const PMIC_AUDIO_HANDLE + handle, + PMIC_AUDIO_VCODEC_TIMESLOT * + const timeslot) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE) && + (timeslot != (PMIC_AUDIO_VCODEC_TIMESLOT *) NULL)) { + rc = PMIC_SUCCESS; + *timeslot = vCodec.secondaryTXtimeslot; + } + + /* Exit the critical section. */ + up(&mutex); + return rc; +} + +/*! + * @brief Set/Enable the Voice CODEC options. + * + * Set or enable various Voice CODEC options. The available options include + * the use of dithering, highpass digital filters, and loopback modes. + * + * @param handle Device handle from pmic_audio_open() call. + * @param config The Voice CODEC options to enable. + * + * @retval PMIC_SUCCESS If the Voice CODEC options were + * successfully configured. + * @retval PMIC_PARAMETER_ERROR If the handle or Voice CODEC options + * were invalid. + * @retval PMIC_ERROR If the Voice CODEC options could not be + * successfully set/enabled. + */ +PMIC_STATUS pmic_audio_vcodec_set_config(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_VCODEC_CONFIG config) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_write = 0; + unsigned int reg_mask = 0; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == vCodec.handle) && (vCodec.handleState == HANDLE_IN_USE)) { + if (config & DITHERING) { + reg_write = SET_BITS(regAUD_CODEC, CDCDITH, 0); + reg_mask = SET_BITS(regAUD_CODEC, CDCDITH, 1); + } + + if (config & INPUT_HIGHPASS_FILTER) { + reg_write |= SET_BITS(regAUD_CODEC, AUDIHPF, 1); + reg_mask |= SET_BITS(regAUD_CODEC, AUDIHPF, 1); + } + + if (config & OUTPUT_HIGHPASS_FILTER) { + reg_write |= SET_BITS(regAUD_CODEC, AUDOHPF, 1); + reg_mask |= SET_BITS(regAUD_CODEC, AUDOHPF, 1); + } + + if (config & DIGITAL_LOOPBACK) { + reg_write |= SET_BITS(regAUD_CODEC, CDCDLM, 1); + reg_mask |= SET_BITS(regAUD_CODEC, CDCDLM, 1); + } + + if (config & ANALOG_LOOPBACK) { + reg_write |= SET_BITS(regAUD_CODEC, CDCALM, 1); + reg_mask |= SET_BITS(regAUD_CODEC, CDCALM, 1); + } + + if (config & VCODEC_MASTER_CLOCK_OUTPUTS) { + reg_write |= SET_BITS(regAUD_CODEC, CDCCLKEN, 1) | + SET_BITS(regAUD_CODEC, CDCTS, 0); + reg_mask |= SET_BITS(regAUD_CODEC, CDCCLKEN, 1) | + SET_BITS(regAUD_CODEC, CDCTS, 1); + + } + + if (config & TRISTATE_TS) { + reg_write |= SET_BITS(regAUD_CODEC, CDCTS, 1); + reg_mask |= SET_BITS(regAUD_CODEC, CDCTS, 1); + } + + if (reg_mask == 0) { + /* We should not reach this point without having to configure + * anything so we flag it as an error. + */ + rc = PMIC_ERROR; + } else { + rc = pmic_write_reg(REG_AUDIO_CODEC, + reg_write, reg_mask); + } + + if (rc == PMIC_SUCCESS) { + vCodec.config |= config; + } + } + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Clear/Disable the Voice CODEC options. + * + * Clear or disable various Voice CODEC options. + * + * @param handle Device handle from pmic_audio_open() call. + * @param config The Voice CODEC options to be cleared/disabled. + * + * @retval PMIC_SUCCESS If the Voice CODEC options were + * successfully cleared/disabled. + * @retval PMIC_PARAMETER_ERROR If the handle or the Voice CODEC options + * were invalid. + * @retval PMIC_ERROR If the Voice CODEC options could not be + * cleared/disabled. + */ +PMIC_STATUS pmic_audio_vcodec_clear_config(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_VCODEC_CONFIG + config) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_write = 0; + unsigned int reg_mask = 0; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == vCodec.handle) && (vCodec.handleState == HANDLE_IN_USE)) { + if (config & DITHERING) { + reg_mask = SET_BITS(regAUD_CODEC, CDCDITH, 1); + reg_write = SET_BITS(regAUD_CODEC, CDCDITH, 1); + } + + if (config & INPUT_HIGHPASS_FILTER) { + reg_mask |= SET_BITS(regAUD_CODEC, AUDIHPF, 1); + } + + if (config & OUTPUT_HIGHPASS_FILTER) { + reg_mask |= SET_BITS(regAUD_CODEC, AUDOHPF, 1); + } + + if (config & DIGITAL_LOOPBACK) { + reg_mask |= SET_BITS(regAUD_CODEC, CDCDLM, 1); + } + + if (config & ANALOG_LOOPBACK) { + reg_mask |= SET_BITS(regAUD_CODEC, CDCALM, 1); + } + + if (config & VCODEC_MASTER_CLOCK_OUTPUTS) { + reg_mask |= SET_BITS(regAUD_CODEC, CDCCLKEN, 1); + } + + if (config & TRISTATE_TS) { + reg_mask |= SET_BITS(regAUD_CODEC, CDCTS, 1); + } + + if (reg_mask == 0) { + /* We should not reach this point without having to configure + * anything so we flag it as an error. + */ + rc = PMIC_ERROR; + } else { + rc = pmic_write_reg(REG_AUDIO_CODEC, + reg_write, reg_mask); + } + + if (rc == PMIC_SUCCESS) { + vCodec.config |= config; + } + + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Get the current Voice CODEC options. + * + * Get the current Voice CODEC options. + * + * @param handle Device handle from pmic_audio_open() call. + * @param config The current set of Voice CODEC options. + * + * @retval PMIC_SUCCESS If the Voice CODEC options were + * successfully retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the Voice CODEC options could not be + * retrieved. + */ +PMIC_STATUS pmic_audio_vcodec_get_config(const PMIC_AUDIO_HANDLE handle, + PMIC_AUDIO_VCODEC_CONFIG * + const config) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE) && + (config != (PMIC_AUDIO_VCODEC_CONFIG *) NULL)) { + *config = vCodec.config; + + rc = PMIC_SUCCESS; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Enable the Voice CODEC bypass audio pathway. + * + * Enables the Voice CODEC bypass pathway for audio data. This allows direct + * output of the voltages on the TX data bus line to the output amplifiers + * (bypassing the digital-to-analog converters within the Voice CODEC). + * + * @param handle Device handle from pmic_audio_open() call. + * + * @retval PMIC_SUCCESS If the Voice CODEC bypass was successfully + * enabled. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the Voice CODEC bypass could not be + * enabled. + */ +PMIC_STATUS pmic_audio_vcodec_enable_bypass(const PMIC_AUDIO_HANDLE handle) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + const unsigned int reg_write = SET_BITS(regAUD_CODEC, CDCBYP, 1); + const unsigned int reg_mask = SET_BITS(regAUD_CODEC, CDCBYP, 1); + + /* No critical section required here since we are not updating any + * global data. + */ + + if ((handle == vCodec.handle) && (vCodec.handleState == HANDLE_IN_USE)) { + rc = pmic_write_reg(REG_AUDIO_CODEC, reg_write, reg_mask); + } + + return rc; +} + +/*! + * @brief Disable the Voice CODEC bypass audio pathway. + * + * Disables the Voice CODEC bypass pathway for audio data. This means that + * the TX data bus line will deliver digital data to the digital-to-analog + * converters within the Voice CODEC. + * + * @param handle Device handle from pmic_audio_open() call. + * + * @retval PMIC_SUCCESS If the Voice CODEC bypass was successfully + * disabled. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the Voice CODEC bypass could not be + * disabled. + */ +PMIC_STATUS pmic_audio_vcodec_disable_bypass(const PMIC_AUDIO_HANDLE handle) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + const unsigned int reg_write = 0; + const unsigned int reg_mask = SET_BITS(regAUD_CODEC, CDCBYP, 1); + + /* No critical section required here since we are not updating any + * global data. + */ + + if ((handle == vCodec.handle) && (vCodec.handleState == HANDLE_IN_USE)) { + rc = pmic_write_reg(REG_AUDIO_CODEC, reg_write, reg_mask); + } + + return rc; +} + +/*@}*/ + +/************************************************************************* + * General Stereo DAC configuration. + ************************************************************************* + */ + +/*! + * @name General Stereo DAC Setup and Configuration APIs + * Functions for general setup and configuration of the PMIC Stereo + * DAC hardware. + */ +/*@{*/ + +/*! + * @brief Set the Stereo DAC clock source and operating characteristics. + * + * Define the Stereo DAC clock source and operating characteristics. This + * must be done before the Stereo DAC is enabled. + * + * + * @param handle Device handle from pmic_audio_open() call. + * @param clockIn Select the clock signal source. + * @param clockFreq Select the clock signal frequency. + * @param samplingRate Select the audio data sampling rate. + * @param invert Enable inversion of the frame sync and/or + * bit clock inputs. + * + * @retval PMIC_SUCCESS If the Stereo DAC clock settings were + * successfully configured. + * @retval PMIC_PARAMETER_ERROR If the handle or clock configuration was + * invalid. + * @retval PMIC_ERROR If the Stereo DAC clock configuration + * could not be set. + */ +PMIC_STATUS pmic_audio_stdac_set_clock(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_CLOCK_IN_SOURCE clockIn, + const PMIC_AUDIO_STDAC_CLOCK_IN_FREQ + clockFreq, + const PMIC_AUDIO_STDAC_SAMPLING_RATE + samplingRate, + const PMIC_AUDIO_CLOCK_INVERT invert) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_value = 0; + unsigned int reg_mask = 0; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + /* Validate all of the calling parameters. */ + if (handle == (PMIC_AUDIO_HANDLE) NULL) { + rc = PMIC_PARAMETER_ERROR; + } else if ((handle == stDAC.handle) && + (stDAC.handleState == HANDLE_IN_USE)) { + if ((clockIn != CLOCK_IN_CLIA) && (clockIn != CLOCK_IN_CLIB)) { + rc = PMIC_PARAMETER_ERROR; + } else if ((stDAC.masterSlave == BUS_MASTER_MODE) + && !((clockFreq >= STDAC_CLI_3_36864MHZ) + && (clockFreq <= STDAC_CLI_33_6MHZ))) { + rc = PMIC_PARAMETER_ERROR; + } else if ((stDAC.masterSlave == BUS_SLAVE_MODE) + && !((clockFreq >= STDAC_MCLK_PLL_DISABLED) + && (clockFreq <= STDAC_BCLK_IN_PLL))) { + rc = PMIC_PARAMETER_ERROR; + } else if (!((samplingRate >= STDAC_RATE_8_KHZ) + && (samplingRate <= STDAC_RATE_96_KHZ))) { + rc = PMIC_PARAMETER_ERROR; + } + /* + else if(!((invert >= NO_INVERT) && (invert <= INVERT_FRAMESYNC))) + { + rc = PMIC_PARAMETER_ERROR; + } */ + else { + reg_mask = SET_BITS(regST_DAC, STDCCLK, 7) | + SET_BITS(regST_DAC, STDCCLKSEL, 1) | + SET_BITS(regST_DAC, SR, 15) | + SET_BITS(regST_DAC, STDCBCLINV, 1) | + SET_BITS(regST_DAC, STDCFSINV, 1); + if (clockIn == CLOCK_IN_CLIA) { + reg_value = SET_BITS(regST_DAC, STDCCLKSEL, 0); + } else { + reg_value = SET_BITS(regST_DAC, STDCCLKSEL, 1); + } + /* How to take care of sample rates in SLAVE mode */ + if ((clockFreq == STDAC_CLI_3_36864MHZ) + || ((clockFreq == STDAC_FSYNC_IN_PLL))) { + reg_value |= SET_BITS(regST_DAC, STDCCLK, 6); + } else if ((clockFreq == STDAC_CLI_12MHZ) + || (clockFreq == STDAC_MCLK_PLL_DISABLED)) { + reg_value |= SET_BITS(regST_DAC, STDCCLK, 5); + } else if (clockFreq == STDAC_CLI_13MHZ) { + reg_value |= SET_BITS(regST_DAC, STDCCLK, 0); + } else if (clockFreq == STDAC_CLI_15_36MHZ) { + reg_value |= SET_BITS(regST_DAC, STDCCLK, 1); + } else if (clockFreq == STDAC_CLI_16_8MHZ) { + reg_value |= SET_BITS(regST_DAC, STDCCLK, 2); + } else if (clockFreq == STDAC_CLI_26MHZ) { + reg_value |= SET_BITS(regST_DAC, STDCCLK, 4); + } else if ((clockFreq == STDAC_CLI_33_6MHZ) + || (clockFreq == STDAC_BCLK_IN_PLL)) { + reg_value |= SET_BITS(regST_DAC, STDCCLK, 7); + } + + reg_value |= SET_BITS(regST_DAC, SR, samplingRate); + + if (invert & INVERT_BITCLOCK) { + reg_value |= SET_BITS(regST_DAC, STDCBCLINV, 1); + } + if (invert & INVERT_FRAMESYNC) { + reg_value |= SET_BITS(regST_DAC, STDCFSINV, 1); + } + if (invert & NO_INVERT) { + reg_value |= SET_BITS(regST_DAC, STDCBCLINV, 0); + reg_value |= SET_BITS(regST_DAC, STDCFSINV, 0); + } + if (pmic_write_reg + (REG_AUDIO_STEREO_DAC, reg_value, + reg_mask) != PMIC_SUCCESS) { + rc = PMIC_ERROR; + } else { + pr_debug("STDAC clock set\n"); + rc = PMIC_SUCCESS; + stDAC.clockIn = clockIn; + stDAC.clockFreq = clockFreq; + stDAC.samplingRate = samplingRate; + stDAC.invert = invert; + } + + } + + } else { + rc = PMIC_PARAMETER_ERROR; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Get the Stereo DAC clock source and operating characteristics. + * + * Get the current Stereo DAC clock source and operating characteristics. + * + * @param handle Device handle from pmic_audio_open() call. + * @param clockIn The clock signal source. + * @param clockFreq The clock signal frequency. + * @param samplingRate The audio data sampling rate. + * @param invert Inversion of the frame sync and/or + * bit clock inputs is enabled/disabled. + * + * @retval PMIC_SUCCESS If the Stereo DAC clock settings were + * successfully retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle invalid. + * @retval PMIC_ERROR If the Stereo DAC clock configuration + * could not be retrieved. + */ +PMIC_STATUS pmic_audio_stdac_get_clock(const PMIC_AUDIO_HANDLE handle, + PMIC_AUDIO_CLOCK_IN_SOURCE * + const clockIn, + PMIC_AUDIO_STDAC_SAMPLING_RATE * + const samplingRate, + PMIC_AUDIO_STDAC_CLOCK_IN_FREQ * + const clockFreq, + PMIC_AUDIO_CLOCK_INVERT * const invert) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == stDAC.handle) && + (stDAC.handleState == HANDLE_IN_USE) && + (clockIn != (PMIC_AUDIO_CLOCK_IN_SOURCE *) NULL) && + (samplingRate != (PMIC_AUDIO_STDAC_SAMPLING_RATE *) NULL) && + (clockFreq != (PMIC_AUDIO_STDAC_CLOCK_IN_FREQ *) NULL) && + (invert != (PMIC_AUDIO_CLOCK_INVERT *) NULL)) { + *clockIn = stDAC.clockIn; + *samplingRate = stDAC.samplingRate; + *clockFreq = stDAC.clockFreq; + *invert = stDAC.invert; + rc = PMIC_SUCCESS; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Set the Stereo DAC primary audio channel timeslot. + * + * Set the Stereo DAC primary audio channel timeslot. This function must be + * used if the default timeslot for the primary audio channel is to be changed. + * + * @param handle Device handle from pmic_audio_open() call. + * @param timeslot Select the primary audio channel timeslot. + * + * @retval PMIC_SUCCESS If the Stereo DAC primary audio channel + * timeslot was successfully configured. + * @retval PMIC_PARAMETER_ERROR If the handle or audio channel timeslot + * was invalid. + * @retval PMIC_ERROR If the Stereo DAC primary audio channel + * timeslot could not be set. + */ +PMIC_STATUS pmic_audio_stdac_set_rxtx_timeslot(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_STDAC_TIMESLOTS + timeslot) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_mask = SET_BITS(regSSI_NETWORK, STDCRXSLOT, 3); + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == stDAC.handle) && (stDAC.handleState == HANDLE_IN_USE)) { + if ((timeslot == USE_TS0_TS1) || (timeslot == USE_TS2_TS3) + || (timeslot == USE_TS4_TS5) || (timeslot == USE_TS6_TS7)) { + if (pmic_write_reg + (REG_AUDIO_SSI_NETWORK, timeslot, + reg_mask) != PMIC_SUCCESS) { + rc = PMIC_ERROR; + } else { + pr_debug("STDAC primary timeslot set\n"); + stDAC.timeslot = timeslot; + rc = PMIC_SUCCESS; + } + + } else { + rc = PMIC_PARAMETER_ERROR; + } + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Get the current Stereo DAC primary audio channel timeslot. + * + * Get the current Stereo DAC primary audio channel timeslot. + * + * @param handle Device handle from pmic_audio_open() call. + * @param timeslot The primary audio channel timeslot. + * + * @retval PMIC_SUCCESS If the Stereo DAC primary audio channel + * timeslot was successfully retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the Stereo DAC primary audio channel + * timeslot could not be retrieved. + */ +PMIC_STATUS pmic_audio_stdac_get_rxtx_timeslot(const PMIC_AUDIO_HANDLE handle, + PMIC_AUDIO_STDAC_TIMESLOTS * + const timeslot) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == stDAC.handle) && + (stDAC.handleState == HANDLE_IN_USE) && + (timeslot != (PMIC_AUDIO_STDAC_TIMESLOTS *) NULL)) { + *timeslot = stDAC.timeslot; + + rc = PMIC_SUCCESS; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Set/Enable the Stereo DAC options. + * + * Set or enable various Stereo DAC options. The available options include + * resetting the digital filter and enabling the bus master clock outputs. + * + * @param handle Device handle from pmic_audio_open() call. + * @param config The Stereo DAC options to enable. + * + * @retval PMIC_SUCCESS If the Stereo DAC options were + * successfully configured. + * @retval PMIC_PARAMETER_ERROR If the handle or Stereo DAC options + * were invalid. + * @retval PMIC_ERROR If the Stereo DAC options could not be + * successfully set/enabled. + */ +PMIC_STATUS pmic_audio_stdac_set_config(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_STDAC_CONFIG config) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_write = 0; + unsigned int reg_mask = 0; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == stDAC.handle) && (stDAC.handleState == HANDLE_IN_USE)) { + if (config & STDAC_MASTER_CLOCK_OUTPUTS) { + reg_write |= SET_BITS(regST_DAC, STDCCLKEN, 1); + reg_mask |= SET_BITS(regST_DAC, STDCCLKEN, 1); + } + + rc = pmic_write_reg(REG_AUDIO_STEREO_DAC, reg_write, reg_mask); + + if (rc == PMIC_SUCCESS) { + stDAC.config |= config; + pr_debug("STDAC config set\n"); + + } + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Clear/Disable the Stereo DAC options. + * + * Clear or disable various Stereo DAC options. + * + * @param handle Device handle from pmic_audio_open() call. + * @param config The Stereo DAC options to be cleared/disabled. + * + * @retval PMIC_SUCCESS If the Stereo DAC options were + * successfully cleared/disabled. + * @retval PMIC_PARAMETER_ERROR If the handle or the Stereo DAC options + * were invalid. + * @retval PMIC_ERROR If the Stereo DAC options could not be + * cleared/disabled. + */ +PMIC_STATUS pmic_audio_stdac_clear_config(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_STDAC_CONFIG config) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + unsigned int reg_write = 0; + unsigned int reg_mask = 0; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == stDAC.handle) && (stDAC.handleState == HANDLE_IN_USE)) { + + if (config & STDAC_MASTER_CLOCK_OUTPUTS) { + reg_mask |= SET_BITS(regST_DAC, STDCCLKEN, 1); + } + + if (reg_mask != 0) { + rc = pmic_write_reg(REG_AUDIO_STEREO_DAC, + reg_write, reg_mask); + + if (rc == PMIC_SUCCESS) { + stDAC.config &= ~config; + } + } + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Get the current Stereo DAC options. + * + * Get the current Stereo DAC options. + * + * @param handle Device handle from pmic_audio_open() call. + * @param config The current set of Stereo DAC options. + * + * @retval PMIC_SUCCESS If the Stereo DAC options were + * successfully retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the Stereo DAC options could not be + * retrieved. + */ +PMIC_STATUS pmic_audio_stdac_get_config(const PMIC_AUDIO_HANDLE handle, + PMIC_AUDIO_STDAC_CONFIG * const config) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == stDAC.handle) && + (stDAC.handleState == HANDLE_IN_USE) && + (config != (PMIC_AUDIO_STDAC_CONFIG *) NULL)) { + *config = stDAC.config; + + rc = PMIC_SUCCESS; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*@}*/ + +/************************************************************************* + * Audio input section configuration. + ************************************************************************* + */ + +/*! + * @name Audio Input Setup and Configuration APIs + * Functions for general setup and configuration of the PMIC audio + * input hardware. + */ +/*@{*/ + +/*! + * @brief Set/Enable the audio input section options. + * + * Set or enable various audio input section options. The only available + * option right now is to enable the automatic disabling of the microphone + * input amplifiers when a microphone/headset is inserted or removed. + * NOT SUPPORTED BY MC13783 + * + * @param handle Device handle from pmic_audio_open() call. + * @param config The audio input section options to enable. + * + * @retval PMIC_SUCCESS If the audio input section options were + * successfully configured. + * @retval PMIC_PARAMETER_ERROR If the handle or audio input section + * options were invalid. + * @retval PMIC_ERROR If the audio input section options could + * not be successfully set/enabled. + */ +PMIC_STATUS pmic_audio_input_set_config(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_INPUT_CONFIG config) +{ + PMIC_STATUS rc = PMIC_NOT_SUPPORTED; + return rc; +} + +/*! + * @brief Clear/Disable the audio input section options. + * + * Clear or disable various audio input section options. + * + * @param handle Device handle from pmic_audio_open() call. + * @param config The audio input section options to be + * cleared/disabled. + * NOT SUPPORTED BY MC13783 + * + * @retval PMIC_SUCCESS If the audio input section options were + * successfully cleared/disabled. + * @retval PMIC_PARAMETER_ERROR If the handle or the audio input section + * options were invalid. + * @retval PMIC_ERROR If the audio input section options could + * not be cleared/disabled. + */ +PMIC_STATUS pmic_audio_input_clear_config(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_INPUT_CONFIG config) +{ + PMIC_STATUS rc = PMIC_NOT_SUPPORTED; + return rc; + +} + +/*! + * @brief Get the current audio input section options. + * + * Get the current audio input section options. + * + * @param[in] handle Device handle from pmic_audio_open() call. + * @param[out] config The current set of audio input section options. + * NOT SUPPORTED BY MC13783 + * + * @retval PMIC_SUCCESS If the audio input section options were + * successfully retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the audio input section options could + * not be retrieved. + */ +PMIC_STATUS pmic_audio_input_get_config(const PMIC_AUDIO_HANDLE handle, + PMIC_AUDIO_INPUT_CONFIG * const config) +{ + PMIC_STATUS rc = PMIC_NOT_SUPPORTED; + return rc; +} + +/*@}*/ + +/************************************************************************* + * Audio recording using the Voice CODEC. + ************************************************************************* + */ + +/*! + * @name Audio Recording Using the Voice CODEC Setup and Configuration APIs + * Functions for general setup and configuration of the PMIC Voice CODEC + * to perform audio recording. + */ +/*@{*/ + +/*! + * @brief Select the microphone inputs to be used for Voice CODEC recording. + * + * Select left (mc13783-only) and right microphone inputs for Voice CODEC + * recording. It is possible to disable or not use a particular microphone + * input channel by specifying NO_MIC as a parameter. + * + * @param handle Device handle from pmic_audio_open() call. + * @param leftChannel Select the left microphone input channel. + * @param rightChannel Select the right microphone input channel. + * + * @retval PMIC_SUCCESS If the microphone input channels were + * successfully enabled. + * @retval PMIC_PARAMETER_ERROR If the handle or microphone input ports + * were invalid. + * @retval PMIC_ERROR If the microphone input channels could + * not be successfully enabled. + */ +PMIC_STATUS pmic_audio_vcodec_set_mic(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_INPUT_PORT leftChannel, + const PMIC_AUDIO_INPUT_PORT rightChannel) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_write = 0; + unsigned int reg_mask = 0; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == vCodec.handle) && (vCodec.handleState == HANDLE_IN_USE)) { + if (!((leftChannel == NO_MIC) || (leftChannel == MIC1_LEFT))) { + rc = PMIC_PARAMETER_ERROR; + } else if (!((rightChannel == NO_MIC) + || (rightChannel == MIC1_RIGHT_MIC_MONO) + || (rightChannel == TXIN_EXT) + || (rightChannel == MIC2_AUX))) { + rc = PMIC_PARAMETER_ERROR; + } else { + if (leftChannel == NO_MIC) { + } else { /* Left channel MIC enable */ + reg_mask = SET_BITS(regAUDIO_TX, AMC1LEN, 1) | + SET_BITS(regAUDIO_TX, RXINREC, 1); + reg_write = SET_BITS(regAUDIO_TX, AMC1LEN, 1) | + SET_BITS(regAUDIO_TX, RXINREC, 0); + } + /*For right channel enable one and clear the other two as well as RXINREC */ + if (rightChannel == NO_MIC) { + } else if (rightChannel == MIC1_RIGHT_MIC_MONO) { + reg_mask |= SET_BITS(regAUDIO_TX, AMC1REN, 1) | + SET_BITS(regAUDIO_TX, RXINREC, 1) | + SET_BITS(regAUDIO_TX, AMC2EN, 1) | + SET_BITS(regAUDIO_TX, ATXINEN, 1); + reg_write |= SET_BITS(regAUDIO_TX, AMC1REN, 1) | + SET_BITS(regAUDIO_TX, RXINREC, 0) | + SET_BITS(regAUDIO_TX, AMC2EN, 0) | + SET_BITS(regAUDIO_TX, ATXINEN, 0); + } else if (rightChannel == MIC2_AUX) { + reg_mask |= SET_BITS(regAUDIO_TX, AMC1REN, 1) | + SET_BITS(regAUDIO_TX, RXINREC, 1) | + SET_BITS(regAUDIO_TX, AMC2EN, 1) | + SET_BITS(regAUDIO_TX, ATXINEN, 1); + reg_write |= SET_BITS(regAUDIO_TX, AMC1REN, 0) | + SET_BITS(regAUDIO_TX, RXINREC, 0) | + SET_BITS(regAUDIO_TX, AMC2EN, 1) | + SET_BITS(regAUDIO_TX, ATXINEN, 0); + } else { /* TX line in */ + reg_mask |= SET_BITS(regAUDIO_TX, AMC1REN, 1) | + SET_BITS(regAUDIO_TX, RXINREC, 1) | + SET_BITS(regAUDIO_TX, AMC2EN, 1) | + SET_BITS(regAUDIO_TX, ATXINEN, 1); + reg_write |= SET_BITS(regAUDIO_TX, AMC1REN, 0) | + SET_BITS(regAUDIO_TX, RXINREC, 0) | + SET_BITS(regAUDIO_TX, AMC2EN, 0) | + SET_BITS(regAUDIO_TX, ATXINEN, 1); + } + + if (reg_mask == 0) { + rc = PMIC_PARAMETER_ERROR; + } else { + rc = pmic_write_reg(REG_AUDIO_TX, + reg_write, reg_mask); + + if (rc == PMIC_SUCCESS) { + pr_debug + ("MIC inputs configured successfully\n"); + vCodec.leftChannelMic.mic = leftChannel; + vCodec.rightChannelMic.mic = + rightChannel; + + } + } + } + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Get the current microphone inputs being used for Voice CODEC + * recording. + * + * Get the left (mc13783-only) and right microphone inputs currently being + * used for Voice CODEC recording. + * + * @param handle Device handle from pmic_audio_open() call. + * @param leftChannel The left microphone input channel. + * @param rightChannel The right microphone input channel. + * + * @retval PMIC_SUCCESS If the microphone input channels were + * successfully retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the microphone input channels could + * not be retrieved. + */ +PMIC_STATUS pmic_audio_vcodec_get_mic(const PMIC_AUDIO_HANDLE handle, + PMIC_AUDIO_INPUT_PORT * const leftChannel, + PMIC_AUDIO_INPUT_PORT * + const rightChannel) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE) && + (leftChannel != (PMIC_AUDIO_INPUT_PORT *) NULL) && + (rightChannel != (PMIC_AUDIO_INPUT_PORT *) NULL)) { + *leftChannel = vCodec.leftChannelMic.mic; + *rightChannel = vCodec.rightChannelMic.mic; + rc = PMIC_SUCCESS; + } + + /* Exit the critical section. */ + up(&mutex); + return rc; +} + +/*! + * @brief Enable/disable the microphone input. + * + * This function enables/disables the current microphone input channel. The + * input amplifier is automatically turned off when the microphone input is + * disabled. + * + * @param handle Device handle from pmic_audio_open() call. + * @param leftChannel The left microphone input channel state. + * @param rightChannel the right microphone input channel state. + * + * @retval PMIC_SUCCESS If the microphone input channels were + * successfully reconfigured. + * @retval PMIC_PARAMETER_ERROR If the handle or microphone input states + * were invalid. + * @retval PMIC_ERROR If the microphone input channels could + * not be reconfigured. + */ +PMIC_STATUS pmic_audio_vcodec_set_mic_on_off(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_INPUT_MIC_STATE + leftChannel, + const PMIC_AUDIO_INPUT_MIC_STATE + rightChannel) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_write = 0; + unsigned int reg_mask = 0; + unsigned int curr_left = 0; + unsigned int curr_right = 0; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == vCodec.handle) && (vCodec.handleState == HANDLE_IN_USE)) { + curr_left = vCodec.leftChannelMic.mic; + curr_right = vCodec.rightChannelMic.mic; + if ((curr_left == NO_MIC) && (curr_right == NO_MIC)) { + rc = PMIC_PARAMETER_ERROR; + } else { + if (curr_left == MIC1_LEFT) { + if ((leftChannel == MICROPHONE_ON) && + (vCodec.leftChannelMic.micOnOff == + MICROPHONE_OFF)) { + /* Enable the microphone */ + reg_mask |= + SET_BITS(regAUDIO_TX, AMC1LEN, + 1) | SET_BITS(regAUDIO_TX, + RXINREC, 1); + reg_write |= + SET_BITS(regAUDIO_TX, AMC1LEN, + 1) | SET_BITS(regAUDIO_TX, + RXINREC, 0); + + } else if ((leftChannel == MICROPHONE_OFF) && + (vCodec.leftChannelMic.micOnOff == + MICROPHONE_ON)) { + /* Disable the microphone */ + reg_mask |= + SET_BITS(regAUDIO_TX, AMC1LEN, + 1) | SET_BITS(regAUDIO_TX, + RXINREC, 1); + reg_write |= + SET_BITS(regAUDIO_TX, AMC1LEN, + 0) | SET_BITS(regAUDIO_TX, + RXINREC, 0); + + } else { + /* Both are in same state . Nothing to be done */ + } + + } + if (curr_right == MIC1_RIGHT_MIC_MONO) { + if ((rightChannel == MICROPHONE_ON) && + (vCodec.leftChannelMic.micOnOff == + MICROPHONE_OFF)) { + /* Enable the microphone */ + reg_mask |= + SET_BITS(regAUDIO_TX, AMC1REN, + 1) | SET_BITS(regAUDIO_TX, + RXINREC, + 1) | + SET_BITS(regAUDIO_TX, AMC2EN, + 1) | SET_BITS(regAUDIO_TX, + ATXINEN, 1); + reg_write |= + SET_BITS(regAUDIO_TX, AMC1REN, + 1) | SET_BITS(regAUDIO_TX, + RXINREC, + 0) | + SET_BITS(regAUDIO_TX, AMC2EN, + 0) | SET_BITS(regAUDIO_TX, + ATXINEN, 0); + } else if ((rightChannel == MICROPHONE_OFF) + && (vCodec.leftChannelMic.micOnOff == + MICROPHONE_ON)) { + /* Disable the microphone */ + reg_mask |= + SET_BITS(regAUDIO_TX, AMC1REN, + 1) | SET_BITS(regAUDIO_TX, + RXINREC, + 1) | + SET_BITS(regAUDIO_TX, AMC2EN, + 1) | SET_BITS(regAUDIO_TX, + ATXINEN, 1); + reg_write |= + SET_BITS(regAUDIO_TX, AMC1REN, + 0) | SET_BITS(regAUDIO_TX, + RXINREC, + 0) | + SET_BITS(regAUDIO_TX, AMC2EN, + 0) | SET_BITS(regAUDIO_TX, + ATXINEN, 0); + } else { + /* Both are in same state . Nothing to be done */ + } + } else if (curr_right == MIC2_AUX) { + if ((rightChannel == MICROPHONE_ON) + && (vCodec.leftChannelMic.micOnOff == + MICROPHONE_OFF)) { + /* Enable the microphone */ + reg_mask |= + SET_BITS(regAUDIO_TX, AMC1REN, + 1) | SET_BITS(regAUDIO_TX, + RXINREC, + 1) | + SET_BITS(regAUDIO_TX, AMC2EN, + 1) | SET_BITS(regAUDIO_TX, + ATXINEN, 1); + reg_write |= + SET_BITS(regAUDIO_TX, AMC1REN, + 0) | SET_BITS(regAUDIO_TX, + RXINREC, + 0) | + SET_BITS(regAUDIO_TX, AMC2EN, + 1) | SET_BITS(regAUDIO_TX, + ATXINEN, 0); + } else if ((rightChannel == MICROPHONE_OFF) + && (vCodec.leftChannelMic.micOnOff == + MICROPHONE_ON)) { + /* Disable the microphone */ + reg_mask |= + SET_BITS(regAUDIO_TX, AMC1REN, + 1) | SET_BITS(regAUDIO_TX, + RXINREC, + 1) | + SET_BITS(regAUDIO_TX, AMC2EN, + 1) | SET_BITS(regAUDIO_TX, + ATXINEN, 1); + reg_write |= + SET_BITS(regAUDIO_TX, AMC1REN, + 0) | SET_BITS(regAUDIO_TX, + RXINREC, + 0) | + SET_BITS(regAUDIO_TX, AMC2EN, + 0) | SET_BITS(regAUDIO_TX, + ATXINEN, 0); + } else { + /* Both are in same state . Nothing to be done */ + } + } else if (curr_right == TXIN_EXT) { + if ((rightChannel == MICROPHONE_ON) + && (vCodec.leftChannelMic.micOnOff == + MICROPHONE_OFF)) { + /* Enable the microphone */ + reg_mask |= + SET_BITS(regAUDIO_TX, AMC1REN, + 1) | SET_BITS(regAUDIO_TX, + RXINREC, + 1) | + SET_BITS(regAUDIO_TX, AMC2EN, + 1) | SET_BITS(regAUDIO_TX, + ATXINEN, 1); + reg_write |= + SET_BITS(regAUDIO_TX, AMC1REN, + 0) | SET_BITS(regAUDIO_TX, + RXINREC, + 0) | + SET_BITS(regAUDIO_TX, AMC2EN, + 0) | SET_BITS(regAUDIO_TX, + ATXINEN, 1); + } else if ((rightChannel == MICROPHONE_OFF) + && (vCodec.leftChannelMic.micOnOff == + MICROPHONE_ON)) { + /* Disable the microphone */ + reg_mask |= + SET_BITS(regAUDIO_TX, AMC1REN, + 1) | SET_BITS(regAUDIO_TX, + RXINREC, + 1) | + SET_BITS(regAUDIO_TX, AMC2EN, + 1) | SET_BITS(regAUDIO_TX, + ATXINEN, 1); + reg_write |= + SET_BITS(regAUDIO_TX, AMC1REN, + 0) | SET_BITS(regAUDIO_TX, + RXINREC, + 0) | + SET_BITS(regAUDIO_TX, AMC2EN, + 0) | SET_BITS(regAUDIO_TX, + ATXINEN, 0); + } else { + /* Both are in same state . Nothing to be done */ + } + } + if (reg_mask == 0) { + } else { + rc = pmic_write_reg(REG_AUDIO_TX, + reg_write, reg_mask); + + if (rc == PMIC_SUCCESS) { + pr_debug + ("MIC states configured successfully\n"); + vCodec.leftChannelMic.micOnOff = + leftChannel; + vCodec.rightChannelMic.micOnOff = + rightChannel; + } + } + } + + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Return the current state of the microphone inputs. + * + * This function returns the current state (on/off) of the microphone + * input channels. + * + * @param handle Device handle from pmic_audio_open() call. + * @param leftChannel The current left microphone input channel + * state. + * @param rightChannel the current right microphone input channel + * state. + * + * @retval PMIC_SUCCESS If the microphone input channel states + * were successfully retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the microphone input channel states + * could not be retrieved. + */ +PMIC_STATUS pmic_audio_vcodec_get_mic_on_off(const PMIC_AUDIO_HANDLE handle, + PMIC_AUDIO_INPUT_MIC_STATE * + const leftChannel, + PMIC_AUDIO_INPUT_MIC_STATE * + const rightChannel) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE) && + (leftChannel != (PMIC_AUDIO_INPUT_MIC_STATE *) NULL) && + (rightChannel != (PMIC_AUDIO_INPUT_MIC_STATE *) NULL)) { + *leftChannel = vCodec.leftChannelMic.micOnOff; + *rightChannel = vCodec.rightChannelMic.micOnOff; + + rc = PMIC_SUCCESS; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Set the microphone input amplifier mode and gain level. + * + * This function sets the current microphone input amplifier operating mode + * and gain level. + * + * @param handle Device handle from pmic_audio_open() call. + * @param leftChannelMode The left microphone input amplifier mode. + * @param leftChannelGain The left microphone input amplifier gain level. + * @param rightChannelMode The right microphone input amplifier mode. + * @param rightChannelGain The right microphone input amplifier gain + * level. + * + * @retval PMIC_SUCCESS If the microphone input amplifiers were + * successfully reconfigured. + * @retval PMIC_PARAMETER_ERROR If the handle or microphone input amplifier + * modes or gain levels were invalid. + * @retval PMIC_ERROR If the microphone input amplifiers could + * not be reconfigured. + */ +PMIC_STATUS pmic_audio_vcodec_set_record_gain(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_MIC_AMP_MODE + leftChannelMode, + const PMIC_AUDIO_MIC_GAIN + leftChannelGain, + const PMIC_AUDIO_MIC_AMP_MODE + rightChannelMode, + const PMIC_AUDIO_MIC_GAIN + rightChannelGain) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_write = 0; + unsigned int reg_mask = 0; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == vCodec.handle) && (vCodec.handleState == HANDLE_IN_USE)) { + if (!(((leftChannelGain >= MIC_GAIN_MINUS_8DB) + && (leftChannelGain <= MIC_GAIN_PLUS_23DB)) + && ((rightChannelGain >= MIC_GAIN_MINUS_8DB) + && (rightChannelGain <= MIC_GAIN_PLUS_23DB)))) { + rc = PMIC_PARAMETER_ERROR; + pr_debug("VCODEC set record gain - wrong gain value\n"); + } else if (((leftChannelMode != AMP_OFF) + && (leftChannelMode != VOLTAGE_TO_VOLTAGE) + && (leftChannelMode != CURRENT_TO_VOLTAGE)) + || ((rightChannelMode != VOLTAGE_TO_VOLTAGE) + && (rightChannelMode != CURRENT_TO_VOLTAGE) + && (rightChannelMode != AMP_OFF))) { + rc = PMIC_PARAMETER_ERROR; + pr_debug("VCODEC set record gain - wrong amp mode\n"); + } else { + if (vCodec.leftChannelMic.mic == MIC1_LEFT) { + reg_mask = SET_BITS(regAUDIO_TX, AMC1LITOV, 1) | + SET_BITS(regAUDIO_TX, PGATXL, 31); + if (leftChannelMode == VOLTAGE_TO_VOLTAGE) { + reg_write = + SET_BITS(regAUDIO_TX, AMC1LITOV, 0); + } else { + reg_write = + SET_BITS(regAUDIO_TX, AMC1LITOV, 1); + } + reg_write |= + SET_BITS(regAUDIO_TX, PGATXL, + leftChannelGain); + } + if (vCodec.rightChannelMic.mic == MIC1_RIGHT_MIC_MONO) { + reg_mask |= + SET_BITS(regAUDIO_TX, AMC1RITOV, + 1) | SET_BITS(regAUDIO_TX, PGATXR, + 31); + if (rightChannelMode == VOLTAGE_TO_VOLTAGE) { + reg_write |= + SET_BITS(regAUDIO_TX, AMC1RITOV, 0); + } else { + reg_write |= + SET_BITS(regAUDIO_TX, AMC1RITOV, 1); + } + reg_write |= + SET_BITS(regAUDIO_TX, PGATXR, + rightChannelGain); + } else if (vCodec.rightChannelMic.mic == MIC2_AUX) { + reg_mask |= SET_BITS(regAUDIO_TX, AMC2ITOV, 1); + reg_mask |= SET_BITS(regAUDIO_TX, PGATXR, 31); + if (rightChannelMode == VOLTAGE_TO_VOLTAGE) { + reg_write |= + SET_BITS(regAUDIO_TX, AMC2ITOV, 0); + } else { + reg_write |= + SET_BITS(regAUDIO_TX, AMC2ITOV, 1); + } + reg_write |= + SET_BITS(regAUDIO_TX, PGATXR, + rightChannelGain); + } else if (vCodec.rightChannelMic.mic == TXIN_EXT) { + reg_mask |= SET_BITS(regAUDIO_TX, PGATXR, 31); + /* No current to voltage option for TX IN amplifier */ + reg_write |= + SET_BITS(regAUDIO_TX, PGATXR, + rightChannelGain); + } + + if (reg_mask == 0) { + } else { + rc = pmic_write_reg(REG_AUDIO_TX, + reg_write, reg_mask); + reg_write = + SET_BITS(regAUDIO_TX, PGATXL, + leftChannelGain); + reg_mask = SET_BITS(regAUDIO_TX, PGATXL, 31); + rc = pmic_write_reg(REG_AUDIO_TX, + reg_write, reg_mask); + + if (rc == PMIC_SUCCESS) { + pr_debug("MIC amp mode and gain set\n"); + vCodec.leftChannelMic.ampMode = + leftChannelMode; + vCodec.leftChannelMic.gain = + leftChannelGain; + vCodec.rightChannelMic.ampMode = + rightChannelMode; + vCodec.rightChannelMic.gain = + rightChannelGain; + + } + } + } + } + + /* Exit critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Get the current microphone input amplifier mode and gain level. + * + * This function gets the current microphone input amplifier operating mode + * and gain level. + * + * @param handle Device handle from pmic_audio_open() call. + * @param leftChannelMode The left microphone input amplifier mode. + * @param leftChannelGain The left microphone input amplifier gain level. + * @param rightChannelMode The right microphone input amplifier mode. + * @param rightChannelGain The right microphone input amplifier gain + * level. + * + * @retval PMIC_SUCCESS If the microphone input amplifier modes + * and gain levels were successfully + * retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the microphone input amplifier modes + * and gain levels could not be retrieved. + */ +PMIC_STATUS pmic_audio_vcodec_get_record_gain(const PMIC_AUDIO_HANDLE handle, + PMIC_AUDIO_MIC_AMP_MODE * + const leftChannelMode, + PMIC_AUDIO_MIC_GAIN * + const leftChannelGain, + PMIC_AUDIO_MIC_AMP_MODE * + const rightChannelMode, + PMIC_AUDIO_MIC_GAIN * + const rightChannelGain) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE) && + (leftChannelMode != (PMIC_AUDIO_MIC_AMP_MODE *) NULL) && + (leftChannelGain != (PMIC_AUDIO_MIC_GAIN *) NULL) && + (rightChannelMode != (PMIC_AUDIO_MIC_AMP_MODE *) NULL) && + (rightChannelGain != (PMIC_AUDIO_MIC_GAIN *) NULL)) { + *leftChannelMode = vCodec.leftChannelMic.ampMode; + *leftChannelGain = vCodec.leftChannelMic.gain; + *rightChannelMode = vCodec.rightChannelMic.ampMode; + *rightChannelGain = vCodec.rightChannelMic.gain; + + rc = PMIC_SUCCESS; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Enable a microphone bias circuit. + * + * This function enables one of the available microphone bias circuits. + * + * @param handle Device handle from pmic_audio_open() call. + * @param biasCircuit The microphone bias circuit to be enabled. + * + * @retval PMIC_SUCCESS If the microphone bias circuit was + * successfully enabled. + * @retval PMIC_PARAMETER_ERROR If the handle or selected microphone bias + * circuit was invalid. + * @retval PMIC_ERROR If the microphone bias circuit could not + * be enabled. + */ +PMIC_STATUS pmic_audio_vcodec_enable_micbias(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_MIC_BIAS + biasCircuit) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_write = 0; + unsigned int reg_mask = 0; + + /* No critical section required here since we are not updating any + * global data. + */ + + if ((handle == vCodec.handle) && (vCodec.handleState == HANDLE_IN_USE)) { + if (biasCircuit & MIC_BIAS1) { + reg_write = SET_BITS(regAUDIO_TX, MC1BEN, 1); + reg_mask = SET_BITS(regAUDIO_TX, MC1BEN, 1); + } + if (biasCircuit & MIC_BIAS2) { + reg_write |= SET_BITS(regAUDIO_TX, MC2BEN, 1); + reg_mask |= SET_BITS(regAUDIO_TX, MC2BEN, 1); + } + if (reg_mask != 0) { + rc = pmic_write_reg(REG_AUDIO_TX, reg_write, reg_mask); + } + } + + return rc; +} + +/*! + * @brief Disable a microphone bias circuit. + * + * This function disables one of the available microphone bias circuits. + * + * @param handle Device handle from pmic_audio_open() call. + * @param biasCircuit The microphone bias circuit to be disabled. + * + * @retval PMIC_SUCCESS If the microphone bias circuit was + * successfully disabled. + * @retval PMIC_PARAMETER_ERROR If the handle or selected microphone bias + * circuit was invalid. + * @retval PMIC_ERROR If the microphone bias circuit could not + * be disabled. + */ +PMIC_STATUS pmic_audio_vcodec_disable_micbias(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_MIC_BIAS + biasCircuit) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_write = 0; + unsigned int reg_mask = 0; + + /* No critical section required here since we are not updating any + * global data. + */ + + if ((handle == vCodec.handle) && (vCodec.handleState == HANDLE_IN_USE)) { + if (biasCircuit & MIC_BIAS1) { + reg_mask = SET_BITS(regAUDIO_TX, MC1BEN, 1); + } + + if (biasCircuit & MIC_BIAS2) { + reg_mask |= SET_BITS(regAUDIO_TX, MC2BEN, 1); + } + + if (reg_mask != 0) { + rc = pmic_write_reg(REG_AUDIO_TX, reg_write, reg_mask); + } + } + + return rc; +} + +/*@}*/ + +/************************************************************************* + * Audio Playback Using the Voice CODEC. + ************************************************************************* + */ + +/*! + * @name Audio Playback Using the Voice CODEC Setup and Configuration APIs + * Functions for general setup and configuration of the PMIC Voice CODEC + * to perform audio playback. + */ +/*@{*/ + +/*! + * @brief Configure and enable the Voice CODEC mixer. + * + * This function configures and enables the Voice CODEC mixer. + * + * @param handle Device handle from pmic_audio_open() call. + * @param rxSecondaryTimeslot The timeslot used for the secondary audio + * channel. + * @param gainIn The secondary audio channel gain level. + * @param gainOut The mixer output gain level. + * + * @retval PMIC_SUCCESS If the Voice CODEC mixer was successfully + * configured and enabled. + * @retval PMIC_PARAMETER_ERROR If the handle or mixer configuration + * was invalid. + * @retval PMIC_ERROR If the Voice CODEC mixer could not be + * reconfigured or enabled. + */ +PMIC_STATUS pmic_audio_vcodec_enable_mixer(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_VCODEC_TIMESLOT + rxSecondaryTimeslot, + const PMIC_AUDIO_VCODEC_MIX_IN_GAIN + gainIn, + const PMIC_AUDIO_VCODEC_MIX_OUT_GAIN + gainOut) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_write = 0; + unsigned int reg_mask = 0; + + /* No critical section required here since we are not updating any + * global data. + */ + + if ((handle == vCodec.handle) && (vCodec.handleState == HANDLE_IN_USE)) { + if (!((rxSecondaryTimeslot >= USE_TS0) + && (rxSecondaryTimeslot <= USE_TS3))) { + pr_debug + ("VCODEC enable mixer - wrong sec rx timeslot\n"); + } else if (!((gainIn >= VCODEC_NO_MIX) + && (gainIn <= VCODEC_MIX_IN_MINUS_12DB))) { + pr_debug("VCODEC enable mixer - wrong mix in gain\n"); + + } else if (!((gainOut >= VCODEC_MIX_OUT_0DB) + && (gainOut <= VCODEC_MIX_OUT_MINUS_6DB))) { + pr_debug("VCODEC enable mixer - wrong mix out gain\n"); + } else { + + reg_mask = SET_BITS(regSSI_NETWORK, CDCRXSECSLOT, 3) | + SET_BITS(regSSI_NETWORK, CDCRXSECGAIN, 3) | + SET_BITS(regSSI_NETWORK, CDCSUMGAIN, 1); + reg_write = + SET_BITS(regSSI_NETWORK, CDCRXSECSLOT, + rxSecondaryTimeslot) | + SET_BITS(regSSI_NETWORK, CDCRXSECGAIN, + gainIn) | SET_BITS(regSSI_NETWORK, + CDCSUMGAIN, gainOut); + rc = pmic_write_reg(REG_AUDIO_SSI_NETWORK, + reg_write, reg_mask); + if (rc == PMIC_SUCCESS) { + pr_debug("Vcodec mixer enabled\n"); + } + } + } + + return rc; +} + +/*! + * @brief Disable the Voice CODEC mixer. + * + * This function disables the Voice CODEC mixer. + * + * @param handle Device handle from pmic_audio_open() call. + * + * @retval PMIC_SUCCESS If the Voice CODEC mixer was successfully + * disabled. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the Voice CODEC mixer could not be + * disabled. + */ +PMIC_STATUS pmic_audio_vcodec_disable_mixer(const PMIC_AUDIO_HANDLE handle) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_mask; + + if ((handle == vCodec.handle) && (vCodec.handleState == HANDLE_IN_USE)) { + reg_mask = SET_BITS(regSSI_NETWORK, CDCRXSECGAIN, 1); + rc = pmic_write_reg(REG_AUDIO_SSI_NETWORK, + VCODEC_NO_MIX, reg_mask); + + if (rc == PMIC_SUCCESS) { + pr_debug("Vcodec mixer disabled\n"); + } + + } + + return rc; +} + +/*@}*/ + +/************************************************************************* + * Audio Playback Using the Stereo DAC. + ************************************************************************* + */ + +/*! + * @name Audio Playback Using the Stereo DAC Setup and Configuration APIs + * Functions for general setup and configuration of the PMIC Stereo DAC + * to perform audio playback. + */ +/*@{*/ + +/*! + * @brief Configure and enable the Stereo DAC mixer. + * + * This function configures and enables the Stereo DAC mixer. + * + * @param handle Device handle from pmic_audio_open() call. + * @param rxSecondaryTimeslot The timeslot used for the secondary audio + * channel. + * @param gainIn The secondary audio channel gain level. + * @param gainOut The mixer output gain level. + * + * @retval PMIC_SUCCESS If the Stereo DAC mixer was successfully + * configured and enabled. + * @retval PMIC_PARAMETER_ERROR If the handle or mixer configuration + * was invalid. + * @retval PMIC_ERROR If the Stereo DAC mixer could not be + * reconfigured or enabled. + */ +PMIC_STATUS pmic_audio_stdac_enable_mixer(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_STDAC_TIMESLOTS + rxSecondaryTimeslot, + const PMIC_AUDIO_STDAC_MIX_IN_GAIN + gainIn, + const PMIC_AUDIO_STDAC_MIX_OUT_GAIN + gainOut) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_write = 0; + unsigned int reg_mask = 0; + + /* No critical section required here since we are not updating any + * global data. + */ + + if ((handle == stDAC.handle) && (stDAC.handleState == HANDLE_IN_USE)) { + if (!((rxSecondaryTimeslot >= USE_TS0_TS1) + && (rxSecondaryTimeslot <= USE_TS6_TS7))) { + rc = PMIC_PARAMETER_ERROR; + pr_debug("STDAC enable mixer - wrong sec timeslot\n"); + } else if (!((gainIn >= STDAC_NO_MIX) + && (gainIn <= STDAC_MIX_IN_MINUS_12DB))) { + rc = PMIC_PARAMETER_ERROR; + pr_debug("STDAC enable mixer - wrong mix in gain\n"); + } else if (!((gainOut >= STDAC_MIX_OUT_0DB) + && (gainOut <= STDAC_MIX_OUT_MINUS_6DB))) { + rc = PMIC_PARAMETER_ERROR; + pr_debug("STDAC enable mixer - wrong mix out gain\n"); + } else { + + reg_mask = SET_BITS(regSSI_NETWORK, STDCRXSECSLOT, 3) | + SET_BITS(regSSI_NETWORK, STDCRXSECGAIN, 3) | + SET_BITS(regSSI_NETWORK, STDCSUMGAIN, 1); + reg_write = + SET_BITS(regSSI_NETWORK, STDCRXSECSLOT, + rxSecondaryTimeslot) | + SET_BITS(regSSI_NETWORK, STDCRXSECGAIN, + gainIn) | SET_BITS(regSSI_NETWORK, + STDCSUMGAIN, gainOut); + rc = pmic_write_reg(REG_AUDIO_SSI_NETWORK, + reg_write, reg_mask); + if (rc == PMIC_SUCCESS) { + pr_debug("STDAC mixer enabled\n"); + } + } + + } + + return rc; +} + +/*! + * @brief Disable the Stereo DAC mixer. + * + * This function disables the Stereo DAC mixer. + * + * @param handle Device handle from pmic_audio_open() call. + * + * @retval PMIC_SUCCESS If the Stereo DAC mixer was successfully + * disabled. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the Stereo DAC mixer could not be + * disabled. + */ +PMIC_STATUS pmic_audio_stdac_disable_mixer(const PMIC_AUDIO_HANDLE handle) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + const unsigned int reg_write = 0; + const unsigned int reg_mask = + SET_BITS(regSSI_NETWORK, STDCRXSECGAIN, 1); + + /* No critical section required here since we are not updating any + * global data. + */ + + if ((handle == stDAC.handle) && (stDAC.handleState == HANDLE_IN_USE)) { + rc = pmic_write_reg(REG_AUDIO_SSI_NETWORK, reg_write, reg_mask); + } + + return rc; +} + +/*@}*/ + +/************************************************************************* + * Audio Output Control + ************************************************************************* + */ + +/*! + * @name Audio Output Section Setup and Configuration APIs + * Functions for general setup and configuration of the PMIC audio output + * section to support playback. + */ +/*@{*/ + +/*! + * @brief Select the audio output ports. + * + * This function selects the audio output ports to be used. This also enables + * the appropriate output amplifiers. + * + * @param handle Device handle from pmic_audio_open() call. + * @param port The audio output ports to be used. + * + * @retval PMIC_SUCCESS If the audio output ports were successfully + * acquired. + * @retval PMIC_PARAMETER_ERROR If the handle or output ports were + * invalid. + * @retval PMIC_ERROR If the audio output ports could not be + * acquired. + */ +PMIC_STATUS pmic_audio_output_set_port(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_OUTPUT_PORT port) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_write = 0; + unsigned int reg_mask = 0; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((port == MONO_ALERT) || (port == MONO_EXTOUT)) { + rc = PMIC_NOT_SUPPORTED; + } else { + if (((handle == stDAC.handle) + && (stDAC.handleState == HANDLE_IN_USE)) + || ((handle == extStereoIn.handle) + && (extStereoIn.handleState == HANDLE_IN_USE)) + || ((handle == vCodec.handle) + && (vCodec.handleState == HANDLE_IN_USE) + && (audioOutput.vCodecOut == VCODEC_MIXER_OUT))) { + /* Stereo signal and MIXER source needs to be routed to the port + / Avoid Codec direct out */ + + if (port & MONO_SPEAKER) { + reg_mask = SET_BITS(regAUDIO_RX_0, ASPEN, 1) | + SET_BITS(regAUDIO_RX_0, ASPSEL, 1); + reg_write = SET_BITS(regAUDIO_RX_0, ASPEN, 1) | + SET_BITS(regAUDIO_RX_0, ASPSEL, 1); + } + if (port & MONO_LOUDSPEAKER) { + reg_mask |= SET_BITS(regAUDIO_RX_0, ALSPEN, 1) | + SET_BITS(regAUDIO_RX_0, ALSPREF, 1) | + SET_BITS(regAUDIO_RX_0, ALSPSEL, 1); + reg_write |= + SET_BITS(regAUDIO_RX_0, ALSPEN, + 1) | SET_BITS(regAUDIO_RX_0, + ALSPREF, + 1) | + SET_BITS(regAUDIO_RX_0, ALSPSEL, 1); + } + if (port & STEREO_HEADSET_LEFT) { + reg_mask |= SET_BITS(regAUDIO_RX_0, AHSLEN, 1) | + SET_BITS(regAUDIO_RX_0, AHSSEL, 1); + reg_write |= + SET_BITS(regAUDIO_RX_0, AHSLEN, + 1) | SET_BITS(regAUDIO_RX_0, + AHSSEL, 1); + } + if (port & STEREO_HEADSET_RIGHT) { + reg_mask |= SET_BITS(regAUDIO_RX_0, AHSREN, 1) | + SET_BITS(regAUDIO_RX_0, AHSSEL, 1); + reg_write |= + SET_BITS(regAUDIO_RX_0, AHSREN, + 1) | SET_BITS(regAUDIO_RX_0, + AHSSEL, 1); + } + if (port & STEREO_OUT_LEFT) { + reg_mask |= + SET_BITS(regAUDIO_RX_0, ARXOUTLEN, + 1) | SET_BITS(regAUDIO_RX_0, + ARXOUTSEL, 1); + reg_write |= + SET_BITS(regAUDIO_RX_0, ARXOUTLEN, + 1) | SET_BITS(regAUDIO_RX_0, + ARXOUTSEL, 1); + } + if (port & STEREO_OUT_RIGHT) { + reg_mask |= + SET_BITS(regAUDIO_RX_0, ARXOUTREN, + 1) | SET_BITS(regAUDIO_RX_0, + ARXOUTSEL, 1); + reg_write |= + SET_BITS(regAUDIO_RX_0, ARXOUTREN, + 1) | SET_BITS(regAUDIO_RX_0, + ARXOUTSEL, 1); + } + if (port & STEREO_LEFT_LOW_POWER) { + reg_mask |= SET_BITS(regAUDIO_RX_0, LSPLEN, 1); + + reg_write |= SET_BITS(regAUDIO_RX_0, LSPLEN, 1); + } + } else if ((handle == vCodec.handle) + && (vCodec.handleState == HANDLE_IN_USE) + && (audioOutput.vCodecOut = VCODEC_DIRECT_OUT)) { + if (port & MONO_SPEAKER) { + reg_mask = SET_BITS(regAUDIO_RX_0, ASPEN, 1) | + SET_BITS(regAUDIO_RX_0, ASPSEL, 1); + reg_write = SET_BITS(regAUDIO_RX_0, ASPEN, 1) | + SET_BITS(regAUDIO_RX_0, ASPSEL, 0); + } + if (port & MONO_LOUDSPEAKER) { + reg_mask |= SET_BITS(regAUDIO_RX_0, ALSPEN, 1) | + SET_BITS(regAUDIO_RX_0, ALSPREF, 1) | + SET_BITS(regAUDIO_RX_0, ALSPSEL, 1); + reg_write |= + SET_BITS(regAUDIO_RX_0, ALSPEN, + 1) | SET_BITS(regAUDIO_RX_0, + ALSPREF, + 1) | + SET_BITS(regAUDIO_RX_0, ALSPSEL, 0); + } + + if (port & STEREO_HEADSET_LEFT) { + reg_mask |= SET_BITS(regAUDIO_RX_0, AHSLEN, 1) | + SET_BITS(regAUDIO_RX_0, AHSSEL, 1); + reg_write |= + SET_BITS(regAUDIO_RX_0, AHSLEN, + 1) | SET_BITS(regAUDIO_RX_0, + AHSSEL, 0); + } + if (port & STEREO_HEADSET_RIGHT) { + reg_mask |= SET_BITS(regAUDIO_RX_0, AHSREN, 1) | + SET_BITS(regAUDIO_RX_0, AHSSEL, 1); + reg_write |= + SET_BITS(regAUDIO_RX_0, AHSREN, + 1) | SET_BITS(regAUDIO_RX_0, + AHSSEL, 0); + } + if (port & STEREO_OUT_LEFT) { + reg_mask |= + SET_BITS(regAUDIO_RX_0, ARXOUTLEN, + 1) | SET_BITS(regAUDIO_RX_0, + ARXOUTSEL, 1); + reg_write |= + SET_BITS(regAUDIO_RX_0, ARXOUTLEN, + 1) | SET_BITS(regAUDIO_RX_0, + ARXOUTSEL, 0); + } + if (port & STEREO_OUT_RIGHT) { + reg_mask |= + SET_BITS(regAUDIO_RX_0, ARXOUTREN, + 1) | SET_BITS(regAUDIO_RX_0, + ARXOUTSEL, 1); + reg_write |= + SET_BITS(regAUDIO_RX_0, ARXOUTREN, + 1) | SET_BITS(regAUDIO_RX_0, + ARXOUTSEL, 0); + } + if (port & MONO_CDCOUT) { + reg_mask |= + SET_BITS(regAUDIO_RX_0, CDCOUTEN, 1); + + reg_write |= + SET_BITS(regAUDIO_RX_0, CDCOUTEN, 1); + } + } + + if (reg_mask == 0) { + + } else { + rc = pmic_write_reg(REG_AUDIO_RX_0, + reg_write, reg_mask); + + if (rc == PMIC_SUCCESS) { + pr_debug("output ports enabled\n"); + audioOutput.outputPort = port; + + } + } + } + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Deselect/disable the audio output ports. + * + * This function disables the audio output ports that were previously enabled + * by calling pmic_audio_output_set_port(). + * + * @param handle Device handle from pmic_audio_open() call. + * @param port The audio output ports to be disabled. + * + * @retval PMIC_SUCCESS If the audio output ports were successfully + * disabled. + * @retval PMIC_PARAMETER_ERROR If the handle or output ports were + * invalid. + * @retval PMIC_ERROR If the audio output ports could not be + * disabled. + */ +PMIC_STATUS pmic_audio_output_clear_port(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_OUTPUT_PORT port) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_write = 0; + unsigned int reg_mask = 0; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((port == MONO_ALERT) || (port == MONO_EXTOUT)) { + rc = PMIC_NOT_SUPPORTED; + } else { + if (((handle == stDAC.handle) + && (stDAC.handleState == HANDLE_IN_USE)) + || ((handle == extStereoIn.handle) + && (extStereoIn.handleState == HANDLE_IN_USE)) + || ((handle == vCodec.handle) + && (vCodec.handleState == HANDLE_IN_USE) + && (audioOutput.vCodecOut = VCODEC_MIXER_OUT))) { + /* Stereo signal and MIXER source needs to be routed to the port / + Avoid Codec direct out */ + if (port & MONO_SPEAKER) { + reg_mask = SET_BITS(regAUDIO_RX_0, ASPEN, 1); + reg_write = SET_BITS(regAUDIO_RX_0, ASPEN, 0); + } + if (port & MONO_LOUDSPEAKER) { + reg_mask |= SET_BITS(regAUDIO_RX_0, ALSPEN, 1) | + SET_BITS(regAUDIO_RX_0, ALSPREF, 1); + + reg_write |= + SET_BITS(regAUDIO_RX_0, ALSPEN, + 0) | SET_BITS(regAUDIO_RX_0, + ALSPREF, 0); + + } + if (port & STEREO_HEADSET_LEFT) { + reg_mask |= SET_BITS(regAUDIO_RX_0, AHSLEN, 1); + reg_write |= SET_BITS(regAUDIO_RX_0, AHSLEN, 0); + } + if (port & STEREO_HEADSET_RIGHT) { + reg_mask |= SET_BITS(regAUDIO_RX_0, AHSREN, 1); + reg_write |= SET_BITS(regAUDIO_RX_0, AHSREN, 0); + } + if (port & STEREO_OUT_LEFT) { + reg_mask |= + SET_BITS(regAUDIO_RX_0, ARXOUTLEN, 1); + reg_write |= + SET_BITS(regAUDIO_RX_0, ARXOUTLEN, 0); + } + if (port & STEREO_OUT_RIGHT) { + reg_mask |= + SET_BITS(regAUDIO_RX_0, ARXOUTREN, 1); + reg_write |= + SET_BITS(regAUDIO_RX_0, ARXOUTREN, 0); + } + if (port & STEREO_LEFT_LOW_POWER) { + reg_mask |= SET_BITS(regAUDIO_RX_0, LSPLEN, 1); + reg_write |= SET_BITS(regAUDIO_RX_0, LSPLEN, 0); + } + } else if ((handle == vCodec.handle) + && (vCodec.handleState == HANDLE_IN_USE) + && (audioOutput.vCodecOut = VCODEC_DIRECT_OUT)) { + if (port & MONO_SPEAKER) { + reg_mask = SET_BITS(regAUDIO_RX_0, ASPEN, 1); + reg_write = SET_BITS(regAUDIO_RX_0, ASPEN, 0); + } + if (port & MONO_LOUDSPEAKER) { + reg_mask |= SET_BITS(regAUDIO_RX_0, ALSPEN, 1) | + SET_BITS(regAUDIO_RX_0, ALSPREF, 1); + reg_write |= + SET_BITS(regAUDIO_RX_0, ALSPEN, + 0) | SET_BITS(regAUDIO_RX_0, + ALSPREF, 0); + } + if (port & STEREO_HEADSET_LEFT) { + reg_mask |= SET_BITS(regAUDIO_RX_0, AHSLEN, 1); + reg_write |= SET_BITS(regAUDIO_RX_0, AHSLEN, 0); + } + if (port & STEREO_HEADSET_RIGHT) { + reg_mask |= SET_BITS(regAUDIO_RX_0, AHSREN, 1); + reg_write |= SET_BITS(regAUDIO_RX_0, AHSREN, 0); + } + if (port & STEREO_OUT_LEFT) { + reg_mask |= + SET_BITS(regAUDIO_RX_0, ARXOUTLEN, 1); + reg_write |= + SET_BITS(regAUDIO_RX_0, ARXOUTLEN, 0); + } + if (port & STEREO_OUT_RIGHT) { + reg_mask |= + SET_BITS(regAUDIO_RX_0, ARXOUTREN, 1); + reg_write |= + SET_BITS(regAUDIO_RX_0, ARXOUTREN, 0); + } + if (port & MONO_CDCOUT) { + reg_mask |= + SET_BITS(regAUDIO_RX_0, CDCOUTEN, 1); + reg_write |= + SET_BITS(regAUDIO_RX_0, CDCOUTEN, 0); + } + } +#ifdef CONFIG_HEADSET_DETECT_ENABLE + + if (port & STEREO_HEADSET_LEFT) { + reg_mask |= SET_BITS(regAUDIO_RX_0, AHSLEN, 1); + reg_write |= SET_BITS(regAUDIO_RX_0, AHSLEN, 0); + } + if (port & STEREO_HEADSET_RIGHT) { + reg_mask |= SET_BITS(regAUDIO_RX_0, AHSREN, 1); + reg_write |= SET_BITS(regAUDIO_RX_0, AHSREN, 0); + } +#endif + + if (reg_mask == 0) { + + } else { + rc = pmic_write_reg(REG_AUDIO_RX_0, + reg_write, reg_mask); + if (rc == PMIC_SUCCESS) { + pr_debug("output ports disabled\n"); + audioOutput.outputPort &= ~port; + } + } + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Get the current audio output ports. + * + * This function retrieves the audio output ports that are currently being + * used. + * + * @param handle Device handle from pmic_audio_open() call. + * @param port The audio output ports currently being used. + * + * @retval PMIC_SUCCESS If the audio output ports were successfully + * retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the audio output ports could not be + * retrieved. + */ +PMIC_STATUS pmic_audio_output_get_port(const PMIC_AUDIO_HANDLE handle, + PMIC_AUDIO_OUTPUT_PORT * const port) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((((handle == stDAC.handle) && + (stDAC.handleState == HANDLE_IN_USE)) || + ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE)) || + ((handle == extStereoIn.handle) && + (extStereoIn.handleState == HANDLE_IN_USE))) && + (port != (PMIC_AUDIO_OUTPUT_PORT *) NULL)) { + *port = audioOutput.outputPort; + + rc = PMIC_SUCCESS; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Set the gain level for the external stereo inputs. + * + * This function sets the gain levels for the external stereo inputs. + * + * @param handle Device handle from pmic_audio_open() call. + * @param gain The external stereo input gain level. + * + * @retval PMIC_SUCCESS If the gain level was successfully set. + * @retval PMIC_PARAMETER_ERROR If the handle or gain level was invalid. + * @retval PMIC_ERROR If the gain level could not be set. + */ +PMIC_STATUS pmic_audio_output_set_stereo_in_gain(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_STEREO_IN_GAIN + gain) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_mask = SET_BITS(regAUDIO_RX_1, ARXINEN, 1) | + SET_BITS(regAUDIO_RX_1, ARXIN, 1); + unsigned int reg_write = SET_BITS(regAUDIO_RX_1, ARXINEN, 1); + + /* No critical section required here since we are not updating any + * global data. + */ + + /* The ARX amplifier for stereo is also enabled over here */ + + if ((gain == STEREO_IN_GAIN_0DB) || (gain == STEREO_IN_GAIN_PLUS_18DB)) { + if ((handle == extStereoIn.handle) && + (extStereoIn.handleState == HANDLE_IN_USE)) { + + if (gain == STEREO_IN_GAIN_0DB) { + reg_write |= SET_BITS(regAUDIO_RX_1, ARXIN, 1); + } else { + reg_write |= SET_BITS(regAUDIO_RX_1, ARXIN, 0); + } + + rc = pmic_write_reg(REG_AUDIO_RX_1, + reg_write, reg_mask); + + if (rc == PMIC_SUCCESS) { + pr_debug("Ext stereo gain set\n"); + extStereoIn.inputGain = gain; + + } + + } else { + rc = PMIC_PARAMETER_ERROR; + } + } + + return rc; +} + +/*! + * @brief Get the current gain level for the external stereo inputs. + * + * This function retrieves the current gain levels for the external stereo + * inputs. + * + * @param handle Device handle from pmic_audio_open() call. + * @param gain The current external stereo input gain + * level. + * + * @retval PMIC_SUCCESS If the gain level was successfully + * retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the gain level could not be retrieved. + */ +PMIC_STATUS pmic_audio_output_get_stereo_in_gain(const PMIC_AUDIO_HANDLE handle, + PMIC_AUDIO_STEREO_IN_GAIN * + const gain) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == extStereoIn.handle) && + (extStereoIn.handleState == HANDLE_IN_USE) && + (gain != (PMIC_AUDIO_STEREO_IN_GAIN *) NULL)) { + *gain = extStereoIn.inputGain; + rc = PMIC_SUCCESS; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Set the output PGA gain level. + * + * This function sets the audio output PGA gain level. + * + * @param handle Device handle from pmic_audio_open() call. + * @param gain The output PGA gain level. + * + * @retval PMIC_SUCCESS If the gain level was successfully set. + * @retval PMIC_PARAMETER_ERROR If the handle or gain level was invalid. + * @retval PMIC_ERROR If the gain level could not be set. + */ +PMIC_STATUS pmic_audio_output_set_pgaGain(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_OUTPUT_PGA_GAIN gain) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_write = 0; + unsigned int reg_mask = 0; + unsigned int reg_gain; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if (!((gain >= OUTPGA_GAIN_MINUS_33DB) + && (gain <= OUTPGA_GAIN_PLUS_6DB))) { + rc = PMIC_NOT_SUPPORTED; + pr_debug("output set PGA gain - wrong gain value\n"); + } else { + if ((gain >= OUTPGA_GAIN_MINUS_33DB) + && (gain <= OUTPGA_GAIN_PLUS_6DB)) { + reg_gain = gain + 2; + } else { + reg_gain = gain; + } + if ((handle == extStereoIn.handle) && + (extStereoIn.handleState == HANDLE_IN_USE)) { + reg_mask = SET_BITS(regAUDIO_RX_1, ARXIN, 15) | + SET_BITS(regAUDIO_RX_1, ARXINEN, 1); + reg_write = SET_BITS(regAUDIO_RX_1, ARXIN, reg_gain) | + SET_BITS(regAUDIO_RX_1, ARXINEN, 1); + } else if ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE)) { + reg_mask = SET_BITS(regAUDIO_RX_1, PGARX, 15); + reg_write = SET_BITS(regAUDIO_RX_1, PGARX, reg_gain); + } else if ((handle == stDAC.handle) && + (stDAC.handleState == HANDLE_IN_USE)) { + reg_mask = SET_BITS(regAUDIO_RX_1, PGAST, 15); + reg_write = SET_BITS(regAUDIO_RX_1, PGAST, reg_gain); + } + + if (reg_mask == 0) { + + } else { + rc = pmic_write_reg(REG_AUDIO_RX_1, + reg_write, reg_mask); + + if (rc == PMIC_SUCCESS) { + pr_debug("Output PGA gains set\n"); + + if (handle == stDAC.handle) { + audioOutput.stDacoutputPGAGain = gain; + } else if (handle == vCodec.handle) { + audioOutput.vCodecoutputPGAGain = gain; + } else { + audioOutput.extStereooutputPGAGain = + gain; + } + } else { + pr_debug + ("Error writing PGA gains to register\n"); + } + } + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Get the output PGA gain level. + * + * This function retrieves the current audio output PGA gain level. + * + * @param handle Device handle from pmic_audio_open() call. + * @param gain The current output PGA gain level. + * + * @retval PMIC_SUCCESS If the gain level was successfully + * retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the gain level could not be retrieved. + */ +PMIC_STATUS pmic_audio_output_get_pgaGain(const PMIC_AUDIO_HANDLE handle, + PMIC_AUDIO_OUTPUT_PGA_GAIN * + const gain) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if (gain != (PMIC_AUDIO_OUTPUT_PGA_GAIN *) NULL) { + if ((handle == extStereoIn.handle) && + (extStereoIn.handleState == HANDLE_IN_USE)) { + *gain = audioOutput.extStereooutputPGAGain; + rc = PMIC_SUCCESS; + } else if ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE)) { + *gain = audioOutput.vCodecoutputPGAGain; + rc = PMIC_SUCCESS; + } else if ((handle == stDAC.handle) && + (stDAC.handleState == HANDLE_IN_USE)) { + *gain = audioOutput.stDacoutputPGAGain; + rc = PMIC_SUCCESS; + } else { + rc = PMIC_PARAMETER_ERROR; + } + } else { + rc = PMIC_PARAMETER_ERROR; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Enable the output mixer. + * + * This function enables the output mixer for the audio stream that + * corresponds to the current handle (i.e., the Voice CODEC, Stereo DAC, or + * the external stereo inputs). + * + * @param handle Device handle from pmic_audio_open() call. + * + * @retval PMIC_SUCCESS If the mixer was successfully enabled. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the mixer could not be enabled. + */ +PMIC_STATUS pmic_audio_output_enable_mixer(const PMIC_AUDIO_HANDLE handle) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_mask = 0; + unsigned int reg_write = 0; + unsigned int reg_mask_mix = 0; + unsigned int reg_write_mix = 0; + + /* No critical section required here since we are not updating any + * global data. + */ + + if (((handle == stDAC.handle) && (stDAC.handleState == HANDLE_IN_USE))) { + reg_mask = SET_BITS(regAUDIO_RX_1, PGASTEN, 1); + reg_write = SET_BITS(regAUDIO_RX_1, PGASTEN, 1); + reg_mask_mix = SET_BITS(regAUDIO_RX_0, ADDSTDC, 1); + reg_write_mix = SET_BITS(regAUDIO_RX_0, ADDSTDC, 1); + } else if ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE)) { + reg_mask = SET_BITS(regAUDIO_RX_1, PGARXEN, 1); + reg_write = SET_BITS(regAUDIO_RX_1, PGARXEN, 1); + audioOutput.vCodecOut = VCODEC_MIXER_OUT; + + reg_mask_mix = SET_BITS(regAUDIO_RX_0, ADDCDC, 1); + reg_write_mix = SET_BITS(regAUDIO_RX_0, ADDCDC, 1); + } else if ((handle == extStereoIn.handle) && + (extStereoIn.handleState == HANDLE_IN_USE)) { + reg_mask = SET_BITS(regAUDIO_RX_1, ARXINEN, 1); + reg_write = SET_BITS(regAUDIO_RX_1, ARXINEN, 1); + reg_mask_mix = SET_BITS(regAUDIO_RX_0, ADDRXIN, 1); + reg_write_mix = SET_BITS(regAUDIO_RX_0, ADDRXIN, 1); + } + + if (reg_mask == 0) { + + } else { + rc = pmic_write_reg(REG_AUDIO_RX_1, reg_write, reg_mask); + + if (rc == PMIC_SUCCESS) { + + rc = pmic_write_reg(REG_AUDIO_RX_0, + reg_write_mix, reg_mask_mix); + if (rc == PMIC_SUCCESS) { + pr_debug("Output PGA mixers enabled\n"); + rc = PMIC_SUCCESS; + } + + } else { + pr_debug("Error writing mixer enable to register\n"); + } + + } + + return rc; +} + +/*! + * @brief Disable the output mixer. + * + * This function disables the output mixer for the audio stream that + * corresponds to the current handle (i.e., the Voice CODEC, Stereo DAC, or + * the external stereo inputs). + * + * @param handle Device handle from pmic_audio_open() call. + * + * @retval PMIC_SUCCESS If the mixer was successfully disabled. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the mixer could not be disabled. + */ +PMIC_STATUS pmic_audio_output_disable_mixer(const PMIC_AUDIO_HANDLE handle) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_mask = 0; + unsigned int reg_write = 0; + + unsigned int reg_mask_mix = 0; + unsigned int reg_write_mix = 0; + + /* No critical section required here since we are not updating any + * global data. + */ + if (((handle == stDAC.handle) && (stDAC.handleState == HANDLE_IN_USE))) { + /*reg_mask = SET_BITS(regAUDIO_RX_1, PGASTEN, 1); + reg_write = SET_BITS(regAUDIO_RX_1, PGASTEN, 0); */ + + reg_mask_mix = SET_BITS(regAUDIO_RX_0, ADDSTDC, 1); + reg_write_mix = SET_BITS(regAUDIO_RX_0, ADDSTDC, 0); + } else if ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE)) { + reg_mask = SET_BITS(regAUDIO_RX_1, PGARXEN, 1); + reg_write = SET_BITS(regAUDIO_RX_1, PGARXEN, 0); + audioOutput.vCodecOut = VCODEC_DIRECT_OUT; + + reg_mask_mix = SET_BITS(regAUDIO_RX_0, ADDCDC, 1); + reg_write_mix = SET_BITS(regAUDIO_RX_0, ADDCDC, 0); + } else if ((handle == extStereoIn.handle) && + (extStereoIn.handleState == HANDLE_IN_USE)) { + /*reg_mask = SET_BITS(regAUDIO_RX_1, ARXINEN, 1); + reg_write = SET_BITS(regAUDIO_RX_1, ARXINEN, 0); */ + + reg_mask_mix = SET_BITS(regAUDIO_RX_0, ADDRXIN, 1); + reg_write_mix = SET_BITS(regAUDIO_RX_0, ADDRXIN, 1); + } + + if (reg_mask == 0) { + + } else { + rc = pmic_write_reg(REG_AUDIO_RX_1, reg_write, reg_mask); + + if (rc == PMIC_SUCCESS) { + + rc = pmic_write_reg(REG_AUDIO_RX_0, + reg_write_mix, reg_mask_mix); + if (rc == PMIC_SUCCESS) { + pr_debug("Output PGA mixers disabled\n"); + } + } + } + return rc; +} + +/*! + * @brief Configure and enable the output balance amplifiers. + * + * This function configures and enables the output balance amplifiers. + * + * @param handle Device handle from pmic_audio_open() call. + * @param leftGain The desired left channel gain level. + * @param rightGain The desired right channel gain level. + * + * @retval PMIC_SUCCESS If the output balance amplifiers were + * successfully configured and enabled. + * @retval PMIC_PARAMETER_ERROR If the handle or gain levels were invalid. + * @retval PMIC_ERROR If the output balance amplifiers could not + * be reconfigured or enabled. + */ +PMIC_STATUS pmic_audio_output_set_balance(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_OUTPUT_BALANCE_GAIN + leftGain, + const PMIC_AUDIO_OUTPUT_BALANCE_GAIN + rightGain) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_mask = 0; + unsigned int reg_write = 0; + unsigned int reg_mask_ch = 0; + unsigned int reg_write_ch = 0; + + /* No critical section required here since we are not updating any + * global data. + */ + + if (!((leftGain >= BAL_GAIN_MINUS_21DB) && (leftGain <= BAL_GAIN_0DB))) { + rc = PMIC_PARAMETER_ERROR; + } else if (!((rightGain >= BAL_GAIN_MINUS_21DB) + && (rightGain <= BAL_GAIN_0DB))) { + rc = PMIC_PARAMETER_ERROR; + } else { + if (((handle == stDAC.handle) && + (stDAC.handleState == HANDLE_IN_USE)) || + ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE)) || + ((handle == extStereoIn.handle) && + (extStereoIn.handleState == HANDLE_IN_USE))) { + /* In mc13783 only one channel can be attenuated wrt the other. + * It is not possible to specify attenuation for both + * This function will return an error if both channels + * are required to be attenuated + * The BALLR bit is set/reset depending on whether leftGain + * or rightGain is specified*/ + if ((rightGain == BAL_GAIN_0DB) + && (leftGain == BAL_GAIN_0DB)) { + /* Nothing to be done */ + } else if ((rightGain != BAL_GAIN_0DB) + && (leftGain == BAL_GAIN_0DB)) { + /* Attenuate right channel */ + reg_mask = SET_BITS(regAUDIO_RX_1, BAL, 7); + reg_mask_ch = SET_BITS(regAUDIO_RX_1, BALLR, 1); + reg_write = + SET_BITS(regAUDIO_RX_1, BAL, + (BAL_GAIN_0DB - rightGain)); + /* The enum and the register values are reversed in order .. */ + reg_write_ch = + SET_BITS(regAUDIO_RX_1, BALLR, 0); + /* BALLR = 0 selects right channel for atten */ + } else if ((rightGain == BAL_GAIN_0DB) + && (leftGain != BAL_GAIN_0DB)) { + /* Attenuate left channel */ + + reg_mask = SET_BITS(regAUDIO_RX_1, BAL, 7); + reg_mask_ch = SET_BITS(regAUDIO_RX_1, BALLR, 1); + reg_write = + SET_BITS(regAUDIO_RX_1, BAL, + (BAL_GAIN_0DB - leftGain)); + reg_write_ch = + SET_BITS(regAUDIO_RX_1, BALLR, 1); + /* BALLR = 1 selects left channel for atten */ + } else { + rc = PMIC_PARAMETER_ERROR; + } + + if ((reg_mask == 0) || (reg_mask_ch == 0)) { + + } else { + rc = pmic_write_reg(REG_AUDIO_RX_1, + reg_write_ch, reg_mask_ch); + + if (rc == PMIC_SUCCESS) { + rc = pmic_write_reg(REG_AUDIO_RX_1, + reg_write, + reg_mask); + + if (rc == PMIC_SUCCESS) { + pr_debug + ("Output balance attenuation set\n"); + audioOutput.balanceLeftGain = + leftGain; + audioOutput.balanceRightGain = + rightGain; + } + } + } + } + } + return rc; +} + +/*! + * @brief Get the current output balance amplifier gain levels. + * + * This function retrieves the current output balance amplifier gain levels. + * + * @param handle Device handle from pmic_audio_open() call. + * @param leftGain The current left channel gain level. + * @param rightGain The current right channel gain level. + * + * @retval PMIC_SUCCESS If the output balance amplifier gain levels + * were successfully retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the output balance amplifier gain levels + * could be retrieved. + */ +PMIC_STATUS pmic_audio_output_get_balance(const PMIC_AUDIO_HANDLE handle, + PMIC_AUDIO_OUTPUT_BALANCE_GAIN * + const leftGain, + PMIC_AUDIO_OUTPUT_BALANCE_GAIN * + const rightGain) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((((handle == stDAC.handle) && + (stDAC.handleState == HANDLE_IN_USE)) || + ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE)) || + ((handle == extStereoIn.handle) && + (extStereoIn.handleState == HANDLE_IN_USE))) && + ((leftGain != (PMIC_AUDIO_OUTPUT_BALANCE_GAIN *) NULL) && + (rightGain != (PMIC_AUDIO_OUTPUT_BALANCE_GAIN *) NULL))) { + *leftGain = audioOutput.balanceLeftGain; + *rightGain = audioOutput.balanceRightGain; + + rc = PMIC_SUCCESS; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Configure and enable the output mono adder. + * + * This function configures and enables the output mono adder. + * + * @param handle Device handle from pmic_audio_open() call. + * @param mode The desired mono adder operating mode. + * + * @retval PMIC_SUCCESS If the mono adder was successfully + * configured and enabled. + * @retval PMIC_PARAMETER_ERROR If the handle or mono adder mode was + * invalid. + * @retval PMIC_ERROR If the mono adder could not be reconfigured + * or enabled. + */ +PMIC_STATUS pmic_audio_output_enable_mono_adder(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_MONO_ADDER_MODE + mode) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_write = 0; + unsigned int reg_mask = SET_BITS(regAUDIO_RX_1, MONO, 3); + + /* No critical section required here since we are not updating any + * global data. + */ + + if ((mode >= MONO_ADDER_OFF) && (mode <= STEREO_OPPOSITE_PHASE)) { + if (((handle == stDAC.handle) && + (stDAC.handleState == HANDLE_IN_USE)) || + ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE)) || + ((handle == extStereoIn.handle) && + (extStereoIn.handleState == HANDLE_IN_USE))) { + if (mode == MONO_ADDER_OFF) { + reg_write = SET_BITS(regAUDIO_RX_1, MONO, 0); + } else if (mode == MONO_ADD_LEFT_RIGHT) { + reg_write = SET_BITS(regAUDIO_RX_1, MONO, 2); + } else if (mode == MONO_ADD_OPPOSITE_PHASE) { + reg_write = SET_BITS(regAUDIO_RX_1, MONO, 3); + } else { /* stereo opposite */ + + reg_write = SET_BITS(regAUDIO_RX_1, MONO, 1); + } + + rc = pmic_write_reg(REG_AUDIO_RX_1, + reg_write, reg_mask); + + if (rc == PMIC_SUCCESS) { + pr_debug("Output mono adder mode set\n"); + + } + + } else { + rc = PMIC_PARAMETER_ERROR; + } + } else { + rc = PMIC_PARAMETER_ERROR; + } + return rc; +} + +/*! + * @brief Disable the output mono adder. + * + * This function disables the output mono adder. + * + * @param handle Device handle from pmic_audio_open() call. + * + * @retval PMIC_SUCCESS If the mono adder was successfully + * disabled. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the mono adder could not be disabled. + */ +PMIC_STATUS pmic_audio_output_disable_mono_adder(const PMIC_AUDIO_HANDLE handle) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + const unsigned int reg_write = 0; + const unsigned int reg_mask = SET_BITS(regAUDIO_RX_1, MONO, 3); + + /* No critical section required here since we are not updating any + * global data. + */ + + if (((handle == stDAC.handle) && + (stDAC.handleState == HANDLE_IN_USE)) || + ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE)) || + ((handle == extStereoIn.handle) && + (extStereoIn.handleState == HANDLE_IN_USE))) { + rc = pmic_write_reg(REG_AUDIO_RX_1, reg_write, reg_mask); + } + + return rc; +} + +/*! + * @brief Configure the mono adder output gain level. + * + * This function configures the mono adder output amplifier gain level. + * + * @param handle Device handle from pmic_audio_open() call. + * @param gain The desired output gain level. + * + * @retval PMIC_SUCCESS If the mono adder output amplifier gain + * level was successfully set. + * @retval PMIC_PARAMETER_ERROR If the handle or gain level was invalid. + * @retval PMIC_ERROR If the mono adder output amplifier gain + * level could not be reconfigured. + */ +PMIC_STATUS pmic_audio_output_set_mono_adder_gain(const PMIC_AUDIO_HANDLE + handle, + const + PMIC_AUDIO_MONO_ADDER_OUTPUT_GAIN + gain) +{ + PMIC_STATUS rc = PMIC_NOT_SUPPORTED; + return rc; +} + +/*! + * @brief Get the current mono adder output gain level. + * + * This function retrieves the current mono adder output amplifier gain level. + * + * @param handle Device handle from pmic_audio_open() call. + * @param gain The current output gain level. + * + * @retval PMIC_SUCCESS If the mono adder output amplifier gain + * level was successfully retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the mono adder output amplifier gain + * level could not be retrieved. + */ +PMIC_STATUS pmic_audio_output_get_mono_adder_gain(const PMIC_AUDIO_HANDLE + handle, + PMIC_AUDIO_MONO_ADDER_OUTPUT_GAIN + * const gain) +{ + PMIC_STATUS rc = PMIC_NOT_SUPPORTED; + return rc; +} + +/*! + * @brief Set various audio output section options. + * + * This function sets one or more audio output section configuration + * options. The currently supported options include whether to disable + * the non-inverting mono speaker output, enabling the loudspeaker common + * bias circuit, enabling detection of headset insertion/removal, and + * whether to automatically disable the headset amplifiers when a headset + * insertion/removal has been detected. + * + * @param handle Device handle from pmic_audio_open() call. + * @param config The desired audio output section + * configuration options to be set. + * + * @retval PMIC_SUCCESS If the desired configuration options were + * all successfully set. + * @retval PMIC_PARAMETER_ERROR If the handle or configuration options + * were invalid. + * @retval PMIC_ERROR If the desired configuration options + * could not be set. + */ +PMIC_STATUS pmic_audio_output_set_config(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_OUTPUT_CONFIG config) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_mask = 0; + unsigned int reg_write = 0; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if (((handle == stDAC.handle) && + (stDAC.handleState == HANDLE_IN_USE)) || + ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE)) || + ((handle == extStereoIn.handle) && + (extStereoIn.handleState == HANDLE_IN_USE))) { + if (config & MONO_SPEAKER_INVERT_OUT_ONLY) { + /* If this is one of the parameters */ + rc = PMIC_NOT_SUPPORTED; + } else { + if (config & MONO_LOUDSPEAKER_COMMON_BIAS) { + reg_mask = SET_BITS(regAUDIO_RX_0, ALSPREF, 1); + reg_write = SET_BITS(regAUDIO_RX_0, ALSPREF, 1); + } + if (config & HEADSET_DETECT_ENABLE) { + reg_mask |= SET_BITS(regAUDIO_RX_0, HSDETEN, 1); + reg_write |= + SET_BITS(regAUDIO_RX_0, HSDETEN, 1); + } + if (config & STEREO_HEADSET_AMP_AUTO_DISABLE) { + reg_mask |= + SET_BITS(regAUDIO_RX_0, HSDETAUTOB, 1); + reg_write |= + SET_BITS(regAUDIO_RX_0, HSDETAUTOB, 1); + } + + if (reg_mask == 0) { + rc = PMIC_PARAMETER_ERROR; + } else { + rc = pmic_write_reg(REG_AUDIO_RX_0, + reg_write, reg_mask); + + if (rc == PMIC_SUCCESS) { + pr_debug("Output config set\n"); + audioOutput.config |= config; + + } + } + } + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Clear various audio output section options. + * + * This function clears one or more audio output section configuration + * options. + * + * @param handle Device handle from pmic_audio_open() call. + * @param config The desired audio output section + * configuration options to be cleared. + * + * @retval PMIC_SUCCESS If the desired configuration options were + * all successfully cleared. + * @retval PMIC_PARAMETER_ERROR If the handle or configuration options + * were invalid. + * @retval PMIC_ERROR If the desired configuration options + * could not be cleared. + */ +PMIC_STATUS pmic_audio_output_clear_config(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_OUTPUT_CONFIG + config) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + /*unsigned int reg_write_RX = 0; + unsigned int reg_mask_RX = 0; + unsigned int reg_write_TX = 0; + unsigned int reg_mask_TX = 0; */ + unsigned int reg_mask = 0; + unsigned int reg_write = 0; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if (((handle == stDAC.handle) && + (stDAC.handleState == HANDLE_IN_USE)) || + ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE)) || + ((handle == extStereoIn.handle) && + (extStereoIn.handleState == HANDLE_IN_USE))) { + if (config & MONO_SPEAKER_INVERT_OUT_ONLY) { + /* If this is one of the parameters */ + rc = PMIC_NOT_SUPPORTED; + } else { + if (config & MONO_LOUDSPEAKER_COMMON_BIAS) { + reg_mask = SET_BITS(regAUDIO_RX_0, ALSPREF, 1); + reg_write = SET_BITS(regAUDIO_RX_0, ALSPREF, 0); + } + + if (config & HEADSET_DETECT_ENABLE) { + reg_mask |= SET_BITS(regAUDIO_RX_0, HSDETEN, 1); + reg_write |= + SET_BITS(regAUDIO_RX_0, HSDETEN, 0); + } + + if (config & STEREO_HEADSET_AMP_AUTO_DISABLE) { + reg_mask |= + SET_BITS(regAUDIO_RX_0, HSDETAUTOB, 1); + reg_write |= + SET_BITS(regAUDIO_RX_0, HSDETAUTOB, 0); + } + + if (reg_mask == 0) { + rc = PMIC_PARAMETER_ERROR; + } else { + rc = pmic_write_reg(REG_AUDIO_RX_0, + reg_write, reg_mask); + + if (rc == PMIC_SUCCESS) { + pr_debug("Output config cleared\n"); + audioOutput.config &= ~config; + + } + } + } + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Get the current audio output section options. + * + * This function retrieves the current audio output section configuration + * option settings. + * + * @param handle Device handle from pmic_audio_open() call. + * @param config The current audio output section + * configuration option settings. + * + * @retval PMIC_SUCCESS If the current configuration options were + * successfully retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the current configuration options + * could not be retrieved. + */ +PMIC_STATUS pmic_audio_output_get_config(const PMIC_AUDIO_HANDLE handle, + PMIC_AUDIO_OUTPUT_CONFIG * + const config) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((((handle == stDAC.handle) && + (stDAC.handleState == HANDLE_IN_USE)) || + ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE)) || + ((handle == extStereoIn.handle) && + (extStereoIn.handleState == HANDLE_IN_USE))) && + (config != (PMIC_AUDIO_OUTPUT_CONFIG *) NULL)) { + *config = audioOutput.config; + + rc = PMIC_SUCCESS; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Enable the phantom ground circuit that is used to help identify + * the type of headset that has been inserted. + * + * This function enables the phantom ground circuit that is used to help + * identify the type of headset (e.g., stereo or mono) that has been inserted. + * + * @param handle Device handle from pmic_audio_open() call. + * + * @retval PMIC_SUCCESS If the phantom ground circuit was + * successfully enabled. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the phantom ground circuit could not + * be enabled. + */ +PMIC_STATUS pmic_audio_output_enable_phantom_ground() +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + const unsigned int reg_mask = SET_BITS(regAUDIO_RX_0, HSPGDIS, 1); + + /* No critical section required here since we are not updating any + * global data. + */ + + rc = pmic_write_reg(REG_AUDIO_RX_0, 0, reg_mask); + if (rc == PMIC_SUCCESS) { + pr_debug("Phantom ground enabled\n"); + + } + return rc; +} + +/*! + * @brief Disable the phantom ground circuit that is used to help identify + * the type of headset that has been inserted. + * + * This function disables the phantom ground circuit that is used to help + * identify the type of headset (e.g., stereo or mono) that has been inserted. + * + * @param handle Device handle from pmic_audio_open() call. + * + * @retval PMIC_SUCCESS If the phantom ground circuit was + * successfully disabled. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the phantom ground circuit could not + * be disabled. + */ +PMIC_STATUS pmic_audio_output_disable_phantom_ground() +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + const unsigned int reg_mask = SET_BITS(regAUDIO_RX_0, HSPGDIS, 1); + + /* No critical section required here since we are not updating any + * global data. + */ + + rc = pmic_write_reg(REG_AUDIO_RX_0, 1, reg_mask); + if (rc == PMIC_SUCCESS) { + pr_debug("Phantom ground disabled\n"); + + } + return rc; +} + +/*@}*/ + +/************************************************************************** + * Static functions. + ************************************************************************** + */ + +/*! + * @name Audio Driver Internal Support Functions + * These non-exported internal functions are used to support the functionality + * of the exported audio APIs. + */ +/*@{*/ + +/*! + * @brief Enables the 5.6V boost for the microphone bias 2 circuit. + * + * This function enables the switching regulator SW3 and configures it to + * provide the 5.6V boost that is required for driving the microphone bias 2 + * circuit when using a 5-pole jack configuration (which is the case for the + * Sphinx board). + * + * @retval PMIC_SUCCESS The 5.6V boost was successfully enabled. + * @retval PMIC_ERROR Failed to enable the 5.6V boost. + */ +/* +static PMIC_STATUS pmic_audio_mic_boost_enable(void) +{ + PMIC_STATUS rc = PMIC_NOT_SUPPORTED; + + return rc; +} +*/ +/*! + * @brief Disables the 5.6V boost for the microphone bias 2 circuit. + * + * This function disables the switching regulator SW3 to turn off the 5.6V + * boost for the microphone bias 2 circuit. + * + * @retval PMIC_SUCCESS The 5.6V boost was successfully disabled. + * @retval PMIC_ERROR Failed to disable the 5.6V boost. + */ +/* +static PMIC_STATUS pmic_audio_mic_boost_disable(void) +{ + PMIC_STATUS rc = PMIC_NOT_SUPPORTED; + + return rc; +} +*/ + +/*! + * @brief Free a device handle previously acquired by calling pmic_audio_open(). + * + * Terminate further access to the PMIC audio hardware that was previously + * acquired by calling pmic_audio_open(). This now allows another thread to + * successfully call pmic_audio_open() to gain access. + * + * Note that we will shutdown/reset the Voice CODEC or Stereo DAC as well as + * any associated audio input/output components that are no longer required. + * + * Also note that this function should only be called with the mutex already + * acquired. + * + * @param handle Device handle from pmic_audio_open() call. + * + * @retval PMIC_SUCCESS If the close request was successful. + * @retval PMIC_PARAMETER_ERROR If the handle is invalid. + */ +static PMIC_STATUS pmic_audio_close_handle(const PMIC_AUDIO_HANDLE handle) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + /* Match up the handle to the audio device and then close it. */ + if ((handle == stDAC.handle) && (stDAC.handleState == HANDLE_IN_USE)) { + /* Also shutdown the Stereo DAC hardware. The simplest way to + * do this is to simply call pmic_audio_reset_device() which will + * restore the ST_DAC register to it's initial power-on state. + * + * This will also shutdown the audio output section if no one + * else is still using it. + */ + rc = pmic_audio_reset_device(stDAC.handle); + + if (rc == PMIC_SUCCESS) { + stDAC.handle = AUDIO_HANDLE_NULL; + stDAC.handleState = HANDLE_FREE; + } + } else if ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE)) { + /* Also shutdown the Voice CODEC and audio input hardware. The + * simplest way to do this is to simply call pmic_audio_reset_device() + * which will restore the AUD_CODEC register to it's initial + * power-on state. + * + * This will also shutdown the audio output section if no one + * else is still using it. + */ + rc = pmic_audio_reset_device(vCodec.handle); + if (rc == PMIC_SUCCESS) { + vCodec.handle = AUDIO_HANDLE_NULL; + vCodec.handleState = HANDLE_FREE; + } + } else if ((handle == extStereoIn.handle) && + (extStereoIn.handleState == HANDLE_IN_USE)) { + + /* Call pmic_audio_reset_device() here to shutdown the audio output + * section if no one else is still using it. + */ + rc = pmic_audio_reset_device(extStereoIn.handle); + + if (rc == PMIC_SUCCESS) { + extStereoIn.handle = AUDIO_HANDLE_NULL; + extStereoIn.handleState = HANDLE_FREE; + } + } + + return rc; +} + +/*! + * @brief Reset the selected audio hardware control registers to their + * power on state. + * + * This resets all of the audio hardware control registers currently + * associated with the device handle back to their power on states. For + * example, if the handle is associated with the Stereo DAC and a + * specific output port and output amplifiers, then this function will + * reset all of those components to their initial power on state. + * + * This function can only be called if the mutex has already been acquired. + * + * @param handle Device handle from pmic_audio_open() call. + * + * @retval PMIC_SUCCESS If the reset operation was successful. + * @retval PMIC_PARAMETER_ERROR If the handle is invalid. + * @retval PMIC_ERROR If the reset was unsuccessful. + */ +static PMIC_STATUS pmic_audio_reset_device(const PMIC_AUDIO_HANDLE handle) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_mask = 0; + + if ((handle == stDAC.handle) && (stDAC.handleState == HANDLE_IN_USE)) { + /* Also shutdown the audio output section if nobody else is using it. + if ((vCodec.handleState == HANDLE_FREE) && + (extStereoIn.handleState == HANDLE_FREE)) + { + pmic_write_reg(REG_RX_AUD_AMPS, RESET_RX_AUD_AMPS, + REG_FULLMASK); + } */ + + rc = pmic_write_reg(REG_AUDIO_STEREO_DAC, + RESET_ST_DAC, REG_FULLMASK); + + if (rc == PMIC_SUCCESS) { + rc = pmic_write_reg(REG_AUDIO_SSI_NETWORK, + RESET_SSI_NETWORK, + REG_SSI_STDAC_MASK); + if (rc == PMIC_SUCCESS) { + /* Also reset the driver state information to match. Note that we + * keep the device handle and event callback settings unchanged + * since these don't affect the actual hardware and we rely on + * the user to explicitly close the handle or deregister callbacks + */ + stDAC.busID = AUDIO_DATA_BUS_1; + stDAC.protocol = NORMAL_MSB_JUSTIFIED_MODE; + stDAC.protocol_set = false; + stDAC.masterSlave = BUS_MASTER_MODE; + stDAC.numSlots = USE_2_TIMESLOTS; + stDAC.clockIn = CLOCK_IN_CLIA; + stDAC.samplingRate = STDAC_RATE_44_1_KHZ; + stDAC.clockFreq = STDAC_CLI_13MHZ; + stDAC.invert = NO_INVERT; + stDAC.timeslot = USE_TS0_TS1; + stDAC.config = (PMIC_AUDIO_STDAC_CONFIG) 0; + + } + } + } else if ((handle == vCodec.handle) + && (vCodec.handleState == HANDLE_IN_USE)) { + /* Disable the audio input section when disabling the Voice CODEC. */ + pmic_write_reg(REG_AUDIO_TX, RESET_AUDIO_TX, REG_FULLMASK); + + rc = pmic_write_reg(REG_AUDIO_CODEC, + RESET_AUD_CODEC, REG_FULLMASK); + + if (rc == PMIC_SUCCESS) { + rc = pmic_write_reg(REG_AUDIO_SSI_NETWORK, + RESET_SSI_NETWORK, + REG_SSI_VCODEC_MASK); + if (rc == PMIC_SUCCESS) { + + /* Also reset the driver state information to match. Note that we + * keep the device handle and event callback settings unchanged + * since these don't affect the actual hardware and we rely on + * the user to explicitly close the handle or deregister callbacks + */ + vCodec.busID = AUDIO_DATA_BUS_2; + vCodec.protocol = NETWORK_MODE; + vCodec.protocol_set = false; + vCodec.masterSlave = BUS_SLAVE_MODE; + vCodec.numSlots = USE_4_TIMESLOTS; + vCodec.clockIn = CLOCK_IN_CLIB; + vCodec.samplingRate = VCODEC_RATE_8_KHZ; + vCodec.clockFreq = VCODEC_CLI_13MHZ; + vCodec.invert = NO_INVERT; + vCodec.timeslot = USE_TS0; + vCodec.config = + INPUT_HIGHPASS_FILTER | + OUTPUT_HIGHPASS_FILTER; + + } + } + + } else if ((handle == extStereoIn.handle) && + (extStereoIn.handleState == HANDLE_IN_USE)) { + /* Disable the Ext stereo Amplifier and disable it as analog mixer input */ + reg_mask = SET_BITS(regAUDIO_RX_1, ARXINEN, 1); + pmic_write_reg(REG_AUDIO_RX_1, 0, reg_mask); + + reg_mask = SET_BITS(regAUDIO_RX_0, ADDRXIN, 1); + pmic_write_reg(REG_AUDIO_RX_0, 0, reg_mask); + + /* We don't need to reset any other registers for this case. */ + rc = PMIC_SUCCESS; + } + + return rc; +} + +/*! + * @brief Deregister the callback function and event mask currently associated + * with an audio device handle. + * + * This function deregisters any existing callback function and event mask for + * the given audio device handle. This is done by either calling the + * pmic_audio_clear_callback() API or by closing the device handle. + * + * Note that this function should only be called with the mutex already + * acquired. We will also acquire the spinlock here to prevent possible + * race conditions with the interrupt handler. + * + * @param[in] callback The current event callback function pointer. + * @param[in] eventMask The current audio event mask. + * + * @retval PMIC_SUCCESS If the callback function and event mask + * were both successfully deregistered. + * @retval PMIC_ERROR If either the callback function or the + * event mask was not successfully + * deregistered. + */ + +static PMIC_STATUS pmic_audio_deregister(void *callback, + PMIC_AUDIO_EVENTS * const eventMask) +{ + unsigned long flags; + pmic_event_callback_t eventNotify; + PMIC_STATUS rc = PMIC_SUCCESS; + + /* Deregister each of the PMIC events that we had previously + * registered for by calling pmic_event_subscribe(). + */ + if (*eventMask & (HEADSET_DETECTED)) { + /* We need to deregister for the A1 amplifier interrupt. */ + eventNotify.func = callback; + eventNotify.param = (void *)(CORE_EVENT_HSDETI); + if (pmic_event_unsubscribe(EVENT_HSDETI, eventNotify) == + PMIC_SUCCESS) { + *eventMask &= ~(HEADSET_DETECTED); + pr_debug("Deregistered for EVENT_HSDETI\n"); + } else { + rc = PMIC_ERROR; + } + } + + if (*eventMask & (HEADSET_STEREO)) { + /* We need to deregister for the A1 amplifier interrupt. */ + eventNotify.func = callback; + eventNotify.param = (void *)(CORE_EVENT_HSLI); + if (pmic_event_unsubscribe(EVENT_HSLI, eventNotify) == + PMIC_SUCCESS) { + *eventMask &= ~(HEADSET_STEREO); + pr_debug("Deregistered for EVENT_HSLI\n"); + } else { + rc = PMIC_ERROR; + } + } + if (*eventMask & (HEADSET_THERMAL_SHUTDOWN)) { + /* We need to deregister for the A1 amplifier interrupt. */ + eventNotify.func = callback; + eventNotify.param = (void *)(CORE_EVENT_ALSPTHI); + if (pmic_event_unsubscribe(EVENT_ALSPTHI, eventNotify) == + PMIC_SUCCESS) { + *eventMask &= ~(HEADSET_THERMAL_SHUTDOWN); + pr_debug("Deregistered for EVENT_ALSPTHI\n"); + } else { + rc = PMIC_ERROR; + } + } + if (*eventMask & (HEADSET_SHORT_CIRCUIT)) { + /* We need to deregister for the A1 amplifier interrupt. */ + eventNotify.func = callback; + eventNotify.param = (void *)(CORE_EVENT_AHSSHORTI); + if (pmic_event_unsubscribe(EVENT_AHSSHORTI, eventNotify) == + PMIC_SUCCESS) { + *eventMask &= ~(HEADSET_SHORT_CIRCUIT); + pr_debug("Deregistered for EVENT_AHSSHORTI\n"); + } else { + rc = PMIC_ERROR; + } + } + + if (rc == PMIC_SUCCESS) { + /* We need to grab the spinlock here to create a critical section to + * avoid any possible race conditions with the interrupt handler + */ + spin_lock_irqsave(&lock, flags); + + /* Restore the initial reset values for the callback function + * and event mask parameters. This should be NULL and zero, + * respectively. + */ + callback = NULL; + *eventMask = 0; + + /* Exit the critical section. */ + spin_unlock_irqrestore(&lock, flags); + } + + return rc; +} + +/*@}*/ + +/************************************************************************** + * Module initialization and termination functions. + * + * Note that if this code is compiled into the kernel, then the + * module_init() function will be called within the device_initcall() + * group. + ************************************************************************** + */ + +/*! + * @name Audio Driver Loading/Unloading Functions + * These non-exported internal functions are used to support the audio + * device driver initialization and de-initialization operations. + */ +/*@{*/ + +/*! + * @brief This is the audio device driver initialization function. + * + * This function is called by the kernel when this device driver is first + * loaded. + */ +static int __init mc13783_pmic_audio_init(void) +{ + printk(KERN_INFO "PMIC Audio driver loading...\n"); + + return 0; +} + +/*! + * @brief This is the audio device driver de-initialization function. + * + * This function is called by the kernel when this device driver is about + * to be unloaded. + */ +static void __exit mc13783_pmic_audio_exit(void) +{ + printk(KERN_INFO "PMIC Audio driver unloading...\n"); + + /* Close all device handles that are still open. This will also + * deregister any callbacks that may still be active. + */ + if (stDAC.handleState == HANDLE_IN_USE) { + pmic_audio_close(stDAC.handle); + } + if (vCodec.handleState == HANDLE_IN_USE) { + pmic_audio_close(vCodec.handle); + } + if (extStereoIn.handleState == HANDLE_IN_USE) { + pmic_audio_close(extStereoIn.handle); + } + + /* Explicitly reset all of the audio registers so that there is no + * possibility of leaving the audio hardware in a state + * where it can cause problems if there is no device driver loaded. + */ + pmic_write_reg(REG_AUDIO_STEREO_DAC, RESET_ST_DAC, REG_FULLMASK); + pmic_write_reg(REG_AUDIO_CODEC, RESET_AUD_CODEC, REG_FULLMASK); + pmic_write_reg(REG_AUDIO_TX, RESET_AUDIO_TX, REG_FULLMASK); + pmic_write_reg(REG_AUDIO_SSI_NETWORK, RESET_SSI_NETWORK, REG_FULLMASK); + pmic_write_reg(REG_AUDIO_RX_0, RESET_AUDIO_RX_0, REG_FULLMASK); + pmic_write_reg(REG_AUDIO_RX_1, RESET_AUDIO_RX_1, REG_FULLMASK); +} + +/*@}*/ + +/* + * Module entry points and description information. + */ + +module_init(mc13783_pmic_audio_init); +module_exit(mc13783_pmic_audio_exit); + +MODULE_DESCRIPTION("PMIC - mc13783 ADC driver"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/pmic/mc13783/pmic_battery.c b/drivers/mxc/pmic/mc13783/pmic_battery.c new file mode 100644 index 000000000000..66f578c8a8c6 --- /dev/null +++ b/drivers/mxc/pmic/mc13783/pmic_battery.c @@ -0,0 +1,938 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mc13783/pmic_battery.c + * @brief This is the main file of PMIC(mc13783) Battery driver. + * + * @ingroup PMIC_BATTERY + */ + +/* + * Includes + */ +#include <linux/platform_device.h> +#include <linux/poll.h> +#include <linux/sched.h> +#include <linux/time.h> +#include <linux/delay.h> +#include <linux/wait.h> + +#include <asm/arch/pmic_battery.h> +#include <asm/arch/pmic_adc.h> + +#include "pmic_battery_defs.h" +#include "../core/pmic_config.h" + +static int pmic_battery_major; + +/*! + * Number of users waiting in suspendq + */ +static int swait = 0; + +/*! + * To indicate whether any of the battery devices are suspending + */ +static int suspend_flag = 0; + +/*! + * The suspendq is used to block application calls + */ +static wait_queue_head_t suspendq; + +static struct class *pmic_battery_class; + +/* EXPORTED FUNCTIONS */ +EXPORT_SYMBOL(pmic_batt_enable_charger); +EXPORT_SYMBOL(pmic_batt_disable_charger); +EXPORT_SYMBOL(pmic_batt_set_charger); +EXPORT_SYMBOL(pmic_batt_get_charger_setting); +EXPORT_SYMBOL(pmic_batt_get_charge_current); +EXPORT_SYMBOL(pmic_batt_enable_eol); +EXPORT_SYMBOL(pmic_batt_bp_enable_eol); +EXPORT_SYMBOL(pmic_batt_disable_eol); +EXPORT_SYMBOL(pmic_batt_set_out_control); +EXPORT_SYMBOL(pmic_batt_set_threshold); +EXPORT_SYMBOL(pmic_batt_led_control); +EXPORT_SYMBOL(pmic_batt_set_reverse_supply); +EXPORT_SYMBOL(pmic_batt_set_unregulated); +EXPORT_SYMBOL(pmic_batt_set_5k_pull); +EXPORT_SYMBOL(pmic_batt_event_subscribe); +EXPORT_SYMBOL(pmic_batt_event_unsubscribe); + +/*! + * This is the suspend of power management for the pmic battery API. + * It suports SAVE and POWER_DOWN state. + * + * @param pdev the device + * @param state the state + * + * @return This function returns 0 if successful. + */ +static int pmic_battery_suspend(struct platform_device *pdev, + pm_message_t state) +{ + unsigned int reg_value = 0; + + suspend_flag = 1; + CHECK_ERROR(pmic_write_reg(REG_CHARGER, reg_value, PMIC_ALL_BITS)); + + return 0; +}; + +/*! + * This is the resume of power management for the pmic battery API. + * It suports RESTORE state. + * + * @param pdev the device + * + * @return This function returns 0 if successful. + */ +static int pmic_battery_resume(struct platform_device *pdev) +{ + suspend_flag = 0; + while (swait > 0) { + swait--; + wake_up_interruptible(&suspendq); + } + + return 0; +}; + +/*! + * This function is used to start charging a battery. For different charger, + * different voltage and current range are supported. \n + * + * + * @param chgr Charger as defined in \b t_batt_charger. + * @param c_voltage Charging voltage. + * @param c_current Charging current. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_batt_enable_charger(t_batt_charger chgr, + unsigned char c_voltage, + unsigned char c_current) +{ + unsigned int val, mask, reg; + + val = 0; + mask = 0; + reg = 0; + + if (suspend_flag == 1) { + return PMIC_ERROR; + } + + switch (chgr) { + case BATT_MAIN_CHGR: + val = BITFVAL(MC13783_BATT_DAC_DAC, c_current) | + BITFVAL(MC13783_BATT_DAC_V_DAC, c_voltage); + mask = BITFMASK(MC13783_BATT_DAC_DAC) | + BITFMASK(MC13783_BATT_DAC_V_DAC); + reg = REG_CHARGER; + break; + + case BATT_CELL_CHGR: + val = BITFVAL(MC13783_BATT_DAC_V_COIN, c_voltage) | + BITFVAL(MC13783_BATT_DAC_COIN_CH_EN, + MC13783_BATT_DAC_COIN_CH_EN_ENABLED); + mask = BITFMASK(MC13783_BATT_DAC_V_COIN) | + BITFMASK(MC13783_BATT_DAC_COIN_CH_EN); + reg = REG_POWER_CONTROL_0; + break; + + case BATT_TRCKLE_CHGR: + val = BITFVAL(MC13783_BATT_DAC_TRCKLE, c_current); + mask = BITFMASK(MC13783_BATT_DAC_TRCKLE); + reg = REG_CHARGER; + break; + + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(reg, val, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function turns off a charger. + * + * @param chgr Charger as defined in \b t_batt_charger. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_batt_disable_charger(t_batt_charger chgr) +{ + unsigned int val, mask, reg; + + val = 0; + mask = 0; + reg = 0; + + if (suspend_flag == 1) { + return PMIC_ERROR; + } + + switch (chgr) { + case BATT_MAIN_CHGR: + val = BITFVAL(MC13783_BATT_DAC_DAC, 0) | + BITFVAL(MC13783_BATT_DAC_V_DAC, 0); + mask = BITFMASK(MC13783_BATT_DAC_DAC) | + BITFMASK(MC13783_BATT_DAC_V_DAC); + reg = REG_CHARGER; + break; + + case BATT_CELL_CHGR: + val = BITFVAL(MC13783_BATT_DAC_COIN_CH_EN, + MC13783_BATT_DAC_COIN_CH_EN_DISABLED); + mask = BITFMASK(MC13783_BATT_DAC_COIN_CH_EN); + reg = REG_POWER_CONTROL_0; + break; + + case BATT_TRCKLE_CHGR: + val = BITFVAL(MC13783_BATT_DAC_TRCKLE, 0); + mask = BITFMASK(MC13783_BATT_DAC_TRCKLE); + reg = REG_CHARGER; + break; + + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(reg, val, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function is used to change the charger setting. + * + * @param chgr Charger as defined in \b t_batt_charger. + * @param c_voltage Charging voltage. + * @param c_current Charging current. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_batt_set_charger(t_batt_charger chgr, + unsigned char c_voltage, + unsigned char c_current) +{ + unsigned int val, mask, reg; + + val = 0; + mask = 0; + reg = 0; + + if (suspend_flag == 1) { + return PMIC_ERROR; + } + + switch (chgr) { + case BATT_MAIN_CHGR: + val = BITFVAL(MC13783_BATT_DAC_DAC, c_current) | + BITFVAL(MC13783_BATT_DAC_V_DAC, c_voltage); + mask = BITFMASK(MC13783_BATT_DAC_DAC) | + BITFMASK(MC13783_BATT_DAC_V_DAC); + reg = REG_CHARGER; + break; + + case BATT_CELL_CHGR: + val = BITFVAL(MC13783_BATT_DAC_V_COIN, c_voltage); + mask = BITFMASK(MC13783_BATT_DAC_V_COIN); + reg = REG_POWER_CONTROL_0; + break; + + case BATT_TRCKLE_CHGR: + val = BITFVAL(MC13783_BATT_DAC_TRCKLE, c_current); + mask = BITFMASK(MC13783_BATT_DAC_TRCKLE); + reg = REG_CHARGER; + break; + + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(reg, val, mask)); + return PMIC_SUCCESS; +} + +/*! + * This function is used to retrive the charger setting. + * + * @param chgr Charger as defined in \b t_batt_charger. + * @param c_voltage Output parameter for charging voltage setting. + * @param c_current Output parameter for charging current setting. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_batt_get_charger_setting(t_batt_charger chgr, + unsigned char *c_voltage, + unsigned char *c_current) +{ + unsigned int val, reg; + + reg = 0; + + if (suspend_flag == 1) { + return PMIC_ERROR; + } + + switch (chgr) { + case BATT_MAIN_CHGR: + case BATT_TRCKLE_CHGR: + reg = REG_CHARGER; + break; + case BATT_CELL_CHGR: + reg = REG_POWER_CONTROL_0; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(reg, &val, PMIC_ALL_BITS)); + + switch (chgr) { + case BATT_MAIN_CHGR: + *c_voltage = BITFEXT(val, MC13783_BATT_DAC_V_DAC);; + *c_current = BITFEXT(val, MC13783_BATT_DAC_DAC); + break; + + case BATT_CELL_CHGR: + *c_voltage = BITFEXT(val, MC13783_BATT_DAC_V_COIN); + *c_current = 0; + break; + + case BATT_TRCKLE_CHGR: + *c_voltage = 0; + *c_current = BITFEXT(val, MC13783_BATT_DAC_TRCKLE); + break; + + default: + return PMIC_PARAMETER_ERROR; + } + + return PMIC_SUCCESS; +} + +/*! + * This function is retrives the main battery charging current. + * + * @param c_current Output parameter for charging current setting. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_batt_get_charge_current(unsigned short *c_current) +{ + t_channel channel; + unsigned short result[8]; + + if (suspend_flag == 1) { + return PMIC_ERROR; + } + + channel = CHARGE_CURRENT; + CHECK_ERROR(pmic_adc_convert(channel, result)); + *c_current = result[0]; + + return PMIC_SUCCESS; +} + +/*! + * This function enables End-of-Life comparator. Not supported on + * mc13783. Use pmic_batt_bp_enable_eol function. + * + * @param threshold End-of-Life threshold. + * + * @return This function returns PMIC_UNSUPPORTED + */ +PMIC_STATUS pmic_batt_enable_eol(unsigned char threshold) +{ + return PMIC_NOT_SUPPORTED; +} + +/*! + * This function enables End-of-Life comparator. + * + * @param typical Falling Edge Threshold threshold. + * @verbatim + BPDET UVDET LOBATL + ____ _____ ___________ + 0 2.6 UVDET + 0.2 + 1 2.6 UVDET + 0.3 + 2 2.6 UVDET + 0.4 + 3 2.6 UVDET + 0.5 + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_batt_bp_enable_eol(t_bp_threshold typical) +{ + unsigned int val, mask; + + if (suspend_flag == 1) { + return PMIC_ERROR; + } + + val = BITFVAL(MC13783_BATT_DAC_EOL_CMP_EN, + MC13783_BATT_DAC_EOL_CMP_EN_ENABLE) | + BITFVAL(MC13783_BATT_DAC_EOL_SEL, typical); + mask = BITFMASK(MC13783_BATT_DAC_EOL_CMP_EN) | + BITFMASK(MC13783_BATT_DAC_EOL_SEL); + + CHECK_ERROR(pmic_write_reg(REG_POWER_CONTROL_0, val, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function disables End-of-Life comparator. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_batt_disable_eol(void) +{ + unsigned int val, mask; + + if (suspend_flag == 1) { + return PMIC_ERROR; + } + + val = BITFVAL(MC13783_BATT_DAC_EOL_CMP_EN, + MC13783_BATT_DAC_EOL_CMP_EN_DISABLE); + mask = BITFMASK(MC13783_BATT_DAC_EOL_CMP_EN); + + CHECK_ERROR(pmic_write_reg(REG_POWER_CONTROL_0, val, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function sets the output controls. + * It sets the FETOVRD and FETCTRL bits of mc13783 + * + * @param control type of control. + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_batt_set_out_control(t_control control) +{ + unsigned int val, mask; + if (suspend_flag == 1) { + return PMIC_ERROR; + } + switch (control) { + case CONTROL_HARDWARE: + val = BITFVAL(MC13783_BATT_DAC_FETOVRD_EN, 0) | + BITFVAL(MC13783_BATT_DAC_FETCTRL_EN, 0); + mask = BITFMASK(MC13783_BATT_DAC_FETOVRD_EN) | + BITFMASK(MC13783_BATT_DAC_FETCTRL_EN); + break; + case CONTROL_BPFET_LOW: + val = BITFVAL(MC13783_BATT_DAC_FETOVRD_EN, 1) | + BITFVAL(MC13783_BATT_DAC_FETCTRL_EN, 0); + mask = BITFMASK(MC13783_BATT_DAC_FETOVRD_EN) | + BITFMASK(MC13783_BATT_DAC_FETCTRL_EN); + break; + case CONTROL_BPFET_HIGH: + val = BITFVAL(MC13783_BATT_DAC_FETOVRD_EN, 1) | + BITFVAL(MC13783_BATT_DAC_FETCTRL_EN, 1); + mask = BITFMASK(MC13783_BATT_DAC_FETOVRD_EN) | + BITFMASK(MC13783_BATT_DAC_FETCTRL_EN); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(REG_CHARGER, val, mask)); + return PMIC_SUCCESS; +} + +/*! + * This function sets over voltage threshold. + * + * @param threshold value of over voltage threshold. + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_batt_set_threshold(int threshold) +{ + unsigned int val, mask; + + if (suspend_flag == 1) { + return PMIC_ERROR; + } + if (threshold > BAT_THRESHOLD_MAX) { + return PMIC_PARAMETER_ERROR; + } + val = BITFVAL(MC13783_BATT_DAC_OVCTRL, threshold); + mask = BITFMASK(MC13783_BATT_DAC_OVCTRL); + CHECK_ERROR(pmic_write_reg(REG_CHARGER, val, mask)); + return PMIC_SUCCESS; +} + +/*! + * This function controls charge LED. + * + * @param on If on is ture, LED will be turned on, + * or otherwise, LED will be turned off. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_batt_led_control(bool on) +{ + unsigned val, mask; + + if (suspend_flag == 1) { + return PMIC_ERROR; + } + + val = BITFVAL(MC13783_BATT_DAC_LED_EN, on); + mask = BITFMASK(MC13783_BATT_DAC_LED_EN); + + CHECK_ERROR(pmic_write_reg(REG_CHARGER, val, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function sets reverse supply mode. + * + * @param enable If enable is ture, reverse supply mode is enable, + * or otherwise, reverse supply mode is disabled. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_batt_set_reverse_supply(bool enable) +{ + unsigned val, mask; + + if (suspend_flag == 1) { + return PMIC_ERROR; + } + + val = BITFVAL(MC13783_BATT_DAC_REVERSE_SUPPLY, enable); + mask = BITFMASK(MC13783_BATT_DAC_REVERSE_SUPPLY); + + CHECK_ERROR(pmic_write_reg(REG_CHARGER, val, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function sets unregulatored charging mode on main battery. + * + * @param enable If enable is ture, unregulated charging mode is + * enable, or otherwise, disabled. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_batt_set_unregulated(bool enable) +{ + unsigned val, mask; + + if (suspend_flag == 1) { + return PMIC_ERROR; + } + + val = BITFVAL(MC13783_BATT_DAC_UNREGULATED, enable); + mask = BITFMASK(MC13783_BATT_DAC_UNREGULATED); + + CHECK_ERROR(pmic_write_reg(REG_CHARGER, val, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function sets a 5K pull down at CHRGRAW. + * To be used in the dual path charging configuration. + * + * @param enable If enable is true, 5k pull down is + * enable, or otherwise, disabled. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_batt_set_5k_pull(bool enable) +{ + unsigned val, mask; + + if (suspend_flag == 1) { + return PMIC_ERROR; + } + + val = BITFVAL(MC13783_BATT_DAC_5K, enable); + mask = BITFMASK(MC13783_BATT_DAC_5K); + + CHECK_ERROR(pmic_write_reg(REG_CHARGER, val, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function is used to un/subscribe on battery event IT. + * + * @param event type of event. + * @param callback event callback function. + * @param sub define if Un/subscribe event. + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS mc13783_battery_event(t_batt_event event, void *callback, bool sub) +{ + pmic_event_callback_t bat_callback; + type_event bat_event; + + bat_callback.func = callback; + bat_callback.param = NULL; + switch (event) { + case BAT_IT_CHG_DET: + bat_event = EVENT_WLOWI; + break; + case BAT_IT_CHG_OVERVOLT: + bat_event = EVENT_CHGOVI; + break; + case BAT_IT_CHG_REVERSE: + bat_event = EVENT_CHGREVI; + break; + case BAT_IT_CHG_SHORT_CIRCUIT: + bat_event = EVENT_CHGSHORTI; + break; + case BAT_IT_CCCV: + bat_event = EVENT_CCCVI; + break; + case BAT_IT_BELOW_THRESHOLD: + bat_event = EVENT_CHRGCURRI; + break; + default: + return PMIC_PARAMETER_ERROR; + } + if (sub == true) { + CHECK_ERROR(pmic_event_subscribe(event, bat_callback)); + } else { + CHECK_ERROR(pmic_event_unsubscribe(event, bat_callback)); + } + return 0; +} + +/*! + * This function is used to subscribe on battery event IT. + * + * @param event type of event. + * @param callback event callback function. + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_batt_event_subscribe(t_batt_event event, void *callback) +{ + if (suspend_flag == 1) { + return PMIC_ERROR; + } + return mc13783_battery_event(event, callback, true); +} + +/*! + * This function is used to un subscribe on battery event IT. + * + * @param event type of event. + * @param callback event callback function. + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_batt_event_unsubscribe(t_batt_event event, void *callback) +{ + if (suspend_flag == 1) { + return PMIC_ERROR; + } + return mc13783_battery_event(event, callback, false); +} + +/*! + * This function implements IOCTL controls on a PMIC Battery device. + * + * @param inode pointer on the node + * @param file pointer on the file + * @param cmd the command + * @param arg the parameter + * @return This function returns 0 if successful. + */ +static int pmic_battery_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + t_charger_setting *chgr_setting = NULL; + unsigned short c_current; + t_eol_setting *eol_setting; + + if (_IOC_TYPE(cmd) != 'p') + return -ENOTTY; + + switch (cmd) { + case PMIC_BATT_CHARGER_CONTROL: + if ((chgr_setting = kmalloc(sizeof(t_charger_setting), + GFP_KERNEL)) == NULL) { + return -ENOMEM; + } + if (copy_from_user(chgr_setting, (t_charger_setting *) arg, + sizeof(t_charger_setting))) { + kfree(chgr_setting); + return -EFAULT; + } + + if (chgr_setting->on != false) { + CHECK_ERROR_KFREE(pmic_batt_enable_charger + (chgr_setting->chgr, + chgr_setting->c_voltage, + chgr_setting->c_current), + (kfree(chgr_setting))); + } else { + CHECK_ERROR(pmic_batt_disable_charger + (chgr_setting->chgr)); + } + + kfree(chgr_setting); + break; + + case PMIC_BATT_SET_CHARGER: + if ((chgr_setting = kmalloc(sizeof(t_charger_setting), + GFP_KERNEL)) == NULL) { + return -ENOMEM; + } + if (copy_from_user(chgr_setting, (t_charger_setting *) arg, + sizeof(t_charger_setting))) { + kfree(chgr_setting); + return -EFAULT; + } + + CHECK_ERROR_KFREE(pmic_batt_set_charger(chgr_setting->chgr, + chgr_setting->c_voltage, + chgr_setting-> + c_current), + (kfree(chgr_setting))); + + kfree(chgr_setting); + break; + + case PMIC_BATT_GET_CHARGER: + if ((chgr_setting = kmalloc(sizeof(t_charger_setting), + GFP_KERNEL)) == NULL) { + return -ENOMEM; + } + if (copy_from_user(chgr_setting, (t_charger_setting *) arg, + sizeof(t_charger_setting))) { + kfree(chgr_setting); + return -EFAULT; + } + + CHECK_ERROR_KFREE(pmic_batt_get_charger_setting + (chgr_setting->chgr, &chgr_setting->c_voltage, + &chgr_setting->c_current), + (kfree(chgr_setting))); + if (copy_to_user + ((t_charger_setting *) arg, chgr_setting, + sizeof(t_charger_setting))) { + return -EFAULT; + } + + kfree(chgr_setting); + break; + + case PMIC_BATT_GET_CHARGER_CURRENT: + CHECK_ERROR(pmic_batt_get_charge_current(&c_current)); + if (copy_to_user((unsigned char *)arg, &c_current, + sizeof(unsigned char *))) { + return -EFAULT; + } + + break; + + case PMIC_BATT_EOL_CONTROL: + if ((eol_setting = kmalloc(sizeof(t_eol_setting), GFP_KERNEL)) + == NULL) { + return -ENOMEM; + } + if (copy_from_user(eol_setting, (t_eol_setting *) arg, + sizeof(t_eol_setting))) { + kfree(eol_setting); + return -EFAULT; + } + + if (eol_setting->enable != false) { + CHECK_ERROR_KFREE(pmic_batt_bp_enable_eol + (eol_setting->typical), + (kfree(chgr_setting))); + } else { + CHECK_ERROR_KFREE(pmic_batt_disable_eol(), + (kfree(chgr_setting))); + } + + kfree(eol_setting); + break; + + case PMIC_BATT_SET_OUT_CONTROL: + CHECK_ERROR(pmic_batt_set_out_control((t_control) arg)); + break; + + case PMIC_BATT_SET_THRESHOLD: + CHECK_ERROR(pmic_batt_set_threshold((int)arg)); + break; + + case PMIC_BATT_LED_CONTROL: + CHECK_ERROR(pmic_batt_led_control((bool) arg)); + break; + + case PMIC_BATT_REV_SUPP_CONTROL: + CHECK_ERROR(pmic_batt_set_reverse_supply((bool) arg)); + break; + + case PMIC_BATT_UNREG_CONTROL: + CHECK_ERROR(pmic_batt_set_unregulated((bool) arg)); + break; + + default: + return -EINVAL; + } + return 0; +} + +/*! + * This function implements the open method on a Pmic battery device. + * + * @param inode pointer on the node + * @param file pointer on the file + * @return This function returns 0. + */ +static int pmic_battery_open(struct inode *inode, struct file *file) +{ + while (suspend_flag == 1) { + swait++; + /* Block if the device is suspended */ + if (wait_event_interruptible(suspendq, (suspend_flag == 0))) { + return -ERESTARTSYS; + } + } + return 0; +} + +/*! + * This function implements the release method on a Pmic battery device. + * + * @param inode pointer on the node + * @param file pointer on the file + * @return This function returns 0. + */ +static int pmic_battery_release(struct inode *inode, struct file *file) +{ + while (suspend_flag == 1) { + swait++; + /* Block if the device is suspended */ + if (wait_event_interruptible(suspendq, (suspend_flag == 0))) { + return -ERESTARTSYS; + } + } + return 0; +} + +static struct file_operations pmic_battery_fops = { + .owner = THIS_MODULE, + .ioctl = pmic_battery_ioctl, + .open = pmic_battery_open, + .release = pmic_battery_release, +}; + +static int pmic_battery_remove(struct platform_device *pdev) +{ + class_device_destroy(pmic_battery_class, MKDEV(pmic_battery_major, 0)); + class_destroy(pmic_battery_class); + unregister_chrdev(pmic_battery_major, PMIC_BATTERY_STRING); + return 0; +} + +static int pmic_battery_probe(struct platform_device *pdev) +{ + int ret = 0; + struct class_device *temp_class; + + pmic_battery_major = register_chrdev(0, PMIC_BATTERY_STRING, + &pmic_battery_fops); + + if (pmic_battery_major < 0) { + printk(KERN_ERR "Unable to get a major for pmic_battery\n"); + return pmic_battery_major; + } + init_waitqueue_head(&suspendq); + + pmic_battery_class = class_create(THIS_MODULE, PMIC_BATTERY_STRING); + if (IS_ERR(pmic_battery_class)) { + printk(KERN_ERR "Error creating PMIC battery class.\n"); + ret = PTR_ERR(pmic_battery_class); + goto err_out1; + } + + temp_class = class_device_create(pmic_battery_class, NULL, + MKDEV(pmic_battery_major, 0), + NULL, PMIC_BATTERY_STRING); + if (IS_ERR(temp_class)) { + printk(KERN_ERR "Error creating PMIC battery class device.\n"); + ret = PTR_ERR(temp_class); + goto err_out2; + } + + printk(KERN_INFO "PMIC Battery successfully probed\n"); + + return ret; + + err_out2: + class_destroy(pmic_battery_class); + err_out1: + unregister_chrdev(pmic_battery_major, PMIC_BATTERY_STRING); + return ret; +} + +static struct platform_driver pmic_battery_driver_ldm = { + .driver = { + .name = "pmic_battery", + .bus = &platform_bus_type, + }, + .suspend = pmic_battery_suspend, + .resume = pmic_battery_resume, + .probe = pmic_battery_probe, + .remove = pmic_battery_remove, +}; + +/* + * Init and Exit + */ + +static int __init pmic_battery_init(void) +{ + pr_debug("PMIC Battery driver loading...\n"); + return platform_driver_register(&pmic_battery_driver_ldm); +} + +static void __exit pmic_battery_exit(void) +{ + platform_driver_unregister(&pmic_battery_driver_ldm); + pr_debug("PMIC Battery driver successfully unloaded\n"); +} + +/* + * Module entry points + */ + +module_init(pmic_battery_init); +module_exit(pmic_battery_exit); + +MODULE_DESCRIPTION("pmic_battery driver"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/pmic/mc13783/pmic_battery_defs.h b/drivers/mxc/pmic/mc13783/pmic_battery_defs.h new file mode 100644 index 000000000000..9f66a185cd2f --- /dev/null +++ b/drivers/mxc/pmic/mc13783/pmic_battery_defs.h @@ -0,0 +1,81 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mc13783/pmic_battery_defs.h + * @brief This is the internal header for PMIC(mc13783) Battery driver. + * + * @ingroup PMIC_BATTERY + */ + +#ifndef __PMIC_BATTERY_DEFS_H__ +#define __PMIC_BATTERY_DEFS_H__ + +#define PMIC_BATTERY_STRING "pmic_battery" + +/* REG_CHARGE */ +#define MC13783_BATT_DAC_V_DAC_LSH 0 +#define MC13783_BATT_DAC_V_DAC_WID 3 +#define MC13783_BATT_DAC_DAC_LSH 3 +#define MC13783_BATT_DAC_DAC_WID 4 +#define MC13783_BATT_DAC_TRCKLE_LSH 7 +#define MC13783_BATT_DAC_TRCKLE_WID 3 +#define MC13783_BATT_DAC_FETOVRD_EN_LSH 10 +#define MC13783_BATT_DAC_FETOVRD_EN_WID 1 +#define MC13783_BATT_DAC_FETCTRL_EN_LSH 11 +#define MC13783_BATT_DAC_FETCTRL_EN_WID 1 +#define MC13783_BATT_DAC_REVERSE_SUPPLY_LSH 13 +#define MC13783_BATT_DAC_REVERSE_SUPPLY_WID 1 +#define MC13783_BATT_DAC_OVCTRL_LSH 15 +#define MC13783_BATT_DAC_OVCTRL_WID 2 +#define MC13783_BATT_DAC_UNREGULATED_LSH 17 +#define MC13783_BATT_DAC_UNREGULATED_WID 1 +#define MC13783_BATT_DAC_LED_EN_LSH 18 +#define MC13783_BATT_DAC_LED_EN_WID 1 +#define MC13783_BATT_DAC_5K_LSH 19 +#define MC13783_BATT_DAC_5K_WID 1 + +#define BITS_OUT_VOLTAGE 0 +#define LONG_OUT_VOLTAGE 3 +#define BITS_CURRENT_MAIN 3 +#define LONG_CURRENT_MAIN 4 +#define BITS_CURRENT_TRICKLE 7 +#define LONG_CURRENT_TRICKLE 3 +#define BIT_FETOVRD 10 +#define BIT_FETCTRL 11 +#define BIT_RVRSMODE 13 +#define BITS_OVERVOLTAGE 15 +#define LONG_OVERVOLTAGE 2 +#define BIT_UNREGULATED 17 +#define BIT_CHRG_LED 18 +#define BIT_CHRGRAWPDEN 19 + +/* REG_POWXER_CONTROL_0 */ +#define MC13783_BATT_DAC_V_COIN_LSH 20 +#define MC13783_BATT_DAC_V_COIN_WID 3 +#define MC13783_BATT_DAC_COIN_CH_EN_LSH 23 +#define MC13783_BATT_DAC_COIN_CH_EN_WID 1 +#define MC13783_BATT_DAC_COIN_CH_EN_ENABLED 1 +#define MC13783_BATT_DAC_COIN_CH_EN_DISABLED 0 +#define MC13783_BATT_DAC_EOL_CMP_EN_LSH 18 +#define MC13783_BATT_DAC_EOL_CMP_EN_WID 1 +#define MC13783_BATT_DAC_EOL_CMP_EN_ENABLE 1 +#define MC13783_BATT_DAC_EOL_CMP_EN_DISABLE 0 +#define MC13783_BATT_DAC_EOL_SEL_LSH 16 +#define MC13783_BATT_DAC_EOL_SEL_WID 2 + +#define DEF_VALUE 0 + +#define BAT_THRESHOLD_MAX 3 + +#endif /* __PMIC_BATTERY_DEFS_H__ */ diff --git a/drivers/mxc/pmic/mc13783/pmic_convity.c b/drivers/mxc/pmic/mc13783/pmic_convity.c new file mode 100644 index 000000000000..959d17240312 --- /dev/null +++ b/drivers/mxc/pmic/mc13783/pmic_convity.c @@ -0,0 +1,2483 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mc13783/pmic_convity.c + * @brief Implementation of the PMIC Connectivity driver APIs. + * + * The PMIC connectivity device driver and this API were developed to support + * the external connectivity capabilities of several power management ICs that + * are available from Freescale Semiconductor, Inc. + * + * The following operating modes, in terms of external connectivity, are + * supported: + * + * @verbatim + Operating Mode mc13783 + --------------- ------- + USB (incl. OTG) Yes + RS-232 Yes + CEA-936 Yes + + @endverbatim + * + * @ingroup PMIC_CONNECTIVITY + */ + +#include <linux/interrupt.h> /* For tasklet interface. */ +#include <linux/platform_device.h> /* For kernel module interface. */ +#include <linux/spinlock.h> /* For spinlock interface. */ +#include <asm/arch/pmic_convity.h> /* For PMIC Connectivity driver interface. */ +#include <asm/arch/pmic_adc.h> /* For PMIC ADC driver interface. */ + +#include "../core/pmic_config.h" + +/* + * mc13783 Connectivity API + */ +/* EXPORTED FUNCTIONS */ +EXPORT_SYMBOL(pmic_convity_open); +EXPORT_SYMBOL(pmic_convity_close); +EXPORT_SYMBOL(pmic_convity_set_mode); +EXPORT_SYMBOL(pmic_convity_get_mode); +EXPORT_SYMBOL(pmic_convity_reset); +EXPORT_SYMBOL(pmic_convity_set_callback); +EXPORT_SYMBOL(pmic_convity_clear_callback); +EXPORT_SYMBOL(pmic_convity_get_callback); +EXPORT_SYMBOL(pmic_convity_usb_set_speed); +EXPORT_SYMBOL(pmic_convity_usb_get_speed); +EXPORT_SYMBOL(pmic_convity_usb_set_power_source); +EXPORT_SYMBOL(pmic_convity_usb_get_power_source); +EXPORT_SYMBOL(pmic_convity_usb_set_xcvr); +EXPORT_SYMBOL(pmic_convity_usb_get_xcvr); +EXPORT_SYMBOL(pmic_convity_usb_otg_set_dlp_duration); +EXPORT_SYMBOL(pmic_convity_usb_otg_get_dlp_duration); +EXPORT_SYMBOL(pmic_convity_usb_otg_set_config); +EXPORT_SYMBOL(pmic_convity_usb_otg_clear_config); +EXPORT_SYMBOL(pmic_convity_usb_otg_get_config); +EXPORT_SYMBOL(pmic_convity_set_output); +EXPORT_SYMBOL(pmic_convity_rs232_set_config); +EXPORT_SYMBOL(pmic_convity_rs232_get_config); +EXPORT_SYMBOL(pmic_convity_cea936_exit_signal); + +/*! @def SET_BITS + * Set a register field to a given value. + */ + +#define SET_BITS(reg, field, value) (((value) << reg.field.offset) & \ + reg.field.mask) + +/*! @def GET_BITS + * Get the current value of a given register field. + */ +#define GET_BITS(reg, value) (((value) & reg.mask) >> \ + reg.offset) + +/*! + * @brief Define the possible states for a device handle. + * + * This enumeration is used to track the current state of each device handle. + */ +typedef enum { + HANDLE_FREE, /*!< Handle is available for use. */ + HANDLE_IN_USE /*!< Handle is currently in use. */ +} HANDLE_STATE; + +/* + * This structure is used to define a specific hardware register field. + * + * All hardware register fields are defined using an offset to the LSB + * and a mask. The offset is used to right shift a register value before + * applying the mask to actually obtain the value of the field. + */ +typedef struct { + const unsigned char offset; /* Offset of LSB of register field. */ + const unsigned int mask; /* Mask value used to isolate register field. */ +} REGFIELD; + +/*! + * @brief This structure is used to identify the fields in the USBCNTRL_REG_0 hardware register. + * + * This structure lists all of the fields within the USBCNTRL_REG_0 hardware + * register. + */ +typedef struct { + REGFIELD FSENB; /*!< USB Full Speed Enable */ + REGFIELD USB_SUSPEND; /*!< USB Suspend Mode Enable */ + REGFIELD USB_PU; /*!< USB Pullup Enable */ + REGFIELD UDP_PD; /*!< USB Data Plus Pulldown Enable */ + REGFIELD UDM_PD; /*!< USB 150K UDP Pullup Enable */ + REGFIELD DP150K_PU; /*!< USB Pullup/Pulldown Override Enable */ + REGFIELD VBUSPDENB; /*!< USB VBUS Pulldown NMOS Switch Enable */ + REGFIELD CURRENT_LIMIT; /*!< USB Regulator Current Limit Setting-3 bits */ + REGFIELD DLP_SRP; /*!< USB Data Line Pulsing Timer Enable */ + REGFIELD SE0_CONN; /*!< USB Pullup Connect When SE0 Detected */ + REGFIELD USBXCVREN; /*!< USB Transceiver Enabled When INTERFACE_MODE[2:0]=000 and RESETB=high */ + REGFIELD PULLOVR; /*!< 1K5 Pullup and UDP/UDM Pulldown Disable When UTXENB=Low */ + REGFIELD INTERFACE_MODE; /*!< Connectivity Interface Mode Select-3 Bits */ + REGFIELD DATSE0; /*!< USB Single or Differential Mode Select */ + REGFIELD BIDIR; /*!< USB Unidirectional/Bidirectional Transmission */ + REGFIELD USBCNTRL; /*!< USB Mode of Operation controlled By USBEN/SPI Pin */ + REGFIELD IDPD; /*!< USB UID Pulldown Enable */ + REGFIELD IDPULSE; /*!< USB Pulse to Gnd on UID Line Generated */ + REGFIELD IDPUCNTRL; /*!< USB UID Pin pulled high By 5ua Curr Source */ + REGFIELD DMPULSE; /*!< USB Positive pulse on the UDM Line Generated */ +} USBCNTRL_REG_0; + +/*! + * @brief This variable is used to access the USBCNTRL_REG_0 hardware register. + * + * This variable defines how to access all of the fields within the + * USBCNTRL_REG_0 hardware register. The initial values consist of the offset + * and mask values needed to access each of the register fields. + */ +static const USBCNTRL_REG_0 regUSB0 = { + {0, 0x000001}, /*!< FSENB */ + {1, 0x000002}, /*!< USB_SUSPEND */ + {2, 0x000004}, /*!< USB_PU */ + {3, 0x000008}, /*!< UDP_PD */ + {4, 0x000010}, /*!< UDM_PD */ + {5, 0x000020}, /*!< DP150K_PU */ + {6, 0x000040}, /*!< VBUSPDENB */ + {7, 0x000380}, /*!< CURRENT_LIMIT */ + {10, 0x000400}, /*!< DLP_SRP */ + {11, 0x000800}, /*!< SE0_CONN */ + {12, 0x001000}, /*!< USBXCVREN */ + {13, 0x002000}, /*!< PULLOVR */ + {14, 0x01c000}, /*!< INTERFACE_MODE */ + {17, 0x020000}, /*!< DATSE0 */ + {18, 0x040000}, /*!< BIDIR */ + {19, 0x080000}, /*!< USBCNTRL */ + {20, 0x100000}, /*!< IDPD */ + {21, 0x200000}, /*!< IDPULSE */ + {22, 0x400000}, /*!< IDPUCNTRL */ + {23, 0x800000} /*!< DMPULSE */ + +}; + +/*! + * @brief This structure is used to identify the fields in the USBCNTRL_REG_1 hardware register. + * + * This structure lists all of the fields within the USBCNTRL_REG_1 hardware + * register. + */ +typedef struct { + REGFIELD VUSBIN; /*!< Controls The Input Source For VUSB */ + REGFIELD VUSB; /*!< VUSB Output Voltage Select-High=3.3V Low=2.775V */ + REGFIELD VUSBEN; /*!< VUSB Output Enable- */ + REGFIELD VBUSEN; /*!< VBUS Output Enable- */ + REGFIELD RSPOL; /*!< Low=RS232 TX on UDM, RX on UDP + High= RS232 TX on UDP, RX on UDM */ + REGFIELD RSTRI; /*!< TX Forced To Tristate in RS232 Mode Only */ + REGFIELD ID100kPU; /*!< 100k UID Pullup Enabled */ +} USBCNTRL_REG_1; + +/*! + * @brief This variable is used to access the USBCNTRL_REG_1 hardware register. + * + * This variable defines how to access all of the fields within the + * USBCNTRL_REG_1 hardware register. The initial values consist of the offset + * and mask values needed to access each of the register fields. + */ +static const USBCNTRL_REG_1 regUSB1 = { + {0, 0x000003}, /*!< VUSBIN-2 Bits */ + {2, 0x000004}, /*!< VUSB */ + {3, 0x000008}, /*!< VUSBEN */ + /*{4, 0x000010} *//*!< Reserved */ + {5, 0x000020}, /*!< VBUSEN */ + {6, 0x000040}, /*!< RSPOL */ + {7, 0x000080}, /*!< RSTRI */ + {8, 0x000100} /*!< ID100kPU */ + /*!< 9-23 Unused */ +}; + +/*! Define a mask to access the entire hardware register. */ +static const unsigned int REG_FULLMASK = 0xffffff; + +/*! Define the mc13783 USBCNTRL_REG_0 register power on reset state. */ +static const unsigned int RESET_USBCNTRL_REG_0 = 0x080060; + +/*! Define the mc13783 USBCNTRL_REG_1 register power on reset state. */ +static const unsigned int RESET_USBCNTRL_REG_1 = 0x000006; + +static pmic_event_callback_t eventNotify; + +/*! + * @brief This structure is used to maintain the current device driver state. + * + * This structure maintains the current state of the connectivity driver. This + * includes both the PMIC hardware state as well as the device handle and + * callback states. + */ + +typedef struct { + PMIC_CONVITY_HANDLE handle; /*!< Device handle. */ + HANDLE_STATE handle_state; /*!< Device handle + state. */ + PMIC_CONVITY_MODE mode; /*!< Device mode. */ + PMIC_CONVITY_CALLBACK callback; /*!< Event callback function pointer. */ + PMIC_CONVITY_EVENTS eventMask; /*!< Event mask. */ + PMIC_CONVITY_USB_SPEED usbSpeed; /*!< USB connection + speed. */ + PMIC_CONVITY_USB_MODE usbMode; /*!< USB connection + mode. */ + PMIC_CONVITY_USB_POWER_IN usbPowerIn; /*!< USB transceiver + power source. */ + PMIC_CONVITY_USB_POWER_OUT usbPowerOut; /*!< USB transceiver + power output + level. */ + PMIC_CONVITY_USB_TRANSCEIVER_MODE usbXcvrMode; /*!< USB transceiver + mode. */ + unsigned int usbDlpDuration; /*!< USB Data Line + Pulsing duration. */ + PMIC_CONVITY_USB_OTG_CONFIG usbOtgCfg; /*!< USB OTG + configuration + options. */ + PMIC_CONVITY_RS232_INTERNAL rs232CfgInternal; /*!< RS-232 internal + connections. */ + PMIC_CONVITY_RS232_EXTERNAL rs232CfgExternal; /*!< RS-232 external + connections. */ +} pmic_convity_state_struct; + +/*! + * @brief This structure is used to maintain the current device driver state. + * + * This structure maintains the current state of the driver in USB mode. This + * includes both the PMIC hardware state as well as the device handle and + * callback states. + */ + +typedef struct { + PMIC_CONVITY_HANDLE handle; /*!< Device handle. */ + HANDLE_STATE handle_state; /*!< Device handle + state. */ + PMIC_CONVITY_MODE mode; /*!< Device mode. */ + PMIC_CONVITY_CALLBACK callback; /*!< Event callback function pointer. */ + PMIC_CONVITY_EVENTS eventMask; /*!< Event mask. */ + PMIC_CONVITY_USB_SPEED usbSpeed; /*!< USB connection + speed. */ + PMIC_CONVITY_USB_MODE usbMode; /*!< USB connection + mode. */ + PMIC_CONVITY_USB_POWER_IN usbPowerIn; /*!< USB transceiver + power source. */ + PMIC_CONVITY_USB_POWER_OUT usbPowerOut; /*!< USB transceiver + power output + level. */ + PMIC_CONVITY_USB_TRANSCEIVER_MODE usbXcvrMode; /*!< USB transceiver + mode. */ + unsigned int usbDlpDuration; /*!< USB Data Line + Pulsing duration. */ + PMIC_CONVITY_USB_OTG_CONFIG usbOtgCfg; /*!< USB OTG + configuration + options. */ +} pmic_convity_usb_state; + +/*! + * @brief This structure is used to maintain the current device driver state. + * + * This structure maintains the current state of the driver in RS_232 mode. This + * includes both the PMIC hardware state as well as the device handle and + * callback states. + */ + +typedef struct { + PMIC_CONVITY_HANDLE handle; /*!< Device handle. */ + HANDLE_STATE handle_state; /*!< Device handle + state. */ + PMIC_CONVITY_MODE mode; /*!< Device mode. */ + PMIC_CONVITY_CALLBACK callback; /*!< Event callback function pointer. */ + PMIC_CONVITY_EVENTS eventMask; /*!< Event mask. */ + PMIC_CONVITY_RS232_INTERNAL rs232CfgInternal; /*!< RS-232 internal + connections. */ + PMIC_CONVITY_RS232_EXTERNAL rs232CfgExternal; /*!< RS-232 external + connections. */ +} pmic_convity_rs232_state; + +/*! + * @brief This structure is used to maintain the current device driver state. + * + * This structure maintains the current state of the driver in cea-936 mode. This + * includes both the PMIC hardware state as well as the device handle and + * callback states. + */ + +typedef struct { + PMIC_CONVITY_HANDLE handle; /*!< Device handle. */ + HANDLE_STATE handle_state; /*!< Device handle + state. */ + PMIC_CONVITY_MODE mode; /*!< Device mode. */ + PMIC_CONVITY_CALLBACK callback; /*!< Event callback function pointer. */ + PMIC_CONVITY_EVENTS eventMask; /*!< Event mask. */ + +} pmic_convity_cea936_state; + +/*! + * @brief Identifies the hardware interrupt source. + * + * This enumeration identifies which of the possible hardware interrupt + * sources actually caused the current interrupt handler to be called. + */ +typedef enum { + CORE_EVENT_4V4 = 1, /*!< Detected USB 4.4 V event. */ + CORE_EVENT_2V0 = 2, /*!< Detected USB 2.0 V event. */ + CORE_EVENT_0V8 = 4, /*!< Detected USB 0.8 V event. */ + CORE_EVENT_ABDET = 8 /*!< Detected USB mini A-B connector event. */ +} PMIC_CORE_EVENT; + +/*! + * @brief This structure defines the reset/power on state for the Connectivity driver. + */ +static const pmic_convity_state_struct reset = { + 0, + HANDLE_FREE, + USB, + NULL, + 0, + USB_FULL_SPEED, + USB_PERIPHERAL, + USB_POWER_INTERNAL, + USB_POWER_3V3, + USB_TRANSCEIVER_OFF, + 0, + USB_PULL_OVERRIDE | USB_VBUS_CURRENT_LIMIT_HIGH, + RS232_TX_USE0VM_RX_UDATVP, + RS232_TX_UDM_RX_UDP +}; + +/*! + * @brief This structure maintains the current state of the Connectivity driver. + * + * The initial values must be identical to the reset state defined by the + * #reset variable. + */ +static pmic_convity_usb_state usb = { + 0, + HANDLE_FREE, + USB, + NULL, + 0, + USB_FULL_SPEED, + USB_PERIPHERAL, + USB_POWER_INTERNAL, + USB_POWER_3V3, + USB_TRANSCEIVER_OFF, + 0, + USB_PULL_OVERRIDE | USB_VBUS_CURRENT_LIMIT_HIGH, +}; + +/*! + * @brief This structure maintains the current state of the Connectivity driver. + * + * The initial values must be identical to the reset state defined by the + * #reset variable. + */ +static pmic_convity_rs232_state rs_232 = { + 0, + HANDLE_FREE, + RS232_1, + NULL, + 0, + RS232_TX_USE0VM_RX_UDATVP, + RS232_TX_UDM_RX_UDP +}; + +/*! + * @brief This structure maintains the current state of the Connectivity driver. + * + * The initial values must be identical to the reset state defined by the + * #reset variable. + */ +static pmic_convity_cea936_state cea_936 = { + 0, + HANDLE_FREE, + CEA936_MONO, + NULL, + 0, +}; + +/*! + * @brief This spinlock is used to provide mutual exclusion. + * + * Create a spinlock that can be used to provide mutually exclusive + * read/write access to the globally accessible "convity" data structure + * that was defined above. Mutually exclusive access is required to + * ensure that the convity data structure is consistent at all times + * when possibly accessed by multiple threads of execution (for example, + * while simultaneously handling a user request and an interrupt event). + * + * We need to use a spinlock sometimes because we need to provide mutual + * exclusion while handling a hardware interrupt. + */ +static spinlock_t lock = SPIN_LOCK_UNLOCKED; + +/*! + * @brief This mutex is used to provide mutual exclusion. + * + * Create a mutex that can be used to provide mutually exclusive + * read/write access to the globally accessible data structures + * that were defined above. Mutually exclusive access is required to + * ensure that the Connectivity data structures are consistent at all + * times when possibly accessed by multiple threads of execution. + * + * Note that we use a mutex instead of the spinlock whenever disabling + * interrupts while in the critical section is not required. This helps + * to minimize kernel interrupt handling latency. + */ +static DECLARE_MUTEX(mutex); + +/* Prototype for the connectivity driver tasklet function. */ +static void pmic_convity_tasklet(struct work_struct *work); + +/*! + * @brief Tasklet handler for the connectivity driver. + * + * Declare a tasklet that will do most of the processing for all of the + * connectivity-related interrupt events (USB4.4VI, USB2.0VI, USB0.8VI, + * and AB_DETI). Note that we cannot do all of the required processing + * within the interrupt handler itself because we may need to call the + * ADC driver to measure voltages as well as calling any user-registered + * callback functions. + */ +DECLARE_WORK(convityTasklet, pmic_convity_tasklet); + +/*! + * @brief Global variable to track currently active interrupt events. + * + * This global variable is used to keep track of all of the currently + * active interrupt events for the connectivity driver. Note that access + * to this variable may occur while within an interrupt context and, + * therefore, must be guarded by using a spinlock. + */ +static PMIC_CORE_EVENT eventID = 0; + +/* Prototypes for all static connectivity driver functions. */ +static PMIC_STATUS pmic_convity_set_mode_internal(const PMIC_CONVITY_MODE mode); +static PMIC_STATUS pmic_convity_deregister_all(void); +static void pmic_convity_event_handler(void *param); + +/************************************************************************** + * General setup and configuration functions. + ************************************************************************** + */ + +/*! + * @name General Setup and Configuration Connectivity APIs + * Functions for setting up and configuring the connectivity hardware. + */ +/*@{*/ + +/*! + * Attempt to open and gain exclusive access to the PMIC connectivity + * hardware. An initial operating mode must also be specified. + * + * If the open request is successful, then a numeric handle is returned + * and this handle must be used in all subsequent function calls. The + * same handle must also be used in the pmic_convity_close() call when use + * of the PMIC connectivity hardware is no longer required. + * + * @param handle device handle from open() call + * @param mode initial connectivity operating mode + * + * @return PMIC_SUCCESS if the open request was successful + */ +PMIC_STATUS pmic_convity_open(PMIC_CONVITY_HANDLE * const handle, + const PMIC_CONVITY_MODE mode) +{ + PMIC_STATUS rc = PMIC_ERROR; + + if (handle == (PMIC_CONVITY_HANDLE *) NULL) { + /* Do not dereference a NULL pointer. */ + return PMIC_ERROR; + } + + /* We only need to acquire a mutex here because the interrupt handler + * never modifies the device handle or device handle state. Therefore, + * we don't need to worry about conflicts with the interrupt handler + * or the need to execute in an interrupt context. + * + * But we do need a critical section here to avoid problems in case + * multiple calls to pmic_convity_open() are made since we can only + * allow one of them to succeed. + */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + /* Check the current device handle state and acquire the handle if + * it is available. + */ + if ((usb.handle_state != HANDLE_FREE) + && (rs_232.handle_state != HANDLE_FREE) + && (cea_936.handle_state != HANDLE_FREE)) { + + /* Cannot open the PMIC connectivity hardware at this time or an invalid + * mode was requested. + */ + *handle = reset.handle; + } else { + + if (mode == USB) { + usb.handle = (PMIC_CONVITY_HANDLE) (&usb); + usb.handle_state = HANDLE_IN_USE; + } else if ((mode == RS232_1) || (mode == RS232_2)) { + rs_232.handle = (PMIC_CONVITY_HANDLE) (&rs_232); + rs_232.handle_state = HANDLE_IN_USE; + } else if ((mode == CEA936_STEREO) || (mode == CEA936_MONO) + || (mode == CEA936_TEST_LEFT) + || (mode == CEA936_TEST_RIGHT)) { + cea_936.handle = (PMIC_CONVITY_HANDLE) (&cea_936); + cea_936.handle_state = HANDLE_IN_USE; + + } + /* Let's begin by acquiring the connectivity device handle. */ + /* Then we can try to set the desired operating mode. */ + rc = pmic_convity_set_mode_internal(mode); + + if (rc == PMIC_SUCCESS) { + /* Successfully set the desired operating mode, now return the + * handle to the caller. + */ + if (mode == USB) { + *handle = usb.handle; + } else if ((mode == RS232_1) || (mode == RS232_2)) { + *handle = rs_232.handle; + } else if ((mode == CEA936_STEREO) + || (mode == CEA936_MONO) + || (mode == CEA936_TEST_LEFT) + || (mode == CEA936_TEST_RIGHT)) { + *handle = cea_936.handle; + } + } else { + /* Failed to set the desired mode, return the handle to an unused + * state. + */ + if (mode == USB) { + usb.handle = reset.handle; + usb.handle_state = reset.handle_state; + } else if ((mode == RS232_1) || (mode == RS232_2)) { + rs_232.handle = reset.handle; + rs_232.handle_state = reset.handle_state; + } else if ((mode == CEA936_STEREO) + || (mode == CEA936_MONO) + || (mode == CEA936_TEST_LEFT) + || (mode == CEA936_TEST_RIGHT)) { + cea_936.handle = reset.handle; + cea_936.handle_state = reset.handle_state; + } + + *handle = reset.handle; + } + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * Terminate further access to the PMIC connectivity hardware. Also allows + * another process to call pmic_convity_open() to gain access. + * + * @param handle device handle from open() call + * + * @return PMIC_SUCCESS if the close request was successful + */ +PMIC_STATUS pmic_convity_close(const PMIC_CONVITY_HANDLE handle) +{ + PMIC_STATUS rc = PMIC_ERROR; + + /* Begin a critical section here to avoid the possibility of race + * conditions if multiple threads happen to call this function and + * pmic_convity_open() at the same time. + */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + /* Confirm that the device handle matches the one assigned in the + * pmic_convity_open() call and then close the connection. + */ + if (((handle == usb.handle) && + (usb.handle_state == HANDLE_IN_USE)) || ((handle == rs_232.handle) + && (rs_232.handle_state == + HANDLE_IN_USE)) + || ((handle == cea_936.handle) + && (cea_936.handle_state == HANDLE_IN_USE))) { + rc = PMIC_SUCCESS; + + /* Deregister for all existing callbacks if necessary and make sure + * that the event handling settings are consistent following the + * close operation. + */ + if ((usb.callback != reset.callback) + || (rs_232.callback != reset.callback) + || (cea_936.callback != reset.callback)) { + /* Deregister the existing callback function and all registered + * events before we completely close the handle. + */ + rc = pmic_convity_deregister_all(); + if (rc == PMIC_SUCCESS) { + + } else if (usb.eventMask != reset.eventMask) { + /* Having a non-zero eventMask without a callback function being + * defined should never occur but let's just make sure here that + * we keep things consistent. + */ + usb.eventMask = reset.eventMask; + /* Mark the connectivity device handle as being closed. */ + usb.handle = reset.handle; + usb.handle_state = reset.handle_state; + + } else if (rs_232.eventMask != reset.eventMask) { + + rs_232.eventMask = reset.eventMask; + /* Mark the connectivity device handle as being closed. */ + rs_232.handle = reset.handle; + rs_232.handle_state = reset.handle_state; + + } else if (cea_936.eventMask != reset.eventMask) { + cea_936.eventMask = reset.eventMask; + /* Mark the connectivity device handle as being closed. */ + cea_936.handle = reset.handle; + cea_936.handle_state = reset.handle_state; + + } + + } + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * Change the current operating mode of the PMIC connectivity hardware. + * The available connectivity operating modes is hardware dependent and + * consists of one or more of the following: USB (including USB On-the-Go), + * RS-232, and CEA-936. Requesting an operating mode that is not supported + * by the PMIC hardware will return PMIC_NOT_SUPPORTED. + * + * @param handle device handle from + open() call + * @param mode desired operating mode + * + * @return PMIC_SUCCESS if the requested mode was successfully set + */ +PMIC_STATUS pmic_convity_set_mode(const PMIC_CONVITY_HANDLE handle, + const PMIC_CONVITY_MODE mode) +{ + PMIC_STATUS rc = PMIC_ERROR; + + /* Use a critical section to maintain a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if (((handle == usb.handle) && + (usb.handle_state == HANDLE_IN_USE)) || ((handle == rs_232.handle) + && (rs_232.handle_state == + HANDLE_IN_USE)) + || ((handle == cea_936.handle) + && (cea_936.handle_state == HANDLE_IN_USE))) { + rc = pmic_convity_set_mode_internal(mode); + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * Get the current operating mode for the PMIC connectivity hardware. + * + * @param handle device handle from open() call + * @param mode the current PMIC connectivity operating mode + * + * @return PMIC_SUCCESS if the requested mode was successfully set + */ +PMIC_STATUS pmic_convity_get_mode(const PMIC_CONVITY_HANDLE handle, + PMIC_CONVITY_MODE * const mode) +{ + PMIC_STATUS rc = PMIC_ERROR; + + /* Use a critical section to maintain a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((((handle == usb.handle) && + (usb.handle_state == HANDLE_IN_USE)) || ((handle == rs_232.handle) + && (rs_232. + handle_state == + HANDLE_IN_USE)) + || ((handle == cea_936.handle) + && (cea_936.handle_state == HANDLE_IN_USE))) + && (mode != (PMIC_CONVITY_MODE *) NULL)) { + + *mode = usb.mode; + + rc = PMIC_SUCCESS; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * Restore all registers to the initial power-on/reset state. + * + * @param handle device handle from open() call + * + * @return PMIC_SUCCESS if the reset was successful + */ +PMIC_STATUS pmic_convity_reset(const PMIC_CONVITY_HANDLE handle) +{ + PMIC_STATUS rc = PMIC_ERROR; + + /* Use a critical section to maintain a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + if (((handle == usb.handle) && + (usb.handle_state == HANDLE_IN_USE)) || ((handle == rs_232.handle) + && (rs_232.handle_state == + HANDLE_IN_USE)) + || ((handle == cea_936.handle) + && (cea_936.handle_state == HANDLE_IN_USE))) { + + /* Reset the PMIC Connectivity register to it's power on state. */ + rc = pmic_write_reg(REG_USB, RESET_USBCNTRL_REG_0, + REG_FULLMASK); + + rc = pmic_write_reg(REG_CHARGE_USB_SPARE, + RESET_USBCNTRL_REG_1, REG_FULLMASK); + + if (rc == PMIC_SUCCESS) { + /* Also reset the device driver state data structure. */ + + } + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * Register a callback function that will be used to signal PMIC connectivity + * events. For example, the USB subsystem should register a callback function + * in order to be notified of device connect/disconnect events. Note, however, + * that non-USB events may also be signalled depending upon the PMIC hardware + * capabilities. Therefore, the callback function must be able to properly + * handle all of the possible events if support for non-USB peripherals is + * also to be included. + * + * @param handle device handle from open() call + * @param func a pointer to the callback function + * @param eventMask a mask selecting events to be notified + * + * @return PMIC_SUCCESS if the callback was successful registered + */ +PMIC_STATUS pmic_convity_set_callback(const PMIC_CONVITY_HANDLE handle, + const PMIC_CONVITY_CALLBACK func, + const PMIC_CONVITY_EVENTS eventMask) +{ + unsigned long flags; + PMIC_STATUS rc = PMIC_ERROR; + + /* We need to start a critical section here to ensure a consistent state + * in case simultaneous calls to pmic_convity_set_callback() are made. In + * that case, we must serialize the calls to ensure that the "callback" + * and "eventMask" state variables are always consistent. + * + * Note that we don't actually need to acquire the spinlock until later + * when we are finally ready to update the "callback" and "eventMask" + * state variables which are shared with the interrupt handler. + */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == usb.handle) && (usb.handle_state == HANDLE_IN_USE)) { + + /* Return an error if either the callback function or event mask + * is not properly defined. + * + * It is also considered an error if a callback function has already + * been defined. If you wish to register for a new set of events, + * then you must first call pmic_convity_clear_callback() to + * deregister the existing callback function and list of events + * before trying to register a new callback function. + */ + if ((func == NULL) || (eventMask == 0) + || (usb.callback != NULL)) { + rc = PMIC_ERROR; + + /* Register for PMIC events from the core protocol driver. */ + } else { + + if ((eventMask & USB_DETECT_4V4_RISE) || + (eventMask & USB_DETECT_4V4_FALL)) { + /* We need to register for the 4.4V interrupt. */ + //EVENT_USBI or EVENT_USB_44VI + eventNotify.func = pmic_convity_event_handler; + eventNotify.param = (void *)(CORE_EVENT_4V4); + rc = pmic_event_subscribe(EVENT_USBI, + eventNotify); + + if (rc != PMIC_SUCCESS) { + return rc; + } + } + + if ((eventMask & USB_DETECT_2V0_RISE) || + (eventMask & USB_DETECT_2V0_FALL)) { + /* We need to register for the 2.0V interrupt. */ + //EVENT_USB_20VI or EVENT_USBI + eventNotify.func = pmic_convity_event_handler; + eventNotify.param = (void *)(CORE_EVENT_2V0); + rc = pmic_event_subscribe(EVENT_USBI, + eventNotify); + + if (rc != PMIC_SUCCESS) { + goto Cleanup_4V4; + } + } + + if ((eventMask & USB_DETECT_0V8_RISE) || + (eventMask & USB_DETECT_0V8_FALL)) { + /* We need to register for the 0.8V interrupt. */ + //EVENT_USB_08VI or EVENT_USBI + eventNotify.func = pmic_convity_event_handler; + eventNotify.param = (void *)(CORE_EVENT_0V8); + rc = pmic_event_subscribe(EVENT_USBI, + eventNotify); + + if (rc != PMIC_SUCCESS) { + goto Cleanup_2V0; + } + } + + if ((eventMask & USB_DETECT_MINI_A) || + (eventMask & USB_DETECT_MINI_B) + || (eventMask & USB_DETECT_NON_USB_ACCESSORY) + || (eventMask & USB_DETECT_FACTORY_MODE)) { + /* We need to register for the AB_DET interrupt. */ + //EVENT_AB_DETI or EVENT_IDI + eventNotify.func = pmic_convity_event_handler; + eventNotify.param = (void *)(CORE_EVENT_ABDET); + rc = pmic_event_subscribe(EVENT_IDI, + eventNotify); + + if (rc != PMIC_SUCCESS) { + goto Cleanup_0V8; + } + } + + /* Use a critical section to maintain a consistent state. */ + spin_lock_irqsave(&lock, flags); + + /* Successfully registered for all events. */ + usb.callback = func; + usb.eventMask = eventMask; + spin_unlock_irqrestore(&lock, flags); + + goto End; + + /* This section unregisters any already registered events if we should + * encounter an error partway through the registration process. Note + * that we don't check the return status here since it is already set + * to PMIC_ERROR before we get here. + */ + Cleanup_0V8: + + if ((eventMask & USB_DETECT_0V8_RISE) || + (eventMask & USB_DETECT_0V8_FALL)) { + //EVENT_USB_08VI or EVENT_USBI + eventNotify.func = pmic_convity_event_handler; + eventNotify.param = (void *)(CORE_EVENT_0V8); + pmic_event_unsubscribe(EVENT_USBI, eventNotify); + goto End; + } + + Cleanup_2V0: + + if ((eventMask & USB_DETECT_2V0_RISE) || + (eventMask & USB_DETECT_2V0_FALL)) { + //EVENT_USB_20VI or EVENT_USBI + eventNotify.func = pmic_convity_event_handler; + eventNotify.param = (void *)(CORE_EVENT_2V0); + pmic_event_unsubscribe(EVENT_USBI, eventNotify); + goto End; + } + + Cleanup_4V4: + + if ((eventMask & USB_DETECT_4V4_RISE) || + (eventMask & USB_DETECT_4V4_FALL)) { + //EVENT_USB_44VI or EVENT_USBI + eventNotify.func = pmic_convity_event_handler; + eventNotify.param = (void *)(CORE_EVENT_4V4); + pmic_event_unsubscribe(EVENT_USBI, eventNotify); + } + } + /* Exit the critical section. */ + + } + End:up(&mutex); + return rc; + +} + +/*! + * Clears the current callback function. If this function returns successfully + * then all future Connectivity events will only be handled by the default + * handler within the Connectivity driver. + * + * @param handle device handle from open() call + * + * @return PMIC_SUCCESS if the callback was successful cleared + */ +PMIC_STATUS pmic_convity_clear_callback(const PMIC_CONVITY_HANDLE handle) +{ + PMIC_STATUS rc = PMIC_ERROR; + + /* Use a critical section to maintain a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + if (((handle == usb.handle) && + (usb.handle_state == HANDLE_IN_USE)) || ((handle == rs_232.handle) + && (rs_232.handle_state == + HANDLE_IN_USE)) + || ((handle == cea_936.handle) + && (cea_936.handle_state == HANDLE_IN_USE))) { + + rc = pmic_convity_deregister_all(); + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * Get the current callback function and event mask. + * + * @param handle device handle from open() call + * @param func the current callback function + * @param eventMask the current event selection mask + * + * @return PMIC_SUCCESS if the callback information was successful + * retrieved + */ +PMIC_STATUS pmic_convity_get_callback(const PMIC_CONVITY_HANDLE handle, + PMIC_CONVITY_CALLBACK * const func, + PMIC_CONVITY_EVENTS * const eventMask) +{ + PMIC_STATUS rc = PMIC_ERROR; + + /* Use a critical section to maintain a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + if ((((handle == usb.handle) && + (usb.handle_state == HANDLE_IN_USE)) || ((handle == rs_232.handle) + && (rs_232. + handle_state == + HANDLE_IN_USE)) + || ((handle == cea_936.handle) + && (cea_936.handle_state == HANDLE_IN_USE))) + && (func != (PMIC_CONVITY_CALLBACK *) NULL) + && (eventMask != (PMIC_CONVITY_EVENTS *) NULL)) { + *func = usb.callback; + *eventMask = usb.eventMask; + + rc = PMIC_SUCCESS; + } + + /* Exit the critical section. */ + + up(&mutex); + + return rc; +} + +/*@*/ + +/************************************************************************** + * USB-specific configuration and setup functions. + ************************************************************************** + */ + +/*! + * @name USB and USB-OTG Connectivity APIs + * Functions for controlling USB and USB-OTG connectivity. + */ +/*@{*/ + +/*! + * Set the USB transceiver speed. + * + * @param handle device handle from open() call + * @param speed the desired USB transceiver speed + * + * @return PMIC_SUCCESS if the transceiver speed was successfully set + */ +PMIC_STATUS pmic_convity_usb_set_speed(const PMIC_CONVITY_HANDLE handle, + const PMIC_CONVITY_USB_SPEED speed) +{ + PMIC_STATUS rc = PMIC_ERROR; + unsigned int reg_value = 0; + unsigned int reg_mask = SET_BITS(regUSB0, FSENB, 1); + + /* Use a critical section to maintain a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if (handle == (rs_232.handle || cea_936.handle)) { + return PMIC_PARAMETER_ERROR; + } else { + if ((handle == usb.handle) && + (usb.handle_state == HANDLE_IN_USE)) { + /* Validate the function parameters and if they are valid, then + * configure the pull-up and pull-down resistors as required for + * the desired operating mode. + */ + if ((speed == USB_HIGH_SPEED)) { + /* + * The USB transceiver also does not support the high speed mode + * (which is also optional under the USB OTG specification). + */ + rc = PMIC_NOT_SUPPORTED; + } else if ((speed != USB_LOW_SPEED) + && (speed != USB_FULL_SPEED)) { + /* Final validity check on the speed parameter. */ + rc = PMIC_ERROR;; + } else { + /* First configure the D+ and D- pull-up/pull-down resistors as + * per the USB OTG specification. + */ + if (speed == USB_FULL_SPEED) { + /* Activate pull-up on D+ and pull-down on D-. */ + reg_value = + SET_BITS(regUSB0, UDM_PD, 1); + } else if (speed == USB_LOW_SPEED) { + /* Activate pull-up on D+ and pull-down on D-. */ + reg_value = SET_BITS(regUSB0, FSENB, 1); + } + + /* Now set the desired USB transceiver speed. Note that + * USB_FULL_SPEED simply requires FSENB=0 (which it + * already is). + */ + + rc = pmic_write_reg(REG_USB, reg_value, + reg_mask); + + if (rc == PMIC_SUCCESS) { + usb.usbSpeed = speed; + } + } + } + } + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * Get the USB transceiver speed. + * + * @param handle device handle from open() call + * @param speed the current USB transceiver speed + * @param mode the current USB transceiver mode + * + * @return PMIC_SUCCESS if the transceiver speed was successfully + * obtained + */ +PMIC_STATUS pmic_convity_usb_get_speed(const PMIC_CONVITY_HANDLE handle, + PMIC_CONVITY_USB_SPEED * const speed, + PMIC_CONVITY_USB_MODE * const mode) +{ + PMIC_STATUS rc = PMIC_ERROR; + + /* Use a critical section to maintain a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == usb.handle) && + (usb.handle_state == HANDLE_IN_USE) && + (speed != (PMIC_CONVITY_USB_SPEED *) NULL) && + (mode != (PMIC_CONVITY_USB_MODE *) NULL)) { + *speed = usb.usbSpeed; + *mode = usb.usbMode; + + rc = PMIC_SUCCESS; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * This function enables/disables VUSB and VBUS output. + * This API configures the VUSBEN and VBUSEN bits of USB register + * + * @param handle device handle from open() call + * @param out_type true, for VBUS + * false, for VUSB + * @param out if true, output is enabled + * if false, output is disabled + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_convity_set_output(const PMIC_CONVITY_HANDLE handle, + bool out_type, bool out) +{ + + PMIC_STATUS rc = PMIC_ERROR; + unsigned int reg_value = 0; + + unsigned int reg_mask = 0; + + /* Use a critical section to maintain a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == usb.handle) && (usb.handle_state == HANDLE_IN_USE)) { + + if ((out_type == 0) && (out == 1)) { + + reg_value = SET_BITS(regUSB1, VUSBEN, 1); + reg_mask = SET_BITS(regUSB1, VUSBEN, 1); + + rc = pmic_write_reg(REG_CHARGE_USB_SPARE, + reg_value, reg_mask); + } else if (out_type == 0 && out == 0) { + reg_mask = SET_BITS(regUSB1, VBUSEN, 1); + + rc = pmic_write_reg(REG_CHARGE_USB_SPARE, + reg_value, reg_mask); + } else if (out_type == 1 && out == 1) { + + reg_value = SET_BITS(regUSB1, VBUSEN, 1); + reg_mask = SET_BITS(regUSB1, VBUSEN, 1); + + rc = pmic_write_reg(REG_CHARGE_USB_SPARE, + reg_value, reg_mask); + } + + else if (out_type == 1 && out == 0) { + + reg_mask = SET_BITS(regUSB1, VBUSEN, 1); + rc = pmic_write_reg(REG_CHARGE_USB_SPARE, + reg_value, reg_mask); + } + + /*else { + + rc = pmic_write_reg(REG_CHARGE_USB_SPARE, + reg_value, reg_mask); + } */ + } + + up(&mutex); + + return rc; +} + +/*! + * Set the USB transceiver's power supply configuration. + * + * @param handle device handle from open() call + * @param pwrin USB transceiver regulator input power source + * @param pwrout USB transceiver regulator output power level + * + * @return PMIC_SUCCESS if the USB transceiver's power supply + * configuration was successfully set + */ +PMIC_STATUS pmic_convity_usb_set_power_source(const PMIC_CONVITY_HANDLE handle, + const PMIC_CONVITY_USB_POWER_IN + pwrin, + const PMIC_CONVITY_USB_POWER_OUT + pwrout) +{ + PMIC_STATUS rc = PMIC_ERROR; + unsigned int reg_value = 0; + unsigned int reg_mask = 0; + /* SET_BITS(regUSB1, VUSBEN, 1) | SET_BITS(regUSB1, VBUSEN, + 1) | SET_BITS(regUSB1, + VUSBIN, + 2) | */ + // SET_BITS(regUSB1, VUSB, 1); +/* SET_BITS(regUSB1, VUSBIN, 1) | SET_BITS(regUSB1, VUSBEN, + 1) | SET_BITS(regUSB1, + VBUSEN, 1);*/ + + /* Use a critical section to maintain a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == usb.handle) && (usb.handle_state == HANDLE_IN_USE)) { + + if (pwrin == USB_POWER_INTERNAL_BOOST) { + reg_value |= SET_BITS(regUSB1, VUSBIN, 0); + reg_mask = SET_BITS(regUSB1, VUSBIN, 1); + } else if (pwrin == USB_POWER_VBUS) { + reg_value |= SET_BITS(regUSB1, VUSBIN, 1); + reg_mask = SET_BITS(regUSB1, VUSBIN, 1); + } + + else if (pwrin == USB_POWER_INTERNAL) { + reg_value |= SET_BITS(regUSB1, VUSBIN, 2); + reg_mask = SET_BITS(regUSB1, VUSBIN, 1); + } + + if (pwrout == USB_POWER_3V3) { + reg_value |= SET_BITS(regUSB1, VUSB, 1); + reg_mask |= SET_BITS(regUSB1, VUSB, 1); + } + + else if (pwrout == USB_POWER_2V775) { + reg_value |= SET_BITS(regUSB1, VUSB, 0); + reg_mask |= SET_BITS(regUSB1, VUSB, 1); + } + rc = pmic_write_reg(REG_CHARGE_USB_SPARE, reg_value, reg_mask); + + if (rc == PMIC_SUCCESS) { + usb.usbPowerIn = pwrin; + usb.usbPowerOut = pwrout; + } + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * Get the USB transceiver's current power supply configuration. + * + * @param handle device handle from open() call + * @param pwrin USB transceiver regulator input power source + * @param pwrout USB transceiver regulator output power level + * + * @return PMIC_SUCCESS if the USB transceiver's power supply + * configuration was successfully retrieved + */ +PMIC_STATUS pmic_convity_usb_get_power_source(const PMIC_CONVITY_HANDLE handle, + PMIC_CONVITY_USB_POWER_IN * + const pwrin, + PMIC_CONVITY_USB_POWER_OUT * + const pwrout) +{ + PMIC_STATUS rc = PMIC_ERROR; + + /* Use a critical section to maintain a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == usb.handle) && + (usb.handle_state == HANDLE_IN_USE) && + (pwrin != (PMIC_CONVITY_USB_POWER_IN *) NULL) && + (pwrout != (PMIC_CONVITY_USB_POWER_OUT *) NULL)) { + *pwrin = usb.usbPowerIn; + *pwrout = usb.usbPowerOut; + + rc = PMIC_SUCCESS; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * Set the USB transceiver's operating mode. + * + * @param handle device handle from open() call + * @param mode desired operating mode + * + * @return PMIC_SUCCESS if the USB transceiver's operating mode + * was successfully configured + */ +PMIC_STATUS pmic_convity_usb_set_xcvr(const PMIC_CONVITY_HANDLE handle, + const PMIC_CONVITY_USB_TRANSCEIVER_MODE + mode) +{ + PMIC_STATUS rc = PMIC_ERROR; + unsigned int reg_value = 0; + unsigned int reg_mask = 0; + + /* Use a critical section to maintain a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == usb.handle) && (usb.handle_state == HANDLE_IN_USE)) { + + if (mode == USB_TRANSCEIVER_OFF) { + reg_value = SET_BITS(regUSB0, USBXCVREN, 0); + reg_mask |= SET_BITS(regUSB0, USB_SUSPEND, 1); + + rc = pmic_write_reg(REG_USB, reg_value, reg_mask); + + } + + if (mode == USB_SINGLE_ENDED_UNIDIR) { + reg_value |= + SET_BITS(regUSB0, DATSE0, 1) | SET_BITS(regUSB0, + BIDIR, 0); + reg_mask |= + SET_BITS(regUSB0, USB_SUSPEND, + 1) | SET_BITS(regUSB0, DATSE0, + 1) | SET_BITS(regUSB0, BIDIR, + 1); + } else if (mode == USB_SINGLE_ENDED_BIDIR) { + reg_value |= + SET_BITS(regUSB0, DATSE0, 1) | SET_BITS(regUSB0, + BIDIR, 1); + reg_mask |= + SET_BITS(regUSB0, USB_SUSPEND, + 1) | SET_BITS(regUSB0, DATSE0, + 1) | SET_BITS(regUSB0, BIDIR, + 1); + } else if (mode == USB_DIFFERENTIAL_UNIDIR) { + reg_value |= + SET_BITS(regUSB0, DATSE0, 0) | SET_BITS(regUSB0, + BIDIR, 0); + reg_mask |= + SET_BITS(regUSB0, USB_SUSPEND, + 1) | SET_BITS(regUSB0, DATSE0, + 1) | SET_BITS(regUSB0, BIDIR, + 1); + } else if (mode == USB_DIFFERENTIAL_BIDIR) { + reg_value |= + SET_BITS(regUSB0, DATSE0, 0) | SET_BITS(regUSB0, + BIDIR, 1); + reg_mask |= + SET_BITS(regUSB0, USB_SUSPEND, + 1) | SET_BITS(regUSB0, DATSE0, + 1) | SET_BITS(regUSB0, BIDIR, + 1); + } + + if (mode == USB_SUSPEND_ON) { + reg_value |= SET_BITS(regUSB0, USB_SUSPEND, 1); + reg_mask |= SET_BITS(regUSB0, USB_SUSPEND, 1); + } else if (mode == USB_SUSPEND_OFF) { + reg_value |= SET_BITS(regUSB0, USB_SUSPEND, 0); + reg_mask |= SET_BITS(regUSB0, USB_SUSPEND, 1); + } + + if (mode == USB_OTG_SRP_DLP_START) { + reg_value |= SET_BITS(regUSB0, USB_PU, 0); + reg_mask |= SET_BITS(regUSB0, USB_SUSPEND, 1); + } else if (mode == USB_OTG_SRP_DLP_STOP) { + reg_value &= SET_BITS(regUSB0, USB_PU, 1); + reg_mask |= SET_BITS(regUSB0, USB_PU, 1); + } + + rc = pmic_write_reg(REG_USB, reg_value, reg_mask); + + if (rc == PMIC_SUCCESS) { + usb.usbXcvrMode = mode; + } + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * Get the USB transceiver's current operating mode. + * + * @param handle device handle from open() call + * @param mode current operating mode + * + * @return PMIC_SUCCESS if the USB transceiver's operating mode + * was successfully retrieved + */ +PMIC_STATUS pmic_convity_usb_get_xcvr(const PMIC_CONVITY_HANDLE handle, + PMIC_CONVITY_USB_TRANSCEIVER_MODE * + const mode) +{ + PMIC_STATUS rc = PMIC_ERROR; + + /* Use a critical section to maintain a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == usb.handle) && + (usb.handle_state == HANDLE_IN_USE) && + (mode != (PMIC_CONVITY_USB_TRANSCEIVER_MODE *) NULL)) { + *mode = usb.usbXcvrMode; + + rc = PMIC_SUCCESS; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * Set the Data Line Pulse duration (in milliseconds) for the USB OTG + * Session Request Protocol. + * + * For mc13783, this feature is not supported.So return PMIC_NOT_SUPPORTED + * + * @param handle device handle from open() call + * @param duration the data line pulse duration (ms) + * + * @return PMIC_SUCCESS if the pulse duration was successfully set + */ +PMIC_STATUS pmic_convity_usb_otg_set_dlp_duration(const PMIC_CONVITY_HANDLE + handle, + const unsigned int duration) +{ + PMIC_STATUS rc = PMIC_NOT_SUPPORTED; + + /* The setting of the dlp duration is not supported by the mc13783 PMIC hardware. */ + + /* No critical section is required. */ + + if ((handle != usb.handle) + || (usb.handle_state != HANDLE_IN_USE)) { + /* Must return error indication for invalid handle parameter to be + * consistent with other APIs. + */ + rc = PMIC_ERROR; + } + + return rc; +} + +/*! + * Get the current Data Line Pulse duration (in milliseconds) for the USB + * OTG Session Request Protocol. + * + * @param handle device handle from open() call + * @param duration the data line pulse duration (ms) + * + * @return PMIC_SUCCESS if the pulse duration was successfully obtained + */ +PMIC_STATUS pmic_convity_usb_otg_get_dlp_duration(const PMIC_CONVITY_HANDLE + handle, + unsigned int *const duration) +{ + PMIC_STATUS rc = PMIC_NOT_SUPPORTED; + + /* The setting of dlp duration is not supported by the mc13783 PMIC hardware. */ + + /* No critical section is required. */ + + if ((handle != usb.handle) + || (usb.handle_state != HANDLE_IN_USE)) { + /* Must return error indication for invalid handle parameter to be + * consistent with other APIs. + */ + rc = PMIC_ERROR; + } + + return rc; +} + +/*! + * Set the USB On-The-Go (OTG) configuration. + * + * @param handle device handle from open() call + * @param cfg desired USB OTG configuration + * + * @return PMIC_SUCCESS if the OTG configuration was successfully set + */ +PMIC_STATUS pmic_convity_usb_otg_set_config(const PMIC_CONVITY_HANDLE handle, + const PMIC_CONVITY_USB_OTG_CONFIG + cfg) +{ + PMIC_STATUS rc = PMIC_ERROR; + unsigned int reg_value = 0; + unsigned int reg_mask = 0; + + /* Use a critical section to maintain a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == usb.handle) && (usb.handle_state == HANDLE_IN_USE)) { + if (cfg & USB_OTG_SE0CONN) { + reg_value = SET_BITS(regUSB0, SE0_CONN, 1); + reg_mask = SET_BITS(regUSB0, SE0_CONN, 1); + } + if (cfg & USBXCVREN) { + reg_value |= SET_BITS(regUSB0, USBXCVREN, 1); + reg_mask |= SET_BITS(regUSB0, USBXCVREN, 1); + } + + if (cfg & USB_OTG_DLP_SRP) { + reg_value |= SET_BITS(regUSB0, DLP_SRP, 1); + reg_mask |= SET_BITS(regUSB0, DLP_SRP, 1); + } + + if (cfg & USB_PULL_OVERRIDE) { + reg_value |= SET_BITS(regUSB0, PULLOVR, 1); + reg_mask |= SET_BITS(regUSB0, PULLOVR, 1); + } + + if (cfg & USB_PU) { + reg_value |= SET_BITS(regUSB0, USB_PU, 1); + reg_mask |= SET_BITS(regUSB0, USB_PU, 1); + } + + if (cfg & USB_UDM_PD) { + reg_value |= SET_BITS(regUSB0, UDM_PD, 1); + reg_mask |= SET_BITS(regUSB0, UDM_PD, 1); + } + + if (cfg & USB_UDP_PD) { + reg_value |= SET_BITS(regUSB0, UDP_PD, 1); + reg_mask |= SET_BITS(regUSB0, UDP_PD, 1); + } + + if (cfg & USB_DP150K_PU) { + reg_value |= SET_BITS(regUSB0, DP150K_PU, 1); + reg_mask |= SET_BITS(regUSB0, DP150K_PU, 1); + } + + if (cfg & USB_USBCNTRL) { + reg_value |= SET_BITS(regUSB0, USBCNTRL, 1); + reg_mask |= SET_BITS(regUSB0, USBCNTRL, 1); + } + + if (cfg & USB_VBUS_CURRENT_LIMIT_HIGH) { + reg_value |= SET_BITS(regUSB0, CURRENT_LIMIT, 0); + } else if (cfg & USB_VBUS_CURRENT_LIMIT_LOW_10MS) { + reg_value |= SET_BITS(regUSB0, CURRENT_LIMIT, 1); + } else if (cfg & USB_VBUS_CURRENT_LIMIT_LOW_20MS) { + reg_value |= SET_BITS(regUSB0, CURRENT_LIMIT, 2); + } else if (cfg & USB_VBUS_CURRENT_LIMIT_LOW_30MS) { + reg_value |= SET_BITS(regUSB0, CURRENT_LIMIT, 3); + } else if (cfg & USB_VBUS_CURRENT_LIMIT_LOW_40MS) { + reg_value |= SET_BITS(regUSB0, CURRENT_LIMIT, 4); + } else if (cfg & USB_VBUS_CURRENT_LIMIT_LOW_50MS) { + reg_value |= SET_BITS(regUSB0, CURRENT_LIMIT, 5); + } else if (cfg & USB_VBUS_CURRENT_LIMIT_LOW_60MS) { + reg_value |= SET_BITS(regUSB0, CURRENT_LIMIT, 6); + } + if (cfg & USB_VBUS_CURRENT_LIMIT_LOW) { + reg_value |= SET_BITS(regUSB0, CURRENT_LIMIT, 7); + reg_mask |= SET_BITS(regUSB0, CURRENT_LIMIT, 7); + } + + if (cfg & USB_VBUS_PULLDOWN) { + reg_value |= SET_BITS(regUSB0, VBUSPDENB, 1); + reg_mask |= SET_BITS(regUSB0, VBUSPDENB, 1); + } + + rc = pmic_write_reg(REG_USB, reg_value, reg_mask); + + if (rc == PMIC_SUCCESS) { + if ((cfg & USB_VBUS_CURRENT_LIMIT_HIGH) || + (cfg & USB_VBUS_CURRENT_LIMIT_LOW) || + (cfg & USB_VBUS_CURRENT_LIMIT_LOW_10MS) || + (cfg & USB_VBUS_CURRENT_LIMIT_LOW_20MS) || + (cfg & USB_VBUS_CURRENT_LIMIT_LOW_30MS) || + (cfg & USB_VBUS_CURRENT_LIMIT_LOW_40MS) || + (cfg & USB_VBUS_CURRENT_LIMIT_LOW_50MS) || + (cfg & USB_VBUS_CURRENT_LIMIT_LOW_60MS)) { + /* Make sure that the VBUS current limit state is + * correctly set to either USB_VBUS_CURRENT_LIMIT_HIGH + * or USB_VBUS_CURRENT_LIMIT_LOW but never both at the + * same time. + * + * We guarantee this by first clearing both of the + * status bits and then resetting the correct one. + */ + usb.usbOtgCfg &= + ~(USB_VBUS_CURRENT_LIMIT_HIGH | + USB_VBUS_CURRENT_LIMIT_LOW | + USB_VBUS_CURRENT_LIMIT_LOW_10MS | + USB_VBUS_CURRENT_LIMIT_LOW_20MS | + USB_VBUS_CURRENT_LIMIT_LOW_30MS | + USB_VBUS_CURRENT_LIMIT_LOW_40MS | + USB_VBUS_CURRENT_LIMIT_LOW_50MS | + USB_VBUS_CURRENT_LIMIT_LOW_60MS); + } + + usb.usbOtgCfg |= cfg; + } + } + //} + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * Clears the USB On-The-Go (OTG) configuration. Multiple configuration settings + * may be OR'd together in a single call. However, selecting conflicting + * settings (e.g., multiple VBUS current limits) will result in undefined + * behavior. + * + * @param handle Device handle from open() call. + * @param cfg USB OTG configuration settings to be cleared. + * + * @retval PMIC_SUCCESS If the OTG configuration was successfully + * cleared. + * @retval PMIC_PARAMETER_ERROR If the handle is invalid. + * @retval PMIC_NOT_SUPPORTED If the desired USB OTG configuration is + * not supported by the PMIC hardware. + */ +PMIC_STATUS pmic_convity_usb_otg_clear_config(const PMIC_CONVITY_HANDLE handle, + const PMIC_CONVITY_USB_OTG_CONFIG + cfg) +{ + PMIC_STATUS rc = PMIC_ERROR; + unsigned int reg_value = 0; + unsigned int reg_mask = 0; + + /* Use a critical section to maintain a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == usb.handle) && (usb.handle_state == HANDLE_IN_USE)) { + /* if ((cfg & USB_VBUS_CURRENT_LIMIT_LOW_10MS) || + (cfg & USB_VBUS_CURRENT_LIMIT_LOW_20MS) || + (cfg & USB_VBUS_CURRENT_LIMIT_LOW_30MS) || + (cfg & USB_VBUS_CURRENT_LIMIT_LOW_40MS) || + (cfg & USB_VBUS_CURRENT_LIMIT_LOW_50MS) || + (cfg & USB_VBUS_CURRENT_LIMIT_LOW_60MS)) + { + rc = PMIC_NOT_SUPPORTED; + } */ + //else + + if (cfg & USB_OTG_SE0CONN) { + reg_mask = SET_BITS(regUSB0, SE0_CONN, 1); + } + + if (cfg & USB_OTG_DLP_SRP) { + reg_mask |= SET_BITS(regUSB0, DLP_SRP, 1); + } + + if (cfg & USB_DP150K_PU) { + reg_mask |= SET_BITS(regUSB0, DP150K_PU, 1); + } + + if (cfg & USB_PULL_OVERRIDE) { + reg_mask |= SET_BITS(regUSB0, PULLOVR, 1); + } + + if (cfg & USB_PU) { + + reg_mask |= SET_BITS(regUSB0, USB_PU, 1); + } + + if (cfg & USB_UDM_PD) { + + reg_mask |= SET_BITS(regUSB0, UDM_PD, 1); + } + + if (cfg & USB_UDP_PD) { + + reg_mask |= SET_BITS(regUSB0, UDP_PD, 1); + } + + if (cfg & USB_USBCNTRL) { + reg_mask |= SET_BITS(regUSB0, USBCNTRL, 1); + } + + if (cfg & USB_VBUS_CURRENT_LIMIT_HIGH) { + reg_mask |= SET_BITS(regUSB0, CURRENT_LIMIT, 0); + } else if (cfg & USB_VBUS_CURRENT_LIMIT_LOW_10MS) { + reg_mask |= SET_BITS(regUSB0, CURRENT_LIMIT, 1); + } else if (cfg & USB_VBUS_CURRENT_LIMIT_LOW_20MS) { + reg_mask |= SET_BITS(regUSB0, CURRENT_LIMIT, 2); + } else if (cfg & USB_VBUS_CURRENT_LIMIT_LOW_30MS) { + reg_mask |= SET_BITS(regUSB0, CURRENT_LIMIT, 3); + } else if (cfg & USB_VBUS_CURRENT_LIMIT_LOW_40MS) { + reg_mask |= SET_BITS(regUSB0, CURRENT_LIMIT, 4); + } else if (cfg & USB_VBUS_CURRENT_LIMIT_LOW_50MS) { + reg_mask |= SET_BITS(regUSB0, CURRENT_LIMIT, 5); + } else if (cfg & USB_VBUS_CURRENT_LIMIT_LOW_60MS) { + reg_mask |= SET_BITS(regUSB0, CURRENT_LIMIT, 6); + } + + if (cfg & USB_VBUS_PULLDOWN) { + // reg_value |= SET_BITS(regUSB0, VBUSPDENB, 1); + reg_mask |= SET_BITS(regUSB0, VBUSPDENB, 1); + } + + rc = pmic_write_reg(REG_USB, reg_value, reg_mask); + + if (rc == PMIC_SUCCESS) { + usb.usbOtgCfg &= ~cfg; + } + } + //} + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * Get the current USB On-The-Go (OTG) configuration. + * + * @param handle device handle from open() call + * @param cfg the current USB OTG configuration + * + * @return PMIC_SUCCESS if the OTG configuration was successfully + * retrieved + */ +PMIC_STATUS pmic_convity_usb_otg_get_config(const PMIC_CONVITY_HANDLE handle, + PMIC_CONVITY_USB_OTG_CONFIG * + const cfg) +{ + PMIC_STATUS rc = PMIC_ERROR; + + /* Use a critical section to maintain a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == usb.handle) && + (usb.handle_state == HANDLE_IN_USE) && + (cfg != (PMIC_CONVITY_USB_OTG_CONFIG *) NULL)) { + *cfg = usb.usbOtgCfg; + + rc = PMIC_SUCCESS; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*@}*/ + +/************************************************************************** + * RS-232-specific configuration and setup functions. + ************************************************************************** + */ + +/*! + * @name RS-232 Serial Connectivity APIs + * Functions for controlling RS-232 serial connectivity. + */ +/*@{*/ + +/*! + * Set the connectivity interface to the selected RS-232 operating + * configuration. Note that the RS-232 operating mode will be automatically + * overridden if the USB_EN is asserted at any time (e.g., when a USB device + * is attached). However, we will also automatically return to the RS-232 + * mode once the USB device is detached. + * + * @param handle device handle from open() call + * @param cfgInternal RS-232 transceiver internal connections + * @param cfgExternal RS-232 transceiver external connections + * + * @return PMIC_SUCCESS if the requested mode was set + */ +PMIC_STATUS pmic_convity_rs232_set_config(const PMIC_CONVITY_HANDLE handle, + const PMIC_CONVITY_RS232_INTERNAL + cfgInternal, + const PMIC_CONVITY_RS232_EXTERNAL + cfgExternal) +{ + PMIC_STATUS rc = PMIC_ERROR; + unsigned int reg_value0 = 0, reg_value1 = 0; + unsigned int reg_mask = SET_BITS(regUSB1, RSPOL, 1); + + /* Use a critical section to maintain a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == rs_232.handle) && (rs_232.handle_state == HANDLE_IN_USE)) { + rc = PMIC_SUCCESS; + + /* Validate the calling parameters. */ + /*if ((cfgInternal != RS232_TX_USE0VM_RX_UDATVP) && + (cfgInternal != RS232_TX_RX_INTERNAL_DEFAULT) && (cfgInternal != RS232_TX_UDATVP_RX_URXVM)) + { + + rc = PMIC_NOT_SUPPORTED; + } */ + if (cfgInternal == RS232_TX_USE0VM_RX_UDATVP) { + + reg_value0 = SET_BITS(regUSB0, INTERFACE_MODE, 1); + + } else if (cfgInternal == RS232_TX_RX_INTERNAL_DEFAULT) { + + reg_value0 = SET_BITS(regUSB0, INTERFACE_MODE, 1); + reg_mask |= SET_BITS(regUSB1, RSPOL, 1); + + } else if (cfgInternal == RS232_TX_UDATVP_RX_URXVM) { + + reg_value0 = SET_BITS(regUSB0, INTERFACE_MODE, 2); + reg_value1 |= SET_BITS(regUSB1, RSPOL, 1); + + } else if ((cfgExternal == RS232_TX_UDM_RX_UDP) || + (cfgExternal == RS232_TX_RX_EXTERNAL_DEFAULT)) { + /* Configure for TX on D+ and RX on D-. */ + reg_value0 |= SET_BITS(regUSB0, INTERFACE_MODE, 1); + reg_value1 |= SET_BITS(regUSB1, RSPOL, 0); + } else if (cfgExternal != RS232_TX_UDM_RX_UDP) { + /* Any other RS-232 configuration is an error. */ + rc = PMIC_ERROR; + } + + if (rc == PMIC_SUCCESS) { + /* Configure for TX on D- and RX on D+. */ + rc = pmic_write_reg(REG_USB, reg_value0, reg_mask); + + rc = pmic_write_reg(REG_CHARGE_USB_SPARE, + reg_value1, reg_mask); + + if (rc == PMIC_SUCCESS) { + rs_232.rs232CfgInternal = cfgInternal; + rs_232.rs232CfgExternal = cfgExternal; + } + } + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * Get the connectivity interface's current RS-232 operating configuration. + * + * @param handle device handle from open() call + * @param cfgInternal RS-232 transceiver internal connections + * @param cfgExternal RS-232 transceiver external connections + * + * @return PMIC_SUCCESS if the requested mode was retrieved + */ +PMIC_STATUS pmic_convity_rs232_get_config(const PMIC_CONVITY_HANDLE handle, + PMIC_CONVITY_RS232_INTERNAL * + const cfgInternal, + PMIC_CONVITY_RS232_EXTERNAL * + const cfgExternal) +{ + PMIC_STATUS rc = PMIC_ERROR; + + /* Use a critical section to maintain a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == rs_232.handle) && + (rs_232.handle_state == HANDLE_IN_USE) && + (cfgInternal != (PMIC_CONVITY_RS232_INTERNAL *) NULL) && + (cfgExternal != (PMIC_CONVITY_RS232_EXTERNAL *) NULL)) { + *cfgInternal = rs_232.rs232CfgInternal; + *cfgExternal = rs_232.rs232CfgExternal; + + rc = PMIC_SUCCESS; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*@}*/ + +/************************************************************************** + * CEA-936-specific configuration and setup functions. + ************************************************************************** + */ + +/*! + * @name CEA-936 Connectivity APIs + * Functions for controlling CEA-936 connectivity. + */ +/*@{*/ + +/*! + * Signal the attached device to exit the current CEA-936 operating mode. + * Returns an error if the current operating mode is not CEA-936. + * + * @param handle device handle from open() call + * @param signal type of exit signal to be sent + * + * @return PMIC_SUCCESS if exit signal was sent + */ +PMIC_STATUS pmic_convity_cea936_exit_signal(const PMIC_CONVITY_HANDLE handle, + const + PMIC_CONVITY_CEA936_EXIT_SIGNAL + signal) +{ + PMIC_STATUS rc = PMIC_ERROR; + unsigned int reg_value = 0; + unsigned int reg_mask = + SET_BITS(regUSB0, IDPD, 1) | SET_BITS(regUSB0, IDPULSE, 1); + + /* Use a critical section to maintain a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle != cea_936.handle) + || (cea_936.handle_state != HANDLE_IN_USE)) { + /* Must return error indication for invalid handle parameter to be + * consistent with other APIs. + */ + rc = PMIC_ERROR; + } else if (signal == CEA936_UID_PULLDOWN_6MS) { + reg_value = + SET_BITS(regUSB0, IDPULSE, 0) | SET_BITS(regUSB0, IDPD, 0); + } else if (signal == CEA936_UID_PULLDOWN_6MS) { + reg_value = SET_BITS(regUSB0, IDPULSE, 1); + } else if (signal == CEA936_UID_PULLDOWN) { + reg_value = SET_BITS(regUSB0, IDPD, 1); + } else if (signal == CEA936_UDMPULSE) { + reg_value = SET_BITS(regUSB0, DMPULSE, 1); + } + + rc = pmic_write_reg(REG_USB, reg_value, reg_mask); + + up(&mutex); + + return rc; +} + +/*@}*/ + +/************************************************************************** + * Static functions. + ************************************************************************** + */ + +/*! + * @name Connectivity Driver Internal Support Functions + * These non-exported internal functions are used to support the functionality + * of the exported connectivity APIs. + */ +/*@{*/ + +/*! + * This internal helper function sets the desired operating mode (either USB + * OTG or RS-232). It must be called with the mutex already acquired. + * + * @param mode the desired operating mode (USB or RS232) + * + * @return PMIC_SUCCESS if the desired operating mode was set + * @return PMIC_NOT_SUPPORTED if the desired operating mode is invalid + */ +static PMIC_STATUS pmic_convity_set_mode_internal(const PMIC_CONVITY_MODE mode) +{ + unsigned int reg_value0 = 0, reg_value1 = 0; + unsigned int reg_mask0 = 0, reg_mask1 = 0; + + //unsigned int reg_mask1 = SET_BITS(regUSB1, VBUSEN, 1); + + PMIC_STATUS rc = PMIC_SUCCESS; + + switch (mode) { + case USB: + /* For the USB mode, we start by tri-stating the USB bus (by + * setting VBUSEN = 0) until a device is connected (i.e., + * until we receive a 4.4V rising edge event). All pull-up + * and pull-down resistors are also disabled until a USB + * device is actually connected and we have determined which + * device is the host and the desired USB bus speed. + * + * Also tri-state the RS-232 buffers (by setting RSTRI = 1). + * This prevents the hardware from automatically returning to + * the RS-232 mode when the USB device is detached. + */ + + reg_value0 = SET_BITS(regUSB0, INTERFACE_MODE, 0); + reg_mask0 = SET_BITS(regUSB0, INTERFACE_MODE, 7); + + /*reg_value1 = SET_BITS(regUSB1, RSTRI, 1); */ + + rc = pmic_write_reg(REG_USB, reg_value0, reg_mask0); + /* if (rc == PMIC_SUCCESS) { + CHECK_ERROR(pmic_write_reg + (REG_CHARGE_USB_SPARE, + reg_value1, reg_mask1)); + } */ + + break; + + case RS232_1: + /* For the RS-232 mode, we tri-state the USB bus (by setting + * VBUSEN = 0) and enable the RS-232 transceiver (by setting + * RS232ENB = 0). + * + * Note that even in the RS-232 mode, if a USB device is + * plugged in, we will receive a 4.4V rising edge event which + * will automatically disable the RS-232 transceiver and + * tri-state the RS-232 buffers. This allows us to temporarily + * switch over to USB mode while the USB device is attached. + * The RS-232 transceiver and buffers will be automatically + * re-enabled when the USB device is detached. + */ + + /* Explicitly disconnect all of the USB pull-down resistors + * and the VUSB power regulator here just to be safe. + * + * But we do connect the internal pull-up resistor on USB_D+ + * to avoid having an extra load on the USB_D+ line when in + * RS-232 mode. + */ + + reg_value0 |= SET_BITS(regUSB0, INTERFACE_MODE, 1) | + SET_BITS(regUSB0, VBUSPDENB, 1) | + SET_BITS(regUSB0, USB_PU, 1); + reg_mask0 = + SET_BITS(regUSB0, INTERFACE_MODE, 7) | SET_BITS(regUSB0, + VBUSPDENB, + 1) | + SET_BITS(regUSB0, USB_PU, 1); + + reg_value1 = SET_BITS(regUSB1, RSPOL, 0); + + rc = pmic_write_reg(REG_USB, reg_value0, reg_mask0); + + if (rc == PMIC_SUCCESS) { + CHECK_ERROR(pmic_write_reg + (REG_CHARGE_USB_SPARE, + reg_value1, reg_mask1)); + } + break; + + case RS232_2: + reg_value0 |= SET_BITS(regUSB0, INTERFACE_MODE, 2) | + SET_BITS(regUSB0, VBUSPDENB, 1) | + SET_BITS(regUSB0, USB_PU, 1); + reg_mask0 = + SET_BITS(regUSB0, INTERFACE_MODE, 7) | SET_BITS(regUSB0, + VBUSPDENB, + 1) | + SET_BITS(regUSB0, USB_PU, 1); + + reg_value1 = SET_BITS(regUSB1, RSPOL, 1); + + rc = pmic_write_reg(REG_USB, reg_value0, reg_mask0); + + if (rc == PMIC_SUCCESS) { + CHECK_ERROR(pmic_write_reg + (REG_CHARGE_USB_SPARE, + reg_value1, reg_mask1)); + } + break; + + case CEA936_MONO: + reg_value0 |= SET_BITS(regUSB0, INTERFACE_MODE, 4); + + rc = pmic_write_reg(REG_USB, reg_value0, reg_mask0); + break; + + case CEA936_STEREO: + reg_value0 |= SET_BITS(regUSB0, INTERFACE_MODE, 5); + reg_mask0 = SET_BITS(regUSB0, INTERFACE_MODE, 7); + + rc = pmic_write_reg(REG_USB, reg_value0, reg_mask0); + break; + + case CEA936_TEST_RIGHT: + reg_value0 |= SET_BITS(regUSB0, INTERFACE_MODE, 6); + reg_mask0 = SET_BITS(regUSB0, INTERFACE_MODE, 7); + + rc = pmic_write_reg(REG_USB, reg_value0, reg_mask0); + break; + + case CEA936_TEST_LEFT: + reg_value0 |= SET_BITS(regUSB0, INTERFACE_MODE, 7); + reg_mask0 = SET_BITS(regUSB0, INTERFACE_MODE, 7); + + rc = pmic_write_reg(REG_USB, reg_value0, reg_mask0); + break; + + default: + rc = PMIC_NOT_SUPPORTED; + } + + if (rc == PMIC_SUCCESS) { + if (mode == USB) { + usb.mode = mode; + } else if ((mode == RS232_1) || (mode == RS232_1)) { + rs_232.mode = mode; + } else if ((mode == CEA936_MONO) || (mode == CEA936_STEREO) || + (mode == CEA936_TEST_RIGHT) + || (mode == CEA936_TEST_LEFT)) { + cea_936.mode = mode; + } + } + + return rc; +} + +/*! + * This internal helper function deregisters all of the currently registered + * callback events. It must be called with the mutual exclusion spinlock + * already acquired. + * + * We've defined the event and callback deregistration code here as a separate + * function because it can be called by either the pmic_convity_close() or the + * pmic_convity_clear_callback() APIs. We also wanted to avoid any possible + * issues with having the same thread calling spin_lock_irq() twice. + * + * Note that the mutex must have already been acquired. We will also acquire + * the spinlock here to avoid any possible race conditions with the interrupt + * handler. + * + * @return PMIC_SUCCESS if all of the callback events were cleared + */ +static PMIC_STATUS pmic_convity_deregister_all(void) +{ + unsigned long flags; + PMIC_STATUS rc = PMIC_SUCCESS; + + /* Deregister each of the PMIC events that we had previously + * registered for by using pmic_event_subscribe(). + */ + + if ((usb.eventMask & USB_DETECT_MINI_A) || + (usb.eventMask & USB_DETECT_MINI_B) || + (usb.eventMask & USB_DETECT_NON_USB_ACCESSORY) || + (usb.eventMask & USB_DETECT_FACTORY_MODE)) { + //EVENT_AB_DETI or EVENT_IDI + eventNotify.func = pmic_convity_event_handler; + eventNotify.param = (void *)(CORE_EVENT_ABDET); + + if (pmic_event_unsubscribe(EVENT_IDI, eventNotify) == + PMIC_SUCCESS) { + /* Also acquire the spinlock here to avoid any possible race + * conditions with the interrupt handler. + */ + + spin_lock_irqsave(&lock, flags); + + usb.eventMask &= ~(USB_DETECT_MINI_A | + USB_DETECT_MINI_B | + USB_DETECT_NON_USB_ACCESSORY | + USB_DETECT_FACTORY_MODE); + + spin_unlock_irqrestore(&lock, flags); + } else { + pr_debug + ("%s: pmic_event_unsubscribe() for EVENT_AB_DETI failed\n", + __FILE__); + rc = PMIC_ERROR; + } + } + + else if ((usb.eventMask & USB_DETECT_0V8_RISE) || + (usb.eventMask & USB_DETECT_0V8_FALL)) { + //EVENT_USB_08VI or EVENT_USBI + eventNotify.func = pmic_convity_event_handler; + eventNotify.param = (void *)(CORE_EVENT_0V8); + if (pmic_event_unsubscribe(EVENT_USBI, eventNotify) == + PMIC_SUCCESS) { + /* Also acquire the spinlock here to avoid any possible race + * conditions with the interrupt handler. + */ + spin_lock_irqsave(&lock, flags); + + usb.eventMask &= ~(USB_DETECT_0V8_RISE | + USB_DETECT_0V8_FALL); + + spin_unlock_irqrestore(&lock, flags); + } else { + pr_debug + ("%s: pmic_event_unsubscribe() for EVENT_USB_08VI failed\n", + __FILE__); + rc = PMIC_ERROR; + } + + } + + else if ((usb.eventMask & USB_DETECT_2V0_RISE) || + (usb.eventMask & USB_DETECT_2V0_FALL)) { + //EVENT_USB_20VI or EVENT_USBI + eventNotify.func = pmic_convity_event_handler; + eventNotify.param = (void *)(CORE_EVENT_2V0); + if (pmic_event_unsubscribe(EVENT_USBI, eventNotify) == + PMIC_SUCCESS) { + /* Also acquire the spinlock here to avoid any possible race + * conditions with the interrupt handler. + */ + spin_lock_irqsave(&lock, flags); + + usb.eventMask &= ~(USB_DETECT_2V0_RISE | + USB_DETECT_2V0_FALL); + + spin_unlock_irqrestore(&lock, flags); + } else { + pr_debug + ("%s: pmic_event_unsubscribe() for EVENT_USB_20VI failed\n", + __FILE__); + rc = PMIC_ERROR; + } + } + + else if ((usb.eventMask & USB_DETECT_4V4_RISE) || + (usb.eventMask & USB_DETECT_4V4_FALL)) { + + //EVENT_USB_44VI or EVENT_USBI + eventNotify.func = pmic_convity_event_handler; + eventNotify.param = (void *)(CORE_EVENT_4V4); + + if (pmic_event_unsubscribe(EVENT_USBI, eventNotify) == + PMIC_SUCCESS) { + + /* Also acquire the spinlock here to avoid any possible race + * conditions with the interrupt handler. + */ + spin_lock_irqsave(&lock, flags); + + usb.eventMask &= ~(USB_DETECT_4V4_RISE | + USB_DETECT_4V4_FALL); + + spin_unlock_irqrestore(&lock, flags); + } else { + pr_debug + ("%s: pmic_event_unsubscribe() for EVENT_USB_44VI failed\n", + __FILE__); + rc = PMIC_ERROR; + } + } + + if (rc == PMIC_SUCCESS) { + /* Also acquire the spinlock here to avoid any possible race + * conditions with the interrupt handler. + */ + spin_lock_irqsave(&lock, flags); + + /* Restore the initial reset values for the callback function + * and event mask parameters. This should be NULL and zero, + * respectively. + * + * Note that we wait until the end here to fully reset everything + * just in case some of the pmic_event_unsubscribe() calls above + * failed for some reason (which normally shouldn't happen). + */ + usb.callback = reset.callback; + usb.eventMask = reset.eventMask; + + spin_unlock_irqrestore(&lock, flags); + } + return rc; +} + +/*! + * This is the default event handler for all connectivity-related events + * and hardware interrupts. + * + * @param param event ID + */ +static void pmic_convity_event_handler(void *param) +{ + unsigned long flags; + + /* Update the global list of active interrupt events. */ + spin_lock_irqsave(&lock, flags); + eventID |= (PMIC_CORE_EVENT) (param); + spin_unlock_irqrestore(&lock, flags); + + /* Schedule the tasklet to be run as soon as it is convenient to do so. */ + schedule_work(&convityTasklet); +} + +/*! + * @brief This is the connectivity driver tasklet that handles interrupt events. + * + * This function is scheduled by the connectivity driver interrupt handler + * pmic_convity_event_handler() to complete the processing of all of the + * connectivity-related interrupt events. + * + * Since this tasklet runs with interrupts enabled, we can safely call + * the ADC driver, if necessary, to properly detect the type of USB connection + * that is being made and to call any user-registered callback functions. + * + * @param arg The parameter that was provided above in + * the DECLARE_TASKLET() macro (unused). + */ +static void pmic_convity_tasklet(struct work_struct *work) +{ + + PMIC_CONVITY_EVENTS activeEvents = 0; + unsigned long flags = 0; + + /* Check the interrupt sense bits to determine exactly what + * event just occurred. + */ + if (eventID & CORE_EVENT_4V4) { + spin_lock_irqsave(&lock, flags); + eventID &= ~CORE_EVENT_4V4; + spin_unlock_irqrestore(&lock, flags); + + activeEvents |= pmic_check_sensor(SENSE_USB4V4S) ? + USB_DETECT_4V4_RISE : USB_DETECT_4V4_FALL; + + if (activeEvents & ~usb.eventMask) { + /* The default handler for 4.4 V rising/falling edge detection + * is to simply ignore the event. + */ + ; + } + } + if (eventID & CORE_EVENT_2V0) { + spin_lock_irqsave(&lock, flags); + eventID &= ~CORE_EVENT_2V0; + spin_unlock_irqrestore(&lock, flags); + + activeEvents |= pmic_check_sensor(SENSE_USB2V0S) ? + USB_DETECT_2V0_RISE : USB_DETECT_2V0_FALL; + if (activeEvents & ~usb.eventMask) { + /* The default handler for 2.0 V rising/falling edge detection + * is to simply ignore the event. + */ + ; + } + } + if (eventID & CORE_EVENT_0V8) { + spin_lock_irqsave(&lock, flags); + eventID &= ~CORE_EVENT_0V8; + spin_unlock_irqrestore(&lock, flags); + + activeEvents |= pmic_check_sensor(SENSE_USB0V8S) ? + USB_DETECT_0V8_RISE : USB_DETECT_0V8_FALL; + + if (activeEvents & ~usb.eventMask) { + /* The default handler for 0.8 V rising/falling edge detection + * is to simply ignore the event. + */ + ; + } + } + if (eventID & CORE_EVENT_ABDET) { + spin_lock_irqsave(&lock, flags); + eventID &= ~CORE_EVENT_ABDET; + spin_unlock_irqrestore(&lock, flags); + + activeEvents |= pmic_check_sensor(SENSE_ID_GNDS) ? + USB_DETECT_MINI_A : 0; + + activeEvents |= pmic_check_sensor(SENSE_ID_FLOATS) ? + USB_DETECT_MINI_B : 0; + } + + /* Begin a critical section here so that we don't register/deregister + * for events or open/close the connectivity driver while the existing + * event handler (if it is currently defined) is in the middle of handling + * the current event. + */ + spin_lock_irqsave(&lock, flags); + + /* Finally, call the user-defined callback function if required. */ + if ((usb.handle_state == HANDLE_IN_USE) && + (usb.callback != NULL) && (activeEvents & usb.eventMask)) { + (*usb.callback) (activeEvents); + } + + spin_unlock_irqrestore(&lock, flags); +} + +/*@}*/ + +/************************************************************************** + * Module initialization and termination functions. + * + * Note that if this code is compiled into the kernel, then the + * module_init() function will be called within the device_initcall() + * group. + ************************************************************************** + */ + +/*! + * @name Connectivity Driver Loading/Unloading Functions + * These non-exported internal functions are used to support the connectivity + * device driver initialization and de-initialization operations. + */ +/*@{*/ + +/*! + * @brief This is the connectivity device driver initialization function. + * + * This function is called by the kernel when this device driver is first + * loaded. + */ +static int __init mc13783_pmic_convity_init(void) +{ + printk(KERN_INFO "PMIC Connectivity driver loading..\n"); + + return 0; +} + +/*! + * @brief This is the Connectivity device driver de-initialization function. + * + * This function is called by the kernel when this device driver is about + * to be unloaded. + */ +static void __exit mc13783_pmic_convity_exit(void) +{ + printk(KERN_INFO "PMIC Connectivity driver unloading\n"); + + /* Close the device handle if it is still open. This will also + * deregister any callbacks that may still be active. + */ + if (usb.handle_state == HANDLE_IN_USE) { + pmic_convity_close(usb.handle); + } else if (usb.handle_state == HANDLE_IN_USE) { + pmic_convity_close(rs_232.handle); + } else if (usb.handle_state == HANDLE_IN_USE) { + pmic_convity_close(cea_936.handle); + } + + /* Reset the PMIC Connectivity register to it's power on state. + * We should do this when unloading the module so that we don't + * leave the hardware in a state which could cause problems when + * no device driver is loaded. + */ + pmic_write_reg(REG_USB, RESET_USBCNTRL_REG_0, REG_FULLMASK); + pmic_write_reg(REG_CHARGE_USB_SPARE, RESET_USBCNTRL_REG_1, + REG_FULLMASK); + /* Note that there is no need to reset the "convity" device driver + * state structure to the reset state since we are in the final + * stage of unloading the device driver. The device driver state + * structure will be automatically and properly reinitialized if + * this device driver is reloaded. + */ +} + +/*@}*/ + +/* + * Module entry points and description information. + */ + +module_init(mc13783_pmic_convity_init); +module_exit(mc13783_pmic_convity_exit); + +MODULE_DESCRIPTION("mc13783 Connectivity device driver"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/pmic/mc13783/pmic_light.c b/drivers/mxc/pmic/mc13783/pmic_light.c new file mode 100644 index 000000000000..60999028b753 --- /dev/null +++ b/drivers/mxc/pmic/mc13783/pmic_light.c @@ -0,0 +1,2768 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mc13783/pmic_light.c + * @brief This is the main file of PMIC(mc13783) Light and Backlight driver. + * + * @ingroup PMIC_LIGHT + */ + +/* + * Includes + */ +#include <linux/platform_device.h> +#include <linux/delay.h> +#include <linux/fs.h> +#include <asm/arch/pmic_light.h> +#include "../core/pmic_config.h" +#include "pmic_light_defs.h" + +#define NB_LIGHT_REG 6 + +static int pmic_light_major; + +/*! + * Number of users waiting in suspendq + */ +static int swait = 0; + +/*! + * To indicate whether any of the light devices are suspending + */ +static int suspend_flag = 0; + +/*! + * The suspendq is used to block application calls + */ +static wait_queue_head_t suspendq; + +static struct class *pmic_light_class; + +/* EXPORTED FUNCTIONS */ +EXPORT_SYMBOL(pmic_bklit_tcled_master_enable); +EXPORT_SYMBOL(pmic_bklit_tcled_master_disable); +EXPORT_SYMBOL(pmic_bklit_master_enable); +EXPORT_SYMBOL(pmic_bklit_master_disable); +EXPORT_SYMBOL(pmic_bklit_set_current); +EXPORT_SYMBOL(pmic_bklit_get_current); +EXPORT_SYMBOL(pmic_bklit_set_dutycycle); +EXPORT_SYMBOL(pmic_bklit_get_dutycycle); +EXPORT_SYMBOL(pmic_bklit_set_cycle_time); +EXPORT_SYMBOL(pmic_bklit_get_cycle_time); +EXPORT_SYMBOL(pmic_bklit_set_mode); +EXPORT_SYMBOL(pmic_bklit_get_mode); +EXPORT_SYMBOL(pmic_bklit_rampup); +EXPORT_SYMBOL(pmic_bklit_off_rampup); +EXPORT_SYMBOL(pmic_bklit_rampdown); +EXPORT_SYMBOL(pmic_bklit_off_rampdown); +EXPORT_SYMBOL(pmic_bklit_enable_edge_slow); +EXPORT_SYMBOL(pmic_bklit_disable_edge_slow); +EXPORT_SYMBOL(pmic_bklit_get_edge_slow); +EXPORT_SYMBOL(pmic_bklit_set_strobemode); +EXPORT_SYMBOL(pmic_tcled_enable); +EXPORT_SYMBOL(pmic_tcled_disable); +EXPORT_SYMBOL(pmic_tcled_get_mode); +EXPORT_SYMBOL(pmic_tcled_ind_set_current); +EXPORT_SYMBOL(pmic_tcled_ind_get_current); +EXPORT_SYMBOL(pmic_tcled_ind_set_blink_pattern); +EXPORT_SYMBOL(pmic_tcled_ind_get_blink_pattern); +EXPORT_SYMBOL(pmic_tcled_fun_set_current); +EXPORT_SYMBOL(pmic_tcled_fun_get_current); +EXPORT_SYMBOL(pmic_tcled_fun_set_cycletime); +EXPORT_SYMBOL(pmic_tcled_fun_get_cycletime); +EXPORT_SYMBOL(pmic_tcled_fun_set_dutycycle); +EXPORT_SYMBOL(pmic_tcled_fun_get_dutycycle); +EXPORT_SYMBOL(pmic_tcled_fun_blendedramps); +EXPORT_SYMBOL(pmic_tcled_fun_sawramps); +EXPORT_SYMBOL(pmic_tcled_fun_blendedbowtie); +EXPORT_SYMBOL(pmic_tcled_fun_chasinglightspattern); +EXPORT_SYMBOL(pmic_tcled_fun_strobe); +EXPORT_SYMBOL(pmic_tcled_fun_rampup); +EXPORT_SYMBOL(pmic_tcled_get_fun_rampup); +EXPORT_SYMBOL(pmic_tcled_fun_rampdown); +EXPORT_SYMBOL(pmic_tcled_get_fun_rampdown); +EXPORT_SYMBOL(pmic_tcled_fun_triode_on); +EXPORT_SYMBOL(pmic_tcled_fun_triode_off); +EXPORT_SYMBOL(pmic_tcled_enable_edge_slow); +EXPORT_SYMBOL(pmic_tcled_disable_edge_slow); +EXPORT_SYMBOL(pmic_tcled_enable_half_current); +EXPORT_SYMBOL(pmic_tcled_disable_half_current); +EXPORT_SYMBOL(pmic_tcled_enable_audio_modulation); +EXPORT_SYMBOL(pmic_tcled_disable_audio_modulation); +EXPORT_SYMBOL(pmic_bklit_set_boost_mode); +EXPORT_SYMBOL(pmic_bklit_get_boost_mode); +EXPORT_SYMBOL(pmic_bklit_config_boost_mode); +EXPORT_SYMBOL(pmic_bklit_gets_boost_mode); + +/*! + * This is the suspend of power management for the pmic light API. + * It suports SAVE and POWER_DOWN state. + * + * @param pdev the device + * @param state the state + * + * @return This function returns 0 if successful. + */ +static int pmic_light_suspend(struct platform_device *dev, pm_message_t state) +{ + suspend_flag = 1; + /* switch off all leds and backlights */ + CHECK_ERROR(pmic_light_init_reg()); + + return 0; +}; + +/*! + * This is the resume of power management for the pmic light API. + * It suports RESTORE state. + * + * @param dev the device + * + * @return This function returns 0 if successful. + */ +static int pmic_light_resume(struct platform_device *pdev) +{ + suspend_flag = 0; + while (swait > 0) { + swait--; + wake_up_interruptible(&suspendq); + } + + return 0; +}; + +/*! + * This function enables backlight & tcled. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_bklit_tcled_master_enable(void) +{ + unsigned int reg_value = 0; + unsigned int mask = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + reg_value = BITFVAL(BIT_LEDEN, 1); + mask = BITFMASK(BIT_LEDEN); + CHECK_ERROR(pmic_write_reg(LREG_0, reg_value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function disables backlight & tcled. + * + * @return This function returns PMIC_SUCCESS if successful + */ +PMIC_STATUS pmic_bklit_tcled_master_disable(void) +{ + unsigned int reg_value = 0; + unsigned int mask = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + reg_value = BITFVAL(BIT_LEDEN, 0); + mask = BITFMASK(BIT_LEDEN); + CHECK_ERROR(pmic_write_reg(LREG_0, reg_value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function enables backlight. Not supported on mc13783 + * Use pmic_bklit_tcled_master_enable. + * + * @return This function returns PMIC_NOT_SUPPORTED + */ +PMIC_STATUS pmic_bklit_master_enable(void) +{ + return PMIC_NOT_SUPPORTED; +} + +/*! + * This function disables backlight. Not supported on mc13783 + * Use pmic_bklit_tcled_master_enable. + * + * @return This function returns PMIC_NOT_SUPPORTED + */ +PMIC_STATUS pmic_bklit_master_disable(void) +{ + return PMIC_NOT_SUPPORTED; +} + +/*! + * This function sets backlight current level. + * + * @param channel Backlight channel + * @param level Backlight current level, as the following table. + * @verbatim + level main & aux keyboard + ------ ----------- -------- + 0 0 mA 0 mA + 1 3 mA 12 mA + 2 6 mA 24 mA + 3 9 mA 36 mA + 4 12 mA 48 mA + 5 15 mA 60 mA + 6 18 mA 72 mA + 7 21 mA 84 mA + @endverbatim + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_bklit_set_current(t_bklit_channel channel, unsigned char level) +{ + unsigned int mask; + unsigned int value; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (channel) { + case BACKLIGHT_LED1: + value = BITFVAL(BIT_CL_MAIN, level); + mask = BITFMASK(BIT_CL_MAIN); + break; + case BACKLIGHT_LED2: + value = BITFVAL(BIT_CL_AUX, level); + mask = BITFMASK(BIT_CL_AUX); + break; + case BACKLIGHT_LED3: + value = BITFVAL(BIT_CL_KEY, level); + mask = BITFMASK(BIT_CL_KEY); + break; + default: + return PMIC_PARAMETER_ERROR; + } + CHECK_ERROR(pmic_write_reg(LREG_2, value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function retrives backlight current level. + * The channels are not individually adjustable, hence + * the channel parameter is ignored. + * + * @param channel Backlight channel (Ignored because the + * channels are not individually adjustable) + * @param level Pointer to store backlight current level result. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_bklit_get_current(t_bklit_channel channel, + unsigned char *level) +{ + unsigned int reg_value = 0; + unsigned int mask = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (channel) { + case BACKLIGHT_LED1: + mask = BITFMASK(BIT_CL_MAIN); + break; + case BACKLIGHT_LED2: + mask = BITFMASK(BIT_CL_AUX); + break; + case BACKLIGHT_LED3: + mask = BITFMASK(BIT_CL_KEY); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(LREG_2, ®_value, mask)); + + switch (channel) { + case BACKLIGHT_LED1: + *level = BITFEXT(reg_value, BIT_CL_MAIN); + break; + case BACKLIGHT_LED2: + *level = BITFEXT(reg_value, BIT_CL_AUX); + break; + case BACKLIGHT_LED3: + *level = BITFEXT(reg_value, BIT_CL_KEY); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + return PMIC_SUCCESS; +} + +/*! + * This function sets a backlight channel duty cycle. + * LED perceived brightness for each zone may be individually set by setting + * duty cycle. The default setting is for 0% duty cycle; this keeps all zone + * drivers turned off even after the master enable command. Each LED current + * sink can be turned on and adjusted for brightness with an independent 4 bit + * word for a duty cycle ranging from 0% to 100% in approximately 6.7% steps. + * + * @param channel Backlight channel. + * @param dc Backlight duty cycle, as the following table. + * @verbatim + dc Duty Cycle (% On-time over Cycle Time) + ------ --------------------------------------- + 0 0% + 1 6.7% + 2 13.3% + 3 20% + 4 26.7% + 5 33.3% + 6 40% + 7 46.7% + 8 53.3% + 9 60% + 10 66.7% + 11 73.3% + 12 80% + 13 86.7% + 14 93.3% + 15 100% + @endverbatim + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_bklit_set_dutycycle(t_bklit_channel channel, unsigned char dc) +{ + unsigned int reg_value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + if (dc > 15) { + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(LREG_2, ®_value, PMIC_ALL_BITS)); + + switch (channel) { + case BACKLIGHT_LED1: + reg_value = reg_value & (~MASK_DUTY_CYCLE); + reg_value = reg_value | (dc << BIT_DUTY_CYCLE); + break; + case BACKLIGHT_LED2: + reg_value = reg_value & (~(MASK_DUTY_CYCLE << INDEX_AUX)); + reg_value = reg_value | (dc << (BIT_DUTY_CYCLE + INDEX_AUX)); + break; + case BACKLIGHT_LED3: + reg_value = reg_value & (~(MASK_DUTY_CYCLE << INDEX_KYD)); + reg_value = reg_value | (dc << (BIT_DUTY_CYCLE + INDEX_KYD)); + break; + default: + return PMIC_PARAMETER_ERROR; + } + CHECK_ERROR(pmic_write_reg(LREG_2, reg_value, PMIC_ALL_BITS)); + return PMIC_SUCCESS; + +} + +/*! + * This function retrives a backlight channel duty cycle. + * + * @param channel Backlight channel. + * @param dc Pointer to backlight duty cycle. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_bklit_get_dutycycle(t_bklit_channel channel, unsigned char *dc) +{ + unsigned int reg_value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + CHECK_ERROR(pmic_read_reg(LREG_2, ®_value, PMIC_ALL_BITS)); + + switch (channel) { + case BACKLIGHT_LED1: + *dc = (int)((reg_value & (MASK_DUTY_CYCLE)) + >> BIT_DUTY_CYCLE); + + break; + case BACKLIGHT_LED2: + *dc = (int)((reg_value & (MASK_DUTY_CYCLE << INDEX_AUX)) + >> (BIT_DUTY_CYCLE + INDEX_AUX)); + break; + case BACKLIGHT_LED3: + *dc = (int)((reg_value & (MASK_DUTY_CYCLE << + INDEX_KYD)) >> (BIT_DUTY_CYCLE + + INDEX_KYD)); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + return PMIC_SUCCESS; +} + +/*! + * This function sets a backlight channel cycle time. + * Cycle Time is defined as the period of a complete cycle of + * Time_on + Time_off. The default Cycle Time is set to 0.01 seconds such that + * the 100 Hz on-off cycling is averaged out by the eye to eliminate + * flickering. Additionally, the Cycle Time can be programmed to intentionally + * extend the period of on-off cycles for a visual pulsating or blinking effect. + * + * @param period Backlight cycle time, as the following table. + * @verbatim + period Cycle Time + -------- ------------ + 0 0.01 seconds + 1 0.1 seconds + 2 0.5 seconds + 3 2 seconds + @endverbatim + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_bklit_set_cycle_time(unsigned char period) +{ + unsigned int mask; + unsigned int value; + + if (suspend_flag == 1) { + return -EBUSY; + } + if (period > 3) { + return PMIC_PARAMETER_ERROR; + } + mask = BITFMASK(BIT_PERIOD); + value = BITFVAL(BIT_PERIOD, period); + CHECK_ERROR(pmic_write_reg(LREG_2, value, mask)); + return PMIC_SUCCESS; +} + +/*! + * This function retrives a backlight channel cycle time setting. + * + * @param period Pointer to save backlight cycle time setting result. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_bklit_get_cycle_time(unsigned char *period) +{ + unsigned int mask; + unsigned int value; + + if (suspend_flag == 1) { + return -EBUSY; + } + mask = BITFMASK(BIT_PERIOD); + CHECK_ERROR(pmic_read_reg(LREG_2, &value, mask)); + *period = BITFEXT(value, BIT_PERIOD); + return PMIC_SUCCESS; +} + +/*! + * This function sets backlight operation mode. There are two modes of + * operations: current control and triode mode. + * The Duty Cycle/Cycle Time control is retained in Triode Mode. Audio + * coupling is not available in Triode Mode. + * + * @param channel Backlight channel. + * @param mode Backlight operation mode. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_bklit_set_mode(t_bklit_channel channel, t_bklit_mode mode) +{ + unsigned int reg_value = 0; + unsigned int clear_val = 0; + unsigned int triode_val = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + CHECK_ERROR(pmic_read_reg(LREG_0, ®_value, PMIC_ALL_BITS)); + + switch (channel) { + case BACKLIGHT_LED1: + clear_val = ~(MASK_TRIODE_MAIN_BL); + triode_val = MASK_TRIODE_MAIN_BL; + break; + case BACKLIGHT_LED2: + clear_val = ~(MASK_TRIODE_MAIN_BL << INDEX_AUXILIARY); + triode_val = (MASK_TRIODE_MAIN_BL << INDEX_AUXILIARY); + break; + case BACKLIGHT_LED3: + clear_val = ~(MASK_TRIODE_MAIN_BL << INDEX_KEYPAD); + triode_val = (MASK_TRIODE_MAIN_BL << INDEX_KEYPAD); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + reg_value = (reg_value & clear_val); + + if (mode == BACKLIGHT_TRIODE_MODE) { + reg_value = (reg_value | triode_val); + } + + CHECK_ERROR(pmic_write_reg(LREG_0, reg_value, PMIC_ALL_BITS)); + return PMIC_SUCCESS; +} + +/*! + * This function gets backlight operation mode. There are two modes of + * operations: current control and triode mode. + * The Duty Cycle/Cycle Time control is retained in Triode Mode. Audio + * coupling is not available in Triode Mode. + * + * @param channel Backlight channel. + * @param mode Backlight operation mode. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_bklit_get_mode(t_bklit_channel channel, t_bklit_mode * mode) +{ + unsigned int reg_value = 0; + unsigned int mask = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (channel) { + case BACKLIGHT_LED1: + mask = BITFMASK(BIT_TRIODE_MAIN_BL); + break; + case BACKLIGHT_LED2: + mask = BITFMASK(BIT_TRIODE_AUX_BL); + break; + case BACKLIGHT_LED3: + mask = BITFMASK(BIT_TRIODE_KEY_BL); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(LREG_0, ®_value, mask)); + + switch (channel) { + case BACKLIGHT_LED1: + *mode = BITFEXT(reg_value, BIT_TRIODE_MAIN_BL); + break; + case BACKLIGHT_LED2: + *mode = BITFEXT(reg_value, BIT_TRIODE_AUX_BL); + break; + case BACKLIGHT_LED3: + *mode = BITFEXT(reg_value, BIT_TRIODE_KEY_BL); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + return PMIC_SUCCESS; +} + +/*! + * This function starts backlight brightness ramp up function; ramp time is + * fixed at 0.5 seconds. + * + * @param channel Backlight channel. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_bklit_rampup(t_bklit_channel channel) +{ + unsigned int reg_value = 0; + unsigned int mask = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (channel) { + case BACKLIGHT_LED1: + mask = BITFMASK(BIT_UP_MAIN_BL); + reg_value = BITFVAL(BIT_UP_MAIN_BL, 1); + break; + case BACKLIGHT_LED2: + mask = BITFMASK(BIT_UP_AUX_BL); + reg_value = BITFVAL(BIT_UP_AUX_BL, 1); + break; + case BACKLIGHT_LED3: + mask = BITFMASK(BIT_UP_KEY_BL); + reg_value = BITFVAL(BIT_UP_KEY_BL, 1); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(LREG_0, reg_value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function stops backlight brightness ramp up function; + * + * @param channel Backlight channel. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_bklit_off_rampup(t_bklit_channel channel) +{ + unsigned int reg_value = 0; + unsigned int mask = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (channel) { + case BACKLIGHT_LED1: + mask = BITFMASK(BIT_UP_MAIN_BL); + break; + case BACKLIGHT_LED2: + mask = BITFMASK(BIT_UP_AUX_BL); + break; + case BACKLIGHT_LED3: + mask = BITFMASK(BIT_UP_KEY_BL); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(LREG_0, reg_value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function starts backlight brightness ramp down function; ramp time is + * fixed at 0.5 seconds. + * + * @param channel Backlight channel. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_bklit_rampdown(t_bklit_channel channel) +{ + unsigned int reg_value = 0; + unsigned int mask = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (channel) { + case BACKLIGHT_LED1: + mask = BITFMASK(BIT_DOWN_MAIN_BL); + reg_value = BITFVAL(BIT_DOWN_MAIN_BL, 1); + break; + case BACKLIGHT_LED2: + mask = BITFMASK(BIT_DOWN_AUX_BL); + reg_value = BITFVAL(BIT_DOWN_AUX_BL, 1); + break; + case BACKLIGHT_LED3: + mask = BITFMASK(BIT_DOWN_KEY_BL); + reg_value = BITFVAL(BIT_DOWN_KEY_BL, 1); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(LREG_0, reg_value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function stops backlight brightness ramp down function. + * + * @param channel Backlight channel. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_bklit_off_rampdown(t_bklit_channel channel) +{ + unsigned int reg_value = 0; + unsigned int mask = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (channel) { + case BACKLIGHT_LED1: + mask = BITFMASK(BIT_DOWN_MAIN_BL); + break; + case BACKLIGHT_LED2: + mask = BITFMASK(BIT_DOWN_AUX_BL); + break; + case BACKLIGHT_LED3: + mask = BITFMASK(BIT_DOWN_KEY_BL); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(LREG_0, reg_value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function enables backlight analog edge slowing mode. Analog Edge + * Slowing slows down the transient edges to reduce the chance of coupling LED + * modulation activity into other circuits. Rise and fall times will be targeted + * for approximately 50usec. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_bklit_enable_edge_slow(void) +{ + unsigned int mask; + unsigned int value; + + if (suspend_flag == 1) { + return -EBUSY; + } + mask = BITFMASK(BIT_SLEWLIMBL); + value = BITFVAL(BIT_SLEWLIMBL, 1); + CHECK_ERROR(pmic_write_reg(LREG_2, value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function disables backlight analog edge slowing mode. The backlight + * drivers will default to an <93>Instant On<94> mode. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_bklit_disable_edge_slow(void) +{ + unsigned int mask; + + if (suspend_flag == 1) { + return -EBUSY; + } + mask = BITFMASK(BIT_SLEWLIMBL); + CHECK_ERROR(pmic_write_reg(LREG_2, 0, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function gets backlight analog edge slowing mode. DThe backlight + * + * @param edge Edge slowing mode. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_bklit_get_edge_slow(bool * edge) +{ + unsigned int mask; + unsigned int value; + + if (suspend_flag == 1) { + return -EBUSY; + } + mask = BITFMASK(BIT_SLEWLIMBL); + CHECK_ERROR(pmic_read_reg(LREG_2, &value, mask)); + *edge = (bool) BITFEXT(value, BIT_SLEWLIMBL); + + return PMIC_SUCCESS; +} + +/*! + * This function sets backlight Strobe Light Pulsing mode. + * + * @param channel Backlight channel. + * @param mode Strobe Light Pulsing mode. + * + * @return This function returns PMIC_NOT_SUPPORTED. + */ +PMIC_STATUS pmic_bklit_set_strobemode(t_bklit_channel channel, + t_bklit_strobe_mode mode) +{ + return PMIC_NOT_SUPPORTED; +} + +/*! + * This function enables tri-color LED. + * + * @param mode Tri-color LED operation mode. + * @param bank Selected tri-color bank + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_enable(t_tcled_mode mode, t_funlight_bank bank) +{ + unsigned int mask = 0; + unsigned int value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (mode) { + case TCLED_FUN_MODE: + switch (bank) { + case TCLED_FUN_BANK1: + mask = MASK_BK1_FL; + value = MASK_BK1_FL; + break; + case TCLED_FUN_BANK2: + mask = MASK_BK2_FL; + value = MASK_BK2_FL; + break; + case TCLED_FUN_BANK3: + mask = MASK_BK3_FL; + value = MASK_BK3_FL; + break; + default: + return PMIC_PARAMETER_ERROR; + } + break; + case TCLED_IND_MODE: + mask = MASK_BK1_FL | MASK_BK2_FL | MASK_BK3_FL; + break; + } + + CHECK_ERROR(pmic_write_reg(LREG_0, value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function disables tri-color LED. + * + * @param bank Selected tri-color bank + * + * @return This function returns PMIC_SUCCESS if successful. + * + */ +PMIC_STATUS pmic_tcled_disable(t_funlight_bank bank) +{ + unsigned int mask = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (bank) { + case TCLED_FUN_BANK1: + mask = MASK_BK1_FL; + break; + case TCLED_FUN_BANK2: + mask = MASK_BK2_FL; + break; + case TCLED_FUN_BANK3: + mask = MASK_BK3_FL; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(LREG_0, 0, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function retrives tri-color LED operation mode. + * + * @param mode Pointer to Tri-color LED operation mode. + * @param bank Selected tri-color bank + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_get_mode(t_tcled_mode * mode, t_funlight_bank bank) +{ + unsigned int val; + unsigned int mask; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (bank) { + case TCLED_FUN_BANK1: + mask = MASK_BK1_FL; + break; + case TCLED_FUN_BANK2: + mask = MASK_BK2_FL; + break; + case TCLED_FUN_BANK3: + mask = MASK_BK3_FL; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(LREG_0, &val, mask)); + + if (val) { + *mode = TCLED_FUN_MODE; + } else { + *mode = TCLED_IND_MODE; + } + + return PMIC_SUCCESS; +} + +/*! + * This function sets a tri-color LED channel current level in indicator mode. + * + * @param channel Tri-color LED channel. + * @param level Current level. + * @param bank Selected tri-color bank + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_ind_set_current(t_ind_channel channel, + t_tcled_cur_level level, + t_funlight_bank bank) +{ + unsigned int reg_conf = 0; + unsigned int mask; + unsigned int value; + + if (suspend_flag == 1) { + return -EBUSY; + } + + if (level > TCLED_CUR_LEVEL_4) { + return PMIC_PARAMETER_ERROR; + } + + switch (bank) { + case TCLED_FUN_BANK1: + reg_conf = LREG_3; + break; + case TCLED_FUN_BANK2: + reg_conf = LREG_4; + break; + case TCLED_FUN_BANK3: + reg_conf = LREG_5; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + switch (channel) { + case TCLED_IND_RED: + value = BITFVAL(BITS_CL_RED, level); + mask = BITFMASK(BITS_CL_RED); + break; + case TCLED_IND_GREEN: + value = BITFVAL(BITS_CL_GREEN, level); + mask = BITFMASK(BITS_CL_GREEN); + break; + case TCLED_IND_BLUE: + value = BITFVAL(BITS_CL_BLUE, level); + mask = BITFMASK(BITS_CL_BLUE); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(reg_conf, value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function retrives a tri-color LED channel current level + * in indicator mode. + * + * @param channel Tri-color LED channel. + * @param level Pointer to current level. + * @param bank Selected tri-color bank + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_ind_get_current(t_ind_channel channel, + t_tcled_cur_level * level, + t_funlight_bank bank) +{ + unsigned int reg_conf = 0; + unsigned int mask = 0; + unsigned int value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (bank) { + case TCLED_FUN_BANK1: + reg_conf = LREG_3; + break; + case TCLED_FUN_BANK2: + reg_conf = LREG_4; + break; + case TCLED_FUN_BANK3: + reg_conf = LREG_5; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + switch (channel) { + case TCLED_IND_RED: + mask = BITFMASK(BITS_CL_RED); + break; + case TCLED_IND_GREEN: + mask = BITFMASK(BITS_CL_GREEN); + break; + case TCLED_IND_BLUE: + mask = BITFMASK(BITS_CL_BLUE); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(reg_conf, &value, mask)); + + switch (channel) { + case TCLED_IND_RED: + *level = BITFEXT(value, BITS_CL_RED); + break; + case TCLED_IND_GREEN: + *level = BITFEXT(value, BITS_CL_GREEN); + break; + case TCLED_IND_BLUE: + *level = BITFEXT(value, BITS_CL_BLUE); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + return PMIC_SUCCESS; +} + +/*! + * This function sets a tri-color LED channel blinking pattern in indication + * mode. + * + * @param channel Tri-color LED channel. + * @param pattern Blinking pattern. + * @param skip If true, skip a cycle after each cycle. + * @param bank Selected tri-color bank + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_ind_set_blink_pattern(t_ind_channel channel, + t_tcled_ind_blink_pattern pattern, + bool skip, t_funlight_bank bank) +{ + unsigned int reg_conf = 0; + unsigned int mask = 0; + unsigned int value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + if (skip == true) { + return PMIC_NOT_SUPPORTED; + } + + switch (bank) { + case TCLED_FUN_BANK1: + reg_conf = LREG_3; + break; + case TCLED_FUN_BANK2: + reg_conf = LREG_4; + break; + case TCLED_FUN_BANK3: + reg_conf = LREG_5; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + switch (channel) { + case TCLED_IND_RED: + value = BITFVAL(BITS_DC_RED, pattern); + mask = BITFMASK(BITS_DC_RED); + break; + case TCLED_IND_GREEN: + value = BITFVAL(BITS_DC_GREEN, pattern); + mask = BITFMASK(BITS_DC_GREEN); + break; + case TCLED_IND_BLUE: + value = BITFVAL(BITS_DC_BLUE, pattern); + mask = BITFMASK(BITS_DC_BLUE); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(reg_conf, value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function retrives a tri-color LED channel blinking pattern in + * indication mode. + * + * @param channel Tri-color LED channel. + * @param pattern Pointer to Blinking pattern. + * @param skip Pointer to a boolean varible indicating if skip + * @param bank Selected tri-color bank + * a cycle after each cycle. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_ind_get_blink_pattern(t_ind_channel channel, + t_tcled_ind_blink_pattern * + pattern, bool * skip, + t_funlight_bank bank) +{ + unsigned int reg_conf = 0; + unsigned int mask = 0; + unsigned int value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (bank) { + case TCLED_FUN_BANK1: + reg_conf = LREG_3; + break; + case TCLED_FUN_BANK2: + reg_conf = LREG_4; + break; + case TCLED_FUN_BANK3: + reg_conf = LREG_5; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + switch (channel) { + case TCLED_IND_RED: + mask = BITFMASK(BITS_DC_RED); + break; + case TCLED_IND_GREEN: + mask = BITFMASK(BITS_DC_GREEN); + break; + case TCLED_IND_BLUE: + mask = BITFMASK(BITS_DC_BLUE); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(reg_conf, &value, mask)); + + switch (channel) { + case TCLED_IND_RED: + *pattern = BITFEXT(value, BITS_DC_RED); + break; + case TCLED_IND_GREEN: + *pattern = BITFEXT(value, BITS_DC_GREEN); + break; + case TCLED_IND_BLUE: + *pattern = BITFEXT(value, BITS_DC_BLUE); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + return PMIC_SUCCESS; +} + +/*! + * This function sets a tri-color LED channel current level in Fun Light mode. + * + * @param bank Tri-color LED bank + * @param channel Tri-color LED channel. + * @param level Current level. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_fun_set_current(t_funlight_bank bank, + t_funlight_channel channel, + t_tcled_cur_level level) +{ + unsigned int reg_conf = 0; + unsigned int mask; + unsigned int value; + + if (suspend_flag == 1) { + return -EBUSY; + } + + if (level > TCLED_CUR_LEVEL_4) { + return PMIC_PARAMETER_ERROR; + } + + switch (bank) { + case TCLED_FUN_BANK1: + reg_conf = LREG_3; + break; + case TCLED_FUN_BANK2: + reg_conf = LREG_4; + break; + case TCLED_FUN_BANK3: + reg_conf = LREG_5; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + switch (channel) { + case TCLED_FUN_CHANNEL1: + value = BITFVAL(BITS_CL_RED, level); + mask = BITFMASK(BITS_CL_RED); + break; + case TCLED_FUN_CHANNEL2: + value = BITFVAL(BITS_CL_GREEN, level); + mask = BITFMASK(BITS_CL_GREEN); + break; + case TCLED_FUN_CHANNEL3: + value = BITFVAL(BITS_CL_BLUE, level); + mask = BITFMASK(BITS_CL_BLUE); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(reg_conf, value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function retrives a tri-color LED channel current level + * in Fun Light mode. + * + * @param bank Tri-color LED bank + * @param channel Tri-color LED channel. + * @param level Pointer to current level. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_fun_get_current(t_funlight_bank bank, + t_funlight_channel channel, + t_tcled_cur_level * level) +{ + unsigned int reg_conf = 0; + unsigned int mask = 0; + unsigned int value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (bank) { + case TCLED_FUN_BANK1: + reg_conf = LREG_3; + break; + case TCLED_FUN_BANK2: + reg_conf = LREG_4; + break; + case TCLED_FUN_BANK3: + reg_conf = LREG_5; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + switch (channel) { + case TCLED_FUN_CHANNEL1: + mask = BITFMASK(BITS_CL_RED); + break; + case TCLED_FUN_CHANNEL2: + mask = BITFMASK(BITS_CL_GREEN); + break; + case TCLED_FUN_CHANNEL3: + mask = BITFMASK(BITS_CL_BLUE); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(reg_conf, &value, mask)); + + switch (channel) { + case TCLED_FUN_CHANNEL1: + *level = BITFEXT(value, BITS_CL_RED); + break; + case TCLED_FUN_CHANNEL2: + *level = BITFEXT(value, BITS_CL_GREEN); + break; + case TCLED_FUN_CHANNEL3: + *level = BITFEXT(value, BITS_CL_BLUE); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + return PMIC_SUCCESS; +} + +/*! + * This function sets tri-color LED cycle time. + * + * @param bank Tri-color LED bank + * @param ct Cycle time. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_fun_set_cycletime(t_funlight_bank bank, + t_tcled_fun_cycle_time ct) +{ + unsigned int reg_conf = 0; + unsigned int mask; + unsigned int value; + + if (suspend_flag == 1) { + return -EBUSY; + } + + if (ct > TC_CYCLE_TIME_4) { + return PMIC_PARAMETER_ERROR; + } + + switch (bank) { + case TCLED_FUN_BANK1: + reg_conf = LREG_3; + break; + case TCLED_FUN_BANK2: + reg_conf = LREG_4; + break; + case TCLED_FUN_BANK3: + reg_conf = LREG_5; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + value = BITFVAL(BIT_PERIOD, ct); + mask = BITFMASK(BIT_PERIOD); + + CHECK_ERROR(pmic_write_reg(reg_conf, value, mask)); + return PMIC_SUCCESS; +} + +/*! + * This function retrives tri-color LED cycle time in Fun Light mode. + * + * @param bank Tri-color LED bank + * @param ct Pointer to cycle time. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_fun_get_cycletime(t_funlight_bank bank, + t_tcled_fun_cycle_time * ct) +{ + unsigned int reg_conf = 0; + unsigned int mask; + unsigned int value; + + if (suspend_flag == 1) { + return -EBUSY; + } + + if (*ct > TC_CYCLE_TIME_4) { + return PMIC_PARAMETER_ERROR; + } + + switch (bank) { + case TCLED_FUN_BANK1: + reg_conf = LREG_3; + break; + case TCLED_FUN_BANK2: + reg_conf = LREG_4; + break; + case TCLED_FUN_BANK3: + reg_conf = LREG_5; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + mask = BITFMASK(BIT_PERIOD); + CHECK_ERROR(pmic_read_reg(reg_conf, &value, mask)); + + *ct = BITFVAL(BIT_PERIOD, value); + + return PMIC_SUCCESS; +} + +/*! + * This function sets a tri-color LED channel duty cycle in Fun Light mode. + * + * @param bank Tri-color LED bank + * @param channel Tri-color LED channel. + * @param dc Duty cycle. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_fun_set_dutycycle(t_funlight_bank bank, + t_funlight_channel channel, + unsigned char dc) +{ + unsigned int reg_conf = 0; + unsigned int mask = 0; + unsigned int value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (bank) { + case TCLED_FUN_BANK1: + reg_conf = LREG_3; + break; + case TCLED_FUN_BANK2: + reg_conf = LREG_4; + break; + case TCLED_FUN_BANK3: + reg_conf = LREG_5; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + switch (channel) { + case TCLED_FUN_CHANNEL1: + value = BITFVAL(BITS_DC_RED, dc); + mask = BITFMASK(BITS_DC_RED); + break; + case TCLED_FUN_CHANNEL2: + value = BITFVAL(BITS_DC_GREEN, dc); + mask = BITFMASK(BITS_DC_GREEN); + break; + case TCLED_FUN_CHANNEL3: + value = BITFVAL(BITS_DC_BLUE, dc); + mask = BITFMASK(BITS_DC_BLUE); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(reg_conf, value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function retrives a tri-color LED channel duty cycle in Fun Light mode. + * + * @param bank Tri-color LED bank + * @param channel Tri-color LED channel. + * @param dc Pointer to duty cycle. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_fun_get_dutycycle(t_funlight_bank bank, + t_funlight_channel channel, + unsigned char *dc) +{ + unsigned int reg_conf = 0; + unsigned int mask = 0; + unsigned int value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (bank) { + case TCLED_FUN_BANK1: + reg_conf = LREG_3; + break; + case TCLED_FUN_BANK2: + reg_conf = LREG_4; + break; + case TCLED_FUN_BANK3: + reg_conf = LREG_5; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + switch (channel) { + case TCLED_FUN_CHANNEL1: + mask = BITFMASK(BITS_DC_RED); + break; + case TCLED_FUN_CHANNEL2: + mask = BITFMASK(BITS_DC_GREEN); + break; + case TCLED_FUN_CHANNEL3: + mask = BITFMASK(BITS_DC_BLUE); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(reg_conf, &value, mask)); + + switch (channel) { + case TCLED_FUN_CHANNEL1: + *dc = BITFEXT(value, BITS_DC_RED); + break; + case TCLED_FUN_CHANNEL2: + *dc = BITFEXT(value, BITS_DC_GREEN); + break; + case TCLED_FUN_CHANNEL3: + *dc = BITFEXT(value, BITS_DC_BLUE); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + return PMIC_SUCCESS; +} + +/*! + * This function initiates Blended Ramp fun light pattern. + * + * @param bank Tri-color LED bank + * @param speed Speed of pattern. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_fun_blendedramps(t_funlight_bank bank, + t_tcled_fun_speed speed) +{ + unsigned int mask = 0; + unsigned int value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (speed) { + case TC_OFF: + value = BITFVAL(BITS_FUN_LIGHT, FUN_LIGHTS_OFF); + break; + case TC_SLOW: + value = BITFVAL(BITS_FUN_LIGHT, BLENDED_RAMPS_SLOW); + break; + case TC_FAST: + value = BITFVAL(BITS_FUN_LIGHT, BLENDED_RAMPS_FAST); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + mask = BITFMASK(BITS_FUN_LIGHT); + CHECK_ERROR(pmic_write_reg(LREG_0, value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function initiates Saw Ramp fun light pattern. + * + * @param bank Tri-color LED bank + * @param speed Speed of pattern. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_fun_sawramps(t_funlight_bank bank, + t_tcled_fun_speed speed) +{ + unsigned int mask = 0; + unsigned int value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (speed) { + case TC_OFF: + value = BITFVAL(BITS_FUN_LIGHT, FUN_LIGHTS_OFF); + break; + case TC_SLOW: + value = BITFVAL(BITS_FUN_LIGHT, SAW_RAMPS_SLOW); + break; + case TC_FAST: + value = BITFVAL(BITS_FUN_LIGHT, SAW_RAMPS_FAST); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + mask = BITFMASK(BITS_FUN_LIGHT); + CHECK_ERROR(pmic_write_reg(LREG_0, value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function initiates Blended Bowtie fun light pattern. + * + * @param bank Tri-color LED bank + * @param speed Speed of pattern. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_fun_blendedbowtie(t_funlight_bank bank, + t_tcled_fun_speed speed) +{ + unsigned int mask = 0; + unsigned int value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (speed) { + case TC_OFF: + value = BITFVAL(BITS_FUN_LIGHT, FUN_LIGHTS_OFF); + break; + case TC_SLOW: + value = BITFVAL(BITS_FUN_LIGHT, BLENDED_INVERSE_RAMPS_SLOW); + break; + case TC_FAST: + value = BITFVAL(BITS_FUN_LIGHT, BLENDED_INVERSE_RAMPS_FAST); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + mask = BITFMASK(BITS_FUN_LIGHT); + CHECK_ERROR(pmic_write_reg(LREG_0, value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function initiates Chasing Lights fun light pattern. + * + * @param bank Tri-color LED bank + * @param pattern Chasing light pattern mode. + * @param speed Speed of pattern. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_fun_chasinglightspattern(t_funlight_bank bank, + t_chaselight_pattern pattern, + t_tcled_fun_speed speed) +{ + unsigned int mask = 0; + unsigned int value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + if (pattern > BGR) { + return PMIC_PARAMETER_ERROR; + } + + switch (speed) { + case TC_OFF: + value = BITFVAL(BITS_FUN_LIGHT, FUN_LIGHTS_OFF); + break; + case TC_SLOW: + if (pattern == PMIC_RGB) { + value = + BITFVAL(BITS_FUN_LIGHT, CHASING_LIGHTS_RGB_SLOW); + } else { + value = + BITFVAL(BITS_FUN_LIGHT, CHASING_LIGHTS_BGR_SLOW); + } + break; + case TC_FAST: + if (pattern == PMIC_RGB) { + value = + BITFVAL(BITS_FUN_LIGHT, CHASING_LIGHTS_RGB_FAST); + } else { + value = + BITFVAL(BITS_FUN_LIGHT, CHASING_LIGHTS_BGR_FAST); + } + break; + default: + return PMIC_PARAMETER_ERROR; + } + + mask = BITFMASK(BITS_FUN_LIGHT); + CHECK_ERROR(pmic_write_reg(LREG_0, value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function initiates Strobe Mode fun light pattern. + * + * @param bank Tri-color LED bank + * @param channel Tri-color LED channel. + * @param speed Speed of pattern. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_fun_strobe(t_funlight_bank bank, + t_funlight_channel channel, + t_tcled_fun_strobe_speed speed) +{ + /* not supported on mc13783 */ + + return PMIC_NOT_SUPPORTED; +} + +/*! + * This function initiates Tri-color LED brightness Ramp Up function; Ramp time + * is fixed at 1 second. + * + * @param bank Tri-color LED bank + * @param channel Tri-color LED channel. + * @param rampup Ramp-up configuration. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_fun_rampup(t_funlight_bank bank, + t_funlight_channel channel, bool rampup) +{ + unsigned int mask = 0; + unsigned int value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (bank) { + case TCLED_FUN_BANK1: + mask = LEDR1RAMPUP; + value = LEDR1RAMPUP; + break; + case TCLED_FUN_BANK2: + mask = LEDR2RAMPUP; + value = LEDR2RAMPUP; + break; + case TCLED_FUN_BANK3: + mask = LEDR3RAMPUP; + value = LEDR3RAMPUP; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + switch (channel) { + case TCLED_FUN_CHANNEL1: + mask = mask; + value = value; + break; + case TCLED_FUN_CHANNEL2: + mask = mask * 2; + value = value * 2; + break; + case TCLED_FUN_CHANNEL3: + mask = mask * 4; + value = value * 4; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + if (!rampup) { + value = 0; + } + + CHECK_ERROR(pmic_write_reg(LREG_1, value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function gets Tri-color LED brightness Ramp Up function; Ramp time + * is fixed at 1 second. + * + * @param bank Tri-color LED bank + * @param channel Tri-color LED channel. + * @param rampup Ramp-up configuration. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_get_fun_rampup(t_funlight_bank bank, + t_funlight_channel channel, bool * rampup) +{ + unsigned int mask = 0; + unsigned int value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (bank) { + case TCLED_FUN_BANK1: + mask = LEDR1RAMPUP; + break; + case TCLED_FUN_BANK2: + mask = LEDR2RAMPUP; + break; + case TCLED_FUN_BANK3: + mask = LEDR3RAMPUP; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + switch (channel) { + case TCLED_FUN_CHANNEL1: + mask = mask; + break; + case TCLED_FUN_CHANNEL2: + mask = mask * 2; + break; + case TCLED_FUN_CHANNEL3: + mask = mask * 4; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(LREG_1, &value, mask)); + if (value) { + *rampup = true; + } else { + *rampup = false; + } + + return PMIC_SUCCESS; +} + +/*! + * This function initiates Tri-color LED brightness Ramp Down function; Ramp + * time is fixed at 1 second. + * + * @param bank Tri-color LED bank + * @param channel Tri-color LED channel. + * @param rampdown Ramp-down configuration. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_fun_rampdown(t_funlight_bank bank, + t_funlight_channel channel, bool rampdown) +{ + unsigned int mask = 0; + unsigned int value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (bank) { + case TCLED_FUN_BANK1: + mask = LEDR1RAMPDOWN; + value = LEDR1RAMPDOWN; + break; + case TCLED_FUN_BANK2: + mask = LEDR2RAMPDOWN; + value = LEDR2RAMPDOWN; + break; + case TCLED_FUN_BANK3: + mask = LEDR3RAMPDOWN; + value = LEDR3RAMPDOWN; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + switch (channel) { + case TCLED_FUN_CHANNEL1: + mask = mask; + value = value; + break; + case TCLED_FUN_CHANNEL2: + mask = mask * 2; + value = value * 2; + break; + case TCLED_FUN_CHANNEL3: + mask = mask * 4; + value = value * 4; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + if (!rampdown) { + value = 0; + } + + CHECK_ERROR(pmic_write_reg(LREG_1, value, mask)); + return PMIC_SUCCESS; +} + +/*! + * This function initiates Tri-color LED brightness Ramp Down function; Ramp + * time is fixed at 1 second. + * + * @param bank Tri-color LED bank + * @param channel Tri-color LED channel. + * @param rampdown Ramp-down configuration. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_get_fun_rampdown(t_funlight_bank bank, + t_funlight_channel channel, + bool * rampdown) +{ + unsigned int mask = 0; + unsigned int value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (bank) { + case TCLED_FUN_BANK1: + mask = LEDR1RAMPDOWN; + break; + case TCLED_FUN_BANK2: + mask = LEDR2RAMPDOWN; + break; + case TCLED_FUN_BANK3: + mask = LEDR3RAMPDOWN; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + switch (channel) { + case TCLED_FUN_CHANNEL1: + mask = mask; + break; + case TCLED_FUN_CHANNEL2: + mask = mask * 2; + break; + case TCLED_FUN_CHANNEL3: + mask = mask * 4; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(LREG_1, &value, mask)); + if (value) { + *rampdown = true; + } else { + *rampdown = false; + } + return PMIC_SUCCESS; +} + +/*! + * This function enables a Tri-color channel triode mode. + * + * @param bank Tri-color LED bank + * @param channel Tri-color LED channel. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_fun_triode_on(t_funlight_bank bank, + t_funlight_channel channel) +{ + unsigned int mask = 0; + unsigned int value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (bank) { + case TCLED_FUN_BANK1: + mask = MASK_BK1_FL; + value = ENABLE_BK1_FL; + break; + case TCLED_FUN_BANK2: + mask = MASK_BK2_FL; + value = ENABLE_BK2_FL; + break; + case TCLED_FUN_BANK3: + mask = MASK_BK3_FL; + value = ENABLE_BK2_FL; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(LREG_0, value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function disables a Tri-color LED channel triode mode. + * + * @param bank Tri-color LED bank + * @param channel Tri-color LED channel. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_fun_triode_off(t_funlight_bank bank, + t_funlight_channel channel) +{ + unsigned int mask = 0; + unsigned int value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (bank) { + case TCLED_FUN_BANK1: + mask = MASK_BK1_FL; + break; + case TCLED_FUN_BANK2: + mask = MASK_BK2_FL; + break; + case TCLED_FUN_BANK3: + mask = MASK_BK3_FL; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(LREG_0, value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function enables Tri-color LED edge slowing. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_enable_edge_slow(void) +{ + unsigned int mask = 0; + unsigned int value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + value = BITFVAL(BIT_SLEWLIMTC, 1); + mask = BITFMASK(BIT_SLEWLIMTC); + + CHECK_ERROR(pmic_write_reg(LREG_1, value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function disables Tri-color LED edge slowing. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_disable_edge_slow(void) +{ + unsigned int mask = 0; + unsigned int value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + value = BITFVAL(BIT_SLEWLIMTC, 0); + mask = BITFMASK(BIT_SLEWLIMTC); + + CHECK_ERROR(pmic_write_reg(LREG_1, value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function enables Tri-color LED half current mode. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_enable_half_current(void) +{ + unsigned int mask = 0; + unsigned int value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + value = BITFVAL(BIT_TC1HALF, 1); + mask = BITFMASK(BIT_TC1HALF); + + CHECK_ERROR(pmic_write_reg(LREG_1, value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function disables Tri-color LED half current mode. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_disable_half_current(void) +{ + unsigned int mask = 0; + unsigned int value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + value = BITFVAL(BIT_TC1HALF, 0); + mask = BITFMASK(BIT_TC1HALF); + + CHECK_ERROR(pmic_write_reg(LREG_1, value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function enables backlight or Tri-color LED audio modulation. + * + * @return This function returns PMIC_NOT_SUPPORTED. + */ +PMIC_STATUS pmic_tcled_enable_audio_modulation(t_led_channel channel, + t_aud_path path, + t_aud_gain gain, bool lpf_bypass) +{ + return PMIC_NOT_SUPPORTED; +} + +/*! + * This function disables backlight or Tri-color LED audio modulation. + * + * @return This function returns PMIC_NOT_SUPPORTED. + */ +PMIC_STATUS pmic_tcled_disable_audio_modulation(void) +{ + return PMIC_NOT_SUPPORTED; +} + +/*! + * This function enables the boost mode. + * Only on mc13783 2.0 or higher + * + * @param en_dis Enable or disable the boost mode + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_bklit_set_boost_mode(bool en_dis) +{ + + pmic_version_t mc13783_ver; + unsigned int mask; + unsigned int value; + mc13783_ver = pmic_get_version(); + if (mc13783_ver.revision >= 20) { + + if (suspend_flag == 1) { + return -EBUSY; + } + + value = BITFVAL(BIT_BOOSTEN, en_dis); + mask = BITFMASK(BIT_BOOSTEN); + CHECK_ERROR(pmic_write_reg(LREG_0, value, mask)); + return PMIC_SUCCESS; + } else { + return PMIC_NOT_SUPPORTED; + } +} + +/*! + * This function gets the boost mode. + * Only on mc13783 2.0 or higher + * + * @param en_dis Enable or disable the boost mode + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_bklit_get_boost_mode(bool * en_dis) +{ + pmic_version_t mc13783_ver; + unsigned int mask; + unsigned int value; + mc13783_ver = pmic_get_version(); + if (mc13783_ver.revision >= 20) { + + if (suspend_flag == 1) { + return -EBUSY; + } + mask = BITFMASK(BIT_BOOSTEN); + CHECK_ERROR(pmic_read_reg(LREG_0, &value, mask)); + *en_dis = BITFEXT(value, BIT_BOOSTEN); + return PMIC_SUCCESS; + } else { + return PMIC_NOT_SUPPORTED; + } +} + +/*! + * This function sets boost mode configuration + * Only on mc13783 2.0 or higher + * + * @param abms Define adaptive boost mode selection + * @param abr Define adaptive boost reference + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_bklit_config_boost_mode(unsigned int abms, unsigned int abr) +{ + unsigned int conf_boost = 0; + unsigned int mask; + unsigned int value; + pmic_version_t mc13783_ver; + + mc13783_ver = pmic_get_version(); + if (mc13783_ver.revision >= 20) { + if (suspend_flag == 1) { + return -EBUSY; + } + + if (abms > MAX_BOOST_ABMS) { + return PMIC_PARAMETER_ERROR; + } + + if (abr > MAX_BOOST_ABR) { + return PMIC_PARAMETER_ERROR; + } + + conf_boost = abms | (abr << 3); + + value = BITFVAL(BITS_BOOST, conf_boost); + mask = BITFMASK(BITS_BOOST); + CHECK_ERROR(pmic_write_reg(LREG_0, value, mask)); + + return PMIC_SUCCESS; + } else { + return PMIC_NOT_SUPPORTED; + } +} + +/*! + * This function gets boost mode configuration + * Only on mc13783 2.0 or higher + * + * @param abms Define adaptive boost mode selection + * @param abr Define adaptive boost reference + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_bklit_gets_boost_mode(unsigned int *abms, unsigned int *abr) +{ + unsigned int mask; + unsigned int value; + pmic_version_t mc13783_ver; + mc13783_ver = pmic_get_version(); + if (mc13783_ver.revision >= 20) { + if (suspend_flag == 1) { + return -EBUSY; + } + + mask = BITFMASK(BITS_BOOST_ABMS); + CHECK_ERROR(pmic_read_reg(LREG_0, &value, mask)); + *abms = BITFEXT(value, BITS_BOOST_ABMS); + + mask = BITFMASK(BITS_BOOST_ABR); + CHECK_ERROR(pmic_read_reg(LREG_0, &value, mask)); + *abr = BITFEXT(value, BITS_BOOST_ABR); + return PMIC_SUCCESS; + } else { + return PMIC_NOT_SUPPORTED; + } +} + +/*! + * This function implements IOCTL controls on a PMIC Light device. + * + + * @param inode pointer on the node + * @param file pointer on the file + * @param cmd the command + * @param arg the parameter + * @return This function returns 0 if successful. + */ +static int pmic_light_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + t_bklit_setting_param *bklit_setting = NULL; + t_tcled_enable_param *tcled_setting; + t_fun_param *fun_param; + t_tcled_ind_param *tcled_ind; + + if (_IOC_TYPE(cmd) != 'p') + return -ENOTTY; + + switch (cmd) { + case PMIC_BKLIT_TCLED_ENABLE: + pmic_bklit_tcled_master_enable(); + break; + + case PMIC_BKLIT_TCLED_DISABLE: + pmic_bklit_tcled_master_disable(); + break; + + case PMIC_BKLIT_ENABLE: + pmic_bklit_master_enable(); + break; + + case PMIC_BKLIT_DISABLE: + pmic_bklit_master_disable(); + break; + + case PMIC_SET_BKLIT: + if ((bklit_setting = kmalloc(sizeof(t_bklit_setting_param), + GFP_KERNEL)) == NULL) { + return -ENOMEM; + } + if (copy_from_user(bklit_setting, (t_bklit_setting_param *) arg, + sizeof(t_bklit_setting_param))) { + kfree(bklit_setting); + return -EFAULT; + } + + CHECK_ERROR_KFREE(pmic_bklit_set_mode(bklit_setting->channel, + bklit_setting->mode), + (kfree(bklit_setting))); + + CHECK_ERROR_KFREE(pmic_bklit_set_current(bklit_setting->channel, + bklit_setting-> + current_level), + (kfree(bklit_setting))); + CHECK_ERROR_KFREE(pmic_bklit_set_dutycycle + (bklit_setting->channel, + bklit_setting->duty_cycle), + (kfree(bklit_setting))); + CHECK_ERROR_KFREE(pmic_bklit_set_cycle_time + (bklit_setting->cycle_time), + (kfree(bklit_setting))); + CHECK_ERROR_KFREE(pmic_bklit_set_boost_mode + (bklit_setting->en_dis), + (kfree(bklit_setting))); + CHECK_ERROR_KFREE(pmic_bklit_config_boost_mode + (bklit_setting->abms, bklit_setting->abr), + (kfree(bklit_setting))); + if (bklit_setting->edge_slow != false) { + CHECK_ERROR_KFREE(pmic_bklit_enable_edge_slow(), + (kfree(bklit_setting))); + } else { + CHECK_ERROR_KFREE(pmic_bklit_disable_edge_slow(), + (kfree(bklit_setting))); + } + + kfree(bklit_setting); + break; + + case PMIC_GET_BKLIT: + if ((bklit_setting = kmalloc(sizeof(t_bklit_setting_param), + GFP_KERNEL)) == NULL) { + return -ENOMEM; + } + + if (copy_from_user(bklit_setting, (t_bklit_setting_param *) arg, + sizeof(t_bklit_setting_param))) { + kfree(bklit_setting); + return -EFAULT; + } + + CHECK_ERROR_KFREE(pmic_bklit_get_current(bklit_setting->channel, + &bklit_setting-> + current_level), + (kfree(bklit_setting))); + CHECK_ERROR_KFREE(pmic_bklit_get_cycle_time + (&bklit_setting->cycle_time), + (kfree(bklit_setting))); + CHECK_ERROR_KFREE(pmic_bklit_get_dutycycle + (bklit_setting->channel, + &bklit_setting->duty_cycle), + (kfree(bklit_setting))); + bklit_setting->strobe = BACKLIGHT_STROBE_NONE; + CHECK_ERROR_KFREE(pmic_bklit_get_mode(bklit_setting->channel, + &bklit_setting->mode), + (kfree(bklit_setting))); + CHECK_ERROR_KFREE(pmic_bklit_get_edge_slow + (&bklit_setting->edge_slow), + (kfree(bklit_setting))); + CHECK_ERROR_KFREE(pmic_bklit_get_boost_mode + (&bklit_setting->en_dis), + (kfree(bklit_setting))); + CHECK_ERROR_KFREE(pmic_bklit_gets_boost_mode + (&bklit_setting->abms, &bklit_setting->abr), + (kfree(bklit_setting))); + + if (copy_to_user((t_bklit_setting_param *) arg, bklit_setting, + sizeof(t_bklit_setting_param))) { + kfree(bklit_setting); + return -EFAULT; + } + kfree(bklit_setting); + break; + + case PMIC_RAMPUP_BKLIT: + CHECK_ERROR(pmic_bklit_rampup((t_bklit_channel) arg)); + break; + + case PMIC_RAMPDOWN_BKLIT: + CHECK_ERROR(pmic_bklit_rampdown((t_bklit_channel) arg)); + break; + + case PMIC_OFF_RAMPUP_BKLIT: + CHECK_ERROR(pmic_bklit_off_rampup((t_bklit_channel) arg)); + break; + + case PMIC_OFF_RAMPDOWN_BKLIT: + CHECK_ERROR(pmic_bklit_off_rampdown((t_bklit_channel) arg)); + break; + + case PMIC_TCLED_ENABLE: + if ((tcled_setting = kmalloc(sizeof(t_tcled_enable_param), + GFP_KERNEL)) + == NULL) { + return -ENOMEM; + } + + if (copy_from_user(tcled_setting, (t_tcled_enable_param *) arg, + sizeof(t_tcled_enable_param))) { + kfree(tcled_setting); + return -EFAULT; + } + CHECK_ERROR_KFREE(pmic_tcled_enable(tcled_setting->mode, + tcled_setting->bank), + (kfree(bklit_setting))); + break; + + case PMIC_TCLED_DISABLE: + CHECK_ERROR(pmic_tcled_disable((t_funlight_bank) arg)); + break; + + case PMIC_TCLED_PATTERN: + if ((fun_param = kmalloc(sizeof(t_fun_param), + GFP_KERNEL)) == NULL) { + return -ENOMEM; + } + if (copy_from_user(fun_param, + (t_fun_param *) arg, sizeof(t_fun_param))) { + kfree(fun_param); + return -EFAULT; + } + + switch (fun_param->pattern) { + case BLENDED_RAMPS_SLOW: + CHECK_ERROR_KFREE(pmic_tcled_fun_blendedramps + (fun_param->bank, TC_SLOW), + (kfree(fun_param))); + break; + + case BLENDED_RAMPS_FAST: + CHECK_ERROR_KFREE(pmic_tcled_fun_blendedramps + (fun_param->bank, TC_FAST), + (kfree(fun_param))); + break; + + case SAW_RAMPS_SLOW: + CHECK_ERROR_KFREE(pmic_tcled_fun_sawramps + (fun_param->bank, TC_SLOW), + (kfree(fun_param))); + break; + + case SAW_RAMPS_FAST: + CHECK_ERROR_KFREE(pmic_tcled_fun_sawramps + (fun_param->bank, TC_FAST), + (kfree(fun_param))); + break; + + case BLENDED_BOWTIE_SLOW: + CHECK_ERROR_KFREE(pmic_tcled_fun_blendedbowtie + (fun_param->bank, TC_SLOW), + (kfree(fun_param))); + break; + + case BLENDED_BOWTIE_FAST: + CHECK_ERROR_KFREE(pmic_tcled_fun_blendedbowtie + (fun_param->bank, TC_FAST), + (kfree(fun_param))); + break; + + case STROBE_SLOW: + CHECK_ERROR_KFREE(pmic_tcled_fun_strobe + (fun_param->bank, fun_param->channel, + TC_STROBE_SLOW), (kfree(fun_param))); + break; + + case STROBE_FAST: + CHECK_ERROR_KFREE(pmic_tcled_fun_strobe + (fun_param->bank, + fun_param->channel, TC_STROBE_SLOW), + (kfree(fun_param))); + break; + + case CHASING_LIGHT_RGB_SLOW: + CHECK_ERROR_KFREE(pmic_tcled_fun_chasinglightspattern + (fun_param->bank, PMIC_RGB, TC_SLOW), + (kfree(fun_param))); + break; + + case CHASING_LIGHT_RGB_FAST: + CHECK_ERROR_KFREE(pmic_tcled_fun_chasinglightspattern + (fun_param->bank, PMIC_RGB, TC_FAST), + (kfree(fun_param))); + break; + + case CHASING_LIGHT_BGR_SLOW: + CHECK_ERROR_KFREE(pmic_tcled_fun_chasinglightspattern + (fun_param->bank, BGR, TC_SLOW), + (kfree(fun_param))); + break; + + case CHASING_LIGHT_BGR_FAST: + CHECK_ERROR_KFREE(pmic_tcled_fun_chasinglightspattern + (fun_param->bank, BGR, TC_FAST), + (kfree(fun_param))); + break; + } + + kfree(fun_param); + break; + + case PMIC_SET_TCLED: + if ((tcled_ind = kmalloc(sizeof(t_tcled_ind_param), GFP_KERNEL)) + == NULL) { + return -ENOMEM; + } + + if (copy_from_user(tcled_ind, (t_tcled_ind_param *) arg, + sizeof(t_tcled_ind_param))) { + kfree(tcled_ind); + return -EFAULT; + } + CHECK_ERROR_KFREE(pmic_tcled_ind_set_current(tcled_ind->channel, + tcled_ind->level, + tcled_ind->bank), + (kfree(tcled_ind))); + CHECK_ERROR_KFREE(pmic_tcled_ind_set_blink_pattern + (tcled_ind->channel, tcled_ind->pattern, + tcled_ind->skip, tcled_ind->bank), + (kfree(tcled_ind))); + CHECK_ERROR_KFREE(pmic_tcled_fun_rampup + (tcled_ind->bank, tcled_ind->channel, + tcled_ind->rampup), (kfree(tcled_ind))); + CHECK_ERROR_KFREE(pmic_tcled_fun_rampdown + (tcled_ind->bank, tcled_ind->channel, + tcled_ind->rampdown), (kfree(tcled_ind))); + if (tcled_ind->half_current) { + CHECK_ERROR_KFREE(pmic_tcled_enable_half_current(), + (kfree(tcled_ind))); + } else { + CHECK_ERROR_KFREE(pmic_tcled_disable_half_current(), + (kfree(tcled_ind))); + } + + kfree(tcled_ind); + break; + + case PMIC_GET_TCLED: + if ((tcled_ind = kmalloc(sizeof(t_tcled_ind_param), GFP_KERNEL)) + == NULL) { + return -ENOMEM; + } + if (copy_from_user(tcled_ind, (t_tcled_ind_param *) arg, + sizeof(t_tcled_ind_param))) { + kfree(tcled_ind); + return -EFAULT; + } + CHECK_ERROR_KFREE(pmic_tcled_ind_get_current(tcled_ind->channel, + &tcled_ind->level, + tcled_ind->bank), + (kfree(tcled_ind))); + CHECK_ERROR_KFREE(pmic_tcled_ind_get_blink_pattern + (tcled_ind->channel, &tcled_ind->pattern, + &tcled_ind->skip, tcled_ind->bank), + (kfree(tcled_ind))); + CHECK_ERROR_KFREE(pmic_tcled_get_fun_rampup + (tcled_ind->bank, tcled_ind->channel, + &tcled_ind->rampup), (kfree(tcled_ind))); + CHECK_ERROR_KFREE(pmic_tcled_get_fun_rampdown + (tcled_ind->bank, tcled_ind->channel, + &tcled_ind->rampdown), (kfree(tcled_ind))); + if (copy_to_user + ((t_tcled_ind_param *) arg, tcled_ind, + sizeof(t_tcled_ind_param))) { + return -EFAULT; + } + kfree(tcled_ind); + + break; + + default: + return -EINVAL; + } + return 0; +} + +/*! + * This function initialize Light registers of mc13783 with 0. + * + * @return This function returns 0 if successful. + */ +int pmic_light_init_reg(void) +{ + CHECK_ERROR(pmic_write_reg(LREG_0, 0, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(LREG_1, 0, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(LREG_2, 0, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(LREG_3, 0, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(LREG_4, 0, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(LREG_5, 0, PMIC_ALL_BITS)); + return 0; +} + +/*! + * This function implements the open method on a mc13783 light device. + * + * @param inode pointer on the node + * @param file pointer on the file + * @return This function returns 0. + */ +static int pmic_light_open(struct inode *inode, struct file *file) +{ + while (suspend_flag == 1) { + swait++; + /* Block if the device is suspended */ + if (wait_event_interruptible(suspendq, (suspend_flag == 0))) { + return -ERESTARTSYS; + } + } + return 0; +} + +/*! + * This function implements the release method on a mc13783 light device. + * + * @param inode pointer on the node + * @param file pointer on the file + * @return This function returns 0. + */ +static int pmic_light_release(struct inode *inode, struct file *file) +{ + while (suspend_flag == 1) { + swait++; + /* Block if the device is suspended */ + if (wait_event_interruptible(suspendq, (suspend_flag == 0))) { + return -ERESTARTSYS; + } + } + return 0; +} + +static struct file_operations pmic_light_fops = { + .owner = THIS_MODULE, + .ioctl = pmic_light_ioctl, + .open = pmic_light_open, + .release = pmic_light_release, +}; + +static int pmic_light_remove(struct platform_device *pdev) +{ + class_device_destroy(pmic_light_class, MKDEV(pmic_light_major, 0)); + class_destroy(pmic_light_class); + unregister_chrdev(pmic_light_major, "pmic_light"); + return 0; +} + +static int pmic_light_probe(struct platform_device *pdev) +{ + int ret = 0; + struct class_device *temp_class; + + while (suspend_flag == 1) { + swait++; + /* Block if the device is suspended */ + if (wait_event_interruptible(suspendq, (suspend_flag == 0))) { + return -ERESTARTSYS; + } + } + pmic_light_major = register_chrdev(0, "pmic_light", &pmic_light_fops); + + if (pmic_light_major < 0) { + printk(KERN_ERR "Unable to get a major for pmic_light\n"); + return pmic_light_major; + } + init_waitqueue_head(&suspendq); + + pmic_light_class = class_create(THIS_MODULE, "pmic_light"); + if (IS_ERR(pmic_light_class)) { + printk(KERN_ERR "Error creating pmic_light class.\n"); + ret = PTR_ERR(pmic_light_class); + goto err_out1; + } + + temp_class = class_device_create(pmic_light_class, NULL, + MKDEV(pmic_light_major, 0), + NULL, "pmic_light"); + if (IS_ERR(temp_class)) { + printk(KERN_ERR "Error creating pmic_light class device.\n"); + ret = PTR_ERR(temp_class); + goto err_out2; + } + + ret = pmic_light_init_reg(); + if (ret != PMIC_SUCCESS) { + goto err_out3; + } + + printk(KERN_INFO "PMIC Light successfully loaded\n"); + return ret; + + err_out3: + class_device_destroy(pmic_light_class, MKDEV(pmic_light_major, 0)); + err_out2: + class_destroy(pmic_light_class); + err_out1: + unregister_chrdev(pmic_light_major, "pmic_light"); + return ret; +} + +static struct platform_driver pmic_light_driver_ldm = { + .driver = { + .name = "pmic_light", + }, + .suspend = pmic_light_suspend, + .resume = pmic_light_resume, + .probe = pmic_light_probe, + .remove = pmic_light_remove, +}; + +/* + * Initialization and Exit + */ + +static int __init pmic_light_init(void) +{ + pr_debug("PMIC Light driver loading...\n"); + return platform_driver_register(&pmic_light_driver_ldm); +} +static void __exit pmic_light_exit(void) +{ + platform_driver_unregister(&pmic_light_driver_ldm); + pr_debug("PMIC Light driver successfully unloaded\n"); +} + +/* + * Module entry points + */ + +subsys_initcall(pmic_light_init); +module_exit(pmic_light_exit); + +MODULE_DESCRIPTION("PMIC_LIGHT"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/pmic/mc13783/pmic_light_defs.h b/drivers/mxc/pmic/mc13783/pmic_light_defs.h new file mode 100644 index 000000000000..80082363d9ed --- /dev/null +++ b/drivers/mxc/pmic/mc13783/pmic_light_defs.h @@ -0,0 +1,144 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mc13783/pmic_light_defs.h + * @brief This is the internal header PMIC(mc13783) Light and Backlight driver. + * + * @ingroup PMIC_LIGHT + */ + +#ifndef __MC13783_LIGHT_DEFS_H__ +#define __MC13783_LIGHT_DEFS_H__ + +#define LREG_0 REG_LED_CONTROL_0 +#define LREG_1 REG_LED_CONTROL_1 +#define LREG_2 REG_LED_CONTROL_2 +#define LREG_3 REG_LED_CONTROL_3 +#define LREG_4 REG_LED_CONTROL_4 +#define LREG_5 REG_LED_CONTROL_5 + +/* REG_LED_CONTROL_0 */ + +#define BIT_LEDEN_LSH 0 +#define BIT_LEDEN_WID 1 +#define MASK_TRIODE_MAIN_BL 0x080 +#define INDEX_AUXILIARY 1 +#define INDEX_KEYPAD 2 +#define BITS_FUN_LIGHT_LSH 17 +#define BITS_FUN_LIGHT_WID 4 +#define MASK_FUN_LIGHT 0x1E0000 +#define MASK_BK1_FL 0x200000 +#define ENABLE_BK1_FL 0x200000 +#define MASK_BK2_FL 0x400000 +#define ENABLE_BK2_FL 0x400000 +#define MASK_BK3_FL 0x800000 +#define ENABLE_BK3_FL 0x800000 +#define BIT_UP_MAIN_BL_LSH 1 +#define BIT_UP_MAIN_BL_WID 1 +#define BIT_UP_AUX_BL_LSH 2 +#define BIT_UP_AUX_BL_WID 1 +#define BIT_UP_KEY_BL_LSH 3 +#define BIT_UP_KEY_BL_WID 1 +#define BIT_DOWN_MAIN_BL_LSH 4 +#define BIT_DOWN_MAIN_BL_WID 1 +#define BIT_DOWN_AUX_BL_LSH 5 +#define BIT_DOWN_AUX_BL_WID 1 +#define BIT_DOWN_KEY_BL_LSH 6 +#define BIT_DOWN_KEY_BL_WID 1 +#define BIT_TRIODE_MAIN_BL_LSH 7 +#define BIT_TRIODE_MAIN_BL_WID 1 +#define BIT_TRIODE_AUX_BL_LSH 8 +#define BIT_TRIODE_AUX_BL_WID 1 +#define BIT_TRIODE_KEY_BL_LSH 9 +#define BIT_TRIODE_KEY_BL_WID 1 + +#define BIT_BOOSTEN_LSH 10 +#define BIT_BOOSTEN_WID 1 +#define BITS_BOOST_LSH 11 +#define BITS_BOOST_WID 5 +#define BITS_BOOST_ABMS_LSH 11 +#define BITS_BOOST_ABMS_WID 3 +#define BITS_BOOST_ABR_LSH 14 +#define BITS_BOOST_ABR_WID 2 + +#define MAX_BOOST_ABMS 7 +#define MAX_BOOST_ABR 3 + +/* REG_LED_CONTROL_1 */ + +#define BIT_SLEWLIMTC_LSH 23 +#define BIT_SLEWLIMTC_WID 1 +#define BIT_TC1HALF_LSH 18 +#define BIT_TC1HALF_WID 1 +#define LEDR1RAMPUP 0x000001 +#define LEDR2RAMPUP 0x000040 +#define LEDR3RAMPUP 0x001000 +#define LEDR1RAMPDOWN 0x000008 +#define LEDR2RAMPDOWN 0x000200 +#define LEDR3RAMPDOWN 0x008000 + +/* REG_LED_CONTROL_2 */ + +#define BIT_SLEWLIMBL_LSH 23 +#define BIT_SLEWLIMBL_WID 1 +#define BIT_DUTY_CYCLE 9 +#define MASK_DUTY_CYCLE 0x001E00 +#define INDEX_AUX 4 +#define INDEX_KYD 8 +#define BIT_CL_MAIN_LSH 0 +#define BIT_CL_MAIN_WID 3 +#define BIT_CL_AUX_LSH 3 +#define BIT_CL_AUX_WID 3 +#define BIT_CL_KEY_LSH 6 +#define BIT_CL_KEY_WID 3 + +/* REG_LED_CONTROL_3 4 5 */ +#define BITS_CL_RED_LSH 0 +#define BITS_CL_RED_WID 2 +#define BITS_CL_GREEN_LSH 2 +#define BITS_CL_GREEN_WID 2 +#define BITS_CL_BLUE_LSH 4 +#define BITS_CL_BLUE_WID 2 +#define BITS_DC_RED_LSH 6 +#define BITS_DC_RED_WID 5 +#define BITS_DC_GREEN_LSH 11 +#define BITS_DC_GREEN_WID 5 +#define BITS_DC_BLUE_LSH 16 +#define BITS_DC_BLUE_WID 5 +#define BIT_PERIOD_LSH 21 +#define BIT_PERIOD_WID 2 + +#define DUTY_CYCLE_MAX 31 + +/* Fun light pattern */ +#define BLENDED_RAMPS_SLOW 0 +#define BLENDED_RAMPS_FAST 1 +#define SAW_RAMPS_SLOW 2 +#define SAW_RAMPS_FAST 3 +#define BLENDED_INVERSE_RAMPS_SLOW 4 +#define BLENDED_INVERSE_RAMPS_FAST 5 +#define CHASING_LIGHTS_RGB_SLOW 6 +#define CHASING_LIGHTS_RGB_FAST 7 +#define CHASING_LIGHTS_BGR_SLOW 8 +#define CHASING_LIGHTS_BGR_FAST 9 +#define FUN_LIGHTS_OFF 15 + +/*! + * This function initialize Light registers of mc13783 with 0. + * + * @return This function returns 0 if successful. + */ +int pmic_light_init_reg(void); + +#endif /* __MC13783_LIGHT_DEFS_H__ */ diff --git a/drivers/mxc/pmic/mc13783/pmic_power.c b/drivers/mxc/pmic/mc13783/pmic_power.c new file mode 100644 index 000000000000..11f96bb7addb --- /dev/null +++ b/drivers/mxc/pmic/mc13783/pmic_power.c @@ -0,0 +1,3079 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mc13783/pmic_power.c + * @brief This is the main file of PMIC(mc13783) Power driver. + * + * @ingroup PMIC_POWER + */ + +/* + * Includes + */ + +#include <linux/platform_device.h> +#include <asm/ioctl.h> +#include <asm/arch/pmic_power.h> + +#include "../core/pmic_config.h" +#include "pmic_power_defs.h" + +static bool VBKUP1_EN = false; +static bool VBKUP2_EN = false; + +/* + * Power Pmic API + */ + +/* EXPORTED FUNCTIONS */ +EXPORT_SYMBOL(pmic_power_off); +EXPORT_SYMBOL(pmic_power_set_pc_config); +EXPORT_SYMBOL(pmic_power_get_pc_config); +EXPORT_SYMBOL(pmic_power_regulator_on); +EXPORT_SYMBOL(pmic_power_regulator_off); +EXPORT_SYMBOL(pmic_power_regulator_set_voltage); +EXPORT_SYMBOL(pmic_power_regulator_get_voltage); +EXPORT_SYMBOL(pmic_power_regulator_set_config); +EXPORT_SYMBOL(pmic_power_regulator_get_config); +EXPORT_SYMBOL(pmic_power_vbkup2_auto_en); +EXPORT_SYMBOL(pmic_power_get_vbkup2_auto_state); +EXPORT_SYMBOL(pmic_power_bat_det_en); +EXPORT_SYMBOL(pmic_power_get_bat_det_state); +EXPORT_SYMBOL(pmic_power_vib_pin_en); +EXPORT_SYMBOL(pmic_power_gets_vib_pin_state); +EXPORT_SYMBOL(pmic_power_get_power_mode_sense); +EXPORT_SYMBOL(pmic_power_set_regen_assig); +EXPORT_SYMBOL(pmic_power_get_regen_assig); +EXPORT_SYMBOL(pmic_power_set_regen_inv); +EXPORT_SYMBOL(pmic_power_get_regen_inv); +EXPORT_SYMBOL(pmic_power_esim_v_en); +EXPORT_SYMBOL(pmic_power_gets_esim_v_state); +EXPORT_SYMBOL(pmic_power_set_auto_reset_en); +EXPORT_SYMBOL(pmic_power_get_auto_reset_en); +EXPORT_SYMBOL(pmic_power_set_conf_button); +EXPORT_SYMBOL(pmic_power_get_conf_button); +EXPORT_SYMBOL(pmic_power_event_sub); +EXPORT_SYMBOL(pmic_power_event_unsub); + +/*! + * This function is called to put the power in a low power state. + * Switching off the platform cannot be decided by + * the power module. It has to be handled by the + * client application. + * + * @param pdev the device structure used to give information on which power + * device (0 through 3 channels) to suspend + * @param state the power state the device is entering + * + * @return The function always returns 0. + */ +static int pmic_power_suspend(struct platform_device *pdev, pm_message_t state) +{ + return 0; +}; + +/*! + * This function is called to resume the power from a low power state. + * + * @param pdev the device structure used to give information on which power + * device (0 through 3 channels) to suspend + * + * @return The function always returns 0. + */ +static int pmic_power_resume(struct platform_device *pdev) +{ + return 0; +}; + +/*! + * This function sets user power off in power control register and thus powers + * off the phone. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_off(void) +{ + unsigned int mask, value; + + mask = BITFMASK(MC13783_PWRCTRL_USER_OFF_SPI); + value = BITFVAL(MC13783_PWRCTRL_USER_OFF_SPI, + MC13783_PWRCTRL_USER_OFF_SPI_ENABLE); + + CHECK_ERROR(pmic_write_reg(REG_POWER_CONTROL_0, value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function sets the power control configuration. + * + * @param pc_config power control configuration. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_set_pc_config(t_pc_config * pc_config) +{ + unsigned int pwrctrl_val_reg0 = 0; + unsigned int pwrctrl_val_reg1 = 0; + unsigned int pwrctrl_mask_reg0 = 0; + unsigned int pwrctrl_mask_reg1 = 0; + + if (pc_config == NULL) { + return PMIC_PARAMETER_ERROR; + } + + if (pc_config->pc_enable != false) { + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_PCEN, + MC13783_PWRCTRL_PCEN_ENABLE); + pwrctrl_val_reg1 |= BITFVAL(MC13783_PWRCTRL_PCT, + pc_config->pc_timer); + } else { + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_PCEN, + MC13783_PWRCTRL_PCEN_DISABLE); + } + pwrctrl_mask_reg0 |= BITFMASK(MC13783_PWRCTRL_PCEN); + pwrctrl_mask_reg1 |= BITFMASK(MC13783_PWRCTRL_PCT); + + if (pc_config->pc_count_enable != false) { + + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_PC_COUNT_EN, + MC13783_PWRCTRL_PC_COUNT_EN_ENABLE); + pwrctrl_val_reg1 |= BITFVAL(MC13783_PWRCTRL_PC_COUNT, + pc_config->pc_count); + pwrctrl_val_reg1 |= BITFVAL(MC13783_PWRCTRL_PC_MAX_CNT, + pc_config->pc_max_count); + } else { + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_PC_COUNT_EN, + MC13783_PWRCTRL_PC_COUNT_EN_DISABLE); + } + pwrctrl_mask_reg0 |= BITFMASK(MC13783_PWRCTRL_PC_COUNT_EN); + pwrctrl_mask_reg1 |= BITFMASK(MC13783_PWRCTRL_PC_MAX_CNT) | + BITFMASK(MC13783_PWRCTRL_PC_COUNT); + + if (pc_config->warm_enable != false) { + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_WARM_EN, + MC13783_PWRCTRL_WARM_EN_ENABLE); + } else { + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_WARM_EN, + MC13783_PWRCTRL_WARM_EN_DISABLE); + } + pwrctrl_mask_reg0 |= BITFMASK(MC13783_PWRCTRL_WARM_EN); + + if (pc_config->user_off_pc != false) { + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_USER_OFF_PC, + MC13783_PWRCTRL_USER_OFF_PC_ENABLE); + } else { + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_WARM_EN, + MC13783_PWRCTRL_USER_OFF_PC_DISABLE); + } + pwrctrl_mask_reg0 |= BITFMASK(MC13783_PWRCTRL_USER_OFF_PC); + + if (pc_config->clk_32k_user_off != false) { + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_32OUT_USER_OFF, + MC13783_PWRCTRL_32OUT_USER_OFF_ENABLE); + } else { + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_32OUT_USER_OFF, + MC13783_PWRCTRL_32OUT_USER_OFF_DISABLE); + } + pwrctrl_mask_reg0 |= BITFMASK(MC13783_PWRCTRL_32OUT_USER_OFF); + + if (pc_config->clk_32k_enable != false) { + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_32OUT_EN, + MC13783_PWRCTRL_32OUT_EN_ENABLE); + } else { + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_32OUT_EN, + MC13783_PWRCTRL_32OUT_EN_DISABLE); + } + pwrctrl_mask_reg0 |= BITFMASK(MC13783_PWRCTRL_32OUT_EN); + + if (pc_config->en_vbkup1 != false) { + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_VBKUP1_EN, + MC13783_PWRCTRL_VBKUP_ENABLE); + VBKUP1_EN = true; + } else { + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_VBKUP1_EN, + MC13783_PWRCTRL_VBKUP_DISABLE); + VBKUP1_EN = false; + } + pwrctrl_mask_reg0 |= BITFMASK(MC13783_PWRCTRL_VBKUP1_EN); + + if (pc_config->en_vbkup2 != false) { + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_VBKUP2_EN, + MC13783_PWRCTRL_VBKUP_ENABLE); + VBKUP2_EN = true; + } else { + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_VBKUP2_EN, + MC13783_PWRCTRL_VBKUP_DISABLE); + VBKUP2_EN = false; + } + pwrctrl_mask_reg0 |= BITFMASK(MC13783_PWRCTRL_VBKUP2_EN); + + if (pc_config->auto_en_vbkup1 != false) { + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_VBKUP1_AUTO_EN, + MC13783_PWRCTRL_VBKUP_ENABLE); + } else { + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_VBKUP1_AUTO_EN, + MC13783_PWRCTRL_VBKUP_DISABLE); + } + pwrctrl_mask_reg0 |= BITFMASK(MC13783_PWRCTRL_VBKUP1_AUTO_EN); + + if (pc_config->auto_en_vbkup2 != false) { + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_VBKUP2_AUTO_EN, + MC13783_PWRCTRL_VBKUP_ENABLE); + } else { + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_VBKUP2_AUTO_EN, + MC13783_PWRCTRL_VBKUP_DISABLE); + } + pwrctrl_mask_reg0 |= BITFMASK(MC13783_PWRCTRL_VBKUP2_AUTO_EN); + + if (VBKUP1_EN != false) { + if (pc_config->vhold_voltage > 3 + || pc_config->vhold_voltage < 0) { + return PMIC_PARAMETER_ERROR; + } else { + + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_VBKUP1, + pc_config->vhold_voltage); + } + } + if (VBKUP2_EN != false) { + if (pc_config->vhold_voltage > 3 + || pc_config->vhold_voltage < 0) { + return PMIC_PARAMETER_ERROR; + } else { + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_VBKUP2, + pc_config->vhold_voltage2); + } + } + pwrctrl_mask_reg0 |= BITFMASK(MC13783_PWRCTRL_VBKUP1) | + BITFMASK(MC13783_PWRCTRL_VBKUP2); + + if (pc_config->mem_allon != false) { + pwrctrl_val_reg1 |= BITFVAL(MC13783_PWRCTRL_MEM_ALLON, + MC13783_PWRCTRL_MEM_ALLON_ENABLE); + pwrctrl_val_reg1 |= BITFVAL(MC13783_PWRCTRL_MEM_TMR, + pc_config->mem_timer); + } else { + pwrctrl_val_reg1 |= BITFVAL(MC13783_PWRCTRL_MEM_ALLON, + MC13783_PWRCTRL_MEM_ALLON_DISABLE); + } + pwrctrl_mask_reg1 |= BITFMASK(MC13783_PWRCTRL_MEM_ALLON) | + BITFMASK(MC13783_PWRCTRL_MEM_TMR); + + CHECK_ERROR(pmic_write_reg(REG_POWER_CONTROL_0, + pwrctrl_val_reg0, pwrctrl_mask_reg0)); + CHECK_ERROR(pmic_write_reg(REG_POWER_CONTROL_1, + pwrctrl_val_reg1, pwrctrl_mask_reg1)); + + return PMIC_SUCCESS; +} + +/*! + * This function retrives the power control configuration. + * + * @param pc_config pointer to power control configuration. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_get_pc_config(t_pc_config * pc_config) +{ + unsigned int pwrctrl_val_reg0 = 0; + unsigned int pwrctrl_val_reg1 = 0; + + if (pc_config == NULL) { + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(REG_POWER_CONTROL_0, + &pwrctrl_val_reg0, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_read_reg(REG_POWER_CONTROL_1, + &pwrctrl_val_reg1, PMIC_ALL_BITS)); + + if (BITFEXT(pwrctrl_val_reg0, MC13783_PWRCTRL_PCEN) + == MC13783_PWRCTRL_PCEN_ENABLE) { + pc_config->pc_enable = true; + pc_config->pc_timer = BITFEXT(pwrctrl_val_reg1, + MC13783_PWRCTRL_PCT); + + } else { + pc_config->pc_enable = false; + pc_config->pc_timer = 0; + } + + if (BITFEXT(pwrctrl_val_reg0, MC13783_PWRCTRL_PC_COUNT_EN) + == MC13783_PWRCTRL_PCEN_ENABLE) { + pc_config->pc_count_enable = true; + pc_config->pc_count = BITFEXT(pwrctrl_val_reg1, + MC13783_PWRCTRL_PC_COUNT); + pc_config->pc_max_count = BITFEXT(pwrctrl_val_reg1, + MC13783_PWRCTRL_PC_MAX_CNT); + } else { + pc_config->pc_count_enable = false; + pc_config->pc_count = 0; + pc_config->pc_max_count = 0; + } + + if (BITFEXT(pwrctrl_val_reg0, MC13783_PWRCTRL_WARM_EN) + == MC13783_PWRCTRL_WARM_EN_ENABLE) { + pc_config->warm_enable = true; + } else { + pc_config->warm_enable = false; + } + + if (BITFEXT(pwrctrl_val_reg0, MC13783_PWRCTRL_USER_OFF_PC) + == MC13783_PWRCTRL_USER_OFF_PC_ENABLE) { + pc_config->user_off_pc = true; + } else { + pc_config->user_off_pc = false; + } + + if (BITFEXT(pwrctrl_val_reg0, MC13783_PWRCTRL_32OUT_USER_OFF) + == MC13783_PWRCTRL_32OUT_USER_OFF_ENABLE) { + pc_config->clk_32k_user_off = true; + } else { + pc_config->clk_32k_user_off = false; + } + + if (BITFEXT(pwrctrl_val_reg0, MC13783_PWRCTRL_32OUT_EN) + == MC13783_PWRCTRL_32OUT_EN_ENABLE) { + pc_config->clk_32k_enable = true; + } else { + pc_config->clk_32k_enable = false; + } + + if (BITFEXT(pwrctrl_val_reg0, MC13783_PWRCTRL_VBKUP1_AUTO_EN) + == MC13783_PWRCTRL_VBKUP_ENABLE) { + pc_config->auto_en_vbkup1 = true; + } else { + pc_config->auto_en_vbkup1 = false; + } + + if (BITFEXT(pwrctrl_val_reg0, MC13783_PWRCTRL_VBKUP2_AUTO_EN) + == MC13783_PWRCTRL_VBKUP_ENABLE) { + pc_config->auto_en_vbkup2 = true; + } else { + pc_config->auto_en_vbkup2 = false; + } + + if (BITFEXT(pwrctrl_val_reg0, MC13783_PWRCTRL_VBKUP1_EN) + == MC13783_PWRCTRL_VBKUP_ENABLE) { + pc_config->en_vbkup1 = true; + pc_config->vhold_voltage = BITFEXT(pwrctrl_val_reg0, + MC13783_PWRCTRL_VBKUP1); + } else { + pc_config->en_vbkup1 = false; + pc_config->vhold_voltage = 0; + } + + if (BITFEXT(pwrctrl_val_reg0, MC13783_PWRCTRL_VBKUP2_EN) + == MC13783_PWRCTRL_VBKUP_ENABLE) { + pc_config->en_vbkup2 = true; + pc_config->vhold_voltage2 = BITFEXT(pwrctrl_val_reg0, + MC13783_PWRCTRL_VBKUP2); + } else { + pc_config->en_vbkup2 = false; + pc_config->vhold_voltage2 = 0; + } + + if (BITFEXT(pwrctrl_val_reg1, MC13783_PWRCTRL_MEM_ALLON) == + MC13783_PWRCTRL_MEM_ALLON_ENABLE) { + pc_config->mem_allon = true; + pc_config->mem_timer = BITFEXT(pwrctrl_val_reg1, + MC13783_PWRCTRL_MEM_TMR); + } else { + pc_config->mem_allon = false; + pc_config->mem_timer = 0; + } + + return PMIC_SUCCESS; +} + +/*! + * This function turns on a regulator. + * + * @param regulator The regulator to be truned on. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_regulator_on(t_pmic_regulator regulator) +{ + unsigned int reg_val = 0, reg_mask = 0; + unsigned int reg; + + switch (regulator) { + case SW_PLL: + reg_val = BITFVAL(MC13783_SWCTRL_PLL_EN, + MC13783_SWCTRL_PLL_EN_ENABLE); + reg_mask = BITFMASK(MC13783_SWCTRL_PLL_EN); + reg = REG_SWITCHERS_4; + break; + case SW_SW3: + reg_val = BITFVAL(MC13783_SWCTRL_SW3_EN, + MC13783_SWCTRL_SW3_EN_ENABLE); + reg_mask = BITFMASK(MC13783_SWCTRL_SW3_EN); + reg = REG_SWITCHERS_5; + break; + case REGU_VAUDIO: + reg_val = BITFVAL(MC13783_REGCTRL_VAUDIO_EN, + MC13783_REGCTRL_VAUDIO_EN_ENABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VAUDIO_EN); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VIOHI: + reg_val = BITFVAL(MC13783_REGCTRL_VIOHI_EN, + MC13783_REGCTRL_VIOHI_EN_ENABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VIOHI_EN); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VIOLO: + reg_val = BITFVAL(MC13783_REGCTRL_VIOLO_EN, + MC13783_REGCTRL_VIOLO_EN_ENABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VIOLO_EN); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VDIG: + reg_val = BITFVAL(MC13783_REGCTRL_VDIG_EN, + MC13783_REGCTRL_VDIG_EN_ENABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VDIG_EN); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VGEN: + reg_val = BITFVAL(MC13783_REGCTRL_VGEN_EN, + MC13783_REGCTRL_VGEN_EN_ENABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VGEN_EN); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VRFDIG: + reg_val = BITFVAL(MC13783_REGCTRL_VRFDIG_EN, + MC13783_REGCTRL_VRFDIG_EN_ENABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VRFDIG_EN); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VRFREF: + reg_val = BITFVAL(MC13783_REGCTRL_VRFREF_EN, + MC13783_REGCTRL_VRFREF_EN_ENABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VRFREF_EN); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VRFCP: + reg_val = BITFVAL(MC13783_REGCTRL_VRFCP_EN, + MC13783_REGCTRL_VRFCP_EN_ENABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VRFCP_EN); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VSIM: + reg_val = BITFVAL(MC13783_REGCTRL_VSIM_EN, + MC13783_REGCTRL_VSIM_EN_ENABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VSIM_EN); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VESIM: + reg_val = BITFVAL(MC13783_REGCTRL_VESIM_EN, + MC13783_REGCTRL_VESIM_EN_ENABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VESIM_EN); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VCAM: + reg_val = BITFVAL(MC13783_REGCTRL_VCAM_EN, + MC13783_REGCTRL_VCAM_EN_ENABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VCAM_EN); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VRFBG: + reg_val = BITFVAL(MC13783_REGCTRL_VRFBG_EN, + MC13783_REGCTRL_VRFBG_EN_ENABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VRFBG_EN); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VVIB: + reg_val = BITFVAL(MC13783_REGCTRL_VVIB_EN, + MC13783_REGCTRL_VVIB_EN_ENABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VVIB_EN); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VRF1: + reg_val = BITFVAL(MC13783_REGCTRL_VRF1_EN, + MC13783_REGCTRL_VRF1_EN_ENABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VRF1_EN); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VRF2: + reg_val = BITFVAL(MC13783_REGCTRL_VRF2_EN, + MC13783_REGCTRL_VRF2_EN_ENABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VRF2_EN); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VMMC1: + reg_val = BITFVAL(MC13783_REGCTRL_VMMC1_EN, + MC13783_REGCTRL_VMMC1_EN_ENABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VMMC1_EN); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VMMC2: + reg_val = BITFVAL(MC13783_REGCTRL_VMMC2_EN, + MC13783_REGCTRL_VMMC2_EN_ENABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VMMC2_EN); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_GPO1: + reg_val = BITFVAL(MC13783_REGCTRL_GPO1_EN, + MC13783_REGCTRL_GPO1_EN_ENABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_GPO1_EN); + reg = REG_POWER_MISCELLANEOUS; + break; + case REGU_GPO2: + reg_val = BITFVAL(MC13783_REGCTRL_GPO2_EN, + MC13783_REGCTRL_GPO2_EN_ENABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_GPO2_EN); + reg = REG_POWER_MISCELLANEOUS; + break; + case REGU_GPO3: + reg_val = BITFVAL(MC13783_REGCTRL_GPO3_EN, + MC13783_REGCTRL_GPO3_EN_ENABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_GPO3_EN); + reg = REG_POWER_MISCELLANEOUS; + break; + case REGU_GPO4: + reg_val = BITFVAL(MC13783_REGCTRL_GPO4_EN, + MC13783_REGCTRL_GPO4_EN_ENABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_GPO4_EN); + reg = REG_POWER_MISCELLANEOUS; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(reg, reg_val, reg_mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function turns off a regulator. + * + * @param regulator The regulator to be truned off. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_regulator_off(t_pmic_regulator regulator) +{ + unsigned int reg_val = 0, reg_mask = 0; + unsigned int reg; + + switch (regulator) { + case SW_PLL: + reg_val = BITFVAL(MC13783_SWCTRL_PLL_EN, + MC13783_SWCTRL_PLL_EN_DISABLE); + reg_mask = BITFMASK(MC13783_SWCTRL_PLL_EN); + reg = REG_SWITCHERS_4; + break; + case SW_SW3: + reg_val = BITFVAL(MC13783_SWCTRL_SW3_EN, + MC13783_SWCTRL_SW3_EN_DISABLE); + reg_mask = BITFMASK(MC13783_SWCTRL_SW3_EN); + reg = REG_SWITCHERS_5; + break; + case REGU_VAUDIO: + reg_val = BITFVAL(MC13783_REGCTRL_VAUDIO_EN, + MC13783_REGCTRL_VAUDIO_EN_DISABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VAUDIO_EN); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VIOHI: + reg_val = BITFVAL(MC13783_REGCTRL_VIOHI_EN, + MC13783_REGCTRL_VIOHI_EN_DISABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VIOHI_EN); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VIOLO: + reg_val = BITFVAL(MC13783_REGCTRL_VIOLO_EN, + MC13783_REGCTRL_VIOLO_EN_DISABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VIOLO_EN); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VDIG: + reg_val = BITFVAL(MC13783_REGCTRL_VDIG_EN, + MC13783_REGCTRL_VDIG_EN_DISABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VDIG_EN); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VGEN: + reg_val = BITFVAL(MC13783_REGCTRL_VGEN_EN, + MC13783_REGCTRL_VGEN_EN_DISABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VGEN_EN); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VRFDIG: + reg_val = BITFVAL(MC13783_REGCTRL_VRFDIG_EN, + MC13783_REGCTRL_VRFDIG_EN_DISABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VRFDIG_EN); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VRFREF: + reg_val = BITFVAL(MC13783_REGCTRL_VRFREF_EN, + MC13783_REGCTRL_VRFREF_EN_DISABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VRFREF_EN); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VRFCP: + reg_val = BITFVAL(MC13783_REGCTRL_VRFCP_EN, + MC13783_REGCTRL_VRFCP_EN_DISABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VRFCP_EN); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VSIM: + reg_val = BITFVAL(MC13783_REGCTRL_VSIM_EN, + MC13783_REGCTRL_VSIM_EN_DISABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VSIM_EN); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VESIM: + reg_val = BITFVAL(MC13783_REGCTRL_VESIM_EN, + MC13783_REGCTRL_VESIM_EN_DISABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VESIM_EN); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VCAM: + reg_val = BITFVAL(MC13783_REGCTRL_VCAM_EN, + MC13783_REGCTRL_VCAM_EN_DISABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VCAM_EN); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VRFBG: + reg_val = BITFVAL(MC13783_REGCTRL_VRFBG_EN, + MC13783_REGCTRL_VRFBG_EN_DISABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VRFBG_EN); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VVIB: + reg_val = BITFVAL(MC13783_REGCTRL_VVIB_EN, + MC13783_REGCTRL_VVIB_EN_DISABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VVIB_EN); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VRF1: + reg_val = BITFVAL(MC13783_REGCTRL_VRF1_EN, + MC13783_REGCTRL_VRF1_EN_DISABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VRF1_EN); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VRF2: + reg_val = BITFVAL(MC13783_REGCTRL_VRF2_EN, + MC13783_REGCTRL_VRF2_EN_DISABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VRF2_EN); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VMMC1: + reg_val = BITFVAL(MC13783_REGCTRL_VMMC1_EN, + MC13783_REGCTRL_VMMC1_EN_DISABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VMMC1_EN); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VMMC2: + reg_val = BITFVAL(MC13783_REGCTRL_VMMC2_EN, + MC13783_REGCTRL_VMMC2_EN_DISABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VMMC2_EN); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_GPO1: + reg_val = BITFVAL(MC13783_REGCTRL_GPO1_EN, + MC13783_REGCTRL_GPO1_EN_DISABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_GPO1_EN); + reg = REG_POWER_MISCELLANEOUS; + break; + case REGU_GPO2: + reg_val = BITFVAL(MC13783_REGCTRL_GPO2_EN, + MC13783_REGCTRL_GPO2_EN_DISABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_GPO2_EN); + reg = REG_POWER_MISCELLANEOUS; + break; + case REGU_GPO3: + reg_val = BITFVAL(MC13783_REGCTRL_GPO3_EN, + MC13783_REGCTRL_GPO3_EN_DISABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_GPO3_EN); + reg = REG_POWER_MISCELLANEOUS; + break; + case REGU_GPO4: + reg_val = BITFVAL(MC13783_REGCTRL_GPO4_EN, + MC13783_REGCTRL_GPO4_EN_DISABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_GPO4_EN); + reg = REG_POWER_MISCELLANEOUS; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(reg, reg_val, reg_mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function sets the regulator output voltage. + * + * @param regulator The regulator to be configured. + * @param voltage The regulator output voltage. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_regulator_set_voltage(t_pmic_regulator regulator, + t_regulator_voltage voltage) +{ + unsigned int reg_val = 0, reg_mask = 0; + unsigned int reg; + + switch (regulator) { + case SW_SW1A: + if ((voltage.sw1a < SW1A_0_9V) || (voltage.sw1a > SW1A_2_2V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_SWSET_SW1A, voltage.sw1a); + reg_mask = BITFMASK(MC13783_SWSET_SW1A); + reg = REG_SWITCHERS_0; + break; + case SW_SW1B: + if ((voltage.sw1b < SW1B_0_9V) || (voltage.sw1b > SW1B_2_2V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_SWSET_SW1B, voltage.sw1b); + reg_mask = BITFMASK(MC13783_SWSET_SW1B); + reg = REG_SWITCHERS_1; + break; + case SW_SW2A: + if ((voltage.sw2a < SW2A_0_9V) || (voltage.sw2a > SW2A_2_2V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_SWSET_SW2A, voltage.sw1a); + reg_mask = BITFMASK(MC13783_SWSET_SW2A); + reg = REG_SWITCHERS_2; + break; + case SW_SW2B: + if ((voltage.sw2b < SW2B_0_9V) || (voltage.sw2b > SW2B_2_2V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_SWSET_SW2B, voltage.sw2b); + reg_mask = BITFMASK(MC13783_SWSET_SW1A); + reg = REG_SWITCHERS_3; + break; + case SW_SW3: + if (voltage.sw3 != SW3_5V) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_SWSET_SW3, voltage.sw3); + reg_mask = BITFMASK(MC13783_SWSET_SW3); + reg = REG_SWITCHERS_5; + break; + case REGU_VIOLO: + if ((voltage.violo < VIOLO_1_2V) || + (voltage.violo > VIOLO_1_8V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_REGSET_VIOLO, voltage.violo); + reg_mask = BITFMASK(MC13783_REGSET_VIOLO); + reg = REG_REGULATOR_SETTING_0; + break; + case REGU_VDIG: + if ((voltage.vdig < VDIG_1_2V) || (voltage.vdig > VDIG_1_8V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_REGSET_VDIG, voltage.vdig); + reg_mask = BITFMASK(MC13783_REGSET_VDIG); + reg = REG_REGULATOR_SETTING_0; + break; + case REGU_VGEN: + if ((voltage.vgen < VGEN_1_2V) || (voltage.vgen > VGEN_2_4V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_REGSET_VGEN, voltage.vgen); + reg_mask = BITFMASK(MC13783_REGSET_VGEN); + reg = REG_REGULATOR_SETTING_0; + break; + case REGU_VRFDIG: + if ((voltage.vrfdig < VRFDIG_1_2V) || + (voltage.vrfdig > VRFDIG_1_875V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_REGSET_VRFDIG, voltage.vrfdig); + reg_mask = BITFMASK(MC13783_REGSET_VRFDIG); + reg = REG_REGULATOR_SETTING_0; + break; + case REGU_VRFREF: + if ((voltage.vrfref < VRFREF_2_475V) || + (voltage.vrfref > VRFREF_2_775V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_REGSET_VRFREF, voltage.vrfref); + reg_mask = BITFMASK(MC13783_REGSET_VRFREF); + reg = REG_REGULATOR_SETTING_0; + break; + case REGU_VRFCP: + if ((voltage.vrfcp < VRFCP_2_7V) || + (voltage.vrfcp > VRFCP_2_775V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_REGSET_VRFCP, voltage.vrfcp); + reg_mask = BITFMASK(MC13783_REGSET_VRFCP); + reg = REG_REGULATOR_SETTING_0; + break; + case REGU_VSIM: + if ((voltage.vsim < VSIM_1_8V) || (voltage.vsim > VSIM_2_9V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_REGSET_VSIM, voltage.vsim); + reg_mask = BITFMASK(MC13783_REGSET_VSIM); + reg = REG_REGULATOR_SETTING_0; + break; + case REGU_VESIM: + if ((voltage.vesim < VESIM_1_8V) || + (voltage.vesim > VESIM_2_9V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_REGSET_VESIM, voltage.vesim); + reg_mask = BITFMASK(MC13783_REGSET_VESIM); + reg = REG_REGULATOR_SETTING_0; + break; + case REGU_VCAM: + if ((voltage.vcam < VCAM_1_5V) || (voltage.vcam > VCAM_3V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_REGSET_VCAM, voltage.vcam); + reg_mask = BITFMASK(MC13783_REGSET_VCAM); + reg = REG_REGULATOR_SETTING_0; + break; + case REGU_VVIB: + if ((voltage.vvib < VVIB_1_3V) || (voltage.vvib > VVIB_3V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_REGSET_VVIB, voltage.vvib); + reg_mask = BITFMASK(MC13783_REGSET_VVIB); + reg = REG_REGULATOR_SETTING_1; + break; + case REGU_VRF1: + if ((voltage.vrf1 < VRF1_1_5V) || (voltage.vrf1 > VRF1_2_775V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_REGSET_VRF1, voltage.vrf1); + reg_mask = BITFMASK(MC13783_REGSET_VRF1); + reg = REG_REGULATOR_SETTING_1; + break; + case REGU_VRF2: + if ((voltage.vrf2 < VRF2_1_5V) || (voltage.vrf2 > VRF2_2_775V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_REGSET_VRF2, voltage.vrf2); + reg_mask = BITFMASK(MC13783_REGSET_VRF2); + reg = REG_REGULATOR_SETTING_1; + break; + case REGU_VMMC1: + if ((voltage.vmmc1 < VMMC1_1_6V) || (voltage.vmmc1 > VMMC1_3V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_REGSET_VMMC1, voltage.vmmc1); + reg_mask = BITFMASK(MC13783_REGSET_VMMC1); + reg = REG_REGULATOR_SETTING_1; + break; + case REGU_VMMC2: + if ((voltage.vmmc2 < VMMC2_1_6V) || (voltage.vmmc2 > VMMC2_3V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_REGSET_VMMC2, voltage.vmmc2); + reg_mask = BITFMASK(MC13783_REGSET_VMMC2); + reg = REG_REGULATOR_SETTING_1; + break; + case REGU_VAUDIO: + case REGU_VIOHI: + case REGU_VRFBG: + case REGU_GPO1: + case REGU_GPO2: + case REGU_GPO3: + case REGU_GPO4: + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(reg, reg_val, reg_mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function retrives the regulator output voltage. + * + * @param regulator The regulator to be truned off. + * @param voltage Pointer to regulator output voltage. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_regulator_get_voltage(t_pmic_regulator regulator, + t_regulator_voltage * voltage) +{ + unsigned int reg_val = 0; + + if (regulator == SW_SW1A) { + CHECK_ERROR(pmic_read_reg(REG_SWITCHERS_0, + ®_val, PMIC_ALL_BITS)); + } else if (regulator == SW_SW1B) { + CHECK_ERROR(pmic_read_reg(REG_SWITCHERS_1, + ®_val, PMIC_ALL_BITS)); + } else if (regulator == SW_SW2A) { + CHECK_ERROR(pmic_read_reg(REG_SWITCHERS_2, + ®_val, PMIC_ALL_BITS)); + } else if (regulator == SW_SW2B) { + CHECK_ERROR(pmic_read_reg(REG_SWITCHERS_3, + ®_val, PMIC_ALL_BITS)); + } else if (regulator == SW_SW3) { + CHECK_ERROR(pmic_read_reg(REG_SWITCHERS_5, + ®_val, PMIC_ALL_BITS)); + } else if ((regulator == REGU_VIOLO) || (regulator == REGU_VDIG) || + (regulator == REGU_VGEN) || + (regulator == REGU_VRFDIG) || + (regulator == REGU_VRFREF) || + (regulator == REGU_VRFCP) || + (regulator == REGU_VSIM) || + (regulator == REGU_VESIM) || (regulator == REGU_VCAM)) { + CHECK_ERROR(pmic_read_reg(REG_REGULATOR_SETTING_0, + ®_val, PMIC_ALL_BITS)); + } else if ((regulator == REGU_VVIB) || (regulator == REGU_VRF1) || + (regulator == REGU_VRF2) || + (regulator == REGU_VMMC1) || (regulator == REGU_VMMC2)) { + CHECK_ERROR(pmic_read_reg(REG_REGULATOR_SETTING_1, + ®_val, PMIC_ALL_BITS)); + } + + switch (regulator) { + case SW_SW1A: + voltage->sw1a = BITFEXT(reg_val, MC13783_SWSET_SW1A); + break; + case SW_SW1B: + voltage->sw1b = BITFEXT(reg_val, MC13783_SWSET_SW1B); + break; + case SW_SW2A: + voltage->sw2a = BITFEXT(reg_val, MC13783_SWSET_SW2A); + break; + case SW_SW2B: + voltage->sw2b = BITFEXT(reg_val, MC13783_SWSET_SW2B); + break; + case SW_SW3: + voltage->sw3 = BITFEXT(reg_val, MC13783_SWSET_SW3); + break; + case REGU_VIOLO: + voltage->violo = BITFEXT(reg_val, MC13783_REGSET_VIOLO); + break; + case REGU_VDIG: + voltage->vdig = BITFEXT(reg_val, MC13783_REGSET_VDIG); + break; + case REGU_VGEN: + voltage->vgen = BITFEXT(reg_val, MC13783_REGSET_VGEN); + break; + case REGU_VRFDIG: + voltage->vrfdig = BITFEXT(reg_val, MC13783_REGSET_VRFDIG); + break; + case REGU_VRFREF: + voltage->vrfref = BITFEXT(reg_val, MC13783_REGSET_VRFREF); + break; + case REGU_VRFCP: + voltage->vrfcp = BITFEXT(reg_val, MC13783_REGSET_VRFCP); + break; + case REGU_VSIM: + voltage->vsim = BITFEXT(reg_val, MC13783_REGSET_VSIM); + break; + case REGU_VESIM: + voltage->vesim = BITFEXT(reg_val, MC13783_REGSET_VESIM); + break; + case REGU_VCAM: + voltage->vcam = BITFEXT(reg_val, MC13783_REGSET_VCAM); + break; + case REGU_VVIB: + voltage->vvib = BITFEXT(reg_val, MC13783_REGSET_VVIB); + break; + case REGU_VRF1: + voltage->vrf1 = BITFEXT(reg_val, MC13783_REGSET_VRF1); + break; + case REGU_VRF2: + voltage->vrf2 = BITFEXT(reg_val, MC13783_REGSET_VRF2); + break; + case REGU_VMMC1: + voltage->vmmc1 = BITFEXT(reg_val, MC13783_REGSET_VMMC1); + break; + case REGU_VMMC2: + voltage->vmmc2 = BITFEXT(reg_val, MC13783_REGSET_VMMC2); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + return PMIC_SUCCESS; +} + +/*! + * This function sets the DVS voltage + * + * @param regulator The regulator to be configured. + * @param dvs The switch Dynamic Voltage Scaling + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_switcher_set_dvs(t_pmic_regulator regulator, + t_regulator_voltage dvs) +{ + unsigned int reg_val = 0, reg_mask = 0; + unsigned int reg; + + switch (regulator) { + case SW_SW1A: + if ((dvs.sw1a < SW1A_0_9V) || (dvs.sw1a > SW1A_2_2V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_SWSET_SW1A_DVS, dvs.sw1a); + reg_mask = BITFMASK(MC13783_SWSET_SW1A_DVS); + reg = REG_SWITCHERS_0; + break; + case SW_SW1B: + if ((dvs.sw1b < SW1B_0_9V) || (dvs.sw1b > SW1B_2_2V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_SWSET_SW1B_DVS, dvs.sw1b); + reg_mask = BITFMASK(MC13783_SWSET_SW1B_DVS); + reg = REG_SWITCHERS_1; + break; + case SW_SW2A: + if ((dvs.sw2a < SW2A_0_9V) || (dvs.sw2a > SW2A_2_2V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_SWSET_SW2A_DVS, dvs.sw2a); + reg_mask = BITFMASK(MC13783_SWSET_SW2A_DVS); + reg = REG_SWITCHERS_2; + break; + case SW_SW2B: + if ((dvs.sw2b < SW2B_0_9V) || (dvs.sw2b > SW2B_2_2V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_SWSET_SW2B_DVS, dvs.sw2b); + reg_mask = BITFMASK(MC13783_SWSET_SW2B_DVS); + reg = REG_SWITCHERS_3; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(reg, reg_val, reg_mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function gets the DVS voltage + * + * @param regulator The regulator to be handled. + * @param dvs The switch Dynamic Voltage Scaling + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_switcher_get_dvs(t_pmic_regulator regulator, + t_regulator_voltage * dvs) +{ + unsigned int reg_val = 0, reg_mask = 0; + unsigned int reg; + + switch (regulator) { + case SW_SW1A: + reg_mask = BITFMASK(MC13783_SWSET_SW1A_DVS); + reg = REG_SWITCHERS_0; + break; + case SW_SW1B: + reg_mask = BITFMASK(MC13783_SWSET_SW1B_DVS); + reg = REG_SWITCHERS_1; + break; + case SW_SW2A: + reg_mask = BITFMASK(MC13783_SWSET_SW2A_DVS); + reg = REG_SWITCHERS_2; + break; + case SW_SW2B: + reg_mask = BITFMASK(MC13783_SWSET_SW2B_DVS); + reg = REG_SWITCHERS_3; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(reg, ®_val, reg_mask)); + + switch (regulator) { + case SW_SW1A: + *dvs = (t_regulator_voltage) BITFEXT(reg_val, + MC13783_SWSET_SW1A_DVS); + break; + case SW_SW1B: + *dvs = (t_regulator_voltage) BITFEXT(reg_val, + MC13783_SWSET_SW1B_DVS); + break; + case SW_SW2A: + *dvs = (t_regulator_voltage) BITFEXT(reg_val, + MC13783_SWSET_SW2A_DVS); + break; + case SW_SW2B: + *dvs = (t_regulator_voltage) BITFEXT(reg_val, + MC13783_SWSET_SW2B_DVS); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + return PMIC_SUCCESS; +} + +/*! + * This function sets the standiby voltage + * + * @param regulator The regulator to be configured. + * @param stby The switch standby voltage + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_switcher_set_stby(t_pmic_regulator regulator, + t_regulator_voltage stby) +{ + unsigned int reg_val = 0, reg_mask = 0; + unsigned int reg; + + switch (regulator) { + case SW_SW1A: + if ((stby.sw1a < SW1A_0_9V) || (stby.sw1a > SW1A_2_2V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_SWSET_SW1A_STDBY, stby.sw1a); + reg_mask = BITFMASK(MC13783_SWSET_SW1A_STDBY); + reg = REG_SWITCHERS_0; + break; + case SW_SW1B: + if ((stby.sw1b < SW1B_0_9V) || (stby.sw1b > SW1B_2_2V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_SWSET_SW1B_STDBY, stby.sw1b); + reg_mask = BITFMASK(MC13783_SWSET_SW1B_STDBY); + reg = REG_SWITCHERS_1; + break; + case SW_SW2A: + if ((stby.sw2a < SW2A_0_9V) || (stby.sw2a > SW2A_2_2V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_SWSET_SW2A_STDBY, stby.sw2a); + reg_mask = BITFMASK(MC13783_SWSET_SW2A_STDBY); + reg = REG_SWITCHERS_2; + break; + case SW_SW2B: + if ((stby.sw2b < SW2B_0_9V) || (stby.sw2b > SW2B_2_2V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_SWSET_SW2B_STDBY, stby.sw2b); + reg_mask = BITFMASK(MC13783_SWSET_SW2B_STDBY); + reg = REG_SWITCHERS_3; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(reg, reg_val, reg_mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function gets the standiby voltage + * + * @param regulator The regulator to be handled. + * @param stby The switch standby voltage + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_switcher_get_stby(t_pmic_regulator regulator, + t_regulator_voltage * stby) +{ + unsigned int reg_val = 0, reg_mask = 0; + unsigned int reg; + + switch (regulator) { + case SW_SW1A: + reg_mask = BITFMASK(MC13783_SWSET_SW1A_STDBY); + reg = REG_SWITCHERS_0; + break; + case SW_SW1B: + reg_mask = BITFMASK(MC13783_SWSET_SW1B_STDBY); + reg = REG_SWITCHERS_1; + break; + case SW_SW2A: + reg_mask = BITFMASK(MC13783_SWSET_SW2A_STDBY); + reg = REG_SWITCHERS_2; + break; + case SW_SW2B: + reg_mask = BITFMASK(MC13783_SWSET_SW2B_STDBY); + reg = REG_SWITCHERS_3; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(reg, ®_val, reg_mask)); + + switch (regulator) { + case SW_SW1A: + *stby = (t_regulator_voltage) BITFEXT(reg_val, + MC13783_SWSET_SW1A_STDBY); + break; + case SW_SW1B: + *stby = (t_regulator_voltage) BITFEXT(reg_val, + MC13783_SWSET_SW1B_STDBY); + break; + case SW_SW2A: + *stby = (t_regulator_voltage) BITFEXT(reg_val, + MC13783_SWSET_SW2A_STDBY); + break; + case SW_SW2B: + *stby = (t_regulator_voltage) BITFEXT(reg_val, + MC13783_SWSET_SW2B_STDBY); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + return PMIC_SUCCESS; +} + +/*! + * This function sets the switchers mode. + * + * @param regulator The regulator to be configured. + * @param mode The switcher mode + * @param stby Switch between main and standby. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_switcher_set_mode(t_pmic_regulator regulator, + t_regulator_sw_mode mode, bool stby) +{ + unsigned int reg_val = 0, reg_mask = 0; + unsigned int reg; + unsigned int l_mode; + + if (mode == SYNC_RECT) { + l_mode = MC13783_SWCTRL_SW_MODE_SYNC_RECT_EN; + } else if (mode == NO_PULSE_SKIP) { + l_mode = MC13783_SWCTRL_SW_MODE_PULSE_NO_SKIP_EN; + } else if (mode == PULSE_SKIP) { + l_mode = MC13783_SWCTRL_SW_MODE_PULSE_SKIP_EN; + } else if (mode == LOW_POWER) { + l_mode = MC13783_SWCTRL_SW_MODE_LOW_POWER_EN; + } else { + return PMIC_PARAMETER_ERROR; + } + + switch (regulator) { + case SW_SW1A: + if (stby) { + reg_val = + BITFVAL(MC13783_SWCTRL_SW1A_STBY_MODE, l_mode); + reg_mask = BITFMASK(MC13783_SWCTRL_SW1A_STBY_MODE); + } else { + reg_val = BITFVAL(MC13783_SWCTRL_SW1A_MODE, l_mode); + reg_mask = BITFMASK(MC13783_SWCTRL_SW1A_MODE); + } + reg = REG_SWITCHERS_4; + break; + case SW_SW1B: + if (stby) { + reg_val = + BITFVAL(MC13783_SWCTRL_SW1B_STBY_MODE, l_mode); + reg_mask = BITFMASK(MC13783_SWCTRL_SW1B_STBY_MODE); + } else { + reg_val = BITFVAL(MC13783_SWCTRL_SW1B_MODE, l_mode); + reg_mask = BITFMASK(MC13783_SWCTRL_SW1B_MODE); + } + reg = REG_SWITCHERS_4; + break; + case SW_SW2A: + if (stby) { + reg_val = + BITFVAL(MC13783_SWCTRL_SW2A_STBY_MODE, l_mode); + reg_mask = BITFMASK(MC13783_SWCTRL_SW2A_STBY_MODE); + } else { + reg_val = BITFVAL(MC13783_SWCTRL_SW2A_MODE, l_mode); + reg_mask = BITFMASK(MC13783_SWCTRL_SW2A_MODE); + } + reg = REG_SWITCHERS_5; + break; + case SW_SW2B: + if (stby) { + reg_val = + BITFVAL(MC13783_SWCTRL_SW2B_STBY_MODE, l_mode); + reg_mask = BITFMASK(MC13783_SWCTRL_SW2B_STBY_MODE); + } else { + reg_val = BITFVAL(MC13783_SWCTRL_SW2B_MODE, l_mode); + reg_mask = BITFMASK(MC13783_SWCTRL_SW2B_MODE); + } + reg = REG_SWITCHERS_5; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(reg, reg_val, reg_mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function gets the switchers mode. + * + * @param regulator The regulator to be handled. + * @param mode The switcher mode. + * @param stby Switch between main and standby. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_switcher_get_mode(t_pmic_regulator regulator, + t_regulator_sw_mode * mode, bool stby) +{ + unsigned int reg_val = 0, reg_mask = 0; + unsigned int reg = 0; + unsigned int l_mode = 0; + + switch (regulator) { + case SW_SW1A: + if (stby) { + reg_mask = BITFMASK(MC13783_SWCTRL_SW1A_STBY_MODE); + } else { + reg_mask = BITFMASK(MC13783_SWCTRL_SW1A_MODE); + } + reg = REG_SWITCHERS_4; + break; + case SW_SW1B: + if (stby) { + reg_mask = BITFMASK(MC13783_SWCTRL_SW1B_STBY_MODE); + } else { + reg_mask = BITFMASK(MC13783_SWCTRL_SW1B_MODE); + } + reg = REG_SWITCHERS_4; + break; + case SW_SW2A: + if (stby) { + reg_mask = BITFMASK(MC13783_SWCTRL_SW2A_STBY_MODE); + } else { + reg_mask = BITFMASK(MC13783_SWCTRL_SW2A_MODE); + } + reg = REG_SWITCHERS_5; + break; + case SW_SW2B: + if (stby) { + reg_mask = BITFMASK(MC13783_SWCTRL_SW2B_STBY_MODE); + } else { + reg_mask = BITFMASK(MC13783_SWCTRL_SW2B_MODE); + } + reg = REG_SWITCHERS_5; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(reg, ®_val, reg_mask)); + + switch (regulator) { + case SW_SW1A: + if (stby) { + l_mode = + BITFEXT(reg_val, MC13783_SWCTRL_SW1A_STBY_MODE); + } else { + l_mode = BITFEXT(reg_val, MC13783_SWCTRL_SW1A_MODE); + } + break; + case SW_SW1B: + if (stby) { + l_mode = + BITFEXT(reg_val, MC13783_SWCTRL_SW1B_STBY_MODE); + } else { + l_mode = BITFEXT(reg_val, MC13783_SWCTRL_SW1B_MODE); + } + break; + case SW_SW2A: + if (stby) { + l_mode = + BITFEXT(reg_val, MC13783_SWCTRL_SW2A_STBY_MODE); + } else { + l_mode = BITFEXT(reg_val, MC13783_SWCTRL_SW2A_MODE); + } + break; + case SW_SW2B: + if (stby) { + l_mode = + BITFEXT(reg_val, MC13783_SWCTRL_SW2B_STBY_MODE); + } else { + l_mode = BITFEXT(reg_val, MC13783_SWCTRL_SW2B_MODE); + } + break; + default: + return PMIC_PARAMETER_ERROR; + } + + if (l_mode == MC13783_SWCTRL_SW_MODE_SYNC_RECT_EN) { + *mode = SYNC_RECT; + } else if (l_mode == MC13783_SWCTRL_SW_MODE_PULSE_NO_SKIP_EN) { + *mode = NO_PULSE_SKIP; + } else if (l_mode == MC13783_SWCTRL_SW_MODE_PULSE_SKIP_EN) { + *mode = PULSE_SKIP; + } else if (l_mode == MC13783_SWCTRL_SW_MODE_LOW_POWER_EN) { + *mode = LOW_POWER; + } else { + return PMIC_PARAMETER_ERROR; + } + + return PMIC_SUCCESS; +} + +/*! + * This function sets the switch dvs speed + * + * @param regulator The regulator to be configured. + * @param speed The dvs speed. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_switcher_set_dvs_speed(t_pmic_regulator regulator, + t_switcher_dvs_speed speed) +{ + unsigned int reg_val = 0, reg_mask = 0; + unsigned int reg; + if (speed > 3 || speed < 0) { + return PMIC_PARAMETER_ERROR; + } + + switch (regulator) { + case SW_SW1A: + reg_val = BITFVAL(MC13783_SWCTRL_SW1A_DVS_SPEED, speed); + reg_mask = BITFMASK(MC13783_SWCTRL_SW1A_DVS_SPEED); + reg = REG_SWITCHERS_4; + break; + case SW_SW1B: + reg_val = BITFVAL(MC13783_SWCTRL_SW2B_DVS_SPEED, speed); + reg_mask = BITFMASK(MC13783_SWCTRL_SW1B_DVS_SPEED); + reg = REG_SWITCHERS_4; + break; + case SW_SW2A: + reg_val = BITFVAL(MC13783_SWCTRL_SW2A_DVS_SPEED, speed); + reg_mask = BITFMASK(MC13783_SWCTRL_SW2A_DVS_SPEED); + reg = REG_SWITCHERS_5; + break; + case SW_SW2B: + reg_val = BITFVAL(MC13783_SWCTRL_SW2B_DVS_SPEED, speed); + reg_mask = BITFMASK(MC13783_SWCTRL_SW2B_DVS_SPEED); + reg = REG_SWITCHERS_5; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(reg, reg_val, reg_mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function gets the switch dvs speed + * + * @param regulator The regulator to be handled. + * @param speed The dvs speed. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_switcher_get_dvs_speed(t_pmic_regulator regulator, + t_switcher_dvs_speed * speed) +{ + unsigned int reg_val = 0, reg_mask = 0; + unsigned int reg; + + switch (regulator) { + case SW_SW1A: + reg_mask = BITFMASK(MC13783_SWCTRL_SW1A_DVS_SPEED); + reg = REG_SWITCHERS_4; + break; + case SW_SW1B: + reg_mask = BITFMASK(MC13783_SWCTRL_SW1B_DVS_SPEED); + reg = REG_SWITCHERS_4; + break; + case SW_SW2A: + reg_mask = BITFMASK(MC13783_SWCTRL_SW2A_DVS_SPEED); + reg = REG_SWITCHERS_5; + break; + case SW_SW2B: + reg_mask = BITFMASK(MC13783_SWCTRL_SW2B_DVS_SPEED); + reg = REG_SWITCHERS_5; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(reg, ®_val, reg_mask)); + + switch (regulator) { + case SW_SW1A: + *speed = BITFEXT(reg_val, MC13783_SWCTRL_SW1A_DVS_SPEED); + break; + case SW_SW1B: + *speed = BITFEXT(reg_val, MC13783_SWCTRL_SW1B_DVS_SPEED); + break; + case SW_SW2A: + *speed = BITFEXT(reg_val, MC13783_SWCTRL_SW2A_DVS_SPEED); + break; + case SW_SW2B: + *speed = BITFEXT(reg_val, MC13783_SWCTRL_SW2B_DVS_SPEED); + break; + default: + break; + } + + return PMIC_SUCCESS; +} + +/*! + * This function sets the switch panic mode + * + * @param regulator The regulator to be configured. + * @param panic_mode Enable or disable panic mode + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_switcher_set_panic_mode(t_pmic_regulator regulator, + bool panic_mode) +{ + unsigned int reg_val = 0, reg_mask = 0; + unsigned int reg; + + switch (regulator) { + case SW_SW1A: + reg_val = BITFVAL(MC13783_SWCTRL_SW1A_PANIC_MODE, panic_mode); + reg_mask = BITFMASK(MC13783_SWCTRL_SW1A_PANIC_MODE); + reg = REG_SWITCHERS_4; + break; + case SW_SW1B: + reg_val = BITFVAL(MC13783_SWCTRL_SW2B_PANIC_MODE, panic_mode); + reg_mask = BITFMASK(MC13783_SWCTRL_SW1B_PANIC_MODE); + reg = REG_SWITCHERS_4; + break; + case SW_SW2A: + reg_val = BITFVAL(MC13783_SWCTRL_SW2A_PANIC_MODE, panic_mode); + reg_mask = BITFMASK(MC13783_SWCTRL_SW2A_PANIC_MODE); + reg = REG_SWITCHERS_5; + break; + case SW_SW2B: + reg_val = BITFVAL(MC13783_SWCTRL_SW2B_PANIC_MODE, panic_mode); + reg_mask = BITFMASK(MC13783_SWCTRL_SW2B_PANIC_MODE); + reg = REG_SWITCHERS_5; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(reg, reg_val, reg_mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function gets the switch panic mode + * + * @param regulator The regulator to be handled + * @param panic_mode Enable or disable panic mode + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_switcher_get_panic_mode(t_pmic_regulator regulator, + bool * panic_mode) +{ + unsigned int reg_val = 0, reg_mask = 0; + unsigned int reg; + + switch (regulator) { + case SW_SW1A: + reg_mask = BITFMASK(MC13783_SWCTRL_SW1A_PANIC_MODE); + reg = REG_SWITCHERS_4; + break; + case SW_SW1B: + reg_mask = BITFMASK(MC13783_SWCTRL_SW1B_PANIC_MODE); + reg = REG_SWITCHERS_4; + break; + case SW_SW2A: + reg_mask = BITFMASK(MC13783_SWCTRL_SW2A_PANIC_MODE); + reg = REG_SWITCHERS_5; + break; + case SW_SW2B: + reg_mask = BITFMASK(MC13783_SWCTRL_SW2B_PANIC_MODE); + reg = REG_SWITCHERS_5; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(reg, ®_val, reg_mask)); + + switch (regulator) { + case SW_SW1A: + *panic_mode = BITFEXT(reg_val, MC13783_SWCTRL_SW1A_PANIC_MODE); + break; + case SW_SW1B: + *panic_mode = BITFEXT(reg_val, MC13783_SWCTRL_SW1B_PANIC_MODE); + break; + case SW_SW2A: + *panic_mode = BITFEXT(reg_val, MC13783_SWCTRL_SW2A_PANIC_MODE); + break; + case SW_SW2B: + *panic_mode = BITFEXT(reg_val, MC13783_SWCTRL_SW2B_PANIC_MODE); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + return PMIC_SUCCESS; +} + +/*! + * This function sets the switch softstart mode + * + * @param regulator The regulator to be configured. + * @param softstart Enable or disable softstart. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_switcher_set_softstart(t_pmic_regulator regulator, + bool softstart) +{ + unsigned int reg_val = 0, reg_mask = 0; + unsigned int reg; + + switch (regulator) { + case SW_SW1A: + reg_val = BITFVAL(MC13783_SWCTRL_SW1A_SOFTSTART, softstart); + reg_mask = BITFMASK(MC13783_SWCTRL_SW1A_SOFTSTART); + reg = REG_SWITCHERS_4; + break; + case SW_SW1B: + reg_val = BITFVAL(MC13783_SWCTRL_SW2B_SOFTSTART, softstart); + reg_mask = BITFMASK(MC13783_SWCTRL_SW1B_SOFTSTART); + reg = REG_SWITCHERS_4; + break; + case SW_SW2A: + reg_val = BITFVAL(MC13783_SWCTRL_SW2A_SOFTSTART, softstart); + reg_mask = BITFMASK(MC13783_SWCTRL_SW2A_SOFTSTART); + reg = REG_SWITCHERS_5; + break; + case SW_SW2B: + reg_val = BITFVAL(MC13783_SWCTRL_SW2B_SOFTSTART, softstart); + reg_mask = BITFMASK(MC13783_SWCTRL_SW2B_SOFTSTART); + reg = REG_SWITCHERS_5; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(reg, reg_val, reg_mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function gets the switch softstart mode + * + * @param regulator The regulator to be handled + * @param softstart Enable or disable softstart. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_switcher_get_softstart(t_pmic_regulator regulator, + bool * softstart) +{ + unsigned int reg_val = 0, reg_mask = 0; + unsigned int reg; + + switch (regulator) { + case SW_SW1A: + reg_mask = BITFMASK(MC13783_SWCTRL_SW1A_SOFTSTART); + reg = REG_SWITCHERS_4; + break; + case SW_SW1B: + reg_mask = BITFMASK(MC13783_SWCTRL_SW1B_SOFTSTART); + reg = REG_SWITCHERS_4; + break; + case SW_SW2A: + reg_mask = BITFMASK(MC13783_SWCTRL_SW2A_SOFTSTART); + reg = REG_SWITCHERS_5; + break; + case SW_SW2B: + reg_mask = BITFMASK(MC13783_SWCTRL_SW2B_SOFTSTART); + reg = REG_SWITCHERS_5; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(reg, ®_val, reg_mask)); + + switch (regulator) { + case SW_SW1A: + *softstart = BITFEXT(reg_val, MC13783_SWCTRL_SW1A_SOFTSTART); + break; + case SW_SW1B: + *softstart = BITFEXT(reg_val, MC13783_SWCTRL_SW2B_SOFTSTART); + break; + case SW_SW2A: + *softstart = BITFEXT(reg_val, MC13783_SWCTRL_SW2A_SOFTSTART); + break; + case SW_SW2B: + *softstart = BITFEXT(reg_val, MC13783_SWCTRL_SW2B_SOFTSTART); + break; + default: + break; + } + + return PMIC_SUCCESS; +} + +/*! + * This function sets the PLL multiplication factor + * + * @param regulator The regulator to be configured. + * @param factor The multiplication factor. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_switcher_set_factor(t_pmic_regulator regulator, + t_switcher_factor factor) +{ + unsigned int reg_val = 0, reg_mask = 0; + + if (regulator != SW_PLL) { + return PMIC_PARAMETER_ERROR; + } + if (factor < FACTOR_28 || factor > FACTOR_35) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_SWCTRL_PLL_FACTOR, factor); + reg_mask = BITFMASK(MC13783_SWCTRL_PLL_FACTOR); + + CHECK_ERROR(pmic_write_reg(REG_SWITCHERS_4, reg_val, reg_mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function gets the PLL multiplication factor + * + * @param regulator The regulator to be handled + * @param factor The multiplication factor. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_switcher_get_factor(t_pmic_regulator regulator, + t_switcher_factor * factor) +{ + unsigned int reg_val = 0, reg_mask = 0; + + if (regulator != SW_PLL) { + return PMIC_PARAMETER_ERROR; + } + reg_mask = BITFMASK(MC13783_SWCTRL_PLL_FACTOR); + + CHECK_ERROR(pmic_read_reg(REG_SWITCHERS_4, ®_val, reg_mask)); + + *factor = BITFEXT(reg_val, MC13783_SWCTRL_PLL_FACTOR); + + return PMIC_SUCCESS; +} + +/*! + * This function enables or disables low power mode. + * + * @param regulator The regulator to be configured. + * @param lp_mode Select nominal or low power mode. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_regulator_set_lp_mode(t_pmic_regulator regulator, + t_regulator_lp_mode lp_mode) +{ + unsigned int reg_val = 0, reg_mask = 0; + unsigned int reg; + unsigned int l_mode, l_stby; + + if (lp_mode == LOW_POWER_DISABLED) { + l_mode = MC13783_REGTRL_LP_MODE_DISABLE; + l_stby = MC13783_REGTRL_STBY_MODE_DISABLE; + } else if (lp_mode == LOW_POWER_CTRL_BY_PIN) { + l_mode = MC13783_REGTRL_LP_MODE_DISABLE; + l_stby = MC13783_REGTRL_STBY_MODE_ENABLE; + } else if (lp_mode == LOW_POWER_EN) { + l_mode = MC13783_REGTRL_LP_MODE_ENABLE; + l_stby = MC13783_REGTRL_STBY_MODE_DISABLE; + } else if (lp_mode == LOW_POWER_AND_LOW_POWER_CTRL_BY_PIN) { + l_mode = MC13783_REGTRL_LP_MODE_ENABLE; + l_stby = MC13783_REGTRL_STBY_MODE_ENABLE; + } else { + return PMIC_PARAMETER_ERROR; + } + + switch (regulator) { + case SW_SW3: + reg_val = BITFVAL(MC13783_SWCTRL_SW3_MODE, l_mode) | + BITFVAL(MC13783_SWCTRL_SW3_STBY, l_stby); + reg_mask = BITFMASK(MC13783_SWCTRL_SW3_MODE) | + BITFMASK(MC13783_SWCTRL_SW3_STBY); + reg = REG_SWITCHERS_5; + break; + case REGU_VAUDIO: + reg_val = BITFVAL(MC13783_REGCTRL_VAUDIO_MODE, l_mode) | + BITFVAL(MC13783_REGCTRL_VAUDIO_STBY, l_stby); + reg_mask = BITFMASK(MC13783_REGCTRL_VAUDIO_MODE) | + BITFMASK(MC13783_REGCTRL_VAUDIO_STBY); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VIOHI: + reg_val = BITFVAL(MC13783_REGCTRL_VIOHI_MODE, l_mode) | + BITFVAL(MC13783_REGCTRL_VIOHI_STBY, l_stby); + reg_mask = BITFMASK(MC13783_REGCTRL_VIOHI_MODE) | + BITFMASK(MC13783_REGCTRL_VIOHI_STBY); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VIOLO: + reg_val = BITFVAL(MC13783_REGCTRL_VIOLO_MODE, l_mode) | + BITFVAL(MC13783_REGCTRL_VIOLO_STBY, l_stby); + reg_mask = BITFMASK(MC13783_REGCTRL_VIOLO_MODE) | + BITFMASK(MC13783_REGCTRL_VIOLO_STBY); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VDIG: + reg_val = BITFVAL(MC13783_REGCTRL_VDIG_MODE, l_mode) | + BITFVAL(MC13783_REGCTRL_VDIG_STBY, l_stby); + reg_mask = BITFMASK(MC13783_REGCTRL_VDIG_MODE) | + BITFMASK(MC13783_REGCTRL_VDIG_STBY); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VGEN: + reg_val = BITFVAL(MC13783_REGCTRL_VGEN_MODE, l_mode) | + BITFVAL(MC13783_REGCTRL_VGEN_STBY, l_stby); + reg_mask = BITFMASK(MC13783_REGCTRL_VGEN_MODE) | + BITFMASK(MC13783_REGCTRL_VGEN_STBY); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VRFDIG: + reg_val = BITFVAL(MC13783_REGCTRL_VRFDIG_MODE, l_mode) | + BITFVAL(MC13783_REGCTRL_VRFDIG_STBY, l_stby); + reg_mask = BITFMASK(MC13783_REGCTRL_VRFDIG_MODE) | + BITFMASK(MC13783_REGCTRL_VRFDIG_STBY); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VRFREF: + reg_val = BITFVAL(MC13783_REGCTRL_VRFREF_MODE, l_mode) | + BITFVAL(MC13783_REGCTRL_VRFREF_STBY, l_stby); + reg_mask = BITFMASK(MC13783_REGCTRL_VRFREF_MODE) | + BITFMASK(MC13783_REGCTRL_VRFREF_STBY); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VRFCP: + reg_val = BITFVAL(MC13783_REGCTRL_VRFCP_MODE, l_mode) | + BITFVAL(MC13783_REGCTRL_VRFCP_STBY, l_stby); + reg_mask = BITFMASK(MC13783_REGCTRL_VRFCP_MODE) | + BITFMASK(MC13783_REGCTRL_VRFCP_STBY); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VSIM: + reg_val = BITFVAL(MC13783_REGCTRL_VSIM_MODE, l_mode) | + BITFVAL(MC13783_REGCTRL_VSIM_STBY, l_stby); + reg_mask = BITFMASK(MC13783_REGCTRL_VSIM_MODE) | + BITFMASK(MC13783_REGCTRL_VSIM_STBY); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VESIM: + reg_val = BITFVAL(MC13783_REGCTRL_VESIM_MODE, l_mode) | + BITFVAL(MC13783_REGCTRL_VESIM_STBY, l_stby); + reg_mask = BITFMASK(MC13783_REGCTRL_VESIM_MODE) | + BITFMASK(MC13783_REGCTRL_VESIM_STBY); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VCAM: + reg_val = BITFVAL(MC13783_REGCTRL_VCAM_MODE, l_mode) | + BITFVAL(MC13783_REGCTRL_VCAM_STBY, l_stby); + reg_mask = BITFMASK(MC13783_REGCTRL_VCAM_MODE) | + BITFMASK(MC13783_REGCTRL_VCAM_STBY); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VRFBG: + if ((lp_mode == LOW_POWER) || + (lp_mode == LOW_POWER_AND_LOW_POWER_CTRL_BY_PIN)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_REGCTRL_VRFBG_STBY, l_mode); + reg_mask = BITFMASK(MC13783_REGCTRL_VRFBG_STBY); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VRF1: + reg_val = BITFVAL(MC13783_REGCTRL_VRF1_MODE, l_mode) | + BITFVAL(MC13783_REGCTRL_VRF1_STBY, l_stby); + reg_mask = BITFMASK(MC13783_REGCTRL_VRF1_MODE) | + BITFMASK(MC13783_REGCTRL_VRF1_STBY); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VRF2: + reg_val = BITFVAL(MC13783_REGCTRL_VRF2_MODE, l_mode) | + BITFVAL(MC13783_REGCTRL_VRF2_STBY, l_stby); + reg_mask = BITFMASK(MC13783_REGCTRL_VRF2_MODE) | + BITFMASK(MC13783_REGCTRL_VRF2_STBY); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VMMC1: + reg_val = BITFVAL(MC13783_REGCTRL_VMMC1_MODE, l_mode) | + BITFVAL(MC13783_REGCTRL_VMMC1_STBY, l_stby); + reg_mask = BITFMASK(MC13783_REGCTRL_VMMC1_MODE) | + BITFMASK(MC13783_REGCTRL_VMMC1_STBY); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VMMC2: + reg_val = BITFVAL(MC13783_REGCTRL_VMMC2_MODE, l_mode) | + BITFVAL(MC13783_REGCTRL_VMMC2_STBY, l_stby); + reg_mask = BITFMASK(MC13783_REGCTRL_VMMC2_MODE) | + BITFMASK(MC13783_REGCTRL_VMMC2_STBY); + reg = REG_REGULATOR_MODE_1; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(reg, reg_val, reg_mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function gets low power mode. + * + * @param regulator The regulator to be handled + * @param lp_mode Select nominal or low power mode. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_regulator_get_lp_mode(t_pmic_regulator regulator, + t_regulator_lp_mode * lp_mode) +{ + unsigned int reg_val = 0, reg_mask = 0; + unsigned int reg; + unsigned int l_mode, l_stby; + + switch (regulator) { + case SW_SW3: + reg_mask = BITFMASK(MC13783_SWCTRL_SW3_MODE) | + BITFMASK(MC13783_SWCTRL_SW3_STBY); + reg = REG_SWITCHERS_5; + break; + case REGU_VAUDIO: + reg_mask = BITFMASK(MC13783_REGCTRL_VAUDIO_MODE) | + BITFMASK(MC13783_REGCTRL_VAUDIO_STBY); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VIOHI: + reg_mask = BITFMASK(MC13783_REGCTRL_VIOHI_MODE) | + BITFMASK(MC13783_REGCTRL_VIOHI_STBY); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VIOLO: + reg_mask = BITFMASK(MC13783_REGCTRL_VIOLO_MODE) | + BITFMASK(MC13783_REGCTRL_VIOLO_STBY); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VDIG: + reg_mask = BITFMASK(MC13783_REGCTRL_VDIG_MODE) | + BITFMASK(MC13783_REGCTRL_VDIG_STBY); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VGEN: + reg_mask = BITFMASK(MC13783_REGCTRL_VGEN_MODE) | + BITFMASK(MC13783_REGCTRL_VGEN_STBY); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VRFDIG: + reg_mask = BITFMASK(MC13783_REGCTRL_VRFDIG_MODE) | + BITFMASK(MC13783_REGCTRL_VRFDIG_STBY); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VRFREF: + reg_mask = BITFMASK(MC13783_REGCTRL_VRFREF_MODE) | + BITFMASK(MC13783_REGCTRL_VRFREF_STBY); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VRFCP: + reg_mask = BITFMASK(MC13783_REGCTRL_VRFCP_MODE) | + BITFMASK(MC13783_REGCTRL_VRFCP_STBY); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VSIM: + reg_mask = BITFMASK(MC13783_REGCTRL_VSIM_MODE) | + BITFMASK(MC13783_REGCTRL_VSIM_STBY); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VESIM: + reg_mask = BITFMASK(MC13783_REGCTRL_VESIM_MODE) | + BITFMASK(MC13783_REGCTRL_VESIM_STBY); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VCAM: + reg_mask = BITFMASK(MC13783_REGCTRL_VCAM_MODE) | + BITFMASK(MC13783_REGCTRL_VCAM_STBY); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VRFBG: + reg_mask = BITFMASK(MC13783_REGCTRL_VRFBG_STBY); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VRF1: + reg_mask = BITFMASK(MC13783_REGCTRL_VRF1_MODE) | + BITFMASK(MC13783_REGCTRL_VRF1_STBY); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VRF2: + reg_mask = BITFMASK(MC13783_REGCTRL_VRF2_MODE) | + BITFMASK(MC13783_REGCTRL_VRF2_STBY); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VMMC1: + reg_mask = BITFMASK(MC13783_REGCTRL_VMMC1_MODE) | + BITFMASK(MC13783_REGCTRL_VMMC1_STBY); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VMMC2: + reg_mask = BITFMASK(MC13783_REGCTRL_VMMC2_MODE) | + BITFMASK(MC13783_REGCTRL_VMMC2_STBY); + reg = REG_REGULATOR_MODE_1; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(reg, ®_val, reg_mask)); + + switch (regulator) { + case SW_SW3: + l_mode = BITFEXT(reg_val, MC13783_SWCTRL_SW3_MODE); + l_stby = BITFEXT(reg_val, MC13783_SWCTRL_SW3_STBY); + break; + case REGU_VAUDIO: + l_mode = BITFEXT(reg_val, MC13783_REGCTRL_VAUDIO_MODE); + l_stby = BITFEXT(reg_val, MC13783_REGCTRL_VAUDIO_STBY); + break; + case REGU_VIOHI: + l_mode = BITFEXT(reg_val, MC13783_REGCTRL_VIOHI_MODE); + l_stby = BITFEXT(reg_val, MC13783_REGCTRL_VIOHI_STBY); + break; + case REGU_VIOLO: + l_mode = BITFEXT(reg_val, MC13783_REGCTRL_VIOLO_MODE); + l_stby = BITFEXT(reg_val, MC13783_REGCTRL_VIOLO_STBY); + break; + case REGU_VDIG: + l_mode = BITFEXT(reg_val, MC13783_REGCTRL_VDIG_MODE); + l_stby = BITFEXT(reg_val, MC13783_REGCTRL_VDIG_STBY); + break; + case REGU_VGEN: + l_mode = BITFEXT(reg_val, MC13783_REGCTRL_VGEN_MODE); + l_stby = BITFEXT(reg_val, MC13783_REGCTRL_VGEN_STBY); + break; + case REGU_VRFDIG: + l_mode = BITFEXT(reg_val, MC13783_REGCTRL_VRFDIG_MODE); + l_stby = BITFEXT(reg_val, MC13783_REGCTRL_VRFDIG_STBY); + break; + case REGU_VRFREF: + l_mode = BITFEXT(reg_val, MC13783_REGCTRL_VRFREF_MODE); + l_stby = BITFEXT(reg_val, MC13783_REGCTRL_VRFREF_STBY); + break; + case REGU_VRFCP: + l_mode = BITFEXT(reg_val, MC13783_REGCTRL_VRFCP_MODE); + l_stby = BITFEXT(reg_val, MC13783_REGCTRL_VRFCP_STBY); + break; + case REGU_VSIM: + l_mode = BITFEXT(reg_val, MC13783_REGCTRL_VSIM_MODE); + l_stby = BITFEXT(reg_val, MC13783_REGCTRL_VSIM_STBY); + break; + case REGU_VESIM: + l_mode = BITFEXT(reg_val, MC13783_REGCTRL_VESIM_MODE); + l_stby = BITFEXT(reg_val, MC13783_REGCTRL_VESIM_STBY); + break; + case REGU_VCAM: + l_mode = BITFEXT(reg_val, MC13783_REGCTRL_VCAM_MODE); + l_stby = BITFEXT(reg_val, MC13783_REGCTRL_VCAM_STBY); + break; + case REGU_VRFBG: + l_mode = MC13783_REGTRL_LP_MODE_DISABLE; + l_stby = BITFEXT(reg_val, MC13783_REGCTRL_VRFBG_STBY); + break; + case REGU_VRF1: + l_mode = BITFEXT(reg_val, MC13783_REGCTRL_VRF1_MODE); + l_stby = BITFEXT(reg_val, MC13783_REGCTRL_VRF1_STBY); + break; + case REGU_VRF2: + l_mode = BITFEXT(reg_val, MC13783_REGCTRL_VRF2_MODE); + l_stby = BITFEXT(reg_val, MC13783_REGCTRL_VRF2_STBY); + break; + case REGU_VMMC1: + l_mode = BITFEXT(reg_val, MC13783_REGCTRL_VMMC1_MODE); + l_stby = BITFEXT(reg_val, MC13783_REGCTRL_VMMC1_STBY); + break; + case REGU_VMMC2: + l_mode = BITFEXT(reg_val, MC13783_REGCTRL_VMMC2_MODE); + l_stby = BITFEXT(reg_val, MC13783_REGCTRL_VMMC2_STBY); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + if ((l_mode == MC13783_REGTRL_LP_MODE_DISABLE) && + (l_stby == MC13783_REGTRL_STBY_MODE_DISABLE)) { + *lp_mode = LOW_POWER_DISABLED; + } else if ((l_mode == MC13783_REGTRL_LP_MODE_DISABLE) && + (l_stby == MC13783_REGTRL_STBY_MODE_ENABLE)) { + *lp_mode = LOW_POWER_CTRL_BY_PIN; + } else if ((l_mode == MC13783_REGTRL_LP_MODE_ENABLE) && + (l_stby == MC13783_REGTRL_STBY_MODE_DISABLE)) { + *lp_mode = LOW_POWER_EN; + } else { + *lp_mode = LOW_POWER_AND_LOW_POWER_CTRL_BY_PIN; + } + + return PMIC_SUCCESS; +} + +/*! + * This function sets the regulator configuration. + * + * @param regulator The regulator to be configured. + * @param config The regulator output configuration. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_regulator_set_config(t_pmic_regulator regulator, + t_regulator_config * config) +{ + if (config == NULL) { + return PMIC_ERROR; + } + + switch (regulator) { + case SW_SW1A: + case SW_SW1B: + case SW_SW2A: + case SW_SW2B: + CHECK_ERROR(pmic_power_regulator_set_voltage + (regulator, config->voltage)); + CHECK_ERROR(pmic_power_switcher_set_dvs + (regulator, config->voltage_lvs)); + CHECK_ERROR(pmic_power_switcher_set_stby + (regulator, config->voltage_stby)); + CHECK_ERROR(pmic_power_switcher_set_mode + (regulator, config->mode, false)); + CHECK_ERROR(pmic_power_switcher_set_mode + (regulator, config->stby_mode, true)); + CHECK_ERROR(pmic_power_switcher_set_dvs_speed + (regulator, config->dvs_speed)); + CHECK_ERROR(pmic_power_switcher_set_panic_mode + (regulator, config->panic_mode)); + CHECK_ERROR(pmic_power_switcher_set_softstart + (regulator, config->softstart)); + break; + case SW_PLL: + CHECK_ERROR(pmic_power_switcher_set_factor + (regulator, config->factor)); + break; + case SW_SW3: + case REGU_VIOLO: + case REGU_VDIG: + case REGU_VGEN: + case REGU_VRFDIG: + case REGU_VRFREF: + case REGU_VRFCP: + case REGU_VSIM: + case REGU_VESIM: + case REGU_VCAM: + case REGU_VRF1: + case REGU_VRF2: + case REGU_VMMC1: + case REGU_VMMC2: + CHECK_ERROR(pmic_power_regulator_set_voltage + (regulator, config->voltage)); + CHECK_ERROR(pmic_power_regulator_set_lp_mode + (regulator, config->lp_mode)); + break; + case REGU_VVIB: + CHECK_ERROR(pmic_power_regulator_set_voltage + (regulator, config->voltage)); + break; + case REGU_VAUDIO: + case REGU_VIOHI: + case REGU_VRFBG: + CHECK_ERROR(pmic_power_regulator_set_lp_mode + (regulator, config->lp_mode)); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + return PMIC_SUCCESS; +} + +/*! + * This function retrives the regulator output configuration. + * + * @param regulator The regulator to be truned off. + * @param config Pointer to regulator configuration. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_regulator_get_config(t_pmic_regulator regulator, + t_regulator_config * config) +{ + if (config == NULL) { + return PMIC_ERROR; + } + + switch (regulator) { + case SW_SW1A: + case SW_SW1B: + case SW_SW2A: + case SW_SW2B: + CHECK_ERROR(pmic_power_regulator_get_voltage + (regulator, &config->voltage)); + CHECK_ERROR(pmic_power_switcher_get_dvs + (regulator, &config->voltage_lvs)); + CHECK_ERROR(pmic_power_switcher_get_stby + (regulator, &config->voltage_stby)); + CHECK_ERROR(pmic_power_switcher_get_mode + (regulator, &config->mode, false)); + CHECK_ERROR(pmic_power_switcher_get_mode + (regulator, &config->stby_mode, true)); + CHECK_ERROR(pmic_power_switcher_get_dvs_speed + (regulator, &config->dvs_speed)); + CHECK_ERROR(pmic_power_switcher_get_panic_mode + (regulator, &config->panic_mode)); + CHECK_ERROR(pmic_power_switcher_get_softstart + (regulator, &config->softstart)); + break; + case SW_PLL: + CHECK_ERROR(pmic_power_switcher_get_factor + (regulator, &config->factor)); + break; + case SW_SW3: + case REGU_VIOLO: + case REGU_VDIG: + case REGU_VGEN: + case REGU_VRFDIG: + case REGU_VRFREF: + case REGU_VRFCP: + case REGU_VSIM: + case REGU_VESIM: + case REGU_VCAM: + case REGU_VRF1: + case REGU_VRF2: + case REGU_VMMC1: + case REGU_VMMC2: + CHECK_ERROR(pmic_power_regulator_get_voltage + (regulator, &config->voltage)); + CHECK_ERROR(pmic_power_regulator_get_lp_mode + (regulator, &config->lp_mode)); + break; + case REGU_VVIB: + CHECK_ERROR(pmic_power_regulator_get_voltage + (regulator, &config->voltage)); + break; + case REGU_VAUDIO: + case REGU_VIOHI: + case REGU_VRFBG: + CHECK_ERROR(pmic_power_regulator_get_lp_mode + (regulator, &config->lp_mode)); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + return PMIC_SUCCESS; +} + +/*! + * This function enables automatically VBKUP2 in the memory hold modes. + * Only on mc13783 2.0 or higher + * + * @param en if true, enable VBKUP2AUTOMH + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_vbkup2_auto_en(bool en) +{ + unsigned int reg_val = 0, reg_mask = 0; + pmic_version_t mc13783_ver; + mc13783_ver = pmic_get_version(); + if (mc13783_ver.revision >= 20) { + reg_val = BITFVAL(MC13783_REGCTRL_VBKUP2AUTOMH, en); + reg_mask = BITFMASK(MC13783_REGCTRL_VBKUP2AUTOMH); + + CHECK_ERROR(pmic_write_reg(REG_POWER_CONTROL_0, + reg_val, reg_mask)); + return PMIC_SUCCESS; + } else { + return PMIC_NOT_SUPPORTED; + } +} + +/*! + * This function gets state of automatically VBKUP2. + * Only on mc13783 2.0 or higher + * + * @param en if true, VBKUP2AUTOMH is enabled + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_get_vbkup2_auto_state(bool * en) +{ + unsigned int reg_val = 0, reg_mask = 0; + pmic_version_t mc13783_ver; + mc13783_ver = pmic_get_version(); + if (mc13783_ver.revision >= 20) { + reg_mask = BITFMASK(MC13783_REGCTRL_VBKUP2AUTOMH); + CHECK_ERROR(pmic_read_reg(REG_POWER_CONTROL_0, + ®_val, reg_mask)); + *en = BITFEXT(reg_val, MC13783_REGCTRL_VBKUP2AUTOMH); + + return PMIC_SUCCESS; + } else { + return PMIC_NOT_SUPPORTED; + } +} + +/*! + * This function enables battery detect function. + * Only on mc13783 2.0 or higher + * + * @param en if true, enable BATTDETEN + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_bat_det_en(bool en) +{ + unsigned int reg_val = 0, reg_mask = 0; + pmic_version_t mc13783_ver; + mc13783_ver = pmic_get_version(); + if (mc13783_ver.revision >= 20) { + reg_val = BITFVAL(MC13783_REGCTRL_BATTDETEN, en); + reg_mask = BITFMASK(MC13783_REGCTRL_BATTDETEN); + + CHECK_ERROR(pmic_write_reg(REG_POWER_CONTROL_0, + reg_val, reg_mask)); + return PMIC_SUCCESS; + } else { + return PMIC_NOT_SUPPORTED; + } +} + +/*! + * This function gets state of battery detect function. + * Only on mc13783 2.0 or higher + * + * @param en if true, BATTDETEN is enabled + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_get_bat_det_state(bool * en) +{ + unsigned int reg_val = 0, reg_mask = 0; + pmic_version_t mc13783_ver; + mc13783_ver = pmic_get_version(); + if (mc13783_ver.revision >= 20) { + reg_mask = BITFMASK(MC13783_REGCTRL_BATTDETEN); + + CHECK_ERROR(pmic_read_reg(REG_POWER_CONTROL_0, + ®_val, reg_mask)); + *en = BITFEXT(reg_val, MC13783_REGCTRL_BATTDETEN); + return PMIC_SUCCESS; + } else { + return PMIC_NOT_SUPPORTED; + } +} + +/*! + * This function enables control of VVIB by VIBEN pin. + * Only on mc13783 2.0 or higher + * + * @param en if true, enable VIBPINCTRL + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_vib_pin_en(bool en) +{ + unsigned int reg_val = 0, reg_mask = 0; + pmic_version_t mc13783_ver; + mc13783_ver = pmic_get_version(); + if (mc13783_ver.revision >= 20) { + reg_val = BITFVAL(MC13783_REGCTRL_VIBPINCTRL, en); + reg_mask = BITFMASK(MC13783_REGCTRL_VIBPINCTRL); + + CHECK_ERROR(pmic_write_reg(REG_POWER_MISCELLANEOUS, + reg_val, reg_mask)); + return PMIC_SUCCESS; + } else { + return PMIC_NOT_SUPPORTED; + } +} + +/*! + * This function gets state of control of VVIB by VIBEN pin. + * Only on mc13783 2.0 or higher + * @param en if true, VIBPINCTRL is enabled + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_gets_vib_pin_state(bool * en) +{ + unsigned int reg_val = 0, reg_mask = 0; + pmic_version_t mc13783_ver; + mc13783_ver = pmic_get_version(); + if (mc13783_ver.revision >= 20) { + reg_mask = BITFMASK(MC13783_REGCTRL_VIBPINCTRL); + CHECK_ERROR(pmic_read_reg(REG_POWER_MISCELLANEOUS, + ®_val, reg_mask)); + *en = BITFEXT(reg_val, MC13783_REGCTRL_VIBPINCTRL); + return PMIC_SUCCESS; + } else { + return PMIC_NOT_SUPPORTED; + } +} + +/*! + * This function returns power up sense value + * + * @param p_up_sense value of power up sense + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_get_power_mode_sense(struct t_p_up_sense * p_up_sense) +{ + unsigned int reg_value = 0; + CHECK_ERROR(pmic_read_reg(REG_POWER_UP_MODE_SENSE, + ®_value, PMIC_ALL_BITS)); + p_up_sense->state_ictest = (STATE_ICTEST_MASK & reg_value); + p_up_sense->state_clksel = ((STATE_CLKSEL_MASK & reg_value) + >> STATE_CLKSEL_BIT); + p_up_sense->state_pums1 = ((STATE_PUMS1_MASK & reg_value) + >> STATE_PUMS1_BITS); + p_up_sense->state_pums2 = ((STATE_PUMS2_MASK & reg_value) + >> STATE_PUMS2_BITS); + p_up_sense->state_pums3 = ((STATE_PUMS3_MASK & reg_value) + >> STATE_PUMS3_BITS); + p_up_sense->state_chrgmode0 = ((STATE_CHRGM1_MASK & reg_value) + >> STATE_CHRGM1_BITS); + p_up_sense->state_chrgmode1 = ((STATE_CHRGM2_MASK & reg_value) + >> STATE_CHRGM2_BITS); + p_up_sense->state_umod = ((STATE_UMOD_MASK & reg_value) + >> STATE_UMOD_BITS); + p_up_sense->state_usben = ((STATE_USBEN_MASK & reg_value) + >> STATE_USBEN_BIT); + p_up_sense->state_sw_1a1b_joined = ((STATE_SW1A_J_B_MASK & reg_value) + >> STATE_SW1A_J_B_BIT); + p_up_sense->state_sw_2a2b_joined = ((STATE_SW2A_J_B_MASK & reg_value) + >> STATE_SW2A_J_B_BIT); + return PMIC_SUCCESS; +} + +/*! + * This function configures the Regen assignment for all regulator + * + * @param regulator type of regulator + * @param en_dis if true, the regulator is enabled by regen. + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_power_set_regen_assig(t_pmic_regulator regulator, bool en_dis) +{ + unsigned int reg_val = 0, reg_mask = 0; + + switch (regulator) { + case REGU_VAUDIO: + reg_val = BITFVAL(MC13783_REGGEN_VAUDIO, en_dis); + reg_mask = BITFMASK(MC13783_REGGEN_VAUDIO); + break; + case REGU_VIOHI: + reg_val = BITFVAL(MC13783_REGGEN_VIOHI, en_dis); + reg_mask = BITFMASK(MC13783_REGGEN_VIOHI); + break; + case REGU_VIOLO: + reg_val = BITFVAL(MC13783_REGGEN_VIOLO, en_dis); + reg_mask = BITFMASK(MC13783_REGGEN_VIOLO); + break; + case REGU_VDIG: + reg_val = BITFVAL(MC13783_REGGEN_VDIG, en_dis); + reg_mask = BITFMASK(MC13783_REGGEN_VDIG); + break; + case REGU_VGEN: + reg_val = BITFVAL(MC13783_REGGEN_VGEN, en_dis); + reg_mask = BITFMASK(MC13783_REGGEN_VGEN); + break; + case REGU_VRFDIG: + reg_val = BITFVAL(MC13783_REGGEN_VRFDIG, en_dis); + reg_mask = BITFMASK(MC13783_REGGEN_VRFDIG); + break; + case REGU_VRFREF: + reg_val = BITFVAL(MC13783_REGGEN_VRFREF, en_dis); + reg_mask = BITFMASK(MC13783_REGGEN_VRFREF); + break; + case REGU_VRFCP: + reg_val = BITFVAL(MC13783_REGGEN_VRFCP, en_dis); + reg_mask = BITFMASK(MC13783_REGGEN_VRFCP); + break; + case REGU_VCAM: + reg_val = BITFVAL(MC13783_REGGEN_VCAM, en_dis); + reg_mask = BITFMASK(MC13783_REGGEN_VCAM); + break; + case REGU_VRFBG: + reg_val = BITFVAL(MC13783_REGGEN_VRFBG, en_dis); + reg_mask = BITFMASK(MC13783_REGGEN_VRFBG); + break; + case REGU_VRF1: + reg_val = BITFVAL(MC13783_REGGEN_VRF1, en_dis); + reg_mask = BITFMASK(MC13783_REGGEN_VRF1); + break; + case REGU_VRF2: + reg_val = BITFVAL(MC13783_REGGEN_VRF2, en_dis); + reg_mask = BITFMASK(MC13783_REGGEN_VRF2); + break; + case REGU_VMMC1: + reg_val = BITFVAL(MC13783_REGGEN_VMMC1, en_dis); + reg_mask = BITFMASK(MC13783_REGGEN_VMMC1); + break; + case REGU_VMMC2: + reg_val = BITFVAL(MC13783_REGGEN_VMMC2, en_dis); + reg_mask = BITFMASK(MC13783_REGGEN_VMMC2); + break; + case REGU_GPO1: + reg_val = BITFVAL(MC13783_REGGEN_GPO1, en_dis); + reg_mask = BITFMASK(MC13783_REGGEN_GPO1); + break; + case REGU_GPO2: + reg_val = BITFVAL(MC13783_REGGEN_GPO2, en_dis); + reg_mask = BITFMASK(MC13783_REGGEN_GPO2); + break; + case REGU_GPO3: + reg_val = BITFVAL(MC13783_REGGEN_GPO3, en_dis); + reg_mask = BITFMASK(MC13783_REGGEN_GPO3); + break; + case REGU_GPO4: + reg_val = BITFVAL(MC13783_REGGEN_GPO4, en_dis); + reg_mask = BITFMASK(MC13783_REGGEN_GPO4); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(REG_REGEN_ASSIGNMENT, reg_val, reg_mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function gets the Regen assignment for all regulator + * + * @param regulator type of regulator + * @param en_dis return value, if true : + * the regulator is enabled by regen. + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_power_get_regen_assig(t_pmic_regulator regulator, + bool * en_dis) +{ + unsigned int reg_val = 0, reg_mask = 0; + + switch (regulator) { + case REGU_VAUDIO: + reg_mask = BITFMASK(MC13783_REGGEN_VAUDIO); + break; + case REGU_VIOHI: + reg_mask = BITFMASK(MC13783_REGGEN_VIOHI); + break; + case REGU_VIOLO: + reg_mask = BITFMASK(MC13783_REGGEN_VIOLO); + break; + case REGU_VDIG: + reg_mask = BITFMASK(MC13783_REGGEN_VDIG); + break; + case REGU_VGEN: + reg_mask = BITFMASK(MC13783_REGGEN_VGEN); + break; + case REGU_VRFDIG: + reg_mask = BITFMASK(MC13783_REGGEN_VRFDIG); + break; + case REGU_VRFREF: + reg_mask = BITFMASK(MC13783_REGGEN_VRFREF); + break; + case REGU_VRFCP: + reg_mask = BITFMASK(MC13783_REGGEN_VRFCP); + break; + case REGU_VCAM: + reg_mask = BITFMASK(MC13783_REGGEN_VCAM); + break; + case REGU_VRFBG: + reg_mask = BITFMASK(MC13783_REGGEN_VRFBG); + break; + case REGU_VRF1: + reg_mask = BITFMASK(MC13783_REGGEN_VRF1); + break; + case REGU_VRF2: + reg_mask = BITFMASK(MC13783_REGGEN_VRF2); + break; + case REGU_VMMC1: + reg_mask = BITFMASK(MC13783_REGGEN_VMMC1); + break; + case REGU_VMMC2: + reg_mask = BITFMASK(MC13783_REGGEN_VMMC2); + break; + case REGU_GPO1: + reg_mask = BITFMASK(MC13783_REGGEN_GPO1); + break; + case REGU_GPO2: + reg_mask = BITFMASK(MC13783_REGGEN_GPO2); + break; + case REGU_GPO3: + reg_mask = BITFMASK(MC13783_REGGEN_GPO3); + break; + case REGU_GPO4: + reg_mask = BITFMASK(MC13783_REGGEN_GPO4); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(REG_REGEN_ASSIGNMENT, ®_val, reg_mask)); + + switch (regulator) { + case REGU_VAUDIO: + *en_dis = BITFEXT(reg_val, MC13783_REGGEN_VAUDIO); + break; + case REGU_VIOHI: + *en_dis = BITFEXT(reg_val, MC13783_REGGEN_VIOHI); + break; + case REGU_VIOLO: + *en_dis = BITFEXT(reg_val, MC13783_REGGEN_VIOLO); + break; + case REGU_VDIG: + *en_dis = BITFEXT(reg_val, MC13783_REGGEN_VDIG); + break; + case REGU_VGEN: + *en_dis = BITFEXT(reg_val, MC13783_REGGEN_VGEN); + break; + case REGU_VRFDIG: + *en_dis = BITFEXT(reg_val, MC13783_REGGEN_VRFDIG); + break; + case REGU_VRFREF: + *en_dis = BITFEXT(reg_val, MC13783_REGGEN_VRFREF); + break; + case REGU_VRFCP: + *en_dis = BITFEXT(reg_val, MC13783_REGGEN_VRFCP); + break; + case REGU_VCAM: + *en_dis = BITFEXT(reg_val, MC13783_REGGEN_VCAM); + break; + case REGU_VRFBG: + *en_dis = BITFEXT(reg_val, MC13783_REGGEN_VRFBG); + break; + case REGU_VRF1: + *en_dis = BITFEXT(reg_val, MC13783_REGGEN_VRF1); + break; + case REGU_VRF2: + *en_dis = BITFEXT(reg_val, MC13783_REGGEN_VRF2); + break; + case REGU_VMMC1: + *en_dis = BITFEXT(reg_val, MC13783_REGGEN_VMMC1); + break; + case REGU_VMMC2: + *en_dis = BITFEXT(reg_val, MC13783_REGGEN_VMMC2); + break; + case REGU_GPO1: + *en_dis = BITFEXT(reg_val, MC13783_REGGEN_GPO1); + break; + case REGU_GPO2: + *en_dis = BITFEXT(reg_val, MC13783_REGGEN_GPO2); + break; + case REGU_GPO3: + *en_dis = BITFEXT(reg_val, MC13783_REGGEN_GPO3); + break; + case REGU_GPO4: + *en_dis = BITFEXT(reg_val, MC13783_REGGEN_GPO4); + break; + default: + break; + } + + return PMIC_SUCCESS; +} + +/*! + * This function sets the Regen polarity. + * + * @param en_dis If true regen is inverted. + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_power_set_regen_inv(bool en_dis) +{ + unsigned int reg_val = 0, reg_mask = 0; + + reg_val = BITFVAL(MC13783_REGGEN_INV, en_dis); + reg_mask = BITFMASK(MC13783_REGGEN_INV); + + CHECK_ERROR(pmic_write_reg(REG_REGEN_ASSIGNMENT, reg_val, reg_mask)); + return PMIC_SUCCESS; +} + +/*! + * This function gets the Regen polarity. + * + * @param en_dis If true regen is inverted. + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_power_get_regen_inv(bool * en_dis) +{ + unsigned int reg_val = 0, reg_mask = 0; + + reg_mask = BITFMASK(MC13783_REGGEN_INV); + CHECK_ERROR(pmic_read_reg(REG_REGEN_ASSIGNMENT, ®_val, reg_mask)); + *en_dis = BITFEXT(reg_val, MC13783_REGGEN_INV); + return PMIC_SUCCESS; +} + +/*! + * This function enables esim control voltage. + * Only on mc13783 2.0 or higher + * + * @param vesim if true, enable VESIMESIMEN + * @param vmmc1 if true, enable VMMC1ESIMEN + * @param vmmc2 if true, enable VMMC2ESIMEN + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_power_esim_v_en(bool vesim, bool vmmc1, bool vmmc2) +{ + unsigned int reg_val = 0, reg_mask = 0; + pmic_version_t mc13783_ver; + mc13783_ver = pmic_get_version(); + if (mc13783_ver.revision >= 20) { + reg_val = BITFVAL(MC13783_REGGEN_VESIMESIM, vesim) | + BITFVAL(MC13783_REGGEN_VMMC1ESIM, vesim) | + BITFVAL(MC13783_REGGEN_VMMC2ESIM, vesim); + reg_mask = BITFMASK(MC13783_REGGEN_VESIMESIM) | + BITFMASK(MC13783_REGGEN_VMMC1ESIM) | + BITFMASK(MC13783_REGGEN_VMMC2ESIM); + CHECK_ERROR(pmic_write_reg(REG_REGEN_ASSIGNMENT, + reg_val, reg_mask)); + return PMIC_SUCCESS; + } else { + return PMIC_NOT_SUPPORTED; + } +} + +/*! + * This function gets esim control voltage values. + * Only on mc13783 2.0 or higher + * + * @param vesim if true, enable VESIMESIMEN + * @param vmmc1 if true, enable VMMC1ESIMEN + * @param vmmc2 if true, enable VMMC2ESIMEN + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_power_gets_esim_v_state(bool * vesim, bool * vmmc1, + bool * vmmc2) +{ + unsigned int reg_val = 0, reg_mask = 0; + pmic_version_t mc13783_ver; + mc13783_ver = pmic_get_version(); + if (mc13783_ver.revision >= 20) { + reg_mask = BITFMASK(MC13783_REGGEN_VESIMESIM) | + BITFMASK(MC13783_REGGEN_VMMC1ESIM) | + BITFMASK(MC13783_REGGEN_VMMC2ESIM); + CHECK_ERROR(pmic_read_reg(REG_REGEN_ASSIGNMENT, + ®_val, reg_mask)); + *vesim = BITFEXT(reg_val, MC13783_REGGEN_VESIMESIM); + *vmmc1 = BITFEXT(reg_val, MC13783_REGGEN_VMMC1ESIM); + *vmmc2 = BITFEXT(reg_val, MC13783_REGGEN_VMMC2ESIM); + return PMIC_SUCCESS; + } else { + return PMIC_NOT_SUPPORTED; + } +} + +/*! + * This function enables auto reset after a system reset. + * + * @param en if true, the auto reset is enabled + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_power_set_auto_reset_en(bool en) +{ + unsigned int reg_val = 0, reg_mask = 0; + + reg_val = BITFVAL(MC13783_AUTO_RESTART, en); + reg_mask = BITFMASK(MC13783_AUTO_RESTART); + + CHECK_ERROR(pmic_write_reg(REG_POWER_CONTROL_2, reg_val, reg_mask)); + return PMIC_SUCCESS; +} + +/*! + * This function gets auto reset configuration. + * + * @param en if true, the auto reset is enabled + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_power_get_auto_reset_en(bool * en) +{ + unsigned int reg_val = 0, reg_mask = 0; + + reg_mask = BITFMASK(MC13783_AUTO_RESTART); + CHECK_ERROR(pmic_read_reg(REG_POWER_CONTROL_2, ®_val, reg_mask)); + *en = BITFEXT(reg_val, MC13783_AUTO_RESTART); + return PMIC_SUCCESS; +} + +/*! + * This function configures a system reset on a button. + * + * @param bt type of button. + * @param sys_rst if true, enable the system reset on this button + * @param deb_time sets the debounce time on this button pin + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_power_set_conf_button(t_button bt, bool sys_rst, int deb_time) +{ + int max_val = 0; + unsigned int reg_val = 0, reg_mask = 0; + + max_val = (1 << MC13783_DEB_BT_ON1B_WID) - 1; + if (deb_time > max_val) { + return PMIC_PARAMETER_ERROR; + } + + switch (bt) { + case BT_ON1B: + reg_val = BITFVAL(MC13783_EN_BT_ON1B, sys_rst) | + BITFVAL(MC13783_DEB_BT_ON1B, deb_time); + reg_mask = BITFMASK(MC13783_EN_BT_ON1B) | + BITFMASK(MC13783_DEB_BT_ON1B); + break; + case BT_ON2B: + reg_val = BITFVAL(MC13783_EN_BT_ON2B, sys_rst) | + BITFVAL(MC13783_DEB_BT_ON2B, deb_time); + reg_mask = BITFMASK(MC13783_EN_BT_ON2B) | + BITFMASK(MC13783_DEB_BT_ON2B); + break; + case BT_ON3B: + reg_val = BITFVAL(MC13783_EN_BT_ON3B, sys_rst) | + BITFVAL(MC13783_DEB_BT_ON3B, deb_time); + reg_mask = BITFMASK(MC13783_EN_BT_ON3B) | + BITFMASK(MC13783_DEB_BT_ON3B); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(REG_POWER_CONTROL_2, reg_val, reg_mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function gets configuration of a button. + * + * @param bt type of button. + * @param sys_rst if true, the system reset is enabled on this button + * @param deb_time gets the debounce time on this button pin + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_power_get_conf_button(t_button bt, + bool * sys_rst, int *deb_time) +{ + unsigned int reg_val = 0, reg_mask = 0; + + switch (bt) { + case BT_ON1B: + reg_mask = BITFMASK(MC13783_EN_BT_ON1B) | + BITFMASK(MC13783_DEB_BT_ON1B); + break; + case BT_ON2B: + reg_mask = BITFMASK(MC13783_EN_BT_ON2B) | + BITFMASK(MC13783_DEB_BT_ON2B); + break; + case BT_ON3B: + reg_mask = BITFMASK(MC13783_EN_BT_ON3B) | + BITFMASK(MC13783_DEB_BT_ON3B); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(REG_POWER_CONTROL_2, ®_val, reg_mask)); + + switch (bt) { + case BT_ON1B: + *sys_rst = BITFEXT(reg_val, MC13783_EN_BT_ON1B); + *deb_time = BITFEXT(reg_val, MC13783_DEB_BT_ON1B); + break; + case BT_ON2B: + *sys_rst = BITFEXT(reg_val, MC13783_EN_BT_ON2B); + *deb_time = BITFEXT(reg_val, MC13783_DEB_BT_ON2B); + break; + case BT_ON3B: + *sys_rst = BITFEXT(reg_val, MC13783_EN_BT_ON3B); + *deb_time = BITFEXT(reg_val, MC13783_DEB_BT_ON3B); + break; + default: + return PMIC_PARAMETER_ERROR; + } + return PMIC_SUCCESS; +} + +/*! + * This function is used to un/subscribe on power event IT. + * + * @param event type of event. + * @param callback event callback function. + * @param sub define if Un/subscribe event. + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_power_event(t_pwr_int event, void *callback, bool sub) +{ + pmic_event_callback_t power_callback; + type_event power_event; + + power_callback.func = callback; + power_callback.param = NULL; + switch (event) { + case PWR_IT_BPONI: + power_event = EVENT_BPONI; + break; + case PWR_IT_LOBATLI: + power_event = EVENT_LOBATLI; + break; + case PWR_IT_LOBATHI: + power_event = EVENT_LOBATHI; + break; + case PWR_IT_ONOFD1I: + power_event = EVENT_ONOFD1I; + break; + case PWR_IT_ONOFD2I: + power_event = EVENT_ONOFD2I; + break; + case PWR_IT_ONOFD3I: + power_event = EVENT_ONOFD3I; + break; + case PWR_IT_SYSRSTI: + power_event = EVENT_SYSRSTI; + break; + case PWR_IT_PWRRDYI: + power_event = EVENT_PWRRDYI; + break; + case PWR_IT_PCI: + power_event = EVENT_PCI; + break; + case PWR_IT_WARMI: + power_event = EVENT_WARMI; + break; + default: + return PMIC_PARAMETER_ERROR; + } + if (sub == true) { + CHECK_ERROR(pmic_event_subscribe(power_event, power_callback)); + } else { + CHECK_ERROR(pmic_event_unsubscribe + (power_event, power_callback)); + } + return PMIC_SUCCESS; +} + +/*! + * This function is used to subscribe on power event IT. + * + * @param event type of event. + * @param callback event callback function. + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_power_event_sub(t_pwr_int event, void *callback) +{ + return pmic_power_event(event, callback, true); +} + +/*! + * This function is used to un subscribe on power event IT. + * + * @param event type of event. + * @param callback event callback function. + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_power_event_unsub(t_pwr_int event, void *callback) +{ + return pmic_power_event(event, callback, false); +} + +/* + * Init and Exit + */ + +static int pmic_power_probe(struct platform_device *pdev) +{ + printk(KERN_INFO "PMIC Power successfully probed\n"); + return 0; +} + +static struct platform_driver pmic_power_driver_ldm = { + .driver = { + .name = "pmic_power", + }, + .suspend = pmic_power_suspend, + .resume = pmic_power_resume, + .probe = pmic_power_probe, + .remove = NULL, +}; + +static int __init pmic_power_init(void) +{ + pr_debug("PMIC Power driver loading..\n"); + return platform_driver_register(&pmic_power_driver_ldm); +} +static void __exit pmic_power_exit(void) +{ + platform_driver_unregister(&pmic_power_driver_ldm); + pr_debug("PMIC Power driver successfully unloaded\n"); +} + +/* + * Module entry points + */ + +subsys_initcall(pmic_power_init); +module_exit(pmic_power_exit); + +MODULE_DESCRIPTION("pmic_power driver"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/pmic/mc13783/pmic_power_defs.h b/drivers/mxc/pmic/mc13783/pmic_power_defs.h new file mode 100644 index 000000000000..38e554146a70 --- /dev/null +++ b/drivers/mxc/pmic/mc13783/pmic_power_defs.h @@ -0,0 +1,509 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mc13783/pmic_power_defs.h + * @brief This is the internal header define of PMIC(mc13783) Power driver. + * + * @ingroup PMIC_POWER + */ + +/* + * Includes + */ + +#ifndef __MC13783_POWER_DEFS_H__ +#define __MC13783_POWER_DEFS_H__ + +/* + * Power Up Mode Sense bits + */ + +#define STATE_ICTEST_MASK 0x000001 + +#define STATE_CLKSEL_BIT 1 +#define STATE_CLKSEL_MASK 0x000002 + +#define STATE_PUMS1_BITS 2 +#define STATE_PUMS1_MASK 0x00000C + +#define STATE_PUMS2_BITS 4 +#define STATE_PUMS2_MASK 0x000030 + +#define STATE_PUMS3_BITS 6 +#define STATE_PUMS3_MASK 0x0000C0 + +#define STATE_CHRGM1_BITS 8 +#define STATE_CHRGM1_MASK 0x000300 + +#define STATE_CHRGM2_BITS 10 +#define STATE_CHRGM2_MASK 0x000C00 + +#define STATE_UMOD_BITS 12 +#define STATE_UMOD_MASK 0x003000 + +#define STATE_USBEN_BIT 14 +#define STATE_USBEN_MASK 0x004000 + +#define STATE_SW1A_J_B_BIT 15 +#define STATE_SW1A_J_B_MASK 0x008000 + +#define STATE_SW2A_J_B_BIT 16 +#define STATE_SW2A_J_B_MASK 0x010000 + +#define PC_COUNT_MAX 3 +#define PC_COUNT_MIN 0 +/* + * Reg Regen + */ +#define MC13783_REGGEN_VAUDIO_LSH 0 +#define MC13783_REGGEN_VAUDIO_WID 1 +#define MC13783_REGGEN_VIOHI_LSH 1 +#define MC13783_REGGEN_VIOHI_WID 1 +#define MC13783_REGGEN_VIOLO_LSH 2 +#define MC13783_REGGEN_VIOLO_WID 1 +#define MC13783_REGGEN_VDIG_LSH 3 +#define MC13783_REGGEN_VDIG_WID 1 +#define MC13783_REGGEN_VGEN_LSH 4 +#define MC13783_REGGEN_VGEN_WID 1 +#define MC13783_REGGEN_VRFDIG_LSH 5 +#define MC13783_REGGEN_VRFDIG_WID 1 +#define MC13783_REGGEN_VRFREF_LSH 6 +#define MC13783_REGGEN_VRFREF_WID 1 +#define MC13783_REGGEN_VRFCP_LSH 7 +#define MC13783_REGGEN_VRFCP_WID 1 +#define MC13783_REGGEN_VCAM_LSH 8 +#define MC13783_REGGEN_VCAM_WID 1 +#define MC13783_REGGEN_VRFBG_LSH 9 +#define MC13783_REGGEN_VRFBG_WID 1 +#define MC13783_REGGEN_VRF1_LSH 10 +#define MC13783_REGGEN_VRF1_WID 1 +#define MC13783_REGGEN_VRF2_LSH 11 +#define MC13783_REGGEN_VRF2_WID 1 +#define MC13783_REGGEN_VMMC1_LSH 12 +#define MC13783_REGGEN_VMMC1_WID 1 +#define MC13783_REGGEN_VMMC2_LSH 13 +#define MC13783_REGGEN_VMMC2_WID 1 +#define MC13783_REGGEN_GPO1_LSH 16 +#define MC13783_REGGEN_GPO1_WID 1 +#define MC13783_REGGEN_GPO2_LSH 17 +#define MC13783_REGGEN_GPO2_WID 1 +#define MC13783_REGGEN_GPO3_LSH 18 +#define MC13783_REGGEN_GPO3_WID 1 +#define MC13783_REGGEN_GPO4_LSH 19 +#define MC13783_REGGEN_GPO4_WID 1 +#define MC13783_REGGEN_INV_LSH 20 +#define MC13783_REGGEN_INV_WID 1 +#define MC13783_REGGEN_VESIMESIM_LSH 21 +#define MC13783_REGGEN_VESIMESIM_WID 1 +#define MC13783_REGGEN_VMMC1ESIM_LSH 22 +#define MC13783_REGGEN_VMMC1ESIM_WID 1 +#define MC13783_REGGEN_VMMC2ESIM_LSH 23 +#define MC13783_REGGEN_VMMC2ESIM_WID 1 + +/* + * Reg Power Control 0 + */ +#define MC13783_PWRCTRL_PCEN_LSH 0 +#define MC13783_PWRCTRL_PCEN_WID 1 +#define MC13783_PWRCTRL_PCEN_ENABLE 1 +#define MC13783_PWRCTRL_PCEN_DISABLE 0 +#define MC13783_PWRCTRL_PC_COUNT_EN_LSH 1 +#define MC13783_PWRCTRL_PC_COUNT_EN_WID 1 +#define MC13783_PWRCTRL_PC_COUNT_EN_ENABLE 1 +#define MC13783_PWRCTRL_PC_COUNT_EN_DISABLE 0 +#define MC13783_PWRCTRL_WARM_EN_LSH 2 +#define MC13783_PWRCTRL_WARM_EN_WID 1 +#define MC13783_PWRCTRL_WARM_EN_ENABLE 1 +#define MC13783_PWRCTRL_WARM_EN_DISABLE 0 +#define MC13783_PWRCTRL_USER_OFF_SPI_LSH 3 +#define MC13783_PWRCTRL_USER_OFF_SPI_WID 1 +#define MC13783_PWRCTRL_USER_OFF_SPI_ENABLE 1 +#define MC13783_PWRCTRL_USER_OFF_PC_LSH 4 +#define MC13783_PWRCTRL_USER_OFF_PC_WID 1 +#define MC13783_PWRCTRL_USER_OFF_PC_ENABLE 1 +#define MC13783_PWRCTRL_USER_OFF_PC_DISABLE 0 +#define MC13783_PWRCTRL_32OUT_USER_OFF_LSH 5 +#define MC13783_PWRCTRL_32OUT_USER_OFF_WID 1 +#define MC13783_PWRCTRL_32OUT_USER_OFF_ENABLE 1 +#define MC13783_PWRCTRL_32OUT_USER_OFF_DISABLE 0 +#define MC13783_PWRCTRL_32OUT_EN_LSH 6 +#define MC13783_PWRCTRL_32OUT_EN_WID 1 +#define MC13783_PWRCTRL_32OUT_EN_ENABLE 1 +#define MC13783_PWRCTRL_32OUT_EN_DISABLE 0 +#define MC13783_REGCTRL_VBKUP2AUTOMH_LSH 7 +#define MC13783_REGCTRL_VBKUP2AUTOMH_WID 1 +#define MC13783_PWRCTRL_VBKUP1_EN_LSH 8 +#define MC13783_PWRCTRL_VBKUP1_EN_WID 1 +#define MC13783_PWRCTRL_VBKUP_ENABLE 1 +#define MC13783_PWRCTRL_VBKUP_DISABLE 0 +#define MC13783_PWRCTRL_VBKUP1_AUTO_EN_LSH 9 +#define MC13783_PWRCTRL_VBKUP1_AUTO_EN_WID 1 +#define MC13783_PWRCTRL_VBKUP1_LSH 10 +#define MC13783_PWRCTRL_VBKUP1_WID 2 +#define MC13783_PWRCTRL_VBKUP2_EN_LSH 12 +#define MC13783_PWRCTRL_VBKUP2_EN_WID 1 +#define MC13783_PWRCTRL_VBKUP2_AUTO_EN_LSH 13 +#define MC13783_PWRCTRL_VBKUP2_AUTO_EN_WID 1 +#define MC13783_PWRCTRL_VBKUP2_LSH 14 +#define MC13783_PWRCTRL_VBKUP2_WID 2 +#define MC13783_REGCTRL_BATTDETEN_LSH 19 +#define MC13783_REGCTRL_BATTDETEN_WID 1 + +/* + * Reg Power Control 1 + */ +#define MC13783_PWRCTRL_PCT_LSH 0 +#define MC13783_PWRCTRL_PCT_WID 8 +#define MC13783_PWRCTRL_PC_COUNT_LSH 8 +#define MC13783_PWRCTRL_PC_COUNT_WID 4 +#define MC13783_PWRCTRL_PC_MAX_CNT_LSH 12 +#define MC13783_PWRCTRL_PC_MAX_CNT_WID 4 +#define MC13783_PWRCTRL_MEM_TMR_LSH 16 +#define MC13783_PWRCTRL_MEM_TMR_WID 4 +#define MC13783_PWRCTRL_MEM_ALLON_LSH 20 +#define MC13783_PWRCTRL_MEM_ALLON_WID 1 +#define MC13783_PWRCTRL_MEM_ALLON_ENABLE 1 +#define MC13783_PWRCTRL_MEM_ALLON_DISABLE 0 + +/* + * Reg Power Control 2 + */ +#define MC13783_AUTO_RESTART_LSH 0 +#define MC13783_AUTO_RESTART_WID 1 +#define MC13783_EN_BT_ON1B_LSH 1 +#define MC13783_EN_BT_ON1B_WID 1 +#define MC13783_EN_BT_ON2B_LSH 2 +#define MC13783_EN_BT_ON2B_WID 1 +#define MC13783_EN_BT_ON3B_LSH 3 +#define MC13783_EN_BT_ON3B_WID 1 +#define MC13783_DEB_BT_ON1B_LSH 4 +#define MC13783_DEB_BT_ON1B_WID 2 +#define MC13783_DEB_BT_ON2B_LSH 6 +#define MC13783_DEB_BT_ON2B_WID 2 +#define MC13783_DEB_BT_ON3B_LSH 8 +#define MC13783_DEB_BT_ON3B_WID 2 + +/* + * Reg Regulator Mode 0 + */ +#define MC13783_REGCTRL_VAUDIO_EN_LSH 0 +#define MC13783_REGCTRL_VAUDIO_EN_WID 1 +#define MC13783_REGCTRL_VAUDIO_EN_ENABLE 1 +#define MC13783_REGCTRL_VAUDIO_EN_DISABLE 0 +#define MC13783_REGCTRL_VAUDIO_STBY_LSH 1 +#define MC13783_REGCTRL_VAUDIO_STBY_WID 1 +#define MC13783_REGCTRL_VAUDIO_MODE_LSH 2 +#define MC13783_REGCTRL_VAUDIO_MODE_WID 1 +#define MC13783_REGCTRL_VIOHI_EN_LSH 3 +#define MC13783_REGCTRL_VIOHI_EN_WID 1 +#define MC13783_REGCTRL_VIOHI_EN_ENABLE 1 +#define MC13783_REGCTRL_VIOHI_EN_DISABLE 0 +#define MC13783_REGCTRL_VIOHI_STBY_LSH 4 +#define MC13783_REGCTRL_VIOHI_STBY_WID 1 +#define MC13783_REGCTRL_VIOHI_MODE_LSH 5 +#define MC13783_REGCTRL_VIOHI_MODE_WID 1 +#define MC13783_REGCTRL_VIOLO_EN_LSH 6 +#define MC13783_REGCTRL_VIOLO_EN_WID 1 +#define MC13783_REGCTRL_VIOLO_EN_ENABLE 1 +#define MC13783_REGCTRL_VIOLO_EN_DISABLE 0 +#define MC13783_REGCTRL_VIOLO_STBY_LSH 7 +#define MC13783_REGCTRL_VIOLO_STBY_WID 1 +#define MC13783_REGCTRL_VIOLO_MODE_LSH 8 +#define MC13783_REGCTRL_VIOLO_MODE_WID 1 +#define MC13783_REGCTRL_VDIG_EN_LSH 9 +#define MC13783_REGCTRL_VDIG_EN_WID 1 +#define MC13783_REGCTRL_VDIG_EN_ENABLE 1 +#define MC13783_REGCTRL_VDIG_EN_DISABLE 0 +#define MC13783_REGCTRL_VDIG_STBY_LSH 10 +#define MC13783_REGCTRL_VDIG_STBY_WID 1 +#define MC13783_REGCTRL_VDIG_MODE_LSH 11 +#define MC13783_REGCTRL_VDIG_MODE_WID 1 +#define MC13783_REGCTRL_VGEN_EN_LSH 12 +#define MC13783_REGCTRL_VGEN_EN_WID 1 +#define MC13783_REGCTRL_VGEN_EN_ENABLE 1 +#define MC13783_REGCTRL_VGEN_EN_DISABLE 0 +#define MC13783_REGCTRL_VGEN_STBY_LSH 13 +#define MC13783_REGCTRL_VGEN_STBY_WID 1 +#define MC13783_REGCTRL_VGEN_MODE_LSH 14 +#define MC13783_REGCTRL_VGEN_MODE_WID 1 +#define MC13783_REGCTRL_VRFDIG_EN_LSH 15 +#define MC13783_REGCTRL_VRFDIG_EN_WID 1 +#define MC13783_REGCTRL_VRFDIG_EN_ENABLE 1 +#define MC13783_REGCTRL_VRFDIG_EN_DISABLE 0 +#define MC13783_REGCTRL_VRFDIG_STBY_LSH 16 +#define MC13783_REGCTRL_VRFDIG_STBY_WID 1 +#define MC13783_REGCTRL_VRFDIG_MODE_LSH 17 +#define MC13783_REGCTRL_VRFDIG_MODE_WID 1 +#define MC13783_REGCTRL_VRFREF_EN_LSH 18 +#define MC13783_REGCTRL_VRFREF_EN_WID 1 +#define MC13783_REGCTRL_VRFREF_EN_ENABLE 1 +#define MC13783_REGCTRL_VRFREF_EN_DISABLE 0 +#define MC13783_REGCTRL_VRFREF_STBY_LSH 19 +#define MC13783_REGCTRL_VRFREF_STBY_WID 1 +#define MC13783_REGCTRL_VRFREF_MODE_LSH 20 +#define MC13783_REGCTRL_VRFREF_MODE_WID 1 +#define MC13783_REGCTRL_VRFCP_EN_LSH 21 +#define MC13783_REGCTRL_VRFCP_EN_WID 1 +#define MC13783_REGCTRL_VRFCP_EN_ENABLE 1 +#define MC13783_REGCTRL_VRFCP_EN_DISABLE 0 +#define MC13783_REGCTRL_VRFCP_STBY_LSH 22 +#define MC13783_REGCTRL_VRFCP_STBY_WID 1 +#define MC13783_REGCTRL_VRFCP_MODE_LSH 23 +#define MC13783_REGCTRL_VRFCP_MODE_WID 1 + +/* + * Reg Regulator Mode 1 + */ +#define MC13783_REGCTRL_VSIM_EN_LSH 0 +#define MC13783_REGCTRL_VSIM_EN_WID 1 +#define MC13783_REGCTRL_VSIM_EN_ENABLE 1 +#define MC13783_REGCTRL_VSIM_EN_DISABLE 0 +#define MC13783_REGCTRL_VSIM_STBY_LSH 1 +#define MC13783_REGCTRL_VSIM_STBY_WID 1 +#define MC13783_REGCTRL_VSIM_MODE_LSH 2 +#define MC13783_REGCTRL_VSIM_MODE_WID 1 +#define MC13783_REGCTRL_VESIM_EN_LSH 3 +#define MC13783_REGCTRL_VESIM_EN_WID 1 +#define MC13783_REGCTRL_VESIM_EN_ENABLE 1 +#define MC13783_REGCTRL_VESIM_EN_DISABLE 0 +#define MC13783_REGCTRL_VESIM_STBY_LSH 4 +#define MC13783_REGCTRL_VESIM_STBY_WID 1 +#define MC13783_REGCTRL_VESIM_MODE_LSH 5 +#define MC13783_REGCTRL_VESIM_MODE_WID 1 +#define MC13783_REGCTRL_VCAM_EN_LSH 6 +#define MC13783_REGCTRL_VCAM_EN_WID 1 +#define MC13783_REGCTRL_VCAM_EN_ENABLE 1 +#define MC13783_REGCTRL_VCAM_EN_DISABLE 0 +#define MC13783_REGCTRL_VCAM_STBY_LSH 7 +#define MC13783_REGCTRL_VCAM_STBY_WID 1 +#define MC13783_REGCTRL_VCAM_MODE_LSH 8 +#define MC13783_REGCTRL_VCAM_MODE_WID 1 +#define MC13783_REGCTRL_VRFBG_EN_LSH 9 +#define MC13783_REGCTRL_VRFBG_EN_WID 1 +#define MC13783_REGCTRL_VRFBG_EN_ENABLE 1 +#define MC13783_REGCTRL_VRFBG_EN_DISABLE 0 +#define MC13783_REGCTRL_VRFBG_STBY_LSH 10 +#define MC13783_REGCTRL_VRFBG_STBY_WID 1 +#define MC13783_REGCTRL_VVIB_EN_LSH 11 +#define MC13783_REGCTRL_VVIB_EN_WID 1 +#define MC13783_REGCTRL_VVIB_EN_ENABLE 1 +#define MC13783_REGCTRL_VVIB_EN_DISABLE 0 +#define MC13783_REGCTRL_VRF1_EN_LSH 12 +#define MC13783_REGCTRL_VRF1_EN_WID 1 +#define MC13783_REGCTRL_VRF1_EN_ENABLE 1 +#define MC13783_REGCTRL_VRF1_EN_DISABLE 0 +#define MC13783_REGCTRL_VRF1_STBY_LSH 13 +#define MC13783_REGCTRL_VRF1_STBY_WID 1 +#define MC13783_REGCTRL_VRF1_MODE_LSH 14 +#define MC13783_REGCTRL_VRF1_MODE_WID 1 +#define MC13783_REGCTRL_VRF2_EN_LSH 15 +#define MC13783_REGCTRL_VRF2_EN_WID 1 +#define MC13783_REGCTRL_VRF2_EN_ENABLE 1 +#define MC13783_REGCTRL_VRF2_EN_DISABLE 0 +#define MC13783_REGCTRL_VRF2_STBY_LSH 16 +#define MC13783_REGCTRL_VRF2_STBY_WID 1 +#define MC13783_REGCTRL_VRF2_MODE_LSH 17 +#define MC13783_REGCTRL_VRF2_MODE_WID 1 +#define MC13783_REGCTRL_VMMC1_EN_LSH 18 +#define MC13783_REGCTRL_VMMC1_EN_WID 1 +#define MC13783_REGCTRL_VMMC1_EN_ENABLE 1 +#define MC13783_REGCTRL_VMMC1_EN_DISABLE 0 +#define MC13783_REGCTRL_VMMC1_STBY_LSH 19 +#define MC13783_REGCTRL_VMMC1_STBY_WID 1 +#define MC13783_REGCTRL_VMMC1_MODE_LSH 20 +#define MC13783_REGCTRL_VMMC1_MODE_WID 1 +#define MC13783_REGCTRL_VMMC2_EN_LSH 21 +#define MC13783_REGCTRL_VMMC2_EN_WID 1 +#define MC13783_REGCTRL_VMMC2_EN_ENABLE 1 +#define MC13783_REGCTRL_VMMC2_EN_DISABLE 0 +#define MC13783_REGCTRL_VMMC2_STBY_LSH 22 +#define MC13783_REGCTRL_VMMC2_STBY_WID 1 +#define MC13783_REGCTRL_VMMC2_MODE_LSH 23 +#define MC13783_REGCTRL_VMMC2_MODE_WID 1 + +/* + * Reg Regulator Misc. + */ +#define MC13783_REGCTRL_GPO1_EN_LSH 6 +#define MC13783_REGCTRL_GPO1_EN_WID 1 +#define MC13783_REGCTRL_GPO1_EN_ENABLE 1 +#define MC13783_REGCTRL_GPO1_EN_DISABLE 0 +#define MC13783_REGCTRL_GPO2_EN_LSH 8 +#define MC13783_REGCTRL_GPO2_EN_WID 1 +#define MC13783_REGCTRL_GPO2_EN_ENABLE 1 +#define MC13783_REGCTRL_GPO2_EN_DISABLE 0 +#define MC13783_REGCTRL_GPO3_EN_LSH 10 +#define MC13783_REGCTRL_GPO3_EN_WID 1 +#define MC13783_REGCTRL_GPO3_EN_ENABLE 1 +#define MC13783_REGCTRL_GPO3_EN_DISABLE 0 +#define MC13783_REGCTRL_GPO4_EN_LSH 12 +#define MC13783_REGCTRL_GPO4_EN_WID 1 +#define MC13783_REGCTRL_GPO4_EN_ENABLE 1 +#define MC13783_REGCTRL_GPO4_EN_DISABLE 0 +#define MC13783_REGCTRL_VIBPINCTRL_LSH 14 +#define MC13783_REGCTRL_VIBPINCTRL_WID 1 + +/* + * Reg Regulator Setting 0 + */ +#define MC13783_REGSET_VIOLO_LSH 2 +#define MC13783_REGSET_VIOLO_WID 2 +#define MC13783_REGSET_VDIG_LSH 4 +#define MC13783_REGSET_VDIG_WID 2 +#define MC13783_REGSET_VGEN_LSH 6 +#define MC13783_REGSET_VGEN_WID 3 +#define MC13783_REGSET_VRFDIG_LSH 9 +#define MC13783_REGSET_VRFDIG_WID 2 +#define MC13783_REGSET_VRFREF_LSH 11 +#define MC13783_REGSET_VRFREF_WID 2 +#define MC13783_REGSET_VRFCP_LSH 13 +#define MC13783_REGSET_VRFCP_WID 1 +#define MC13783_REGSET_VSIM_LSH 14 +#define MC13783_REGSET_VSIM_WID 1 +#define MC13783_REGSET_VESIM_LSH 15 +#define MC13783_REGSET_VESIM_WID 1 +#define MC13783_REGSET_VCAM_LSH 16 +#define MC13783_REGSET_VCAM_WID 3 + +/* + * Reg Regulator Setting 1 + */ +#define MC13783_REGSET_VVIB_LSH 0 +#define MC13783_REGSET_VVIB_WID 2 +#define MC13783_REGSET_VRF1_LSH 2 +#define MC13783_REGSET_VRF1_WID 2 +#define MC13783_REGSET_VRF2_LSH 4 +#define MC13783_REGSET_VRF2_WID 2 +#define MC13783_REGSET_VMMC1_LSH 6 +#define MC13783_REGSET_VMMC1_WID 3 +#define MC13783_REGSET_VMMC2_LSH 9 +#define MC13783_REGSET_VMMC2_WID 3 + +/* + * Reg Switcher 0 + */ +#define MC13783_SWSET_SW1A_LSH 0 +#define MC13783_SWSET_SW1A_WID 6 +#define MC13783_SWSET_SW1A_DVS_LSH 6 +#define MC13783_SWSET_SW1A_DVS_WID 6 +#define MC13783_SWSET_SW1A_STDBY_LSH 12 +#define MC13783_SWSET_SW1A_STDBY_WID 6 + +/* + * Reg Switcher 1 + */ +#define MC13783_SWSET_SW1B_LSH 0 +#define MC13783_SWSET_SW1B_WID 6 +#define MC13783_SWSET_SW1B_DVS_LSH 6 +#define MC13783_SWSET_SW1B_DVS_WID 6 +#define MC13783_SWSET_SW1B_STDBY_LSH 12 +#define MC13783_SWSET_SW1B_STDBY_WID 6 + +/* + * Reg Switcher 2 + */ +#define MC13783_SWSET_SW2A_LSH 0 +#define MC13783_SWSET_SW2A_WID 6 +#define MC13783_SWSET_SW2A_DVS_LSH 6 +#define MC13783_SWSET_SW2A_DVS_WID 6 +#define MC13783_SWSET_SW2A_STDBY_LSH 12 +#define MC13783_SWSET_SW2A_STDBY_WID 6 + +/* + * Reg Switcher 3 + */ +#define MC13783_SWSET_SW2B_LSH 0 +#define MC13783_SWSET_SW2B_WID 6 +#define MC13783_SWSET_SW2B_DVS_LSH 6 +#define MC13783_SWSET_SW2B_DVS_WID 6 +#define MC13783_SWSET_SW2B_STDBY_LSH 12 +#define MC13783_SWSET_SW2B_STDBY_WID 6 + +/* + * Reg Switcher 4 + */ +#define MC13783_SWCTRL_SW1A_MODE_LSH 0 +#define MC13783_SWCTRL_SW1A_MODE_WID 2 +#define MC13783_SWCTRL_SW1A_STBY_MODE_LSH 2 +#define MC13783_SWCTRL_SW1A_STBY_MODE_WID 2 +#define MC13783_SWCTRL_SW1A_DVS_SPEED_LSH 6 +#define MC13783_SWCTRL_SW1A_DVS_SPEED_WID 2 +#define MC13783_SWCTRL_SW1A_PANIC_MODE_LSH 8 +#define MC13783_SWCTRL_SW1A_PANIC_MODE_WID 1 +#define MC13783_SWCTRL_SW1A_SOFTSTART_LSH 9 +#define MC13783_SWCTRL_SW1A_SOFTSTART_WID 1 +#define MC13783_SWCTRL_SW1B_MODE_LSH 10 +#define MC13783_SWCTRL_SW1B_MODE_WID 2 +#define MC13783_SWCTRL_SW1B_STBY_MODE_LSH 12 +#define MC13783_SWCTRL_SW1B_STBY_MODE_WID 2 +#define MC13783_SWCTRL_SW1B_DVS_SPEED_LSH 14 +#define MC13783_SWCTRL_SW1B_DVS_SPEED_WID 2 +#define MC13783_SWCTRL_SW1B_PANIC_MODE_LSH 16 +#define MC13783_SWCTRL_SW1B_PANIC_MODE_WID 1 +#define MC13783_SWCTRL_SW1B_SOFTSTART_LSH 17 +#define MC13783_SWCTRL_SW1B_SOFTSTART_WID 1 +#define MC13783_SWCTRL_PLL_EN_LSH 18 +#define MC13783_SWCTRL_PLL_EN_WID 1 +#define MC13783_SWCTRL_PLL_EN_ENABLE 1 +#define MC13783_SWCTRL_PLL_EN_DISABLE 0 +#define MC13783_SWCTRL_PLL_FACTOR_LSH 19 +#define MC13783_SWCTRL_PLL_FACTOR_WID 3 + +/* + * Reg Switcher 5 + */ +#define MC13783_SWCTRL_SW2A_MODE_LSH 0 +#define MC13783_SWCTRL_SW2A_MODE_WID 2 +#define MC13783_SWCTRL_SW2A_STBY_MODE_LSH 2 +#define MC13783_SWCTRL_SW2A_STBY_MODE_WID 2 +#define MC13783_SWCTRL_SW2A_DVS_SPEED_LSH 6 +#define MC13783_SWCTRL_SW2A_DVS_SPEED_WID 2 +#define MC13783_SWCTRL_SW2A_PANIC_MODE_LSH 8 +#define MC13783_SWCTRL_SW2A_PANIC_MODE_WID 1 +#define MC13783_SWCTRL_SW2A_SOFTSTART_LSH 9 +#define MC13783_SWCTRL_SW2A_SOFTSTART_WID 1 +#define MC13783_SWCTRL_SW2B_MODE_LSH 10 +#define MC13783_SWCTRL_SW2B_MODE_WID 2 +#define MC13783_SWCTRL_SW2B_STBY_MODE_LSH 12 +#define MC13783_SWCTRL_SW2B_STBY_MODE_WID 2 +#define MC13783_SWCTRL_SW2B_DVS_SPEED_LSH 14 +#define MC13783_SWCTRL_SW2B_DVS_SPEED_WID 2 +#define MC13783_SWCTRL_SW2B_PANIC_MODE_LSH 16 +#define MC13783_SWCTRL_SW2B_PANIC_MODE_WID 1 +#define MC13783_SWCTRL_SW2B_SOFTSTART_LSH 17 +#define MC13783_SWCTRL_SW2B_SOFTSTART_WID 1 +#define MC13783_SWSET_SW3_LSH 18 +#define MC13783_SWSET_SW3_WID 2 +#define MC13783_SWCTRL_SW3_EN_LSH 20 +#define MC13783_SWCTRL_SW3_EN_WID 2 +#define MC13783_SWCTRL_SW3_EN_ENABLE 1 +#define MC13783_SWCTRL_SW3_EN_DISABLE 0 +#define MC13783_SWCTRL_SW3_STBY_LSH 21 +#define MC13783_SWCTRL_SW3_STBY_WID 1 +#define MC13783_SWCTRL_SW3_MODE_LSH 22 +#define MC13783_SWCTRL_SW3_MODE_WID 1 + +/* + * Switcher configuration + */ +#define MC13783_SWCTRL_SW_MODE_SYNC_RECT_EN 0 +#define MC13783_SWCTRL_SW_MODE_PULSE_NO_SKIP_EN 1 +#define MC13783_SWCTRL_SW_MODE_PULSE_SKIP_EN 2 +#define MC13783_SWCTRL_SW_MODE_LOW_POWER_EN 3 +#define MC13783_REGTRL_LP_MODE_ENABLE 1 +#define MC13783_REGTRL_LP_MODE_DISABLE 0 +#define MC13783_REGTRL_STBY_MODE_ENABLE 1 +#define MC13783_REGTRL_STBY_MODE_DISABLE 0 + +#endif /* __MC13783_POWER_DEFS_H__ */ diff --git a/drivers/mxc/pmic/mc13783/pmic_rtc.c b/drivers/mxc/pmic/mc13783/pmic_rtc.c new file mode 100644 index 000000000000..94ba4efacf60 --- /dev/null +++ b/drivers/mxc/pmic/mc13783/pmic_rtc.c @@ -0,0 +1,549 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mc13783/pmic_rtc.c + * @brief This is the main file of PMIC(mc13783) RTC driver. + * + * @ingroup PMIC_RTC + */ + +/* + * Includes + */ +#include <linux/wait.h> +#include <linux/poll.h> +#include <linux/platform_device.h> +#include <asm/arch/pmic_rtc.h> + +#include "../core/pmic_config.h" +#include "pmic_rtc_defs.h" + +#define PMIC_LOAD_ERROR_MSG \ +"PMIC card was not correctly detected. Stop loading PMIC RTC driver\n" + +/* + * Global variables + */ +static int pmic_rtc_major; +static void callback_alarm_asynchronous(void *); +static void callback_alarm_synchronous(void *); +static unsigned int pmic_rtc_poll(struct file *file, poll_table * wait); +static DECLARE_WAIT_QUEUE_HEAD(queue_alarm); +static DECLARE_WAIT_QUEUE_HEAD(pmic_rtc_wait); +static pmic_event_callback_t alarm_callback; +static pmic_event_callback_t rtc_callback; +static int pmic_rtc_detected = 0; +static bool pmic_rtc_done = 0; +static struct class *pmic_rtc_class; + +static DECLARE_MUTEX(mutex); + +/* EXPORTED FUNCTIONS */ +EXPORT_SYMBOL(pmic_rtc_set_time); +EXPORT_SYMBOL(pmic_rtc_get_time); +EXPORT_SYMBOL(pmic_rtc_set_time_alarm); +EXPORT_SYMBOL(pmic_rtc_get_time_alarm); +EXPORT_SYMBOL(pmic_rtc_wait_alarm); +EXPORT_SYMBOL(pmic_rtc_event_sub); +EXPORT_SYMBOL(pmic_rtc_event_unsub); +EXPORT_SYMBOL(pmic_rtc_loaded); + +/* + * Real Time Clock Pmic API + */ + +/*! + * This is the callback function called on TSI Pmic event, used in asynchronous + * call. + */ +static void callback_alarm_asynchronous(void *unused) +{ + pmic_rtc_done = true; +} + +/*! + * This is the callback function is used in test code for (un)sub. + */ +static void callback_test_sub(void) +{ + printk(KERN_INFO "*****************************************\n"); + printk(KERN_INFO "***** PMIC RTC 'Alarm IT CallBack' ******\n"); + printk(KERN_INFO "*****************************************\n"); +} + +/*! + * This is the callback function called on TSI Pmic event, used in synchronous + * call. + */ +static void callback_alarm_synchronous(void *unused) +{ + printk(KERN_INFO "*** Alarm IT Pmic ***\n"); + wake_up(&queue_alarm); +} + +/*! + * This function wait the Alarm event + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_rtc_wait_alarm(void) +{ + DEFINE_WAIT(wait); + alarm_callback.func = callback_alarm_synchronous; + alarm_callback.param = NULL; + CHECK_ERROR(pmic_event_subscribe(EVENT_TODAI, alarm_callback)); + prepare_to_wait(&queue_alarm, &wait, TASK_UNINTERRUPTIBLE); + schedule(); + finish_wait(&queue_alarm, &wait); + CHECK_ERROR(pmic_event_unsubscribe(EVENT_TODAI, alarm_callback)); + return PMIC_SUCCESS; +} + +/*! + * This function set the real time clock of PMIC + * + * @param pmic_time value of date and time + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_rtc_set_time(struct timeval * pmic_time) +{ + unsigned int tod_reg_val = 0; + unsigned int day_reg_val = 0; + unsigned int mask, value; + + tod_reg_val = pmic_time->tv_sec % 86400; + day_reg_val = pmic_time->tv_sec / 86400; + + mask = BITFMASK(MC13783_RTCTIME_TIME); + value = BITFVAL(MC13783_RTCTIME_TIME, tod_reg_val); + CHECK_ERROR(pmic_write_reg(REG_RTC_TIME, value, mask)); + + mask = BITFMASK(MC13783_RTCDAY_DAY); + value = BITFVAL(MC13783_RTCDAY_DAY, day_reg_val); + CHECK_ERROR(pmic_write_reg(REG_RTC_DAY, value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function get the real time clock of PMIC + * + * @param pmic_time return value of date and time + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_rtc_get_time(struct timeval * pmic_time) +{ + unsigned int tod_reg_val = 0; + unsigned int day_reg_val = 0; + unsigned int mask, value; + + mask = BITFMASK(MC13783_RTCTIME_TIME); + CHECK_ERROR(pmic_read_reg(REG_RTC_TIME, &value, mask)); + tod_reg_val = BITFEXT(value, MC13783_RTCTIME_TIME); + + mask = BITFMASK(MC13783_RTCDAY_DAY); + CHECK_ERROR(pmic_read_reg(REG_RTC_DAY, &value, mask)); + day_reg_val = BITFEXT(value, MC13783_RTCDAY_DAY); + + pmic_time->tv_sec = (unsigned long)((unsigned long)(tod_reg_val & + 0x0001FFFF) + + (unsigned long)(day_reg_val * + 86400)); + return PMIC_SUCCESS; +} + +/*! + * This function set the real time clock alarm of PMIC + * + * @param pmic_time value of date and time + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_rtc_set_time_alarm(struct timeval * pmic_time) +{ + unsigned int tod_reg_val = 0; + unsigned int day_reg_val = 0; + unsigned int mask, value; + + down_interruptible(&mutex); + tod_reg_val = pmic_time->tv_sec % 86400; + day_reg_val = pmic_time->tv_sec / 86400; + + mask = BITFMASK(MC13783_RTCALARM_TIME); + value = BITFVAL(MC13783_RTCALARM_TIME, tod_reg_val); + CHECK_ERROR(pmic_write_reg(REG_RTC_ALARM, value, mask)); + + mask = BITFMASK(MC13783_RTCALARM_DAY); + value = BITFVAL(MC13783_RTCALARM_DAY, day_reg_val); + CHECK_ERROR(pmic_write_reg(REG_RTC_DAY_ALARM, value, mask)); + up(&mutex); + return PMIC_SUCCESS; +} + +/*! + * This function get the real time clock alarm of PMIC + * + * @param pmic_time return value of date and time + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_rtc_get_time_alarm(struct timeval * pmic_time) +{ + unsigned int tod_reg_val = 0; + unsigned int day_reg_val = 0; + unsigned int mask, value; + + mask = BITFMASK(MC13783_RTCALARM_TIME); + CHECK_ERROR(pmic_read_reg(REG_RTC_ALARM, &value, mask)); + tod_reg_val = BITFEXT(value, MC13783_RTCALARM_TIME); + + mask = BITFMASK(MC13783_RTCALARM_DAY); + CHECK_ERROR(pmic_read_reg(REG_RTC_DAY_ALARM, &value, mask)); + day_reg_val = BITFEXT(value, MC13783_RTCALARM_DAY); + + pmic_time->tv_sec = (unsigned long)((unsigned long)(tod_reg_val & + 0x0001FFFF) + + (unsigned long)(day_reg_val * + 86400)); + + return PMIC_SUCCESS; +} + +/*! + * This function is used to un/subscribe on RTC event IT. + * + * @param event type of event. + * @param callback event callback function. + * @param sub define if Un/subscribe event. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_rtc_event(t_rtc_int event, void *callback, bool sub) +{ + type_event rtc_event; + if (callback == NULL) { + return PMIC_ERROR; + } else { + rtc_callback.func = callback; + rtc_callback.param = NULL; + } + switch (event) { + case RTC_IT_ALARM: + rtc_event = EVENT_TODAI; + break; + case RTC_IT_1HZ: + rtc_event = EVENT_E1HZI; + break; + case RTC_IT_RST: + rtc_event = EVENT_RTCRSTI; + break; + default: + return PMIC_PARAMETER_ERROR; + } + if (sub == true) { + CHECK_ERROR(pmic_event_subscribe(rtc_event, rtc_callback)); + } else { + CHECK_ERROR(pmic_event_unsubscribe(rtc_event, rtc_callback)); + } + return PMIC_SUCCESS; +} + +/*! + * This function is used to subscribe on RTC event IT. + * + * @param event type of event. + * @param callback event callback function. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_rtc_event_sub(t_rtc_int event, void *callback) +{ + CHECK_ERROR(pmic_rtc_event(event, callback, true)); + return PMIC_SUCCESS; +} + +/*! + * This function is used to un subscribe on RTC event IT. + * + * @param event type of event. + * @param callback event callback function. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_rtc_event_unsub(t_rtc_int event, void *callback) +{ + CHECK_ERROR(pmic_rtc_event(event, callback, false)); + return PMIC_SUCCESS; +} + +/* Called without the kernel lock - fine */ +static unsigned int pmic_rtc_poll(struct file *file, poll_table * wait) +{ + /*poll_wait(file, &pmic_rtc_wait, wait); */ + + if (pmic_rtc_done) + return POLLIN | POLLRDNORM; + return 0; +} + +/*! + * This function implements IOCTL controls on a PMIC RTC device. + * + * @param inode pointer on the node + * @param file pointer on the file + * @param cmd the command + * @param arg the parameter + * @return This function returns 0 if successful. + */ +static int pmic_rtc_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct timeval *pmic_time = NULL; + + if (_IOC_TYPE(cmd) != 'p') + return -ENOTTY; + + if (arg) { + if ((pmic_time = kmalloc(sizeof(struct timeval), + GFP_KERNEL)) == NULL) { + return -ENOMEM; + } + /* if (copy_from_user(pmic_time, (struct timeval *)arg, + sizeof(struct timeval))) { + return -EFAULT; + } */ + } + + switch (cmd) { + case PMIC_RTC_SET_TIME: + if (copy_from_user(pmic_time, (struct timeval *)arg, + sizeof(struct timeval))) { + return -EFAULT; + } + pr_debug("SET RTC\n"); + CHECK_ERROR(pmic_rtc_set_time(pmic_time)); + break; + case PMIC_RTC_GET_TIME: + if (copy_to_user((struct timeval *)arg, pmic_time, + sizeof(struct timeval))) { + return -EFAULT; + } + pr_debug("GET RTC\n"); + CHECK_ERROR(pmic_rtc_get_time(pmic_time)); + break; + case PMIC_RTC_SET_ALARM: + if (copy_from_user(pmic_time, (struct timeval *)arg, + sizeof(struct timeval))) { + return -EFAULT; + } + pr_debug("SET RTC ALARM\n"); + CHECK_ERROR(pmic_rtc_set_time_alarm(pmic_time)); + break; + case PMIC_RTC_GET_ALARM: + if (copy_to_user((struct timeval *)arg, pmic_time, + sizeof(struct timeval))) { + return -EFAULT; + } + pr_debug("GET RTC ALARM\n"); + CHECK_ERROR(pmic_rtc_get_time_alarm(pmic_time)); + break; + case PMIC_RTC_WAIT_ALARM: + printk(KERN_INFO "WAIT ALARM...\n"); + CHECK_ERROR(pmic_rtc_event_sub(RTC_IT_ALARM, + callback_test_sub)); + CHECK_ERROR(pmic_rtc_wait_alarm()); + printk(KERN_INFO "ALARM DONE\n"); + CHECK_ERROR(pmic_rtc_event_unsub(RTC_IT_ALARM, + callback_test_sub)); + break; + case PMIC_RTC_ALARM_REGISTER: + printk(KERN_INFO "PMIC RTC ALARM REGISTER\n"); + alarm_callback.func = callback_alarm_asynchronous; + alarm_callback.param = NULL; + CHECK_ERROR(pmic_event_subscribe(EVENT_TODAI, alarm_callback)); + break; + case PMIC_RTC_ALARM_UNREGISTER: + printk(KERN_INFO "PMIC RTC ALARM UNREGISTER\n"); + alarm_callback.func = callback_alarm_asynchronous; + alarm_callback.param = NULL; + CHECK_ERROR(pmic_event_unsubscribe + (EVENT_TODAI, alarm_callback)); + pmic_rtc_done = false; + break; + default: + pr_debug("%d unsupported ioctl command\n", (int)cmd); + return -EINVAL; + } + + if (arg) { + if (copy_to_user((struct timeval *)arg, pmic_time, + sizeof(struct timeval))) { + return -EFAULT; + } + kfree(pmic_time); + } + + return 0; +} + +/*! + * This function implements the open method on a PMIC RTC device. + * + * @param inode pointer on the node + * @param file pointer on the file + * @return This function returns 0. + */ +static int pmic_rtc_open(struct inode *inode, struct file *file) +{ + return 0; +} + +/*! + * This function implements the release method on a PMIC RTC device. + * + * @param inode pointer on the node + * @param file pointer on the file + * @return This function returns 0. + */ +static int pmic_rtc_release(struct inode *inode, struct file *file) +{ + return 0; +} + +/*! + * This function is called to put the RTC in a low power state. + * There is no need for power handlers for the RTC device. + * The RTC cannot be suspended. + * + * @param pdev the device structure used to give information on which RTC + * device (0 through 3 channels) to suspend + * @param state the power state the device is entering + * + * @return The function always returns 0. + */ +static int pmic_rtc_suspend(struct platform_device *pdev, pm_message_t state) +{ + return 0; +} + +/*! + * This function is called to resume the RTC from a low power state. + * + * @param pdev the device structure used to give information on which RTC + * device (0 through 3 channels) to suspend + * + * @return The function always returns 0. + */ +static int pmic_rtc_resume(struct platform_device *pdev) +{ + return 0; +} + +/*! + * This structure contains pointers to the power management callback functions. + */ + +static struct file_operations pmic_rtc_fops = { + .owner = THIS_MODULE, + .ioctl = pmic_rtc_ioctl, + .poll = pmic_rtc_poll, + .open = pmic_rtc_open, + .release = pmic_rtc_release, +}; + +int pmic_rtc_loaded(void) +{ + return pmic_rtc_detected; +} + +static int pmic_rtc_remove(struct platform_device *pdev) +{ + class_device_destroy(pmic_rtc_class, MKDEV(pmic_rtc_major, 0)); + class_destroy(pmic_rtc_class); + unregister_chrdev(pmic_rtc_major, "pmic_rtc"); + return 0; +} + +static int pmic_rtc_probe(struct platform_device *pdev) +{ + int ret = 0; + struct class_device *temp_class; + + pmic_rtc_major = register_chrdev(0, "pmic_rtc", &pmic_rtc_fops); + if (pmic_rtc_major < 0) { + printk(KERN_ERR "Unable to get a major for pmic_rtc\n"); + return pmic_rtc_major; + } + + pmic_rtc_class = class_create(THIS_MODULE, "pmic_rtc"); + if (IS_ERR(pmic_rtc_class)) { + printk(KERN_ERR "Error creating pmic rtc class.\n"); + ret = PTR_ERR(pmic_rtc_class); + goto err_out1; + } + + temp_class = class_device_create(pmic_rtc_class, NULL, + MKDEV(pmic_rtc_major, 0), + NULL, "pmic_rtc"); + if (IS_ERR(temp_class)) { + printk(KERN_ERR "Error creating pmic rtc class device.\n"); + ret = PTR_ERR(temp_class); + goto err_out2; + } + + pmic_rtc_detected = 1; + printk(KERN_INFO "PMIC RTC successfully probed\n"); + return ret; + + err_out2: + class_destroy(pmic_rtc_class); + err_out1: + unregister_chrdev(pmic_rtc_major, "pmic_rtc"); + return ret; +} + +static struct platform_driver pmic_rtc_driver_ldm = { + .driver = { + .name = "pmic_rtc", + .owner = THIS_MODULE, + }, + .suspend = pmic_rtc_suspend, + .resume = pmic_rtc_resume, + .probe = pmic_rtc_probe, + .remove = pmic_rtc_remove, +}; + +static int __init pmic_rtc_init(void) +{ + pr_debug("PMIC RTC driver loading...\n"); + return platform_driver_register(&pmic_rtc_driver_ldm); +} +static void __exit pmic_rtc_exit(void) +{ + platform_driver_unregister(&pmic_rtc_driver_ldm); + pr_debug("PMIC RTC driver successfully unloaded\n"); +} + +/* + * Module entry points + */ + +subsys_initcall(pmic_rtc_init); +module_exit(pmic_rtc_exit); + +MODULE_DESCRIPTION("Pmic_rtc driver"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/pmic/mc13783/pmic_rtc_defs.h b/drivers/mxc/pmic/mc13783/pmic_rtc_defs.h new file mode 100644 index 000000000000..16e968dd9977 --- /dev/null +++ b/drivers/mxc/pmic/mc13783/pmic_rtc_defs.h @@ -0,0 +1,47 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef __MC13783_RTC_DEFS_H__ +#define __MC13783_RTC_DEFS_H__ + +/*! + * @file mc13783/pmic_rtc_defs.h + * @brief This is the internal header of PMIC(mc13783) RTC driver. + * + * @ingroup PMIC_RTC + */ + +/* + * RTC Time + */ +#define MC13783_RTCTIME_TIME_LSH 0 +#define MC13783_RTCTIME_TIME_WID 17 + +/* + * RTC Alarm + */ +#define MC13783_RTCALARM_TIME_LSH 0 +#define MC13783_RTCALARM_TIME_WID 17 + +/* + * RTC Day + */ +#define MC13783_RTCDAY_DAY_LSH 0 +#define MC13783_RTCDAY_DAY_WID 15 + +/* + * RTC Day alarm + */ +#define MC13783_RTCALARM_DAY_LSH 0 +#define MC13783_RTCALARM_DAY_WID 15 + +#endif /* __MC13783_RTC_DEFS_H__ */ diff --git a/drivers/mxc/security/Kconfig b/drivers/mxc/security/Kconfig new file mode 100644 index 000000000000..f41a84ac84fd --- /dev/null +++ b/drivers/mxc/security/Kconfig @@ -0,0 +1,101 @@ +menu "MXC Security Drivers" + +config MXC_SECURITY_SCC + tristate "MXC SCC Driver" + default n + ---help--- + This module contains the core API's for accessing the SCC module. + If you are unsure about this, say N here. +config SCC_DEBUG + bool "MXC SCC Module debugging" + depends on MXC_SECURITY_SCC + ---help--- + This is an option for use by developers; most people should + say N here. This enables HAC module debugging. + +config MXC_SECURITY_SCC2 + tristate "MXC SCC2 Driver" + depends on ARCH_MXC92323 + default n + ---help--- + This module contains the core API's for accessing the SCC2 module. + If you are unsure about this, say N here. +config SCC_DEBUG + bool "MXC SCC2 Module debugging" + depends on MXC_SECURITY_SCC2 + ---help--- + This is an option for use by developers; most people should + say N here. This enables HAC module debugging. + + +config MXC_SECURITY_RNG + tristate "MXC RNG Driver" + depends on ARCH_MXC + depends on !ARCH_MXC91321 + depends on !ARCH_MX27 + default n + select MXC_SECURITY_CORE + ---help--- + This module contains the core API's for accessing the RNG module. + If you are unsure about this, say N here. + +config MXC_RNG_TEST_DRIVER + bool "MXC RNG debug register" + depends on MXC_SECURITY_RNG + default n + ---help--- + This option enables the RNG kcore driver to provide peek-poke facility + into the RNG device registers. Enable this, only for development and + testing purposes. +config MXC_RNG_DEBUG + bool "MXC RNG Module Dubugging" + depends on MXC_SECURITY_RNG + default n + ---help--- + This is an option for use by developers; most people should + say N here. This enables RNG module debugging. + + +config MXC_SECURITY_HAC + tristate "MXC HAC Driver" + depends on ARCH_MXC + depends on ARCH_MXC91131 + default n + select MXC_SECURITY_CORE + ---help--- + This module contains the core API's for accessing the HAC module. + If you are unsure about this, say N here. + +config MXC_HAC_TEST_DEBUG + bool "MXC HAC Module debugging" + depends on MXC_SECURITY_HAC + ---help--- + This is an option for use by developers; most people should + say N here. This enables HAC module debugging. + +config MXC_SECURITY_RTIC + tristate "MXC RTIC Driver" + depends on ARCH_MXC + depends on !ARCH_MX21 + default n + select MXC_SECURITY_CORE + ---help--- + This module contains the core API's for accessing the RTIC module. + If you are unsure about this, say N here. + +config MXC_RTIC_TEST_DEBUG + bool "MXC RTIC module debugging" + depends on MXC_SECURITY_RTIC + default n + ---help--- + This is an option for use by developers; most people should + say N here. This enables RTIC module debugging. + +config MXC_SECURITY_CORE + tristate + +if (ARCH_MXC91231 || ARCH_MXC91321 || ARCH_MX27 || ARCH_MXC92323) && MXC_SECURITY_RNG=n && MXC_SECURITY_HAC=n +source "drivers/mxc/security/sahara2/Kconfig" +endif + +endmenu diff --git a/drivers/mxc/security/Makefile b/drivers/mxc/security/Makefile new file mode 100644 index 000000000000..73e21cf76f9a --- /dev/null +++ b/drivers/mxc/security/Makefile @@ -0,0 +1,21 @@ +# Makefile for the Linux MXC Security API +ifeq ($( SCC_DEBUG),y) +EXTRA_CFLAGS += -DDEBUG +endif +ifeq ($(MXC_HAC_TEST_DEBUG),y) +EXTRA_CFLAGS += -DDEBUG +endif +ifeq ($(MXC_RTIC_TEST_DEBUG),y) +EXTRA_CFLAGS += -DDEBUG +endif +EXTRA_CFLAGS += -DMXC -DLINUX_KERNEL + + + +obj-$(CONFIG_MXC_SECURITY_SCC2) += scc2_driver.o +obj-$(CONFIG_MXC_SECURITY_SCC) += mxc_scc.o +obj-$(CONFIG_MXC_SECURITY_RTIC) += mxc_rtic.o +obj-$(CONFIG_MXC_SECURITY_HAC) += mxc_hacc.o +obj-$(CONFIG_MXC_SECURITY_RNG) += rng/ +obj-$(CONFIG_MXC_SECURITY_CORE) += mxc_sec_mod.o +obj-$(CONFIG_MXC_SAHARA) += sahara2/ diff --git a/drivers/mxc/security/mxc_hacc.c b/drivers/mxc/security/mxc_hacc.c new file mode 100644 index 000000000000..46829cd791e1 --- /dev/null +++ b/drivers/mxc/security/mxc_hacc.c @@ -0,0 +1,523 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mxc_hacc.c + * + * @brief APIs for HAC Module. + * + * This file provides the APIs for accessing Hash Acceleration (HAC) + * module. HAC module accelerates the creation of a SHA-1 hash over + * selected memory spaces. The SHA-1 algorithm is a one-way hash + * algorithm that creates 160 bit hash of any length input. + * + * @ingroup MXC_Security + */ + +#include <linux/io.h> +#include <linux/clk.h> +#ifdef CONFIG_MXC_HAC_TEST_DEBUG +#include <linux/module.h> +#endif /* CONFIG_MXC_HAC_TEST_DEBUG */ +#include "mxc_hacc.h" + +/*! + * This variable indicates whether HAC module is in suspend or in resume + * mode. + */ +static unsigned short hac_suspend_state = 0; + +/*! + * This API configures the start address and block length of the data that + * needs to be hashed. Start address indicates starting location from where + * the data in the flash memory is to be hashed. The number of blocks that + * needs to be hashed is loaded in the block count register. This API does + * the starting of Hashing process or continue with Hashing of next block of + * data configured in the START_ADDR, BLOCK_COUNT register depending on the + * hash parameter passed. + * + * @param start_address Starting address of the flash memory to be hashed. + * user has to pass physical address here. + * @param blk_len Number of blocks to be hashed. + * @param option Mode of operation like Start or Continue hashing.\n + * Following parameters are passed: + * HAC_START : Starts the Hashing process. + * HAC_LAST_START : Starts the Hashing process with + * last block of data. + * HAC_CONTINUE : Continue the Hashing process. + * HAC_LAST : Continue the Hashing process with + * last block of data. + * + * + * @return HAC_SUCCESS Successfully hashed the data.\n + * HAC_FAILURE Error in the parameters passed. + * HAC_BUSY HAC module is busy in Hashing process. + */ +hac_ret hac_hash_data(ulong start_address, ulong blk_len, hac_hash option) +{ + struct clk *clk; + ulong hac_start, hac_blk_cnt, hac_ctl; + hac_ret ret_val = HAC_SUCCESS; + + clk = clk_get(NULL, "hac_clk"); + clk_enable(clk); + hac_start = __raw_readl(HAC_START_ADDR); + hac_blk_cnt = __raw_readl(HAC_BLK_CNT); + hac_ctl = __raw_readl(HAC_CTL); + if (hac_suspend_state == 1) { + pr_debug("HAC Module: HAC Module is in suspend mode.\n"); + return -EPERM; + } + pr_debug("Function %s. HAC Module: Start address: 0x%08lX, " + "block length: 0x%08lX, hash option: 0x%08X\n", + __FUNCTION__, start_address, blk_len, option); + /* Validating the parameters. Checking for start address to be in + 512 bit boundary(64 byte) and block count value must not to be + zero. */ + if ((!start_address) || (blk_len > HAC_MAX_BLOCK_LENGTH) || + (blk_len == 0) || (!((start_address % 64) == 0))) { + pr_debug("HAC Module: Invalid parameters passed. \n"); + return HAC_FAILURE; + } + if ((hac_ctl & HAC_CTL_BUSY) == 0) { + hac_start = start_address; + __raw_writel(hac_start, HAC_START_ADDR); + hac_blk_cnt = blk_len; + __raw_writel(hac_blk_cnt, HAC_BLK_CNT); + pr_debug("HAC Module: Hashing start address 0x%08lX\n ", + start_address); + pr_debug("HAC Module: Hashing blk length 0x%08lX\n ", blk_len); + } else { + pr_debug("HAC Module: HAC module is busy in Hashing " + "process.\n"); + return HAC_HASH_BUSY; + } + + switch (option) { + case HAC_START: + /* + * HAC_START will starts the Hashing of data in the memory. + * Before starting the Hashing process, it checks for 'STOP' + * bit, 'DONE' bit and 'ERROR' bit is set in the HAC Control + * register. If 'STOP' bit is set, it clears the 'STOP' bit in + * HAC Control register. If 'DONE' bit and 'ERROR' bit are + * set, they are cleared. + */ + pr_debug("HAC Module: Starts the hashing process \n"); + /* Checking if the Stop bit is been set. */ + if ((hac_ctl & HAC_CTL_STOP) == HAC_CTL_STOP) { + pr_debug("HAC Module: STOP bit is set while" + "starting the Hashing\n"); + hac_ctl &= ~HAC_CTL_STOP; + __raw_writel(hac_ctl, HAC_CTL); + } + /* Checking if the 'DONE' bit and 'ERROR' bit is been set. + If they are set write to clear those bits */ + if (((hac_ctl & HAC_CTL_DONE) == HAC_CTL_DONE) || + ((hac_ctl & HAC_CTL_ERROR) == HAC_CTL_ERROR)) { + pr_debug("HAC Module: DONE and ERROR bit is set" + "while starting the Hashing\n"); + hac_ctl |= HAC_CTL_DONE; + __raw_writel(hac_ctl, HAC_CTL); + hac_ctl |= HAC_CTL_ERROR; + __raw_writel(hac_ctl, HAC_CTL); + } + hac_ctl |= HAC_CTL_START; + __raw_writel(hac_ctl, HAC_CTL); + break; + + case HAC_START_LAST: + /* + * HAC_START_LAST will starts the Hashing of last block of data + * in the memory. Since this is last block of data in the + * memory 'PAD' bit in HAC control register is set to add + * appropriate padding to the end of the data structure. + * Before starting the Hashing process, it checks for 'STOP' + * bit, 'DONE' bit and 'ERROR' bit is set in the HAC Control + * register. If 'STOP' bit is set, it clears the 'STOP' bit in + * HAC Control register. If 'DONE' bit and 'ERROR' bit are + * set, they are cleared. + */ + pr_debug("HAC Module: Starts with last block" + "the hashing process \n"); + /* Checking if the Stop bit is been set. */ + if ((hac_ctl & HAC_CTL_STOP) == HAC_CTL_STOP) { + pr_debug("HAC Module: STOP bit is set while" + "starting the Hashing\n"); + hac_ctl &= ~HAC_CTL_STOP; + __raw_writel(hac_ctl, HAC_CTL); + } + /* Checking if the 'DONE' bit and 'ERROR' bit is been set. + If they are set write to clear those bits */ + if (((hac_ctl & HAC_CTL_DONE) == HAC_CTL_DONE) || + ((hac_ctl & HAC_CTL_ERROR) == HAC_CTL_ERROR)) { + pr_debug(" HAC Module: DONE and ERROR bit is set" + "while starting the Hashing\n"); + hac_ctl |= HAC_CTL_DONE; + __raw_writel(hac_ctl, HAC_CTL); + hac_ctl |= HAC_CTL_ERROR; + __raw_writel(hac_ctl, HAC_CTL); + } + hac_ctl |= HAC_CTL_START; + __raw_writel(hac_ctl, HAC_CTL); + /* Hash for the last block by padding it. */ + pr_debug("HAC Module: Setting the PAD bit while start" + "Hashing the last block\n"); + hac_ctl |= HAC_CTL_PAD; + __raw_writel(hac_ctl, HAC_CTL); + break; + + case HAC_CONTINUE: + /* + * HAC_CONTINUE will continue the Hashing of data in the memory. + * This will continue the hashing processing by taking into + * consideration of the previous hash result and continues + * further hashing of the new data block. Before continue the + * Hashing process, it checks for 'STOP' bit, 'DONE' bit and + * 'ERROR' bit is set in the HAC Control register. If 'STOP' + * bit is set, it clears the 'STOP' bit in Control register. + * If 'DONE' bit is set, it clears the 'DONE' bit in control + * register. If 'ERROR' bit is set, then error message is + * indicated to the user. + */ + pr_debug("HAC Module: Continue hashing process. \n"); + /* Checking if the Stop bit is been set. */ + if ((hac_ctl & HAC_CTL_STOP) == HAC_CTL_STOP) { + hac_ctl &= ~HAC_CTL_STOP; + __raw_writel(hac_ctl, HAC_CTL); + } + /* Checking if the 'DONE' bit is been set. If it is set write + one to clear the bit */ + if ((hac_ctl & HAC_CTL_DONE) == HAC_CTL_DONE) { + hac_ctl |= HAC_CTL_DONE; + __raw_writel(hac_ctl, HAC_CTL); + } + /* Checking if 'ERROR' bit is been set. If it is set resturn + return back indicating error in Hashing porcess. */ + if ((hac_ctl & HAC_CTL_ERROR) == HAC_CTL_ERROR) { + return HAC_FAILURE; + } + hac_ctl |= HAC_CTL_CONTINUE; + __raw_writel(hac_ctl, HAC_CTL); + break; + + case HAC_LAST: + /* + * HAC_LAST will continue the Hashing of last block of data + * in the memory. Since this is last block of data in the + * memory 'PAD' bit in HAC control register is set to add + * appropriate padding to the end of the data structure. + * This will continue the hashing processing by taking into + * consideration of the previous hash result and continues + * further hashing of the new data block. Before continue the + * Hashing process, it checks for 'STOP' bit, 'DONE' bit and + * 'ERROR' bit is set in the HAC Control register. If 'STOP' + * bit is set, it clears the 'STOP' bit in Control register. + * If 'DONE' bit is set, it clears the 'DONE' bit in control + * register. If 'ERROR' bit is set, then error message is + * indicated to the user. + */ + pr_debug("HAC Module: Last block to hash. \n"); + /* Checking if the Stop bit is been set. */ + if ((hac_ctl & HAC_CTL_STOP) == HAC_CTL_STOP) { + hac_ctl &= ~HAC_CTL_STOP; + __raw_writel(hac_ctl, HAC_CTL); + } + /* Checking if the 'DONE' bit is been set. If it is set write + one to clear the bit */ + if ((hac_ctl & HAC_CTL_DONE) == HAC_CTL_DONE) { + hac_ctl |= HAC_CTL_DONE; + __raw_writel(hac_ctl, HAC_CTL); + } + /* Checking if 'ERROR' bit is been set. If it is set resturn + return back indicating error in Hashing porcess. */ + if ((hac_ctl & HAC_CTL_ERROR) == HAC_CTL_ERROR) { + return HAC_FAILURE; + } + hac_ctl |= HAC_CTL_CONTINUE; + __raw_writel(hac_ctl, HAC_CTL); + /* Continuing the hash for the last block by padding it. */ + hac_ctl |= HAC_CTL_PAD; + __raw_writel(hac_ctl, HAC_CTL); + break; + + default: + ret_val = HAC_FAILURE; + /* NOT RESPONDING */ + break; + } + return ret_val; +} + +/*! + * This API returns the status of the Hashing. + * + * @return HAC_BUSY : Indicated HAC Module is busy with Hashing.\n + * HAC_DONE : Indicates Hashing of data is done.\n + * HAC_ERR : Indicates error has occurred during Hashing.\n + * HAC_UNKNOWN: Hashing status unknown. This may be when the + * hashing process has not been initiated atleast + * once or 'ERROR' bit or 'DONE' bits were reset + * after the hashing process was completed. + */ +hac_hash_status hac_hashing_status(void) +{ + ulong hac_ctl; + hac_ctl = __raw_readl(HAC_CTL); + if ((hac_ctl & HAC_CTL_BUSY) != 0) { + pr_debug("HAC Module: Hash module is in busy state \n"); + return HAC_BUSY; + } else if ((hac_ctl & HAC_CTL_DONE) != 0) { + /* Clearing the done bit of the control register */ + pr_debug("HAC Module: Hashing of data is done \n"); + return HAC_DONE; + } else if ((hac_ctl & HAC_CTL_ERROR) != 0) { + /* Clearing the error bit of the control register */ + pr_debug("HAC Module: Error has occurred during hashing \n"); + return HAC_ERR; + } else { + return HAC_UNKNOWN; + } +} + +/*! + * This API returns the status of the Hash module. + * + * @return Value of the Hashing control register. + */ +ulong hac_get_status(void) +{ + ulong hac_ctl = __raw_readl(HAC_CTL); + pr_debug("HAC Module: Hashing status register value 0x%08lX\n ", + hac_ctl); + return hac_ctl; +} + +/*! + * This API stops the Hashing of data when the Hashing is in progress. + */ +hac_ret hac_stop(void) +{ + ulong hac_ctl; + hac_ctl = __raw_readl(HAC_CTL); + if (hac_suspend_state == 1) { + pr_debug("HAC Module: HAC Module is in suspend mode.\n"); + return HAC_FAILURE; + } + pr_debug("HAC Module: Stop hashing process. \n"); + hac_ctl |= HAC_CTL_STOP; + __raw_writel(hac_ctl, HAC_CTL); + return HAC_SUCCESS; +} + +/*! + * This API reads 160 bit hash result from Hash result register. The data is + * copied to the memory pointed by the input pointer. + * + * @param hash_result_reg structure Pointer where the hash result is + * copied. + */ +hac_ret hac_hash_result(hac_hash_rlt * hash_result_reg) +{ + ulong hac_hsh4, hac_hsh3, hac_hsh2, hac_hsh1, hac_hsh0; + struct clk *clk; + + clk = clk_get(NULL, "hac_clk"); + hac_hsh4 = __raw_readl(HAC_HSH4); + hac_hsh3 = __raw_readl(HAC_HSH3); + hac_hsh2 = __raw_readl(HAC_HSH2); + hac_hsh1 = __raw_readl(HAC_HSH1); + hac_hsh0 = __raw_readl(HAC_HSH0); + clk_disable(clk); + if (hac_suspend_state == 1) { + pr_debug("HAC Module: HAC Module is in suspend mode.\n"); + return HAC_FAILURE; + } + pr_debug("HAC Module: Read hash result \n"); + hash_result_reg->hash_result[0] = hac_hsh4; + hash_result_reg->hash_result[1] = hac_hsh3; + hash_result_reg->hash_result[2] = hac_hsh2; + hash_result_reg->hash_result[3] = hac_hsh1; + hash_result_reg->hash_result[4] = hac_hsh0; + return HAC_SUCCESS; +} + +/*! + * This API will initiates software reset of the entire HAC module. It resets + * all state machine to their default values. All status bits (BUSY/ERROR/DONE) + * and any pending interrupts are cleared. + * + * @return HAC_SUCCESS Successfully in doing software reset.\n + * HAC_FAILURE Error in doing software reset. + */ +hac_ret hac_swrst(void) +{ + ulong hac_ctl; + ulong hac_ret = HAC_SUCCESS; + hac_ctl = __raw_readl(HAC_CTL); + pr_debug("HAC Module: HAC Software reset function. \n"); + if (hac_suspend_state == 1) { + pr_debug("HAC MODULE: HAC Module is in suspend mode.\n"); + return HAC_FAILURE; + } + hac_ctl |= HAC_CTL_SWRST; + __raw_writel(hac_ctl, HAC_CTL); + return hac_ret; +} + +/*! + * This API configures the burst mode of the HAC. When Burst mode set in HAC + * Control register then ARM9 is configured for a 16-WORD burst, while Burst + * mode is cleared then ARM9 is configured for a incremental burst. + * + * @param burst_mode Configures burst mode operations. + * + * @return HAC_SUCCESS Successfully in configuring burst mode.\n + * HAC_FAILURE Error in configuring burst mode. + */ +hac_ret hac_burst_mode(hac_burst_mode_config burst_mode) +{ + ulong hac_ctl; + ulong hac_ret = HAC_SUCCESS; + hac_ctl = __raw_readl(HAC_CTL); + pr_debug("HAC Module: HAC Burst Mode function. \n"); + if (hac_suspend_state == 1) { + pr_debug("HAC MODULE: HAC Module is in suspend mode.\n"); + return HAC_FAILURE; + } + switch (burst_mode) { + case HAC_INR_BURST: + hac_ctl |= HAC_CTL_BURST_MODE; + break; + + case HAC_16WORD_BURST: + hac_ctl &= ~HAC_CTL_BURST_MODE; + break; + + default: + hac_ret = HAC_FAILURE; + break; + } + return hac_ret; +} + +/*! + * This API configures HAC burst read nature. + * + * @param burst_read Configures burst read. + * + * @return HAC_SUCCESS Successfully in configuring burst read.\n + * HAC_FAILURE Error in configuring burst read. + */ +hac_ret hac_burst_read(hac_burst_read_config burst_read) +{ + ulong hac_ctl; + ulong hac_ret = HAC_SUCCESS; + hac_ctl = __raw_readl(HAC_CTL); + pr_debug("HAC Module: HAC Burst Read function. \n"); + if (hac_suspend_state == 1) { + pr_debug("HAC MODULE: HAC Module is in suspend mode.\n"); + return HAC_FAILURE; + } + switch (burst_read) { + case HAC_16WORD_BURST_READ: + __raw_writel(HAC_CTL_16WORD_BURST, HAC_CTL); + break; + + case HAC_8WORD_BURST_READ: + hac_ctl &= ~HAC_CTL_NO_BURST_READ; + hac_ctl |= HAC_CTL_8WORD_BURST; + __raw_writel(hac_ctl, HAC_CTL); + break; + + case HAC_4WORD_BURST_READ: + hac_ctl &= ~HAC_CTL_NO_BURST_READ; + hac_ctl |= HAC_CTL_4WORD_BURST; + __raw_writel(hac_ctl, HAC_CTL); + break; + + case HAC_NO_WORD_BURST_READ: + hac_ctl &= ~HAC_CTL_NO_BURST_READ; + hac_ctl |= HAC_CTL_NO_BURST_READ; + break; + + default: + hac_ret = HAC_FAILURE; + break; + } + return hac_ret; +} + +#ifdef CONFIG_PM +/*! + * This function is called to put the HAC in a low power state. Refer to the + * document driver-model/driver.txt in the kernel source tree for more + * information. + * + * @param pdev the device structure used to give information on HAC + * to suspend. + * @param state the power state the device is entering. + * + * @return The function always returns HAC_SUCCESS. + */ +hac_ret hac_suspend(struct platform_device * pdev, pm_message_t state) +{ + ulong hac_ctl; + struct clk *clk; + + hac_ctl = __raw_readl(HAC_CTL); + clk = clk_get(NULL, "hac_clk"); + hac_suspend_state = 1; + + pr_debug("HAC Module: In suspend power down.\n"); + + /* Enable stop bits in HAC Control Register. */ + hac_ctl |= HAC_CTL_STOP; + __raw_writel(hac_ctl, HAC_CTL); + clk_disable(clk); + + return HAC_SUCCESS; +} + +/*! + * This function is called to bring the HAC back from a low power state. + * Refer to the document driver-model/driver.txt in the kernel source tree + * for more information. + * + * @param pdev the device structure used to give information on HAC + * to resume. + * + * @return The function always returns HAC_SUCCESS. + */ +hac_ret hac_resume(struct platform_device * pdev) +{ + ulong hac_ctl; + struct clk *clk; + + clk = clk_get(NULL, "hac_clk"); + clk_enable(clk); + hac_ctl = __raw_readl(HAC_CTL); + + pr_debug("HAC Module: Resume power on.\n"); + /* Disable stop bit in HAC Control register. */ + hac_ctl &= ~HAC_CTL_STOP; + __raw_writel(hac_ctl, HAC_CTL); + + hac_suspend_state = 0; + + return HAC_SUCCESS; +} +#else +#define mxc_hac_suspend NULL +#define mxc_hac_resume NULL +#endif /* CONFIG_PM */ diff --git a/drivers/mxc/security/mxc_hacc.h b/drivers/mxc/security/mxc_hacc.h new file mode 100644 index 000000000000..ecbdcd588d49 --- /dev/null +++ b/drivers/mxc/security/mxc_hacc.h @@ -0,0 +1,145 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mxc_hacc.h + * + * @brief The header file for Hash Acceleration (HAC) module. + * This file contains all register defines and bit definition of HAC module. + * + * @ingroup MXC_Security + */ + +#ifndef __MXC_HACC_H__ +#define __MXC_HACC_H__ + +#include <asm/arch/mxc_security_api.h> +#include <asm/hardware.h> +#include <asm/errno.h> + +/* + * HAC Control register + */ +#define HAC_CTL IO_ADDRESS(HAC_BASE_ADDR + 0x00) + +/* + * HAC Start Address Register + */ +#define HAC_START_ADDR IO_ADDRESS(HAC_BASE_ADDR + 0x04) + +/* + * HAC Block Count Register + */ +#define HAC_BLK_CNT IO_ADDRESS(HAC_BASE_ADDR + 0x08) + +/* + * HAC Hash Register 4 Register + */ +#define HAC_HSH4 IO_ADDRESS(HAC_BASE_ADDR + 0x0C) + +/* + * HAC Hash Register 3 Register + */ +#define HAC_HSH3 IO_ADDRESS(HAC_BASE_ADDR + 0x10) + +/* + * HAC Hash Register 2 Register + */ +#define HAC_HSH2 IO_ADDRESS(HAC_BASE_ADDR + 0x14) + +/* + * HAC Hash Register 1 Register + */ +#define HAC_HSH1 IO_ADDRESS(HAC_BASE_ADDR + 0x18) + +/* + * HAC Hash Register 0 Register + */ +#define HAC_HSH0 IO_ADDRESS(HAC_BASE_ADDR + 0x1C) + +/*! + * HAC Hash Done status + */ +#define HAC_CTL_DONE 0x01 + +/*! + * HAC Hash Error Status + */ +#define HAC_CTL_ERROR 0x02 + +/*! + * HAC Hash Accelerator Module Busy Status + */ +#define HAC_CTL_BUSY 0x04 + +/*! + * HAC HAC Interrupt Mask Bit. + */ +#define HAC_CTL_IMSK 0x08 + +/*! + * HAC Command to Stop hash processing + */ +#define HAC_CTL_STOP 0x10 + +/*! + * HAC Command for software reset + */ +#define HAC_CTL_SWRST 0x20 + +/*! + * HAC Command to start hash processing + */ +#define HAC_CTL_START 0x40 + +/*! + * HAC Command to continue hash processing + */ +#define HAC_CTL_CONTINUE 0x80 + +/*! + * HAC Command to do padding + */ +#define HAC_CTL_PAD 0x100 + +/*! + * HAC Command not to do burst read + */ +#define HAC_CTL_NO_BURST_READ 0x600 + +/*! + * HAC Command to do 4 word Burst read + */ +#define HAC_CTL_4WORD_BURST 0x100 + +/*! + * HAC Command to do 8 word Burst read + */ +#define HAC_CTL_8WORD_BURST 0x400 + +/*! + * HAC Command to do 16 word Burst read + */ +#define HAC_CTL_16WORD_BURST 0x000 + +/*! + * HAC Command to configure the burst mode + */ +#define HAC_CTL_BURST_MODE 0x800 + +/*! + * Maximum block length that can be configured to the HAC module. + */ +#define HAC_MAX_BLOCK_LENGTH 0x7FFFFF + +#endif /* __MXC_HACC_H__ */ diff --git a/drivers/mxc/security/mxc_rtic.c b/drivers/mxc/security/mxc_rtic.c new file mode 100644 index 000000000000..9dae7dec421e --- /dev/null +++ b/drivers/mxc/security/mxc_rtic.c @@ -0,0 +1,838 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mxc_rtic.c + * + * @brief APIs for RTIC Module. + * + * \b + * This file provides the APIs for accessing Run-Time Integrity Checker (RTIC) + * module. RTIC module accelerates the creation of a SHA-1 hash over + * selected memory spaces. The RTIC has the ability to verify the memory + * contents during system boot and during run-time execution. + * + * @ingroup MXC_Security + */ + +#include <linux/io.h> +#include <linux/platform_device.h> +#include <linux/clk.h> +#ifdef CONFIG_MXC_RTIC_TEST_DEBUG +#include <linux/module.h> +#endif /* CONFIG_MXC_RTIC_TEST_DEBUG */ +#include <asm/io.h> +#include <asm/arch/clock.h> +#include "mxc_rtic.h" + +/*! + * The following api is used to enable the RTIC IP CLK and RTIC HCLK. + * Before start using other APIs make sure that init is called. + * + * @param void + * + * return void + */ + +void rtic_init(void) +{ + struct clk *clk; + + clk = clk_get(NULL, "rtic_clk"); + clk_enable(clk); +} + +/*! + * This API configures the memory block (A, B, C, D) into + * RUN_TIME Mode for Hashing of data in memory. RTIC does not support + * enabling multiple memory blocks that aren't grouped together + * (i.e. enabling only memory blocks A & C without memory B enabled). + * + * @param mode RTIC mode of operation (ONE_TIME or RUN_TIME). + * @param mem_blk Memory block to be hashed. + * + * @return RTIC_SUCCESS Successfully hashed the data.\n + * RTIC_FAILURE Error in the parameters passed. + */ +rtic_ret rtic_configure_mode(rtic_mode mode, rtic_memblk mem_blk) +{ + ulong rtic_ctrl, rtic_sts, rtic_cmd; + rtic_ret ret_val = RTIC_SUCCESS; + /* Read for RTIC Registers value. */ + rtic_ctrl = __raw_readl(RTIC_CONTROL); + rtic_sts = __raw_readl(RTIC_STATUS); + rtic_cmd = __raw_readl(RTIC_COMMAND); + /* + * RTIC does not support enabling multiple memory blocks that aren't + * grouped together(i.e. enabling only memory blocks A & C without + * memory B enabled). + */ + /* Check for RTIC Busy bit before writing into RTIC Registers. */ + if ((rtic_sts & RTIC_BUSY) != 0) { + pr_debug("RTIC Module: RTIC is in BUSY in Hashing\n"); + return RTIC_FAILURE; + } + switch (mode) { + case RTIC_ONE_TIME: + break; + + case RTIC_RUN_TIME: +#ifndef CONFIG_ARCH_MX27 + /* Check RUN Time Disable bit before enabling RUN Time memory + blocks. */ + if ((rtic_cmd & RTIC_RUN_TIME_DISABLE) == RTIC_RUN_TIME_DISABLE) { + rtic_cmd &= ~RTIC_RUN_TIME_DISABLE; + __raw_writel(rtic_cmd, RTIC_COMMAND); + } +#endif /* CONFIG_ARCH_MX27 */ + switch (mem_blk) { + case RTIC_A1: + pr_debug("RTIC Module:Memory Block A is enabled for" + "Run_Time Hashing.\n"); + rtic_ctrl &= ~RTIC_CTL_HASHONCE_MEMA_BLK_EN; + rtic_ctrl |= RTIC_CTL_RUNTIME_MEMA_BLK_EN; + __raw_writel(rtic_ctrl, RTIC_CONTROL); + break; + + case RTIC_B1: + pr_debug("RTIC Module:Memory Block B is " + "enabled for Run_Time Hashing.\n"); + rtic_ctrl &= ~RTIC_CTL_HASHONCE_MEMB_BLK_EN; + rtic_ctrl |= RTIC_CTL_RUNTIME_MEMB_BLK_EN; + __raw_writel(rtic_ctrl, RTIC_CONTROL); + break; + + case RTIC_C1: + pr_debug("RTIC Module:Memory Block C is " + "enabled for Run_Time Hashing.\n"); + rtic_ctrl &= ~RTIC_CTL_HASHONCE_MEMC_BLK_EN; + rtic_ctrl |= RTIC_CTL_RUNTIME_MEMC_BLK_EN; + __raw_writel(rtic_ctrl, RTIC_CONTROL); + break; + + case RTIC_D1: + pr_debug("RTIC Module:Memory Block D is " + "enabled for Run_Time Hashing.\n"); + rtic_ctrl &= ~RTIC_CTL_HASHONCE_MEMD_BLK_EN; + rtic_ctrl |= RTIC_CTL_RUNTIME_MEMD_BLK_EN; + __raw_writel(rtic_ctrl, RTIC_CONTROL); + break; + + default: + ret_val = RTIC_FAILURE; + break; + + } + break; + default: + ret_val = RTIC_FAILURE; + break; + } + return ret_val; +} + +/*! + * This API allows to configure start address,block length of the memory + * and select (SHA1/SHA256) Hash algorithm to hash the data. Start address + * indicates starting location from where the data in the memory is to + * be hashed. The number of blocks that needs to be hashed is loaded in + * the block count register. There are four memory blocks available. The + * user can configure any one of these four memory blocks by passing their + * appropriate address and block length to be Hashed. SHA 256 is supported + * only in RTIC2. This API configures the memory block (A, B, C, D) into + * ONE_TIME Mode for Hashing of data in memory. + * + * @param start_addr Starting address of the memory to be hashed. + * @param blk_len Block length of data in memory. + * @param mem_blk Memory block to be hashed. + * @param hash_select Select the (SHA1/SHA256)Hash Algorithm. + * + * @return RTIC_SUCCESS Successfully hashed the data.\n + * RTIC_FAILURE Error in the parameters passed. + */ + +rtic_ret rtic_configure_mem_blk(ulong start_addr, ulong blk_len, + rtic_memblk mem_blk, int hash_select) +{ + rtic_ret ret_val = RTIC_SUCCESS; + ulong rtic_sts, rtic_ctrl; + /* Read for RTIC Registers value. */ + rtic_sts = __raw_readl(RTIC_STATUS); + rtic_ctrl = __raw_readl(RTIC_CONTROL); + /* Validating the parameters passed. Checking for start address is + * word boundary aligned. Checking for block length is multiple of + * 4 bytes. */ + if ((!start_addr) || (blk_len > RTIC_MAX_BLOCK_LENGTH) || + (!((start_addr % 4) == 0)) || (!((blk_len % 4) == 0))) { + return RTIC_FAILURE; + } + /* Check for RTIC Busy bit before writing into RTIC Registers. */ + if ((rtic_sts & RTIC_BUSY) != 0) { + pr_debug("RTIC Module: RTIC is in BUSY in Hashing\n"); + return RTIC_FAILURE; + } + switch (mem_blk) { + case RTIC_A1: + pr_debug("RTIC Module: Mem Block A1 start address " + "0x%08lX\n", start_addr); + __raw_writel(start_addr, RTIC_MEMAADDR1); + pr_debug("RTIC Module: Mem Block A1 block len " + "0x%08lX\n", blk_len); + __raw_writel(blk_len, RTIC_MEMALEN1); + rtic_ctrl |= RTIC_CTL_HASHONCE_MEMA_BLK_EN; + if (hash_select) { + rtic_ctrl |= RTIC_CTL_HASHALGO_MEMA_BLK_EN; + } + __raw_writel(rtic_ctrl, RTIC_CONTROL); + break; + + case RTIC_A2: + pr_debug("RTIC Module: Mem Block A2 start address" + "0x%08lX\n", start_addr); + __raw_writel(start_addr, RTIC_MEMAADDR2); + pr_debug("RTIC Module: Mem Block A2 block len " + "0x%08lX\n", blk_len); + __raw_writel(blk_len, RTIC_MEMALEN2); + rtic_ctrl |= RTIC_CTL_HASHONCE_MEMA_BLK_EN; + if (hash_select) + rtic_ctrl |= RTIC_CTL_HASHALGO_MEMA_BLK_EN; + __raw_writel(rtic_ctrl, RTIC_CONTROL); + break; + + case RTIC_B1: + pr_debug("RTIC Module: Mem Block B1 start address " + "0x%08lX\n", start_addr); + __raw_writel(start_addr, RTIC_MEMBADDR1); + pr_debug("RTIC Module: Mem Block B1 block len " + "0x%08lX\n", blk_len); + __raw_writel(blk_len, RTIC_MEMBLEN1); + rtic_ctrl |= RTIC_CTL_HASHONCE_MEMB_BLK_EN; + if (hash_select) + rtic_ctrl |= RTIC_CTL_HASHALGO_MEMB_BLK_EN; + __raw_writel(rtic_ctrl, RTIC_CONTROL); + + break; + + case RTIC_B2: + pr_debug("RTIC Module: Mem Block B2 start address " + "0x%08lX\n", start_addr); + __raw_writel(start_addr, RTIC_MEMBADDR2); + pr_debug("RTIC Module: Mem Block B2 block len " + "0x%08lX\n", blk_len); + __raw_writel(blk_len, RTIC_MEMBLEN2); + rtic_ctrl |= RTIC_CTL_HASHONCE_MEMB_BLK_EN; + if (hash_select) + rtic_ctrl |= RTIC_CTL_HASHALGO_MEMB_BLK_EN; + __raw_writel(rtic_ctrl, RTIC_CONTROL); + break; + + case RTIC_C1: + pr_debug("RTIC Module: Mem Block C1 start address " + "0x%08lX\n", start_addr); + __raw_writel(start_addr, RTIC_MEMCADDR1); + pr_debug("RTIC Module: Mem Block C1 block len " + "0x%08lX\n", blk_len); + __raw_writel(blk_len, RTIC_MEMCLEN1); + rtic_ctrl |= RTIC_CTL_HASHONCE_MEMC_BLK_EN; + if (hash_select) + rtic_ctrl |= RTIC_CTL_HASHALGO_MEMC_BLK_EN; + __raw_writel(rtic_ctrl, RTIC_CONTROL); + + break; + + case RTIC_C2: + pr_debug("RTIC Module: Mem Block C2 start address " + "0x%08lX\n", start_addr); + __raw_writel(start_addr, RTIC_MEMCADDR2); + pr_debug("RTIC Module: Mem Block C2 block len " + "0x%08lX\n", blk_len); + __raw_writel(blk_len, RTIC_MEMCLEN2); + rtic_ctrl |= RTIC_CTL_HASHONCE_MEMC_BLK_EN; + if (hash_select) + rtic_ctrl |= RTIC_CTL_HASHALGO_MEMC_BLK_EN; + __raw_writel(rtic_ctrl, RTIC_CONTROL); + + break; + + case RTIC_D1: + pr_debug("RTIC Module: Mem Block D1 start address " + "0x%08lX\n", start_addr); + __raw_writel(start_addr, RTIC_MEMDADDR1); + pr_debug("RTIC Module: Mem Block D1 block len " + "0x%08lX\n", blk_len); + __raw_writel(blk_len, RTIC_MEMDLEN1); + rtic_ctrl |= RTIC_CTL_HASHONCE_MEMD_BLK_EN; + if (hash_select) + rtic_ctrl |= RTIC_CTL_HASHALGO_MEMD_BLK_EN; + __raw_writel(rtic_ctrl, RTIC_CONTROL); + + break; + + case RTIC_D2: + pr_debug("RTIC Module: Mem Block D2 start address " + "0x%08lX\n", start_addr); + __raw_writel(start_addr, RTIC_MEMDADDR2); + pr_debug("RTIC Module: Mem Block D2 block len " + "0x%08lX\n", blk_len); + __raw_writel(blk_len, RTIC_MEMDLEN2); + rtic_ctrl |= RTIC_CTL_HASHONCE_MEMD_BLK_EN; + if (hash_select) + rtic_ctrl |= RTIC_CTL_HASHALGO_MEMD_BLK_EN; + __raw_writel(rtic_ctrl, RTIC_CONTROL); + + break; + + default: + ret_val = RTIC_FAILURE; + break; + } + return ret_val; +} + +/*! + * This API will configure to start the Hashing of data in memory + * either in One-Time Hash mode or Run-Time Hash mode. + * + * @param mode RTIC mode of operation (ONE_TIME or RUN_TIME). + * + * @return RTIC_SUCCESS Successfully hashed the data.\n + * RTIC_FAILURE Error in the parameters passed. + */ +rtic_ret rtic_start_hash(rtic_mode mode) +{ + rtic_ret ret_val = RTIC_SUCCESS; + ulong rtic_cmd, rtic_sts; + /* Read for RTIC Registers value. */ + rtic_cmd = __raw_readl(RTIC_COMMAND); + rtic_sts = __raw_readl(RTIC_STATUS); + /* Check for RTIC Busy bit before writing into RTIC Registers. */ + if ((rtic_sts & RTIC_BUSY) != 0) { + pr_debug("RTIC Module: RTIC is in BUSY in Hashing\n"); + return RTIC_FAILURE; + } + + switch (mode) { + case RTIC_ONE_TIME: + pr_debug("RTIC Module: Starts the One_time hashing" + "process \n"); + rtic_cmd |= RTIC_CMD_HASH_ONCE; + __raw_writel(rtic_cmd, RTIC_COMMAND); + break; + + case RTIC_RUN_TIME: + pr_debug("RTIC Module: Starts the Run_time hashing" + "process \n"); +#ifndef CONFIG_ARCH_MX27 + /* Check RUN Time Disable bit before starting RUN Time + Hashing. */ + if ((rtic_cmd & RTIC_RUN_TIME_DISABLE) == RTIC_RUN_TIME_DISABLE) { + rtic_cmd &= ~RTIC_RUN_TIME_DISABLE; + } +#endif /* CONFIG_ARCH_MX27 */ + rtic_cmd |= RTIC_CMD_RUN_TIME_CHK; + __raw_writel(rtic_cmd, RTIC_COMMAND); + break; + + default: + ret_val = RTIC_FAILURE; + break; + } + + return ret_val; +} + +/*! + * This API will read the RTIC status register. + * + * @return Status of Hashing. + */ +ulong rtic_get_status(void) +{ + ulong rtic_sts; + /* Read for RTIC Registers value. */ + rtic_sts = __raw_readl(RTIC_STATUS); + pr_debug("RTIC Module: Hashing status register value 0x%08lX\n ", + rtic_sts); + return rtic_sts; +} + +/*! + * This API will read the RTIC control register. + * + * @return Control register value. + */ +ulong rtic_get_control(void) +{ + ulong rtic_ctrl; + /* Read for RTIC Registers value. */ + rtic_ctrl = __raw_readl(RTIC_CONTROL); + pr_debug("RTIC Module: Hashing control register value 0x%08lX\n ", + rtic_ctrl); + return rtic_ctrl; +} + +/*! + * This API enables or disables interrupt for RTIC module. + * + * @param irq_en To enable or disable interrupt. + * + * @return RTIC_SUCCESS RTIC Interrupt configured Successfully .\n + * RTIC_FAILURE Error RTIC Interrupt configured. + */ +rtic_ret rtic_configure_interrupt(rtic_interrupt irq_en) +{ + rtic_ret ret_val = RTIC_SUCCESS; + ulong rtic_ctrl, rtic_sts; + /* Read for RTIC Registers value. */ + rtic_ctrl = __raw_readl(RTIC_CONTROL); + rtic_sts = __raw_readl(RTIC_STATUS); + /* Check for RTIC Busy bit before writing into RTIC Registers. */ + if ((rtic_sts & RTIC_BUSY) != 0) { + pr_debug("RTIC Module: RTIC is in BUSY in Hashing\n"); + return RTIC_FAILURE; + } + switch (irq_en) { + case RTIC_INTERRUPT_ENABLE: + pr_debug("RTIC Module: RTIC Interrupt enabled.\n"); + /* If in interrupt mode then, set the irq enable bit in + the RTIC control register */ + rtic_ctrl |= RTIC_CTL_IRQ_EN; + __raw_writel(rtic_ctrl, RTIC_CONTROL); + break; + + case RTIC_INTERRUPT_DISABLE: + pr_debug("RTIC Module: RTIC Interrupt Disabled.\n"); + /* If in polling mode, then disable the irq enable bit in + the RTIC Control register */ + rtic_ctrl &= ~RTIC_CTL_IRQ_EN; + __raw_writel(rtic_ctrl, RTIC_CONTROL); + break; + + default: + ret_val = RTIC_FAILURE; + break; + } + return ret_val; +} + +/*! + * This API reads the Fault address of the RTIC module. + * + * @return Fault address register value. + */ +ulong rtic_get_faultaddress(void) +{ + ulong rtic_faultaddr; + /* Read for RTIC Registers value. */ + rtic_faultaddr = __raw_readl(RTIC_FAULTADDR); + pr_debug("RTIC Module: Hashing fault register value 0x%08lX\n ", + rtic_faultaddr); + return rtic_faultaddr; +} + +/*! + * This API reads 160/256 bit hash result from Hash result register for + * SHA1/SHA256.The data is copied to the memory pointed by the input pointer. + * SHA256 is supported only in RTIC2. + * + * @param hash_result_reg Hashed Value. + * @param hash_reg Hash result register address. + * @param hash_num Number of Hash Values. + * + * @return RTIC_SUCCESS Successfully hashed the data.\n + * RTIC_FAILURE Error in the parameters passed. + */ + +rtic_ret rtic_hash_read(rtic_hash_rlt * hash_result_reg, ulong hash_reg, + int hash_num) +{ + int i; + rtic_ret ret_val = RTIC_SUCCESS; + for (i = 0; i < hash_num; i++) { + hash_result_reg->hash_result[i] = __raw_readl(hash_reg); + hash_reg += 4; + } + return ret_val; + +} + +/*! + * This API writes 160/256 bit hash result from input pointer to Hash + * result register.This is used after warm boot to restore the hash + * Value and continue with Run Time Hashing.It avoid doing One time + * Hashing again if it is done before Warm Boot.This feature is supported + * only for RTIC2. + * + * @param hash_result_reg Hashed Value. + * @param hash_reg Hash Register Address. + * @param hash_num Number of 4 bytes for a Hash value. + * + * @return RTIC_SUCCESS Successfully hashed the data.\n + * RTIC_FAILURE Error in the parameters passed. + */ + +rtic_ret rtic_hash_write(rtic_hash_rlt * hash_result_reg, rtic_memblk mem_blk, + int hash_num) +{ + int i; + ulong hash_reg; + rtic_ret ret_val = RTIC_SUCCESS; + hash_reg = RTIC_MEMAHASHRES0 + (mem_blk * 20); + for (i = 0; i < hash_num; i++) { + __raw_writel(hash_result_reg->hash_result[i], hash_reg); + hash_reg += 4; + } + return ret_val; +} + +/*! + * This API reads 160/256 bit hash result from Hash result register for + * SHA1/SHA256.The data is copied to the memory pointed by the input pointer. + * SHA256 is supported only in RTIC2. + * + * @param mem_blk Memory block to be hashed. + * @param mode RTIC mode of operation (ONE_TIME or RUN_TIME). + * @param hash_result_reg Hashed value. + * + * @return RTIC_SUCCESS Successfully hashed the data.\n + * RTIC_FAILURE Error in the parameters passed. + */ +rtic_ret +rtic_hash_result(rtic_memblk mem_blk, rtic_mode mode, + rtic_hash_rlt * hash_result_reg) +{ + rtic_ret ret_val = RTIC_SUCCESS; + ulong rtic_sts, rtic_ctrl; + /* Read for RTIC Registers value. */ + rtic_sts = __raw_readl(RTIC_STATUS); + rtic_ctrl = __raw_readl(RTIC_CONTROL); + switch (mode) { + case RTIC_ONE_TIME: + /* In the one-time hash, Check Done interrupt before + reading hash results. */ + if ((rtic_sts & RTIC_DONE) == RTIC_DONE) { + switch (mem_blk) { + case RTIC_A1: + pr_debug("RTIC Module: Read mem blk A hash" + "result\n"); + if ((rtic_ctrl & RTIC_CTL_HASHALGO_MEMA_BLK_EN) + == RTIC_CTL_HASHALGO_MEMA_BLK_EN) { + rtic_hash_read(hash_result_reg, + RTIC_MEMAHASHRES0, 8); + } else + rtic_hash_read(hash_result_reg, + RTIC_MEMAHASHRES0, 5); + break; + + case RTIC_B1: + pr_debug("RTIC Module: Read mem blk B hash" + "result\n"); + if ((rtic_ctrl & RTIC_CTL_HASHALGO_MEMB_BLK_EN) + == RTIC_CTL_HASHALGO_MEMB_BLK_EN) { + rtic_hash_read(hash_result_reg, + RTIC_MEMBHASHRES0, 8); + } else + rtic_hash_read(hash_result_reg, + RTIC_MEMBHASHRES0, 5); + break; + + case RTIC_C1: + pr_debug("RTIC Module: Read mem blk C hash" + "result\n"); + if ((rtic_ctrl & RTIC_CTL_HASHALGO_MEMC_BLK_EN) + == RTIC_CTL_HASHALGO_MEMC_BLK_EN) { + rtic_hash_read(hash_result_reg, + RTIC_MEMCHASHRES0, 8); + } else + rtic_hash_read(hash_result_reg, + RTIC_MEMCHASHRES0, 5); + break; + + case RTIC_D1: + pr_debug("RTIC Module: Read mem blk D hash" + "result\n"); + if ((rtic_ctrl & RTIC_CTL_HASHALGO_MEMD_BLK_EN) + == RTIC_CTL_HASHALGO_MEMD_BLK_EN) { + rtic_hash_read(hash_result_reg, + RTIC_MEMDHASHRES0, 8); + } else + rtic_hash_read(hash_result_reg, + RTIC_MEMDHASHRES0, 5); + break; + + default: + ret_val = RTIC_FAILURE; + break; + } + } else { + pr_debug("RTIC Module: Memory blocks are not hashed " + "for boot authentication.\n"); + ret_val = RTIC_FAILURE; + } + break; + + case RTIC_RUN_TIME: + switch (mem_blk) { + case RTIC_A1: + pr_debug("RTIC Module: run-time check doesn't update" + "Hash result value.\n"); + ret_val = RTIC_FAILURE; + break; + + case RTIC_B1: + pr_debug("RTIC Module: run-time check doesn't update" + "Hash result value.\n"); + ret_val = RTIC_FAILURE; + break; + + case RTIC_C1: + pr_debug("RTIC Module: run-time check doesn't update" + "Hash result value.\n"); + ret_val = RTIC_FAILURE; + break; + + case RTIC_D1: + pr_debug("RTIC Module: run-time check doesn't update" + "Hash result value.\n"); + ret_val = RTIC_FAILURE; + break; + + default: + ret_val = RTIC_FAILURE; + break; + } + break; + + default: + ret_val = RTIC_FAILURE; + break; + } + return ret_val; +} + +/*! + * This API configures RTIC DMA Burst Size. It configures maximum number of + * words to burst read through DMA. This cannot be modified during run-time. + * + * @param dma_burst Maximum DMA Burst Size. + * + * @return RTIC_SUCCESS RTIC DMA successfully configured.\n + * RTIC_FAILURE Error DMA configuration. + */ +rtic_ret rtic_dma_burst_read(rtic_dma_word dma_burst) +{ + ulong rtic_ctrl; + ulong rtic_dma_delay, rtic_sts; + rtic_ret ret_val = RTIC_SUCCESS; + pr_debug("RTIC DMA burst read confirgured.\n"); + /* Read for RTIC Registers value. */ + rtic_ctrl = __raw_readl(RTIC_CONTROL); + rtic_sts = __raw_readl(RTIC_STATUS); + rtic_dma_delay = __raw_readl(RTIC_DMA); + /* Check for RTIC Busy bit before writing into RTIC Registers. */ + if ((rtic_sts & RTIC_BUSY) != 0) { + pr_debug("RTIC Module: RTIC is in BUSY in Hashing\n"); + return RTIC_FAILURE; + } + switch (dma_burst) { + case RTIC_DMA_1_WORD: + rtic_ctrl &= ~DMA_BURST_CLR; + rtic_ctrl |= DMA_1_WORD; + __raw_writel(rtic_ctrl, RTIC_CONTROL); + break; + case RTIC_DMA_2_WORD: + rtic_ctrl &= ~DMA_BURST_CLR; + rtic_ctrl |= DMA_2_WORD; + __raw_writel(rtic_ctrl, RTIC_CONTROL); + break; + case RTIC_DMA_4_WORD: + rtic_ctrl &= ~DMA_BURST_CLR; + rtic_ctrl |= DMA_4_WORD; + __raw_writel(rtic_ctrl, RTIC_CONTROL); + break; + case RTIC_DMA_8_WORD: + rtic_ctrl &= ~DMA_BURST_CLR; + rtic_ctrl |= DMA_8_WORD; + __raw_writel(rtic_ctrl, RTIC_CONTROL); + break; + case RTIC_DMA_16_WORD: + rtic_ctrl &= ~DMA_BURST_CLR; + rtic_ctrl |= DMA_16_WORD; + __raw_writel(rtic_ctrl, RTIC_CONTROL); + break; + default: + ret_val = RTIC_FAILURE; + break; + } + return ret_val; +} + +/*! + * This API sets DMA throttle register to 0x00 during boot time to minimize the + * performance impact at startup. + * + * @return RTIC_SUCCESS DMA throttle register set to 0x00 successfully. + * RTIC_FAILURE Failure in programing DMA throttle register.. + */ +rtic_ret rtic_hash_once_dma_throttle(void) +{ + ulong rtic_sts; + ulong rtic_ctrl; + + rtic_ret ret_val = RTIC_SUCCESS; + /* Read for RTIC Registers value. */ + rtic_sts = __raw_readl(RTIC_STATUS); + /* Check for RTIC Busy bit before writing into RTIC Registers. */ + if ((rtic_sts & RTIC_BUSY) != 0) { + pr_debug("RTIC Module: RTIC is in BUSY in Hashing\n"); + return RTIC_FAILURE; + } + + if (cpu_is_mx27() || (cpu_is_mx31_rev(CHIP_REV_1_0) == 1)) { + __raw_writel(0x00, RTIC_DMA); + } else if (cpu_is_mx31_rev(CHIP_REV_2_0) >= 1) { + /* Setting Hash Once DMA Throttle timer to Zero. */ + rtic_ctrl = __raw_readl(RTIC_CONTROL); + rtic_ctrl &= ~HASH_ONCE_DMA_THROTTLE_CLR; + __raw_writel(rtic_ctrl, RTIC_CONTROL); + } + + return ret_val; +} + +/*! This API programs the DMA Programmable Timer to set to specify how many + * cycles to wait between DMA bus access. + * + * @param dma_delay DMA Bus Duty Cycle Delay. + * + * @return RTIC_SUCCESS RTIC DMA Duty Cycle delay set successfully.\n + * RTIC_FAILURE Failure in programing DMA bus delay. + */ +rtic_ret rtic_dma_delay(ulong dma_delay) +{ + ulong rtic_sts; + rtic_ret ret_val = RTIC_SUCCESS; + /* Read for RTIC Registers value. */ + rtic_sts = __raw_readl(RTIC_STATUS); + /* Check for RTIC Busy bit before writing into RTIC Registers. */ + if ((rtic_sts & RTIC_BUSY) != 0) { + pr_debug("RTIC Module: RTIC is in BUSY in Hashing\n"); + return RTIC_FAILURE; + } + __raw_writel(dma_delay, RTIC_DMA); + return ret_val; +} + +/*! + * This API Configures DMA Watchdog timer. + * + * @param wd_timer DMA Watchdog timer value. + * + * @return RTIC_SUCCESS RTIC DMA Watchdog Timer set successfully.\n + * RTIC_FAILURE Failure in programing DMA Watchdog Timer. + * + */ +rtic_ret rtic_wd_timer(ulong wd_timer) +{ + ulong rtic_sts; + rtic_ret ret_val = RTIC_SUCCESS; + /* Read for RTIC Registers value. */ + rtic_sts = __raw_readl(RTIC_STATUS); + /* Check for RTIC Busy bit before writing into RTIC Registers. */ + if ((rtic_sts & RTIC_BUSY) != 0) { + pr_debug("RTIC Module: RTIC is in BUSY in Hashing\n"); + return RTIC_FAILURE; + } + __raw_writel(wd_timer, RTIC_WDTIMER); + return ret_val; +} + +/*! + * This API is used for Software reset RTIC Module. + * + * @param rtic_rst To Enable and Disable RTIC SW Reset. + * + * @return RTIC_SUCCESS RTIC SW Reset Configured successfully.\n + * RTIC_FAILURE Failure in configuring. + */ +rtic_ret rtic_sw_reset(rtic_sw_rst rtic_rst) +{ + ulong rtic_cmd, rtic_sts; + rtic_ret ret_val = RTIC_SUCCESS; + /* Read for RTIC Registers value. */ + rtic_cmd = __raw_readl(RTIC_COMMAND); + rtic_sts = __raw_readl(RTIC_STATUS); + /* Check for RTIC Busy bit before writing into RTIC Registers. */ + if ((rtic_sts & RTIC_BUSY) != 0) { + pr_debug("RTIC Module: RTIC is in BUSY in Hashing\n"); + return RTIC_FAILURE; + } + switch (rtic_rst) { + case RTIC_RST_ENABLE: + pr_debug("RTIC Module: RTIC SW Reset enabled.\n"); + rtic_cmd |= RTIC_CMD_SW_RESET; + __raw_writel(rtic_cmd, RTIC_COMMAND); + break; + + case RTIC_RST_DISABLE: + pr_debug("RTIC Module: RTIC SW Reset Disabled.\n"); + rtic_cmd &= ~RTIC_CMD_SW_RESET; + __raw_writel(rtic_cmd, RTIC_COMMAND); + break; + + default: + ret_val = RTIC_FAILURE; + break; + } + return ret_val; +} + +/*! + * This API is used RTIC to clear IRQ. + * + * @param rtic_irq_clr To Clear RTIC IRQ. + * + * @return RTIC_SUCCESS Clear RTIC IRQ Successfully.\n + * RTIC_FAILURE Failure in Clearing RTIC IRQ. + */ +rtic_ret rtic_clr_irq(rtic_clear_irq rtic_irq_clr) +{ + ulong rtic_cmd, rtic_sts; + rtic_ret ret_val = RTIC_SUCCESS; + /* Read for RTIC Registers value. */ + rtic_cmd = __raw_readl(RTIC_COMMAND); + rtic_sts = __raw_readl(RTIC_STATUS); + /* Check for RTIC Busy bit before writing into RTIC Registers. */ + if ((rtic_sts & RTIC_BUSY) != 0) { + pr_debug("RTIC Module: RTIC is in BUSY in Hashing\n"); + return RTIC_FAILURE; + } + switch (rtic_irq_clr) { + case RTIC_CLR_IRQ_ENABLE: + pr_debug("RTIC Module: RTIC SW Reset enabled.\n"); + rtic_cmd |= RTIC_CMD_CLRIRQ; + __raw_writel(rtic_cmd, RTIC_COMMAND); + break; + + case RTIC_CLR_IRQ_DISABLE: + pr_debug("RTIC Module: RTIC SW Reset Disabled.\n"); + rtic_cmd &= ~RTIC_CMD_CLRIRQ; + __raw_writel(rtic_cmd, RTIC_COMMAND); + break; + + default: + ret_val = RTIC_FAILURE; + break; + } + return ret_val; +} diff --git a/drivers/mxc/security/mxc_rtic.h b/drivers/mxc/security/mxc_rtic.h new file mode 100644 index 000000000000..13b73421670c --- /dev/null +++ b/drivers/mxc/security/mxc_rtic.h @@ -0,0 +1,317 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mxc_rtic.h + * + * @brief The header file for Run-Time Integrity Checker (RTIC) module. + * This file contains all register defines and bit definition of RTIC module. + * + * @ingroup MXC_Security + */ + +#ifndef __MXC_RTIC_H__ +#define __MXC_RTIC_H__ + +#include <asm/arch/mxc_security_api.h> +#include <asm/arch/hardware.h> +#include <asm-generic/errno-base.h> + +/* + * RTIC status register + */ +#define RTIC_STATUS IO_ADDRESS(RTIC_BASE_ADDR + 0x00) + +/* + * RTIC Command Register + */ +#define RTIC_COMMAND IO_ADDRESS(RTIC_BASE_ADDR + 0x04) + +/* + * RTIC Control Register + */ +#define RTIC_CONTROL IO_ADDRESS(RTIC_BASE_ADDR + 0x08) + +/* + * RTIC Delay Timer/DMA Throttle + */ +#define RTIC_DMA IO_ADDRESS(RTIC_BASE_ADDR + 0x0C) + +/* + * RTIC Memory A Address 1 + */ +#define RTIC_MEMAADDR1 IO_ADDRESS(RTIC_BASE_ADDR + 0x10) + +/* + * RTIC Memory A Length 1 + */ +#define RTIC_MEMALEN1 IO_ADDRESS(RTIC_BASE_ADDR + 0x14) + +/* + * RTIC Memory A Address 2 + */ +#define RTIC_MEMAADDR2 IO_ADDRESS(RTIC_BASE_ADDR + 0x18) +/* + * RTIC Memory A Length 2 + */ +#define RTIC_MEMALEN2 IO_ADDRESS(RTIC_BASE_ADDR + 0x1C) + +/* + * RTIC Memory B Address 1 + */ +#define RTIC_MEMBADDR1 IO_ADDRESS(RTIC_BASE_ADDR + 0x30) + +/* + * RTIC Memory B Length 1 + */ +#define RTIC_MEMBLEN1 IO_ADDRESS(RTIC_BASE_ADDR + 0x34) + +/* + * RTIC Memory B Address 2 + */ +#define RTIC_MEMBADDR2 IO_ADDRESS(RTIC_BASE_ADDR + 0x38) + +/* + * RTIC Memory B Length 2 + */ +#define RTIC_MEMBLEN2 IO_ADDRESS(RTIC_BASE_ADDR + 0x3C) + +/* + * RTIC Memory C Address 1 + */ +#define RTIC_MEMCADDR1 IO_ADDRESS(RTIC_BASE_ADDR + 0x50) + +/* + * RTIC Memory C Length 1 + */ +#define RTIC_MEMCLEN1 IO_ADDRESS(RTIC_BASE_ADDR + 0x54) + +/* + * RTIC Memery C Address 2 + */ +#define RTIC_MEMCADDR2 IO_ADDRESS(RTIC_BASE_ADDR + 0x58) + +/* + * RTIC Memory C Length 2 + */ +#define RTIC_MEMCLEN2 IO_ADDRESS(RTIC_BASE_ADDR + 0x5C) + +/* + * RTIC Memory D Address 1 + */ +#define RTIC_MEMDADDR1 IO_ADDRESS(RTIC_BASE_ADDR + 0x70) + +/* + * RTIC Memory D Length 1 + */ +#define RTIC_MEMDLEN1 IO_ADDRESS(RTIC_BASE_ADDR + 0x74) + +/* + * RTIC Memory D Address 2 + */ +#define RTIC_MEMDADDR2 IO_ADDRESS(RTIC_BASE_ADDR + 0x78) + +/* + * RTIC Memory D Length 2 + */ +#define RTIC_MEMDLEN2 IO_ADDRESS(RTIC_BASE_ADDR + 0x7C) + +/* + * RTIC Fault address Register. + */ +#define RTIC_FAULTADDR IO_ADDRESS(RTIC_BASE_ADDR + 0x90) + +/* + * RTIC Watchdog Timeout + */ +#define RTIC_WDTIMER IO_ADDRESS(RTIC_BASE_ADDR + 0x94) + +/* + * RTIC Memory A Hash Result Word A + */ +#define RTIC_MEMAHASHRES0 IO_ADDRESS(RTIC_BASE_ADDR + 0xA0) + +/* + * RTIC Memory B Hash Result Word A + */ +#define RTIC_MEMBHASHRES0 IO_ADDRESS(RTIC_BASE_ADDR + 0xC0) + +/* + * RTIC Memory C Hash Result Word A + */ +#define RTIC_MEMCHASHRES0 IO_ADDRESS(RTIC_BASE_ADDR + 0xE0) +/* + * RTIC Memory D Hash Result Word A + */ +#define RTIC_MEMDHASHRES0 IO_ADDRESS(RTIC_BASE_ADDR + 0x100) +/* + * RTIC Memory Integrity Status + */ +#define RTIC_STAT_MEMBLK 0x100 + +/* + * RTIC Address Error + */ +#define RTIC_STAT_ADDR_ERR 0x200 + +/* + * RTIC Length Error + */ +#define RTIC_STAT_LEN_ERR 0x400 + +/* + * RTIC Clear Interrupts command + */ +#define RTIC_CMD_CLRIRQ 0x01 + +/* + * RTIC SW reset command + */ +#define RTIC_CMD_SW_RESET 0x02 + +/* + * RTIC Hash Once command + */ +#define RTIC_CMD_HASH_ONCE 0x04 + +/* + * RTIC Run Time Check Command + */ +#define RTIC_CMD_RUN_TIME_CHK 0x08 + +/* + * RTIC IRQ EN + */ +#define RTIC_CTL_IRQ_EN 0x01 + +/* + * RTIC Hash ALGO 256 Memory block A enable + */ +#define RTIC_CTL_HASHALGO_MEMA_BLK_EN 0x1000 + +/* + * RTIC Hash ALGO 256 Memory block B Enable + */ +#define RTIC_CTL_HASHALGO_MEMB_BLK_EN 0x2000 + +/* + * RTIC Hash ALGO 256 Memory block C Enable + */ +#define RTIC_CTL_HASHALGO_MEMC_BLK_EN 0x4000 + +/* + * RTIC Hash ALGO 256 Memory block D Enable + */ +#define RTIC_CTL_HASHALGO_MEMD_BLK_EN 0x8000 + +/* + * RTIC Hash Once Memory block A enable + */ +#define RTIC_CTL_HASHONCE_MEMA_BLK_EN 0x10 + +/* + * RTIC Hash Once Memory block B Enable + */ +#define RTIC_CTL_HASHONCE_MEMB_BLK_EN 0x20 + +/* + * RTIC Hash Once Memory block C Enable + */ +#define RTIC_CTL_HASHONCE_MEMC_BLK_EN 0x40 + +/* + * RTIC Hash Once Memory block D Enable + */ +#define RTIC_CTL_HASHONCE_MEMD_BLK_EN 0x80 + +/* + * RTIC Run Time Memory blk A Enable + */ +#define RTIC_CTL_RUNTIME_MEMA_BLK_EN 0x100 + +/* + * RTIC Run Time Memory blk B Enable + */ +#define RTIC_CTL_RUNTIME_MEMB_BLK_EN 0x200 + +/* + * RTIC Run Time Memory blk C Enable + */ +#define RTIC_CTL_RUNTIME_MEMC_BLK_EN 0x400 + +/* + * RTIC Run Time Memory blk D Enable + */ +#define RTIC_CTL_RUNTIME_MEMD_BLK_EN 0x800 + +/*! + * Maximum block length that can be configured to the RTIC module. + */ +#define RTIC_MAX_BLOCK_LENGTH 0xFFFFFFFC + +/*! + * This define is used to clear DMA Burst in RTIC Control register. + */ +#define DMA_BURST_CLR 0xE + +//#ifndef CONFIG_ARCH_MX27 +/*! + * This define will clear Hash Once DMA Throttle Programmable timer. + */ +#define HASH_ONCE_DMA_THROTTLE_CLR (0xFF << 16) + +//#endif /* CONFIG_ARCH_MX27 */ + +/*! + * This define is used to configure DMA Burst read as 1 word. + */ +#define DMA_1_WORD 0x00 + +/*! + * This define is used to configure DMA Burst read as 2 words. + */ +#define DMA_2_WORD 0x02 + +/*! + * This define is used to configure DMA Burst read as 4 words. + */ +#define DMA_4_WORD 0x04 + +/*! + * This define is used to configure DMA Burst read as 8 words. + */ +#define DMA_8_WORD 0x06 + +/*! + * This define is used to configure DMA Burst read as 16 words. + */ +#define DMA_16_WORD 0x08 + +/*! + * This define is used for checking RTIC is busy or not. + */ +#define RTIC_BUSY 0x01 + +#ifndef CONFIG_ARCH_MX27 +/*! + * This define is used for checking RUN Time Disable bit is set or not. + */ +#define RTIC_RUN_TIME_DISABLE 0x10 + +#endif /* CONFIG_ARCH_MX27 */ + +/*! + * This define is used to check Done bit set in RTIC Status register. + */ +#define RTIC_DONE 0x02 +#endif /* __MXC_RTIC_H__ */ diff --git a/drivers/mxc/security/mxc_scc.c b/drivers/mxc/security/mxc_scc.c new file mode 100644 index 000000000000..c3b41d49904f --- /dev/null +++ b/drivers/mxc/security/mxc_scc.c @@ -0,0 +1,2297 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mxc_scc.c + * + * @brief This is the driver code for the Security Controller (SCC). It has no device + * driver interface, so no user programs may access it. Its interaction with + * the Linux kernel is from calls to #scc_init() when the driver is loaded, and + * #scc_cleanup() should the driver be unloaded. The driver uses locking and + * (task-sleep/task-wakeup) functions of the kernel. It also registers itself + * to handle the interrupt line(s) from the SCC. + * + * Other drivers in the kernel may use the remaining API functions to get at + * the services of the SCC. The main service provided is the Secure Memory, + * which allows encoding and decoding of secrets with a per-chip secret key. + * + * The SCC is single-threaded, and so is this module. When the scc_crypt() + * routine is called, it will lock out other accesses to the function. If + * another task is already in the module, the subsequent caller will spin on a + * lock waiting for the other access to finish. + * + * Note that long crypto operations could cause a task to spin for a while, + * preventing other kernel work (other than interrupt processing) to get done. + * + * The external (kernel module) interface is through the following functions: + * @li scc_get_configuration() + * @li scc_crypt() + * @li scc_zeroize_memories() + * @li scc_monitor_security_failure() + * @li scc_stop_monitoring_security_failure() + * @li scc_set_sw_alarm() + * @li scc_read_register() + * @li scc_write_register() + * + * All other functions are internal to the driver. + * + * @ingroup MXCSCC + */ + +#include <linux/platform_device.h> +#include <linux/delay.h> +#include <linux/clk.h> +#include "mxc_scc_internals.h" + + +/****************************************************************************** + * + * Global / Static Variables + * + *****************************************************************************/ + +/*! + * This is type void* so that a) it cannot directly be dereferenced, + * and b) pointer arithmetic on it will function in a 'normal way' for + * the offsets in scc_defines.h + * + * scc_base is the location in the iomap where the SCC's registers + * (and memory) start. + * + * The referenced data is declared volatile so that the compiler will + * not make any assumptions about the value of registers in the SCC, + * and thus will always reload the register into CPU memory before + * using it (i.e. wherever it is referenced in the driver). + * + * This value should only be referenced by the #SCC_READ_REGISTER and + * #SCC_WRITE_REGISTER macros and their ilk. All dereferences must be + * 32 bits wide. + */ +//#define static +static void *scc_base; +static struct clk *scc_clk; + +/*! Array to hold function pointers registered by + #scc_monitor_security_failure() and processed by + #scc_perform_callbacks() */ +static void (*scc_callbacks[SCC_CALLBACK_SIZE]) (void); + +/*! Structure returned by #scc_get_configuration() */ +static scc_config_t scc_configuration = { + .driver_major_version = SCC_DRIVER_MAJOR_VERSION_1, + .driver_minor_version = SCC_DRIVER_MINOR_VERSION_5, + .scm_version = -1, + .smn_version = -1, + .block_size_bytes = -1, + .black_ram_size_blocks = -1, + .red_ram_size_blocks = -1 +}; + +/*! Key Control Information. Integrity is controlled by use of + #scc_crypto_lock. */ +static struct scc_key_slot scc_key_info[SCC_KEY_SLOTS]; + +/*! Internal flag to know whether SCC is in Failed state (and thus many + * registers are unavailable). Once it goes failed, it never leaves it. */ +static volatile enum scc_status scc_availability = SCC_STATUS_INITIAL; + +/*! Flag to say whether interrupt handler has been registered for + * SMN interrupt */ +static int smn_irq_set = 0; + +/*! Flag to say whether interrupt handler has been registered for + * SCM interrupt */ +static int scm_irq_set = 0; + +/*! This lock protects the #scc_callbacks list as well as the @c + * callbacks_performed flag in #scc_perform_callbacks. Since the data this + * protects may be read or written from either interrupt or base level, all + * operations should use the irqsave/irqrestore or similar to make sure that + * interrupts are inhibited when locking from base level. + */ +static spinlock_t scc_callbacks_lock = SPIN_LOCK_UNLOCKED; + +/*! + * Ownership of this lock prevents conflicts on the crypto operation in the SCC + * and the integrity of the #scc_key_info. + */ +static spinlock_t scc_crypto_lock = SPIN_LOCK_UNLOCKED; + +/*! Calculated once for quick reference to size of the unreserved space in one + * RAM in SCM. + */ +static uint32_t scc_memory_size_bytes; + +/*! Calculated once for quick reference to size of SCM address space */ +static uint32_t scm_highest_memory_address; + +/*! The lookup table for an 8-bit value. Calculated once + * by #scc_init_ccitt_crc(). + */ +static uint16_t scc_crc_lookup_table[256]; +uint8_t make_vpu_partition(void); + +/*! Fixed padding for appending to plaintext to fill out a block */ +static uint8_t scc_block_padding[8] = + { SCC_DRIVER_PAD_CHAR, 0, 0, 0, 0, 0, 0, 0 }; + + +/*! + * This is the set of errors which signal that access to the SCM RAM has + * failed or will fail. + */ +#define SCM_ACCESS_ERRORS \ + (SCM_ERR_USER_ACCESS | SCM_ERR_ILLEGAL_ADDRESS | \ + SCM_ERR_ILLEGAL_MASTER | SCM_ERR_CACHEABLE_ACCESS | \ + SCM_ERR_UNALIGNED_ACCESS | SCM_ERR_BYTE_ACCESS | \ + SCM_ERR_INTERNAL_ERROR | SCM_ERR_SMN_BLOCKING_ACCESS | \ + SCM_ERR_CIPHERING | SCM_ERR_ZEROIZING | SCM_ERR_BUSY) + +/*! + * This function is called to put the SCC in a low power state. + * + * @param pdev the device structure used to give information on which SCC + * device (0 through 3 channels) to suspend + * @param state the power state the device is entering + * + * @return The function always returns 0. + */ +static int mxc_scc_suspend(struct platform_device *pdev, pm_message_t state) +{ + pr_debug(" MXC SCC driver suspend function\n"); + /* Turn off clock */ + clk_disable(scc_clk); + return 0; +} + +/*! + * This function is called to resume the SCC from a low power state. + * + * @param pdev the device structure used to give information on which SCC + * device (0 through 3 channels) to suspend + * + * @return The function always returns 0. + */ +static int mxc_scc_resume(struct platform_device *pdev) +{ + pr_debug("MXC SCC driver resume function\n"); + /* Turn on clock */ + clk_enable(scc_clk); + + return 0; +} + +static int mxc_scc_probe(struct platform_device *pdev); +static int mxc_scc_remove(struct platform_device *pdev); +/*! + * This structure contains pointers to the power management callback functions. + */ +static struct platform_driver mxc_scc_driver = { + .driver = { + .name = "mxc_scc", + .bus = &platform_bus_type, + .owner = THIS_MODULE, + }, + .probe = mxc_scc_probe, + .remove = mxc_scc_remove, + .suspend = mxc_scc_suspend, + .resume = mxc_scc_resume, +}; + +#undef static +/*! + * Registering the SCC driver + * + */ +static int scc_init(void) +{ + int ret; + ret = platform_driver_register(&mxc_scc_driver); + return ret; +} + +/****************************************************************************** + * + * Function Implementations - Externally Accessible + * + *****************************************************************************/ + +/*****************************************************************************/ +/* fn mxc_scc_probe() */ +/*****************************************************************************/ +/*! + * Initialize the driver at boot time or module load time. + * + * Register with the kernel as the interrupt handler for the SCC interrupt + * line(s). + * + * Map the SCC's register space into the driver's memory space. + * + * Query the SCC for its configuration and status. Save the configuration in + * #scc_configuration and save the status in #scc_availability. Called by the + * kernel. + * + * Do any locking/wait queue initialization which may be necessary. + * + * The availability fuse may be checked, depending on platform. + */ +static int mxc_scc_probe(struct platform_device *pdev) +{ + uint32_t smn_status; + int i; + int return_value = -EIO; /* assume error */ + /* Enable the SCC clocks */ + pr_debug(KERN_ALERT "SCC: Enabling the SCC CLK ... \n"); + scc_clk = clk_get(NULL, "scc_clk"); + clk_enable(scc_clk); + if (scc_availability == SCC_STATUS_INITIAL) { + + /* Set this until we get an initial reading */ + scc_availability = SCC_STATUS_CHECKING; + + /* Initialize the constant for the CRC function */ + scc_init_ccitt_crc(); + + /* initialize the callback table */ + for (i = 0; i < SCC_CALLBACK_SIZE; i++) { + scc_callbacks[i] = 0; + } + + /* Initialize key slots */ + for (i = 0; i < SCC_KEY_SLOTS; i++) { + scc_key_info[i].offset = i * SCC_KEY_SLOT_SIZE; + scc_key_info[i].status = 0; /* unassigned */ + } + + /* See whether there is an SCC available */ + if (0 && !SCC_ENABLED()) { + printk + ("SCC: Fuse for SCC is set to disabled. Exiting.\n"); + } else { + /* Map the SCC (SCM and SMN) memory on the internal bus into + kernel address space */ + + scc_base = ioremap_nocache(SCC_BASE, SCC_ADDRESS_RANGE); + + /* If that worked, we can try to use the SCC */ + if (scc_base == NULL) { + pr_debug + ("SCC: Register mapping failed. Exiting.\n"); + } else { + /* Get SCM into 'clean' condition w/interrupts cleared & + disabled */ + SCC_WRITE_REGISTER(SCM_INTERRUPT_CTRL, + SCM_INTERRUPT_CTRL_CLEAR_INTERRUPT + | + SCM_INTERRUPT_CTRL_MASK_INTERRUPTS); + + /* Clear error status register (any write will do it) */ + SCC_WRITE_REGISTER(SCM_ERROR_STATUS, 0); + + /* + * There is an SCC. Determine its current state. Side effect + * is to populate scc_config and scc_availability + */ + smn_status = scc_grab_config_values(); + + /* Try to set up interrupt handler(s) */ + if (scc_availability == SCC_STATUS_OK) { + if (setup_interrupt_handling() != 0) { + /** + * The error could be only that the SCM interrupt was + * not set up. This interrupt is always masked, so + * that is not an issue. + * + * The SMN's interrupt may be shared on that line, it + * may be separate, or it may not be wired. Do what + * is necessary to check its status. + * + * Although the driver is coded for possibility of not + * having SMN interrupt, the fact that there is one + * means it should be available and used. + */ +#ifdef USE_SMN_INTERRUPT + if (!smn_irq_set) { /* Separate. Check SMN binding */ +#elif !defined(NO_SMN_INTERRUPT) + if (!scm_irq_set) { /* Shared. Check SCM binding */ +#else + if (FALSE) { /* SMN not wired at all. Ignore. */ +#endif + /* setup was not able to set up SMN interrupt */ + scc_availability = + SCC_STATUS_UNIMPLEMENTED; + } + } /* interrupt handling returned non-zero */ + } /* availability is OK */ + if (scc_availability == SCC_STATUS_OK) { + /* Get SMN into 'clean' condition w/interrupts cleared & + enabled */ + SCC_WRITE_REGISTER(SMN_COMMAND, + SMN_COMMAND_CLEAR_INTERRUPT + | + SMN_COMMAND_ENABLE_INTERRUPT); + } + /* availability is still OK */ + } /* if scc_base != NULL */ + + } /* if SCC_ENABLED() */ + + /* + * If status is SCC_STATUS_UNIMPLEMENTED or is still + * SCC_STATUS_CHECKING, could be leaving here with the driver partially + * initialized. In either case, cleanup (which will mark the SCC as + * UNIMPLEMENTED). + */ + if (scc_availability == SCC_STATUS_CHECKING || + scc_availability == SCC_STATUS_UNIMPLEMENTED) { + mxc_scc_remove(pdev); + } else { + return_value = 0; /* All is well */ + } + } + /* ! STATUS_INITIAL */ + pr_debug("SCC: Driver Status is %s\n", + (scc_availability == SCC_STATUS_INITIAL) ? "INITIAL" : + (scc_availability == SCC_STATUS_CHECKING) ? "CHECKING" : + (scc_availability == + SCC_STATUS_UNIMPLEMENTED) ? "UNIMPLEMENTED" : (scc_availability + == + SCC_STATUS_OK) ? + "OK" : (scc_availability == + SCC_STATUS_FAILED) ? "FAILED" : "UNKNOWN"); + + return return_value; +} /* mxc_scc_probe */ + +/*****************************************************************************/ +/* fn mxc_scc_remove() */ +/*****************************************************************************/ +/*! + * Perform cleanup before driver/module is unloaded by setting the machine + * state close to what it was when the driver was loaded. This function is + * called when the kernel is shutting down or when this driver is being + * unloaded. + * + * A driver like this should probably never be unloaded, especially if there + * are other module relying upon the callback feature for monitoring the SCC + * status. + * + * In any case, cleanup the callback table (by clearing out all of the + * pointers). Deregister the interrupt handler(s). Unmap SCC registers. + * + */ +static int mxc_scc_remove(struct platform_device *pdev) +{ + int i; + + /* Mark the driver / SCC as unusable. */ + scc_availability = SCC_STATUS_UNIMPLEMENTED; + + /* Clear out callback table */ + for (i = 0; i < SCC_CALLBACK_SIZE; i++) { + scc_callbacks[i] = 0; + } + + /* If SCC has been mapped in, clean it up and unmap it */ + if (scc_base) { + /* For the SCM, disable interrupts, zeroize RAMs. Interrupt + * status will appear because zeroize will complete. */ + SCC_WRITE_REGISTER(SCM_INTERRUPT_CTRL, + SCM_INTERRUPT_CTRL_MASK_INTERRUPTS | + SCM_INTERRUPT_CTRL_ZEROIZE_MEMORY); + + /* For the SMN, clear and disable interrupts */ + SCC_WRITE_REGISTER(SMN_COMMAND, SMN_COMMAND_CLEAR_INTERRUPT); + + /* remove virtual mapping */ + iounmap((void *)scc_base); + } + + /* Now that interrupts cannot occur, disassociate driver from the interrupt + * lines. + */ + + /* Deregister SCM interrupt handler */ + if (scm_irq_set) { + free_irq(INT_SCC_SCM, NULL); + } + + /* Deregister SMN interrupt handler */ + if (smn_irq_set) { +#ifdef USE_SMN_INTERRUPT + free_irq(INT_SCC_SMN, NULL); +#endif + } + pr_debug("SCC driver cleaned up.\n"); + return 0; + +} /* mxc_scc_remove */ + +static void scc_cleanup(void) +{ + platform_driver_unregister(&mxc_scc_driver); +} + +/*****************************************************************************/ +/* fn scc_get_configuration() */ +/*****************************************************************************/ +scc_config_t *scc_get_configuration(void) +{ + + /** + * If there is no SCC, yet the driver exists, the value -1 will be in + * the #scc_config_t fields for other than the driver versions. + */ + return &scc_configuration; +} /* scc_get_configuration */ + +/*****************************************************************************/ +/* fn scc_zeroize_memories() */ +/*****************************************************************************/ +scc_return_t scc_zeroize_memories(void) +{ + scc_return_t return_status = SCC_RET_FAIL; + uint32_t status; + + + if (scc_availability == SCC_STATUS_OK) { + unsigned long irq_flags; /* for IRQ save/restore */ + + /* Lock access to crypto memory of the SCC */ + spin_lock_irqsave(&scc_crypto_lock, irq_flags); + + /* Start the Zeroize by setting a bit in the SCM_INTERRUPT_CTRL + * register */ + SCC_WRITE_REGISTER(SCM_INTERRUPT_CTRL, + SCM_INTERRUPT_CTRL_MASK_INTERRUPTS + | SCM_INTERRUPT_CTRL_ZEROIZE_MEMORY); + + scc_wait_completion(); + + /* Get any error info */ + status = SCC_READ_REGISTER(SCM_ERROR_STATUS); + + /* unlock the SCC */ + spin_unlock_irqrestore(&scc_crypto_lock, irq_flags); + + if (!(status & SCM_ERR_ZEROIZE_FAILED)) { + return_status = SCC_RET_OK; + } + else { + pr_debug + ("SCC: Zeroize failed. SCM Error Status is 0x%08x\n", + status); + } + + /* Clear out any status. */ + SCC_WRITE_REGISTER(SCM_INTERRUPT_CTRL, + SCM_INTERRUPT_CTRL_CLEAR_INTERRUPT + | SCM_INTERRUPT_CTRL_MASK_INTERRUPTS); + + /* and any error status */ + SCC_WRITE_REGISTER(SCM_ERROR_STATUS, 0); + } + + return return_status; +} /* scc_zeroize_memories */ + +/*****************************************************************************/ +/* fn scc_crypt() */ +/*****************************************************************************/ +scc_return_t +scc_crypt(unsigned long count_in_bytes, uint8_t * data_in, + uint8_t * init_vector, scc_enc_dec_t direction, + scc_crypto_mode_t crypto_mode, scc_verify_t check_mode, + uint8_t * data_out, unsigned long *count_out_bytes) +{ + scc_return_t return_code = SCC_RET_FAIL; + + + (void)scc_update_state(); /* in case no interrupt line from SMN */ + + /* make initial error checks */ + if (scc_availability != SCC_STATUS_OK + || count_in_bytes == 0 + || data_in == 0 + || data_out == 0 + || (crypto_mode != SCC_CBC_MODE && crypto_mode != SCC_ECB_MODE) + || (crypto_mode == SCC_CBC_MODE && init_vector == NULL) + || (direction != SCC_ENCRYPT && direction != SCC_DECRYPT) + || (check_mode == SCC_VERIFY_MODE_NONE && + count_in_bytes % SCC_BLOCK_SIZE_BYTES() != 0) + || (direction == SCC_DECRYPT && + count_in_bytes % SCC_BLOCK_SIZE_BYTES() != 0) + || (check_mode != SCC_VERIFY_MODE_NONE && + check_mode != SCC_VERIFY_MODE_CCITT_CRC)) { + pr_debug("SCC: scc_crypt() detected bad argument\n"); + } else { + /* Start settings for write to SCM_CONTROL register */ + uint32_t scc_control = SCM_CONTROL_START_CIPHER; + unsigned long irq_flags; /* for IRQ save/restore */ + + /* Lock access to crypto memory of the SCC */ + spin_lock_irqsave(&scc_crypto_lock, irq_flags); + + /* Special needs for CBC Mode */ + if (crypto_mode == SCC_CBC_MODE) { + scc_control |= SCM_CBC_MODE; /* change default of ECB */ + /* Put in Initial Context. Vector registers are contiguous */ + copy_to_scc(init_vector, SCM_INIT_VECTOR_0, + SCC_BLOCK_SIZE_BYTES(), NULL); + } + + /* Fill the RED_START register */ + SCC_WRITE_REGISTER(SCM_RED_START, + SCM_NON_RESERVED_OFFSET / + SCC_BLOCK_SIZE_BYTES()); + + /* Fill the BLACK_START register */ + SCC_WRITE_REGISTER(SCM_BLACK_START, + SCM_NON_RESERVED_OFFSET / + SCC_BLOCK_SIZE_BYTES()); + + if (direction == SCC_ENCRYPT) { + /* Check for sufficient space in data_out */ + if (check_mode == SCC_VERIFY_MODE_NONE) { + if (*count_out_bytes < count_in_bytes) { + return_code = + SCC_RET_INSUFFICIENT_SPACE; + } + } else { /* SCC_VERIFY_MODE_CCITT_CRC */ + /* Calculate extra bytes needed for crc (2) and block + padding */ + int padding_needed = + CRC_SIZE_BYTES + SCC_BLOCK_SIZE_BYTES() - + ((count_in_bytes + CRC_SIZE_BYTES) + % SCC_BLOCK_SIZE_BYTES()); + + /* Verify space is available */ + if (*count_out_bytes < + count_in_bytes + padding_needed) { + return_code = + SCC_RET_INSUFFICIENT_SPACE; + } + } + /* If did not detect space error, do the encryption */ + if (return_code != SCC_RET_INSUFFICIENT_SPACE) { + return_code = + scc_encrypt(count_in_bytes, data_in, + scc_control, data_out, + check_mode == + SCC_VERIFY_MODE_CCITT_CRC, + count_out_bytes); + } + + } + /* direction == SCC_ENCRYPT */ + else { /* SCC_DECRYPT */ + /* Check for sufficient space in data_out */ + if (check_mode == SCC_VERIFY_MODE_NONE) { + if (*count_out_bytes < count_in_bytes) { + return_code = + SCC_RET_INSUFFICIENT_SPACE; + } + } else { /* SCC_VERIFY_MODE_CCITT_CRC */ + /* Do initial check. Assume last block (of padding) and CRC + * will get stripped. After decipher is done and padding is + * removed, will know exact value. + */ + int possible_size = + (int)count_in_bytes - CRC_SIZE_BYTES - + SCC_BLOCK_SIZE_BYTES(); + if ((int)*count_out_bytes < possible_size) { + pr_debug + ("SCC: insufficient decrypt space %ld/%d.\n", + *count_out_bytes, possible_size); + return_code = + SCC_RET_INSUFFICIENT_SPACE; + } + } + + /* If did not detect space error, do the decryption */ + if (return_code != SCC_RET_INSUFFICIENT_SPACE) { + return_code = + scc_decrypt(count_in_bytes, data_in, + scc_control, data_out, + check_mode == + SCC_VERIFY_MODE_CCITT_CRC, + count_out_bytes); + } + + } /* SCC_DECRYPT */ + + /* unlock the SCC */ + spin_unlock_irqrestore(&scc_crypto_lock, irq_flags); + + } /* else no initial error */ + + return return_code; +} /* scc_crypt */ + +/*****************************************************************************/ +/* fn scc_set_sw_alarm() */ +/*****************************************************************************/ +void scc_set_sw_alarm(void) +{ + + + /* Update scc_availability based on current SMN status. This might + * perform callbacks. + */ + (void)scc_update_state(); + + /* if everything is OK, make it fail */ + if (scc_availability == SCC_STATUS_OK) { + + /* sound the alarm (and disable SMN interrupts */ + SCC_WRITE_REGISTER(SMN_COMMAND, SMN_COMMAND_SET_SOFTWARE_ALARM); + + scc_availability = SCC_STATUS_FAILED; /* Remember what we've done */ + + /* In case SMN interrupt is not available, tell the world */ + scc_perform_callbacks(); + } + + return; +} /* scc_set_sw_alarm */ + +/*****************************************************************************/ +/* fn scc_monitor_security_failure() */ +/*****************************************************************************/ +scc_return_t scc_monitor_security_failure(void callback_func(void)) +{ + int i; + unsigned long irq_flags; /* for IRQ save/restore */ + scc_return_t return_status = SCC_RET_TOO_MANY_FUNCTIONS; + int function_stored = FALSE; + + + /* Acquire lock of callbacks table. Could be spin_lock_irq() if this + * routine were just called from base (not interrupt) level + */ + spin_lock_irqsave(&scc_callbacks_lock, irq_flags); + + /* Search through table looking for empty slot */ + for (i = 0; i < SCC_CALLBACK_SIZE; i++) { + if (scc_callbacks[i] == callback_func) { + if (function_stored) { + /* Saved duplicate earlier. Clear this later one. */ + scc_callbacks[i] = NULL; + } + /* Exactly one copy is now stored */ + return_status = SCC_RET_OK; + break; + } else if (scc_callbacks[i] == NULL && !function_stored) { + /* Found open slot. Save it and remember */ + scc_callbacks[i] = callback_func; + return_status = SCC_RET_OK; + function_stored = TRUE; + } + } + + /* Free the lock */ + spin_unlock_irqrestore(&scc_callbacks_lock, irq_flags); + + return return_status; +} /* scc_monitor_security_failure */ + +/*****************************************************************************/ +/* fn scc_stop_monitoring_security_failure() */ +/*****************************************************************************/ +void scc_stop_monitoring_security_failure(void callback_func(void)) +{ + unsigned long irq_flags; /* for IRQ save/restore */ + int i; + + + /* Acquire lock of callbacks table. Could be spin_lock_irq() if this + * routine were just called from base (not interrupt) level + */ + spin_lock_irqsave(&scc_callbacks_lock, irq_flags); + + /* Search every entry of the table for this function */ + for (i = 0; i < SCC_CALLBACK_SIZE; i++) { + if (scc_callbacks[i] == callback_func) { + scc_callbacks[i] = NULL; /* found instance - clear it out */ + break; + } + } + + /* Free the lock */ + spin_unlock_irqrestore(&scc_callbacks_lock, irq_flags); + + return; +} /* scc_stop_monitoring_security_failure */ + +/*****************************************************************************/ +/* fn scc_read_register() */ +/*****************************************************************************/ +scc_return_t scc_read_register(int register_offset, uint32_t * value) +{ + scc_return_t return_status = SCC_RET_FAIL; + uint32_t smn_status; + uint32_t scm_status; + + + /* First layer of protection -- completely unaccessible SCC */ + if (scc_availability != SCC_STATUS_UNIMPLEMENTED) { + + /* Second layer -- that offset is valid */ + if (register_offset != SMN_BITBANK_DECREMENT && /* write only! */ + check_register_offset(register_offset) == SCC_RET_OK) { + + /* Get current status / update local state */ + smn_status = scc_update_state(); + scm_status = SCC_READ_REGISTER(SCM_STATUS); + + /* + * Third layer - verify that the register being requested is + * available in the current state of the SCC. + */ + if ((return_status = + check_register_accessible(register_offset, + smn_status, + scm_status)) == + SCC_RET_OK) { + *value = SCC_READ_REGISTER(register_offset); + } + } + } + + return return_status; +} /* scc_read_register */ + +/*****************************************************************************/ +/* fn scc_write_register() */ +/*****************************************************************************/ +scc_return_t scc_write_register(int register_offset, uint32_t value) +{ + scc_return_t return_status = SCC_RET_FAIL; + uint32_t smn_status; + uint32_t scm_status; + + + /* First layer of protection -- completely unaccessible SCC */ + if (scc_availability != SCC_STATUS_UNIMPLEMENTED) { + + /* Second layer -- that offset is valid */ + if (!(register_offset == SCM_STATUS || /* These registers are */ + register_offset == SCM_CONFIGURATION || /* Read Only */ + register_offset == SMN_BIT_COUNT || + register_offset == SMN_TIMER) && + check_register_offset(register_offset) == SCC_RET_OK) { + + /* Get current status / update local state */ + smn_status = scc_update_state(); + scm_status = SCC_READ_REGISTER(SCM_STATUS); + + /* + * Third layer - verify that the register being requested is + * available in the current state of the SCC. + */ + if (check_register_accessible + (register_offset, smn_status, scm_status) == 0) { + SCC_WRITE_REGISTER(register_offset, value); + return_status = SCC_RET_OK; + } + } + } + + return return_status; +} /* scc_write_register() */ + +/****************************************************************************** + * + * Function Implementations - Internal + * + *****************************************************************************/ + +/*****************************************************************************/ +/* fn scc_irq() */ +/*****************************************************************************/ +/*! + * This is the interrupt handler for the SCC. + * + * This function checks the SMN Status register to see whether it + * generated the interrupt, then it checks the SCM Status register to + * see whether it needs attention. + * + * If an SMN Interrupt is active, then the SCC state set to failure, and + * #scc_perform_callbacks() is invoked to notify any interested parties. + * + * The SCM Interrupt should be masked, as this driver uses polling to determine + * when the SCM has completed a crypto or zeroing operation. Therefore, if the + * interrupt is active, the driver will just clear the interrupt and (re)mask. + * + * @param irq Channel number for the IRQ. (@c SCC_INT_SMN or @c SCC_INT_SCM). + * @param dev_id Pointer to the identification of the device. Ignored. + */ +static irqreturn_t scc_irq(int irq, void *dev_id) +{ + uint32_t smn_status; + uint32_t scm_status; + int handled = 0; /* assume interrupt isn't from SMN */ +#if defined(USE_SMN_INTERRUPT) + int smn_irq = INT_SCC_SMN; /* SMN interrupt is on a line by itself */ +#elif defined (NO_SMN_INTERRUPT) + int smn_irq = -1; /* not wired to CPU at all */ +#else + int smn_irq = INT_SCC_SCM; /* SMN interrupt shares a line with SCM */ +#endif + + /* Update current state... This will perform callbacks... */ + smn_status = scc_update_state(); + + /* SMN is on its own interrupt line. Verify the IRQ was triggered + * before clearing the interrupt and marking it handled. */ + if (irq == smn_irq && smn_status & SMN_STATUS_SMN_STATUS_IRQ) { + SCC_WRITE_REGISTER(SMN_COMMAND, SMN_COMMAND_CLEAR_INTERRUPT); + handled++; /* tell kernel that interrupt was handled */ + } + + /* Check on the health of the SCM */ + scm_status = SCC_READ_REGISTER(SCM_STATUS); + + /* The driver masks interrupts, so this should never happen. */ + if (irq == INT_SCC_SCM && scm_status & SCM_STATUS_INTERRUPT_STATUS) { + /* but if it does, try to prevent it in the future */ + SCC_WRITE_REGISTER(SCM_INTERRUPT_CTRL, + SCM_INTERRUPT_CTRL_CLEAR_INTERRUPT + | SCM_INTERRUPT_CTRL_MASK_INTERRUPTS); + handled++; + } + + /* Any non-zero value of handled lets kernel know we got something */ + return IRQ_RETVAL(handled); +} + +/*****************************************************************************/ +/* fn scc_perform_callbacks() */ +/*****************************************************************************/ +/*! Perform callbacks registered by #scc_monitor_security_failure(). + * + * Make sure callbacks only happen once... Since there may be some reason why + * the interrupt isn't generated, this routine could be called from base(task) + * level. + * + * One at a time, go through #scc_callbacks[] and call any non-null pointers. + */ +static void scc_perform_callbacks(void) +{ + static int callbacks_performed = 0; + unsigned long irq_flags; /* for IRQ save/restore */ + int i; + + /* Acquire lock of callbacks table and callbacks_performed flag */ + spin_lock_irqsave(&scc_callbacks_lock, irq_flags); + + if (!callbacks_performed) { + callbacks_performed = 1; + + /* Loop over all of the entries in the table */ + for (i = 0; i < SCC_CALLBACK_SIZE; i++) { + /* If not null, ... */ + if (scc_callbacks[i]) { + scc_callbacks[i] (); /* invoke the callback routine */ + } + } + } + + spin_unlock_irqrestore(&scc_callbacks_lock, irq_flags); + + return; +} + +/*****************************************************************************/ +/* fn copy_to_scc() */ +/*****************************************************************************/ +/*! + * Move data from possibly unaligned source and realign for SCC, possibly + * while calculating CRC. + * + * Multiple calls can be made to this routine (without intervening calls to + * #copy_from_scc(), as long as the sum total of bytes copied is a multiple of + * four (SCC native word size). + * + * @param[in] from Location in memory + * @param[out] to Location in SCC + * @param[in] count_bytes Number of bytes to copy + * @param[in,out] crc Pointer to CRC. Initial value must be + * #CRC_CCITT_START if this is the start of + * message. Output is the resulting (maybe + * partial) CRC. If NULL, no crc is calculated. + * + * @return Zero - success. Non-zero - SCM status bits defining failure. + */ +static uint32_t +copy_to_scc(const uint8_t * from, uint32_t to, unsigned long count_bytes, + uint16_t * crc) +{ + int i; + uint32_t scm_word; + uint16_t current_crc = 0; /* local copy for fast access */ + uint32_t status; + + pr_debug("SCC: copying %ld bytes to 0x%0x.\n", count_bytes, to); + + status = SCC_READ_REGISTER(SCM_ERROR_STATUS) & SCM_ACCESS_ERRORS; + if (status != 0) { + pr_debug("SCC copy_to_scc(): Error status detected (before copy):" + " %08x\n", status); + /* clear out errors left behind by somebody else */ + SCC_WRITE_REGISTER(SCM_ERROR_STATUS, status); + } + + if (crc) { + current_crc = *crc; + } + + /* Initialize value being built for SCM. If we are starting 'clean', + * set it to zero. Otherwise pick up partial value which had been saved + * earlier. */ + if (SCC_BYTE_OFFSET(to) == 0) { + scm_word = 0; + } else { + scm_word = SCC_READ_REGISTER(SCC_WORD_PTR(to)); /* recover */ + } + + /* Now build up SCM words and write them out when each is full */ + for (i = 0; i < count_bytes; i++) { + uint8_t byte = *from++; /* value from plaintext */ + +#ifdef __BIG_ENDIAN + scm_word = (scm_word << 8) | byte; /* add byte to SCM word */ +#else + scm_word = (byte << 24) | (scm_word >> 8); +#endif + /* now calculate CCITT CRC */ + if (crc) { + CALC_CRC(byte, current_crc); + } + + to++; /* bump location in SCM */ + + /* check for full word */ + if (SCC_BYTE_OFFSET(to) == 0) { + SCC_WRITE_REGISTER((uint32_t) (to - 4), scm_word); /* write it out */ + } + } + + /* If at partial word after previous loop, save it in SCM memory for + next time. */ + if (SCC_BYTE_OFFSET(to) != 0) { + SCC_WRITE_REGISTER(SCC_WORD_PTR(to), scm_word); /* save */ + } + + /* Copy CRC back */ + if (crc) { + *crc = current_crc; + } + + status = SCC_READ_REGISTER(SCM_ERROR_STATUS) & SCM_ACCESS_ERRORS; + if (status != 0) { + pr_debug("SCC copy_to_scc(): Error status detected: %08x\n", + status); + /* Clear any/all bits. */ + SCC_WRITE_REGISTER(SCM_ERROR_STATUS, status); + } + return status; +} + +/*****************************************************************************/ +/* fn copy_from_scc() */ +/*****************************************************************************/ +/*! + * Move data from aligned 32-bit source and place in (possibly unaligned) + * target, and maybe calculate CRC at the same time. + * + * Multiple calls can be made to this routine (without intervening calls to + * #copy_to_scc(), as long as the sum total of bytes copied is be a multiple + * of four. + * + * @param[in] from Location in SCC + * @param[out] to Location in memory + * @param[in] count_bytes Number of bytes to copy + * @param[in,out] crc Pointer to CRC. Initial value must be + * #CRC_CCITT_START if this is the start of + * message. Output is the resulting (maybe + * partial) CRC. If NULL, crc is not calculated. + * + * @return Zero - success. Non-zero - SCM status bits defining failure. + */ +static uint32_t +copy_from_scc(const uint32_t from, uint8_t * to, unsigned long count_bytes, + uint16_t * crc) +{ + uint32_t running_from = from; + uint32_t scm_word; + uint16_t current_crc = 0; /* local copy for fast access */ + uint32_t status; + pr_debug("SCC: copying %ld bytes from 0x%x.\n", count_bytes, from); + status = SCC_READ_REGISTER(SCM_ERROR_STATUS) & SCM_ACCESS_ERRORS; + if (status != 0) { + pr_debug + ("SCC copy_from_scc(): Error status detected (before copy):" + " %08x\n", status); + /* clear out errors left behind by somebody else */ + SCC_WRITE_REGISTER(SCM_ERROR_STATUS, status); + } + + if (crc) { + current_crc = *crc; + } + + /* Read word which is sitting in SCM memory. Ignore byte offset */ + scm_word = SCC_READ_REGISTER(SCC_WORD_PTR(running_from)); + + /* If necessary, move the 'first' byte into place */ + if (SCC_BYTE_OFFSET(running_from) != 0) { +#ifdef __BIG_ENDIAN + scm_word <<= 8 * SCC_BYTE_OFFSET(running_from); +#else + scm_word >>= 8 * SCC_BYTE_OFFSET(running_from); +#endif + } + + /* Now build up SCM words and write them out when each is full */ + while (count_bytes--) { + uint8_t byte; /* value from plaintext */ + +#ifdef __BIG_ENDIAN + byte = (scm_word & 0xff000000) >> 24; /* pull byte out of SCM word */ + scm_word <<= 8; /* shift over to remove the just-pulled byte */ +#else + byte = (scm_word & 0xff); + scm_word >>= 8; /* shift over to remove the just-pulled byte */ +#endif + *to++ = byte; /* send byte to memory */ + + /* now calculate CRC */ + if (crc) { + CALC_CRC(byte, current_crc); + } + + running_from++; + /* check for empty word */ + if (count_bytes && SCC_BYTE_OFFSET(running_from) == 0) { + /* read one in */ + scm_word = SCC_READ_REGISTER((uint32_t) running_from); + } + } + + if (crc) { + *crc = current_crc; + } + + status = SCC_READ_REGISTER(SCM_ERROR_STATUS) & SCM_ACCESS_ERRORS; + if (status != 0) { + pr_debug("SCC copy_from_scc(): Error status detected: %08x\n", + status); + /* Clear any/all bits. */ + SCC_WRITE_REGISTER(SCM_ERROR_STATUS, status); + } + + return status; +} + +/*****************************************************************************/ +/* fn scc_strip_padding() */ +/*****************************************************************************/ +/*! + * Remove padding from plaintext. Search backwards for #SCC_DRIVER_PAD_CHAR, + * verifying that each byte passed over is zero (0). Maximum number of bytes + * to examine is 8. + * + * @param[in] from Pointer to byte after end of message + * @param[out] count_bytes_stripped Number of padding bytes removed by this + * function. + * + * @return #SCC_RET_OK if all goes, well, #SCC_RET_FAIL if padding was + * not present. +*/ +static scc_return_t +scc_strip_padding(uint8_t * from, unsigned *count_bytes_stripped) +{ + int i = SCC_BLOCK_SIZE_BYTES(); + scc_return_t return_code = SCC_RET_VERIFICATION_FAILED; + + /* + * Search backwards looking for the magic marker. If it isn't found, + * make sure that a 0 byte is there in its place. Stop after the maximum + * amount of padding (8 bytes) has been searched); + */ + while (i-- > 0) { + if (*--from == SCC_DRIVER_PAD_CHAR) { + *count_bytes_stripped = SCC_BLOCK_SIZE_BYTES() - i; + return_code = SCC_RET_OK; + break; + } else if (*from != 0) { /* if not marker, check for 0 */ + pr_debug("SCC: Found non-zero interim pad: 0x%x\n", + *from); + break; + } + } + + return return_code; +} + +/*****************************************************************************/ +/* fn scc_update_state() */ +/*****************************************************************************/ +/*! + * Make certain SCC is still running. + * + * Side effect is to update #scc_availability and, if the state goes to failed, + * run #scc_perform_callbacks(). + * + * (If #SCC_BRINGUP is defined, bring SCC to secure state if it is found to be + * in health check state) + * + * @return Current value of #SMN_STATUS register. + */ +static uint32_t scc_update_state(void) +{ + uint32_t smn_status_register = SMN_STATE_FAIL; + int smn_state; + + /* if FAIL or UNIMPLEMENTED, don't bother */ + if (scc_availability == SCC_STATUS_CHECKING || + scc_availability == SCC_STATUS_OK) { + + smn_status_register = SCC_READ_REGISTER(SMN_STATUS); + smn_state = smn_status_register & SMN_STATUS_STATE_MASK; + +#ifdef SCC_BRINGUP + /* If in Health Check while booting, try to 'bringup' to Secure mode */ + if (scc_availability == SCC_STATUS_CHECKING && + smn_state == SMN_STATE_HEALTH_CHECK) { + /* Code up a simple algorithm for the ASC */ + SCC_WRITE_REGISTER(SMN_SEQUENCE_START, 0xaaaa); + SCC_WRITE_REGISTER(SMN_SEQUENCE_END, 0x5555); + SCC_WRITE_REGISTER(SMN_SEQUENCE_CHECK, 0x5555); + /* State should be SECURE now */ + smn_status_register = SCC_READ_REGISTER(SMN_STATUS); + smn_state = smn_status_register & SMN_STATUS_STATE_MASK; + } +#endif + + /* + * State should be SECURE or NON_SECURE for operation of the part. If + * FAIL, mark failed (i.e. limited access to registers). Any other + * state, mark unimplemented, as the SCC is unuseable. + */ + if (smn_state == SMN_STATE_SECURE + || smn_state == SMN_STATE_NON_SECURE) { + /* Healthy */ + scc_availability = SCC_STATUS_OK; + } else if (smn_state == SMN_STATE_FAIL) { + scc_availability = SCC_STATUS_FAILED; /* uh oh - unhealthy */ + scc_perform_callbacks(); + pr_debug("SCC: SCC went into FAILED mode\n"); + } else { + /* START, ZEROIZE RAM, HEALTH CHECK, or unknown */ + scc_availability = SCC_STATUS_UNIMPLEMENTED; /* unuseable */ + pr_debug("SCC: SCC declared UNIMPLEMENTED\n"); + } + } + /* if availability is initial or ok */ + return smn_status_register; +} + +/*****************************************************************************/ +/* fn scc_init_ccitt_crc() */ +/*****************************************************************************/ +/*! + * Populate the partial CRC lookup table. + * + * @return none + * + */ +void scc_init_ccitt_crc(void) +{ + int dividend; /* index for lookup table */ + uint16_t remainder; /* partial value for a given dividend */ + int bit; /* index into bits of a byte */ + + /* + * Compute the remainder of each possible dividend. + */ + for (dividend = 0; dividend < 256; ++dividend) { + /* + * Start with the dividend followed by zeros. + */ + remainder = dividend << (8); + + /* + * Perform modulo-2 division, a bit at a time. + */ + for (bit = 8; bit > 0; --bit) { + /* + * Try to divide the current data bit. + */ + if (remainder & 0x8000) { + remainder = (remainder << 1) ^ CRC_POLYNOMIAL; + } else { + remainder = (remainder << 1); + } + } + + /* + * Store the result into the table. + */ + scc_crc_lookup_table[dividend] = remainder; + } + +} /* scc_init_ccitt_crc() */ + +/*****************************************************************************/ +/* fn grab_config_values() */ +/*****************************************************************************/ +/*! + * grab_config_values() will read the SCM Configuration and SMN Status + * registers and store away version and size information for later use. + * + * @return The current value of the SMN Status register. + */ +static uint32_t scc_grab_config_values(void) +{ + uint32_t config_register; + uint32_t smn_status_register = SMN_STATE_FAIL; + + if (scc_availability != SCC_STATUS_UNIMPLEMENTED) { + /* access the SCC - these are 'safe' registers */ + config_register = SCC_READ_REGISTER(SCM_CONFIGURATION); + pr_debug("SCC Driver: SCM config is 0x%08x\n", config_register); + + /* Get SMN status and update scc_availability */ + smn_status_register = scc_update_state(); + pr_debug("SCC Driver: SMN status is 0x%08x\n", + smn_status_register); + + /* save sizes and versions information for later use */ + scc_configuration.block_size_bytes = (config_register & + SCM_CFG_BLOCK_SIZE_MASK) + >> SCM_CFG_BLOCK_SIZE_SHIFT; + + scc_configuration.red_ram_size_blocks = (config_register & + SCM_CFG_RED_SIZE_MASK) + >> SCM_CFG_RED_SIZE_SHIFT; +#ifdef CONFIG_VIRTIO_SUPPORT + scc_configuration.red_ram_size_blocks = 128; +#endif + + scc_configuration.black_ram_size_blocks = (config_register & + SCM_CFG_BLACK_SIZE_MASK) + >> SCM_CFG_BLACK_SIZE_SHIFT; +#ifdef CONFIG_VIRTIO_SUPPORT + scc_configuration.black_ram_size_blocks = 128; +#endif + + scc_configuration.scm_version = (config_register + & SCM_CFG_VERSION_ID_MASK) + >> SCM_CFG_VERSION_ID_SHIFT; + + scc_configuration.smn_version = (smn_status_register & + SMN_STATUS_VERSION_ID_MASK) + >> SMN_STATUS_VERSION_ID_SHIFT; + + if (scc_configuration.scm_version != SCM_VERSION_1) { + scc_availability = SCC_STATUS_UNIMPLEMENTED; /* Unknown version */ + } + + scc_memory_size_bytes = (SCC_BLOCK_SIZE_BYTES() * + scc_configuration. + black_ram_size_blocks) + - SCM_NON_RESERVED_OFFSET; + + /* This last is for driver consumption only */ + scm_highest_memory_address = SCM_BLACK_MEMORY + + (SCC_BLOCK_SIZE_BYTES() * + scc_configuration.black_ram_size_blocks); + } + + return smn_status_register; +} /* grab_config_values */ + +/*****************************************************************************/ +/* fn setup_interrupt_handling() */ +/*****************************************************************************/ +/*! + * Register the SCM and SMN interrupt handlers. + * + * Called from #scc_init() + * + * @return 0 on success + */ +static int setup_interrupt_handling(void) +{ + int smn_error_code = -1; + int scm_error_code = -1; + + /* Disnable SCM interrupts */ + SCC_WRITE_REGISTER(SCM_INTERRUPT_CTRL, + SCM_INTERRUPT_CTRL_CLEAR_INTERRUPT + | SCM_INTERRUPT_CTRL_MASK_INTERRUPTS); + +#ifdef USE_SMN_INTERRUPT + /* Install interrupt service routine for SMN. */ + smn_error_code = request_irq(INT_SCC_SMN, scc_irq, 0, + SCC_DRIVER_NAME, NULL); + if (smn_error_code != 0) { + pr_debug + ("SCC Driver: Error installing SMN Interrupt Handler: %d\n", + smn_error_code); + } else { + smn_irq_set = 1; /* remember this for cleanup */ + /* Enable SMN interrupts */ + SCC_WRITE_REGISTER(SMN_COMMAND, + SMN_COMMAND_CLEAR_INTERRUPT | + SMN_COMMAND_ENABLE_INTERRUPT); + } +#else + smn_error_code = 0; /* no problems... will handle later */ +#endif + + /* + * Install interrupt service routine for SCM (or both together). + */ + scm_error_code = request_irq(INT_SCC_SCM, scc_irq, 0, + SCC_DRIVER_NAME, NULL); + if (scm_error_code != 0) { +#ifndef MXC + pr_debug + ("SCC Driver: Error installing SCM Interrupt Handler: %d\n", + scm_error_code); +#else + pr_debug + ("SCC Driver: Error installing SCC Interrupt Handler: %d\n", + scm_error_code); +#endif + } else { + scm_irq_set = 1; /* remember this for cleanup */ +#if defined(USE_SMN_INTERRUPT) && !defined(NO_SMN_INTERRUPT) + /* Enable SMN interrupts */ + SCC_WRITE_REGISTER(SMN_COMMAND, + SMN_COMMAND_CLEAR_INTERRUPT | + SMN_COMMAND_ENABLE_INTERRUPT); +#endif + } + + /* Return an error if one was encountered */ + return scm_error_code ? scm_error_code : smn_error_code; +} /* setup_interrupt_handling */ + +/*****************************************************************************/ +/* fn scc_do_crypto() */ +/*****************************************************************************/ +/*! Have the SCM perform the crypto function. + * + * Set up length register, and the store @c scm_control into control register + * to kick off the operation. Wait for completion, gather status, clear + * interrupt / status. + * + * @param byte_count number of bytes to perform in this operation + * @param scm_control Bit values to be set in @c SCM_CONTROL register + * + * @return 0 on success, value of #SCM_ERROR_STATUS on failure + */ +static uint32_t scc_do_crypto(int byte_count, uint32_t scm_control) +{ + int block_count = byte_count / SCC_BLOCK_SIZE_BYTES(); + uint32_t crypto_status; + + /* clear any outstanding flags */ + SCC_WRITE_REGISTER(SCM_INTERRUPT_CTRL, + SCM_INTERRUPT_CTRL_CLEAR_INTERRUPT + | SCM_INTERRUPT_CTRL_MASK_INTERRUPTS); + + /* In length register, 0 means 1, etc. */ + SCC_WRITE_REGISTER(SCM_LENGTH, block_count - 1); + + /* set modes and kick off the operation */ + SCC_WRITE_REGISTER(SCM_CONTROL, scm_control); + + scc_wait_completion(); + + /* Mask for done and error bits */ + crypto_status = SCC_READ_REGISTER(SCM_STATUS) + & (SCM_STATUS_CIPHERING_DONE + | SCM_STATUS_LENGTH_ERROR | SCM_STATUS_INTERNAL_ERROR); + + /* Only done bit should be on */ + if (crypto_status != SCM_STATUS_CIPHERING_DONE) { + /* Replace with error status instead */ + crypto_status = SCC_READ_REGISTER(SCM_ERROR_STATUS); + pr_debug("SCM Failure: 0x%x\n", crypto_status); + if (crypto_status == 0) { + /* That came up 0. Turn on arbitrary bit to signal error. */ + crypto_status = SCM_ERR_INTERNAL_ERROR; + } + } else { + crypto_status = 0; + } + + pr_debug("SCC: Done waiting.\n"); + + /* Clear out any status. */ + SCC_WRITE_REGISTER(SCM_INTERRUPT_CTRL, + SCM_INTERRUPT_CTRL_CLEAR_INTERRUPT + | SCM_INTERRUPT_CTRL_MASK_INTERRUPTS); + + /* And clear any error status */ + SCC_WRITE_REGISTER(SCM_ERROR_STATUS, 0); + + return crypto_status; +} + +/*****************************************************************************/ +/* fn scc_encrypt() */ +/*****************************************************************************/ +/*! + * Perform an encryption on the input. If @c verify_crc is true, a CRC must be + * calculated on the plaintext, and appended, with padding, before computing + * the ciphertext. + * + * @param[in] count_in_bytes Count of bytes of plaintext + * @param[in] data_in Pointer to the plaintext + * @param[in] scm_control Bit values for the SCM_CONTROL register + * @param[in,out] data_out Pointer for storing ciphertext + * @param[in] add_crc Flag for computing CRC - 0 no, else yes + * @param[in,out] count_out_bytes Number of bytes available at @c data_out + */ +static scc_return_t +scc_encrypt(uint32_t count_in_bytes, uint8_t * data_in, uint32_t scm_control, + uint8_t * data_out, int add_crc, unsigned long *count_out_bytes) +{ + scc_return_t return_code = SCC_RET_FAIL; /* initialised for failure */ + uint32_t input_bytes_left = count_in_bytes; /* local copy */ + uint32_t output_bytes_copied = 0; /* running total */ + uint32_t bytes_to_process; /* multi-purpose byte counter */ + uint16_t crc = CRC_CCITT_START; /* running CRC value */ + crc_t *crc_ptr = NULL; /* Reset if CRC required */ + uint32_t scm_location = SCM_RED_MEMORY + SCM_NON_RESERVED_OFFSET; /* byte address into SCM RAM */ + uint32_t scm_bytes_remaining = scc_memory_size_bytes; /* free RED RAM */ + uint8_t padding_buffer[PADDING_BUFFER_MAX_BYTES]; /* CRC+padding holder */ + unsigned padding_byte_count = 0; /* Reset if padding required */ + uint32_t scm_error_status = 0; /* No known SCM error initially */ + + /* Set location of CRC and prepare padding bytes if required */ + if (add_crc != 0) { + crc_ptr = &crc; + padding_byte_count = SCC_BLOCK_SIZE_BYTES() + - (count_in_bytes + + CRC_SIZE_BYTES) % SCC_BLOCK_SIZE_BYTES(); + memcpy(padding_buffer + CRC_SIZE_BYTES, scc_block_padding, + padding_byte_count); + } + + /* Process remaining input or padding data */ + while (input_bytes_left > 0) { + + /* Determine how much work to do this pass */ + bytes_to_process = (input_bytes_left > scm_bytes_remaining) ? + scm_bytes_remaining : input_bytes_left; + + /* Copy plaintext into SCM RAM, calculating CRC if required */ + copy_to_scc(data_in, scm_location, bytes_to_process, crc_ptr); + + /* Adjust pointers & counters */ + input_bytes_left -= bytes_to_process; + data_in += bytes_to_process; + scm_location += bytes_to_process; + scm_bytes_remaining -= bytes_to_process; + + /* Add CRC and padding after the last byte is copied if required */ + if ((input_bytes_left == 0) && (crc_ptr != NULL)) { + + /* Copy CRC into padding buffer MSB first */ + padding_buffer[0] = (crc >> 8) & 0xFF; + padding_buffer[1] = crc & 0xFF; + + /* Reset pointers and counter */ + data_in = padding_buffer; + input_bytes_left = CRC_SIZE_BYTES + padding_byte_count; + crc_ptr = NULL; /* CRC no longer required */ + + /* Go round loop again to copy CRC and padding to SCM */ + continue; + } + + /* if no input and crc_ptr */ + /* Now have block-sized plaintext in SCM to encrypt */ + /* Encrypt plaintext; exit loop on error */ + bytes_to_process = scm_location - + (SCM_RED_MEMORY + SCM_NON_RESERVED_OFFSET); + + if (output_bytes_copied + bytes_to_process > *count_out_bytes) { + return_code = SCC_RET_INSUFFICIENT_SPACE; + scm_error_status = -1; /* error signal */ + pr_debug + ("SCC: too many ciphertext bytes for space available\n"); + break; + } + pr_debug("SCC: Starting encryption. %x for %d bytes (%p/%p)\n", + scm_control, bytes_to_process, + (void *)SCC_READ_REGISTER(SCM_RED_START), + (void *)SCC_READ_REGISTER(SCM_BLACK_START)); + scm_error_status = scc_do_crypto(bytes_to_process, scm_control); + if (scm_error_status != 0) { + break; + } + + /* Copy out ciphertext */ + copy_from_scc(SCM_BLACK_MEMORY + SCM_NON_RESERVED_OFFSET, + data_out, bytes_to_process, NULL); + + /* Adjust pointers and counters for next loop */ + output_bytes_copied += bytes_to_process; + data_out += bytes_to_process; + scm_location = SCM_RED_MEMORY + SCM_NON_RESERVED_OFFSET; + scm_bytes_remaining = scc_memory_size_bytes; + + } /* input_bytes_left > 0 */ + + /* If no SCM error, set OK status and save ouput byte count */ + if (scm_error_status == 0) { + return_code = SCC_RET_OK; + *count_out_bytes = output_bytes_copied; + } + + return return_code; +} /* scc_encrypt */ + +/*****************************************************************************/ +/* fn scc_decrypt() */ +/*****************************************************************************/ +/*! + * Perform a decryption on the input. If @c verify_crc is true, the last block + * (maybe the two last blocks) is special - it should contain a CRC and + * padding. These must be stripped and verified. + * + * @param[in] count_in_bytes Count of bytes of ciphertext + * @param[in] data_in Pointer to the ciphertext + * @param[in] scm_control Bit values for the SCM_CONTROL register + * @param[in,out] data_out Pointer for storing plaintext + * @param[in] verify_crc Flag for running CRC - 0 no, else yes + * @param[in,out] count_out_bytes Number of bytes available at @c data_out + + */ +static scc_return_t +scc_decrypt(uint32_t count_in_bytes, uint8_t * data_in, uint32_t scm_control, + uint8_t * data_out, int verify_crc, unsigned long *count_out_bytes) +{ + scc_return_t return_code = SCC_RET_FAIL; + uint32_t bytes_left = count_in_bytes; /* local copy */ + uint32_t bytes_copied = 0; /* running total of bytes going to user */ + uint32_t bytes_to_copy = 0; /* Number in this encryption 'chunk' */ + uint16_t crc = CRC_CCITT_START; /* running CRC value */ + uint32_t scm_location = SCM_BLACK_MEMORY + SCM_NON_RESERVED_OFFSET; /* next target for ctext */ + unsigned padding_byte_count; /* number of bytes of padding stripped */ + uint8_t last_two_blocks[2 * SCC_BLOCK_SIZE_BYTES()]; /* temp */ + uint32_t scm_error_status = 0; /* register value */ + + scm_control |= SCM_DECRYPT_MODE; + + if (verify_crc) { + /* Save last two blocks (if there are at least two) of ciphertext for + special treatment. */ + bytes_left -= SCC_BLOCK_SIZE_BYTES(); + if (bytes_left >= SCC_BLOCK_SIZE_BYTES()) { + bytes_left -= SCC_BLOCK_SIZE_BYTES(); + } + } + + /* Copy ciphertext into SCM BLACK memory */ + while (bytes_left && scm_error_status == 0) { + + /* Determine how much work to do this pass */ + if (bytes_left > (scc_memory_size_bytes)) { + bytes_to_copy = scc_memory_size_bytes; + } else { + bytes_to_copy = bytes_left; + } + + if (bytes_copied + bytes_to_copy > *count_out_bytes) { + scm_error_status = -1; + break; + } + copy_to_scc(data_in, scm_location, bytes_to_copy, NULL); + data_in += bytes_to_copy; /* move pointer */ + + pr_debug("SCC: Starting decryption of %d bytes.\n", + bytes_to_copy); + + /* Do the work, wait for completion */ + scm_error_status = scc_do_crypto(bytes_to_copy, scm_control); + + copy_from_scc(SCM_RED_MEMORY + SCM_NON_RESERVED_OFFSET, + data_out, bytes_to_copy, &crc); + bytes_copied += bytes_to_copy; + data_out += bytes_to_copy; + scm_location = SCM_BLACK_MEMORY + SCM_NON_RESERVED_OFFSET; + + /* Do housekeeping */ + bytes_left -= bytes_to_copy; + + } /* while bytes_left */ + + /* At this point, either the process is finished, or this is verify mode */ + + if (scm_error_status == 0) { + if (!verify_crc) { + *count_out_bytes = bytes_copied; + return_code = SCC_RET_OK; + } else { + /* Verify mode. There are one or two blocks of unprocessed + * ciphertext sitting at data_in. They need to be moved to the + * SCM, decrypted, searched to remove padding, then the plaintext + * copied back to the user (while calculating CRC, of course). + */ + + /* Calculate ciphertext still left */ + bytes_to_copy = count_in_bytes - bytes_copied; + + copy_to_scc(data_in, scm_location, bytes_to_copy, NULL); + data_in += bytes_to_copy; /* move pointer */ + + pr_debug("SCC: Finishing decryption (%d bytes).\n", + bytes_to_copy); + + /* Do the work, wait for completion */ + scm_error_status = + scc_do_crypto(bytes_to_copy, scm_control); + + if (scm_error_status == 0) { + /* Copy decrypted data back from SCM RED memory */ + copy_from_scc(SCM_RED_MEMORY + + SCM_NON_RESERVED_OFFSET, + last_two_blocks, bytes_to_copy, + NULL); + + /* (Plaintext) + crc + padding should be in temp buffer */ + if (scc_strip_padding + (last_two_blocks + bytes_to_copy, + &padding_byte_count) == SCC_RET_OK) { + bytes_to_copy -= + padding_byte_count + CRC_SIZE_BYTES; + + /* verify enough space in user buffer */ + if (bytes_copied + bytes_to_copy <= + *count_out_bytes) { + int i = 0; + + /* Move out last plaintext and calc CRC */ + while (i < bytes_to_copy) { + CALC_CRC(last_two_blocks + [i], crc); + *data_out++ = + last_two_blocks + [i++]; + bytes_copied++; + } + + /* Verify the CRC by running over itself */ + CALC_CRC(last_two_blocks + [bytes_to_copy], crc); + CALC_CRC(last_two_blocks + [bytes_to_copy + 1], + crc); + if (crc == 0) { + /* Just fine ! */ + *count_out_bytes = + bytes_copied; + return_code = + SCC_RET_OK; + } else { + return_code = + SCC_RET_VERIFICATION_FAILED; + pr_debug + ("SCC: CRC values are %04x, %02x%02x\n", + crc, + last_two_blocks + [bytes_to_copy], + last_two_blocks + [bytes_to_copy + + 1]); + } + } /* if space available */ + } /* if scc_strip_padding... */ + else { + /* bad padding means bad verification */ + return_code = + SCC_RET_VERIFICATION_FAILED; + } + } + /* scm_error_status == 0 */ + } /* verify_crc */ + } + + /* scm_error_status == 0 */ + return return_code; +} /* scc_decrypt */ + +/*****************************************************************************/ +/* fn scc_alloc_slot() */ +/*****************************************************************************/ +/*! + * Allocate a key slot to fit the requested size. + * + * @param value_size_bytes Size of the key or other secure data + * @param owner_id Value to tie owner to slot + * @param[out] slot Handle to access or deallocate slot + * + * @return SCC_RET_OK on success, SCC_RET_INSUFFICIENT_SPACE if not slots of + * requested size are available. + */ +scc_return_t +scc_alloc_slot(uint32_t value_size_bytes, uint64_t owner_id, uint32_t * slot) +{ + scc_return_t status = SCC_RET_FAIL; + unsigned long irq_flags; + + /* ACQUIRE LOCK to prevent others from using SCC crypto */ + spin_lock_irqsave(&scc_crypto_lock, irq_flags); + + pr_debug("SCC: Allocating %d-byte slot for 0x%Lx\n", value_size_bytes, + owner_id); + + if ((value_size_bytes != 0) && (value_size_bytes <= SCC_MAX_KEY_SIZE)) { + int i; + + for (i = 0; i < SCC_KEY_SLOTS; i++) { + if (scc_key_info[i].status == 0) { + scc_key_info[i].owner_id = owner_id; + scc_key_info[i].length = value_size_bytes; + scc_key_info[i].status = 1; /* assigned! */ + *slot = i; + status = SCC_RET_OK; + break; /* exit 'for' loop */ + } + } + + if (status != SCC_RET_OK) { + status = SCC_RET_INSUFFICIENT_SPACE; + } else { + pr_debug("SCC: Allocated slot %d (0x%Lx)\n", i, owner_id); + } + } + + spin_unlock_irqrestore(&scc_crypto_lock, irq_flags); + + return status; +} + +/*****************************************************************************/ +/* fn verify_slot_access() */ +/*****************************************************************************/ +static inline scc_return_t verify_slot_access(uint64_t owner_id, uint32_t slot, + uint32_t access_len) +{ + register scc_return_t status; + + if ((slot < SCC_KEY_SLOTS) && scc_key_info[slot].status + && (scc_key_info[slot].owner_id == owner_id) + && (access_len <= SCC_KEY_SLOT_SIZE)) { + status = SCC_RET_OK; + pr_debug("SCC: Verify on slot %d succeeded\n", slot); + } else { + if (slot >= SCC_KEY_SLOTS) { + pr_debug("SCC: Verify on bad slot (%d) failed\n", slot); + } else if (scc_key_info[slot].status) { + pr_debug("SCC: Verify on slot %d failed (%Lx) \n", slot, + owner_id); + } else { + pr_debug("SCC: Verify on slot %d failed: not allocated\n", + slot); + } + status = SCC_RET_FAIL; + } + + return status; +} + +/*****************************************************************************/ +/* fn scc_dealloc_slot() */ +/*****************************************************************************/ +scc_return_t scc_dealloc_slot(uint64_t owner_id, uint32_t slot) +{ + scc_return_t status; + unsigned long irq_flags; + int i; + + /* ACQUIRE LOCK to prevent others from using SCC crypto */ + spin_lock_irqsave(&scc_crypto_lock, irq_flags); + + status = verify_slot_access(owner_id, slot, 0); + + if (status == SCC_RET_OK) { + scc_key_info[slot].owner_id = 0; + scc_key_info[slot].status = 0; /* unassign */ + + /* clear old info */ + for (i = 0; i < SCC_KEY_SLOT_SIZE; i += 4) { + SCC_WRITE_REGISTER(SCM_RED_MEMORY + + scc_key_info[slot].offset + i, 0); + SCC_WRITE_REGISTER(SCM_BLACK_MEMORY + + scc_key_info[slot].offset + i, 0); + } + pr_debug("SCC: Deallocated slot %d\n", slot); + } + + spin_unlock_irqrestore(&scc_crypto_lock, irq_flags); + + return status; +} + +/*****************************************************************************/ +/* fn scc_load_slot() */ +/*****************************************************************************/ +/*! + * Load a value into a slot. + * + * @param owner_id Value of owner of slot + * @param slot Handle of slot + * @param key_data Data to load into the slot + * @param key_length Length, in bytes, of @c key_data to copy to SCC. + * + * @return SCC_RET_OK on success. SCC_RET_FAIL will be returned if slot + * specified cannot be accessed for any reason, or SCC_RET_INSUFFICIENT_SPACE + * if @c key_length exceeds the size of the slot. + */ +scc_return_t +scc_load_slot(uint64_t owner_id, uint32_t slot, uint8_t * key_data, + uint32_t key_length) +{ + scc_return_t status; + unsigned long irq_flags; + + /* ACQUIRE LOCK to prevent others from using SCC crypto */ + spin_lock_irqsave(&scc_crypto_lock, irq_flags); + + status = verify_slot_access(owner_id, slot, key_length); + if ((status == SCC_RET_OK) && (key_data != NULL)) { + status = SCC_RET_FAIL; /* reset expectations */ + + if (key_length > SCC_KEY_SLOT_SIZE) { + pr_debug + ("SCC: scc_load_slot() rejecting key of %d bytes.\n", + key_length); + status = SCC_RET_INSUFFICIENT_SPACE; + } else { + if (copy_to_scc(key_data, + SCM_RED_MEMORY + + scc_key_info[slot].offset, key_length, + NULL)) { + pr_debug + ("SCC: RED copy_to_scc() failed for scc_load_slot()\n"); + } else { + status = SCC_RET_OK; + } + } + } + + spin_unlock_irqrestore(&scc_crypto_lock, irq_flags); + + return status; +} /* scc_load_slot */ + +/*****************************************************************************/ +/* fn scc_encrypt_slot() */ +/*****************************************************************************/ +/*! + * Allocate a key slot to fit the requested size. + * + * @param owner_id Value of owner of slot + * @param slot Handle of slot + * @param length Length, in bytes, of @c black_data + * @param black_data Location to store result of encrypting RED data in slot + * + * @return SCC_RET_OK on success, SCC_RET_FAIL if slot specified cannot be + * accessed for any reason. + */ +scc_return_t scc_encrypt_slot(uint64_t owner_id, uint32_t slot, + uint32_t length, uint8_t * black_data) +{ + unsigned long irq_flags; + scc_return_t status; + uint32_t crypto_status; + uint32_t slot_offset = + scc_key_info[slot].offset / SCC_BLOCK_SIZE_BYTES(); + + /* ACQUIRE LOCK to prevent others from using crypto or releasing slot */ + spin_lock_irqsave(&scc_crypto_lock, irq_flags); + + status = verify_slot_access(owner_id, slot, length); + if (status == SCC_RET_OK) { + SCC_WRITE_REGISTER(SCM_BLACK_START, slot_offset); + SCC_WRITE_REGISTER(SCM_RED_START, slot_offset); + + /* Use OwnerID as CBC IV to tie Owner to data */ + SCC_WRITE_REGISTER(SCM_INIT_VECTOR_0, *(uint32_t *) & owner_id); + SCC_WRITE_REGISTER(SCM_INIT_VECTOR_1, + *(((uint32_t *) & owner_id) + 1)); + + /* Set modes and kick off the encryption */ + crypto_status = scc_do_crypto(length, + SCM_CONTROL_START_CIPHER | + SCM_CBC_MODE); + + if (crypto_status != 0) { + pr_debug("SCM encrypt red crypto failure: 0x%x\n", + crypto_status); + } else { + + /* Give blob back to caller */ + if (!copy_from_scc + (SCM_BLACK_MEMORY + scc_key_info[slot].offset, + black_data, length, NULL)) { + status = SCC_RET_OK; + pr_debug("SCC: Encrypted slot %d\n", slot); + } + } + } + + spin_unlock_irqrestore(&scc_crypto_lock, irq_flags); + + return status; +} + +/*****************************************************************************/ +/* fn scc_decrypt_slot() */ +/*****************************************************************************/ +/*! + * Decrypt some black data and leave result in the slot. + * + * @param owner_id Value of owner of slot + * @param slot Handle of slot + * @param length Length, in bytes, of @c black_data + * @param black_data Location of data to dencrypt and store in slot + * + * @return SCC_RET_OK on success, SCC_RET_FAIL if slot specified cannot be + * accessed for any reason. + */ +scc_return_t scc_decrypt_slot(uint64_t owner_id, uint32_t slot, + uint32_t length, const uint8_t * black_data) +{ + unsigned long irq_flags; + scc_return_t status; + uint32_t crypto_status; + uint32_t slot_offset = + scc_key_info[slot].offset / SCC_BLOCK_SIZE_BYTES(); + + /* ACQUIRE LOCK to prevent others from using crypto or releasing slot */ + spin_lock_irqsave(&scc_crypto_lock, irq_flags); + + status = verify_slot_access(owner_id, slot, length); + if (status == SCC_RET_OK) { + status = SCC_RET_FAIL; /* reset expectations */ + + /* Place black key in to BLACK RAM and set up the SCC */ + copy_to_scc(black_data, + SCM_BLACK_MEMORY + scc_key_info[slot].offset, + length, NULL); + + SCC_WRITE_REGISTER(SCM_BLACK_START, slot_offset); + SCC_WRITE_REGISTER(SCM_RED_START, slot_offset); + + /* Use OwnerID as CBC IV to tie Owner to data */ + SCC_WRITE_REGISTER(SCM_INIT_VECTOR_0, *(uint32_t *) & owner_id); + SCC_WRITE_REGISTER(SCM_INIT_VECTOR_1, + *(((uint32_t *) & owner_id) + 1)); + + /* Set modes and kick off the decryption */ + crypto_status = scc_do_crypto(length, + SCM_CONTROL_START_CIPHER + | SCM_CBC_MODE | + SCM_DECRYPT_MODE); + + if (crypto_status != 0) { + pr_debug("SCM decrypt black crypto failure: 0x%x\n", + crypto_status); + } else { + status = SCC_RET_OK; + } + } + + spin_unlock_irqrestore(&scc_crypto_lock, irq_flags); + + return status; +} + +/*****************************************************************************/ +/* fn scc_get_slot_info() */ +/*****************************************************************************/ +/*! + * Determine address and value length for a give slot. + * + * @param owner_id Value of owner of slot + * @param slot Handle of slot + * @param address Location to store kernel address of slot data + * @param value_size_bytes Location to store allocated length of data in slot. + * May be NULL if value is not needed by caller. + * @param slot_size_bytes Location to store max length data in slot + * May be NULL if value is not needed by caller. + * + * @return SCC_RET_OK or error indication + */ +scc_return_t +scc_get_slot_info(uint64_t owner_id, uint32_t slot, uint32_t * address, + uint32_t * value_size_bytes, uint32_t * slot_size_bytes) +{ + scc_return_t status = verify_slot_access(owner_id, slot, 0); + + if (status == SCC_RET_OK) { + *address = + SCC_BASE + SCM_RED_MEMORY + scc_key_info[slot].offset; + if (value_size_bytes != NULL) { + *value_size_bytes = scc_key_info[slot].length; + } + if (slot_size_bytes != NULL) { + *slot_size_bytes = SCC_KEY_SLOT_SIZE; + } + } + + return status; +} +uint8_t make_vpu_partition() { + printk(KERN_INFO"No VPU partition allocated\n"); + return 0; +} + +/*****************************************************************************/ +/* fn scc_wait_completion() */ +/*****************************************************************************/ +/*! + * Poll looking for end-of-cipher indication. Only used + * if @c SCC_SCM_SLEEP is not defined. + * + * @internal + * + * Crypto under 230 or so bytes is done after the first loop, all + * the way up to five sets of spins for 1024 bytes. (8- and 16-byte functions + * are done when we first look. Zeroizing takes one pass around. + */ +static void scc_wait_completion(void) +{ + int i = 0; + + /* check for completion by polling */ + while (!is_cipher_done() && (i++ < SCC_CIPHER_MAX_POLL_COUNT)) { + /* kill time if loop not optimized away */ + udelay(1000); + } + pr_debug("SCC: Polled DONE %d times\n", i); +} /* scc_wait_completion() */ + +/*****************************************************************************/ +/* fn is_cipher_done() */ +/*****************************************************************************/ +/*! + * This function returns non-zero if SCM Status register indicates + * that a cipher has terminated or some other interrupt-generating + * condition has occurred. + */ +static int is_cipher_done(void) +{ + register uint32_t scm_status; + register int cipher_done; + + scm_status = SCC_READ_REGISTER(SCM_STATUS); + + /* + * Done when 'SCM is currently performing a function' bits are zero + */ + cipher_done = !(scm_status & (SCM_STATUS_ZEROIZING | + SCM_STATUS_CIPHERING)); + + return cipher_done; +} /* is_cipher_done() */ + +/*****************************************************************************/ +/* fn offset_within_smn() */ +/*****************************************************************************/ +/*! + * Check that the offset is with the bounds of the SMN register set. + * + * @param[in] register_offset register offset of SMN. + * + * @return 1 if true, 0 if false (not within SMN) + */ +static inline int offset_within_smn(uint32_t register_offset) +{ + return register_offset >= SMN_STATUS && register_offset <= SMN_TIMER; +} + +/*****************************************************************************/ +/* fn offset_within_scm() */ +/*****************************************************************************/ +/*! + * Check that the offset is with the bounds of the SCM register set. + * + * @param[in] register_offset Register offset of SCM + * + * @return 1 if true, 0 if false (not within SCM) + */ +static inline int offset_within_scm(uint32_t register_offset) +{ + return (register_offset >= SCM_RED_START) + && (register_offset < scm_highest_memory_address); + /* Although this would cause trouble for zeroize testing, this change would + * close a security whole which currently allows any kernel program to access + * any location in RED RAM. Perhaps enforce in non-SCC_DEBUG compiles? + && (register_offset <= SCM_INIT_VECTOR_1); */ +} + +/*****************************************************************************/ +/* fn check_register_accessible() */ +/*****************************************************************************/ +/*! + * Given the current SCM and SMN status, verify that access to the requested + * register should be OK. + * + * @param[in] register_offset register offset within SCC + * @param[in] smn_status recent value from #SMN_STATUS + * @param[in] scm_status recent value from #SCM_STATUS + * + * @return #SCC_RET_OK if ok, #SCC_RET_FAIL if not + */ +static scc_return_t +check_register_accessible(uint32_t register_offset, uint32_t smn_status, + uint32_t scm_status) +{ + int error_code = SCC_RET_FAIL; + + /* Verify that the register offset passed in is not among the verboten set + * if the SMN is in Fail mode. + */ + if (offset_within_smn(register_offset)) { + if ((smn_status & SMN_STATUS_STATE_MASK) == SMN_STATE_FAIL) { + if (!((register_offset == SMN_STATUS) || + (register_offset == SMN_COMMAND) || + (register_offset == SMN_DEBUG_DETECT_STAT))) { + pr_debug + ("SCC Driver: Note: Security State is in FAIL state.\n"); + } /* register not a safe one */ + else { + /* SMN is in FAIL, but register is a safe one */ + error_code = SCC_RET_OK; + } + } /* State is FAIL */ + else { + /* State is not fail. All registers accessible. */ + error_code = SCC_RET_OK; + } + } + /* offset within SMN */ + /* Not SCM register. Check for SCM busy. */ + else if (offset_within_scm(register_offset)) { + /* This is the 'cannot access' condition in the SCM */ + if ((scm_status & SCM_STATUS_BUSY) + /* these are always available - rest fail on busy */ + && !((register_offset == SCM_STATUS) || + (register_offset == SCM_ERROR_STATUS) || + (register_offset == SCM_INTERRUPT_CTRL) || + (register_offset == SCM_CONFIGURATION))) { + pr_debug + ("SCC Driver: Note: Secure Memory is in BUSY state.\n"); + } /* status is busy & register inaccessible */ + else { + error_code = SCC_RET_OK; + } + } + /* offset within SCM */ + return error_code; + +} /* check_register_accessible() */ + +/*****************************************************************************/ +/* fn check_register_offset() */ +/*****************************************************************************/ +/*! + * Check that the offset is with the bounds of the SCC register set. + * + * @param[in] register_offset register offset of SMN. + * + * #SCC_RET_OK if ok, #SCC_RET_FAIL if not + */ +static scc_return_t check_register_offset(uint32_t register_offset) +{ + int return_value = SCC_RET_FAIL; + + /* Is it valid word offset ? */ + if (SCC_BYTE_OFFSET(register_offset) == 0) { + /* Yes. Is register within SCM? */ + if (offset_within_scm(register_offset)) { + return_value = SCC_RET_OK; /* yes, all ok */ + } + /* Not in SCM. Now look within the SMN */ + else if (offset_within_smn(register_offset)) { + return_value = SCC_RET_OK; /* yes, all ok */ + } + } + + return return_value; +} + +#ifdef SCC_REGISTER_DEBUG + +/*****************************************************************************/ +/* fn dbg_scc_read_register() */ +/*****************************************************************************/ +/*! + * Noisily read a 32-bit value to an SCC register. + * @param offset The address of the register to read. + * + * @return The register value + * */ +static uint32_t dbg_scc_read_register(uint32_t offset) +{ + uint32_t value; + + value = __raw_readl(scc_base + offset); + +#ifndef SCC_RAM_DEBUG /* print no RAM references */ + if ((offset < SCM_RED_MEMORY) || (offset >= scm_highest_memory_address)) { +#endif + pr_debug("SCC RD: 0x%4x : 0x%08x\n", offset, value); +#ifndef SCC_RAM_DEBUG + } +#endif + + return value; +} + +/*****************************************************************************/ +/* fn dbg_scc_write_register() */ +/*****************************************************************************/ +/* + * Noisily read a 32-bit value to an SCC register. + * @param offset The address of the register to written. + * + * @param value The new register value + */ +static void dbg_scc_write_register(uint32_t offset, uint32_t value) +{ + +#ifndef SCC_RAM_DEBUG /* print no RAM references */ + if ((offset < SCM_RED_MEMORY) || (offset >= scm_highest_memory_address)) { +#endif + pr_debug("SCC WR: 0x%4x : 0x%08x\n", offset, value); +#ifndef SCC_RAM_DEBUG + } +#endif + + (void)__raw_writel(value, scc_base + offset); +} + +#endif /* SCC_REGISTER_DEBUG */ diff --git a/drivers/mxc/security/mxc_scc_internals.h b/drivers/mxc/security/mxc_scc_internals.h new file mode 100644 index 000000000000..e19cbd4c3e72 --- /dev/null +++ b/drivers/mxc/security/mxc_scc_internals.h @@ -0,0 +1,325 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef __MXC_SCC_INTERNALS_H__ +#define __MXC_SCC_INTERNALS_H__ + +/*! + * @file mxc_scc_internals.h + * + * @brief This is intended to be the file which contains most or all of the code or + * changes need to port the driver. It also includes other definitions needed + * by the driver. + * + * This header file should only ever be included by scc_driver.c + * + * Compile-time flags minimally needed: + * + * @li Some sort of platform flag. + * @li Some start-of-SCC consideration, such as SCC_BASE_ADDR + * + * Some changes which could be made when porting this driver: + * #SCC_SPIN_COUNT + * + * @ingroup MXCSCC + */ + +#include <linux/version.h> /* Current version Linux kernel */ +#include <linux/module.h> /* Basic support for loadable modules, + printk */ +#include <linux/init.h> /* module_init, module_exit */ +#include <linux/kernel.h> /* General kernel system calls */ +#include <linux/sched.h> /* for interrupt.h */ +#include <linux/spinlock.h> +#include <linux/interrupt.h> /* IRQ / interrupt definitions */ +#include <asm/io.h> /* ioremap() */ +#include <asm/arch/mxc_scc_driver.h> + +/* Get handle on certain per-platform symbols */ + +#include <asm/arch/iim.h> +#include <asm/arch/mxc_scc.h> + +/*! + * This macro is used to determine whether the SCC is enabled/available + * on the platform. This macro may need to be ported. + */ +#define SCC_FUSE IO_ADDRESS(IIM_BASE_ADDR + MXC_IIMHWV1) +#define SCC_ENABLED() ((SCC_FUSE & MXC_IIMHWV1_SCC_DISABLE) == 0) + +/*! + * Turn on generation of run-time operational, debug, and error messages + */ + +/*! + * Turn on generation of run-time logging of access to the SCM and SMN + * registers. + */ +//#define SCC_REGISTER_DEBUG + +/*! + * Turn on generation of run-time logging of access to the SCM Red and + * Black memories. Will only work if #SCC_REGISTER_DEBUG is also defined. + */ +//#define SCC_RAM_DEBUG + +/*! + * If the driver finds the SCC in HEALTH_CHECK state, go ahead and + * run a quick ASC to bring it to SECURE state. + */ +#define SCC_BRINGUP + +/*! + * Define the number of Stored Keys which the SCC driver will make available. + * Value shall be from 0 to 20. Default is zero (0). + */ +#define SCC_KEY_SLOTS 20 + +#ifndef SCC_KEY_SLOTS +#define SCC_KEY_SLOTS 0 + +#else + +#if (SCC_KEY_SLOTS < 0) || (SCC_KEY_SLOTS > 20) +#error Bad value for SCC_KEY_SLOTS +#endif + +#endif + +/*! + * Maximum length of key/secret value which can be stored in SCC. + */ +#define SCC_MAX_KEY_SIZE 32 + +/*! + * This is the size, in bytes, of each key slot, and therefore the maximum size + * of the wrapped key. + */ +#define SCC_KEY_SLOT_SIZE 32 + +/*! + * This is the offset into each RAM of the base of the area which is + * not used for Stored Keys. + */ +#define SCM_NON_RESERVED_OFFSET (SCC_KEY_SLOTS * SCC_KEY_SLOT_SIZE) + +/* These come for free with Linux, but may need to be set in a port. */ +#ifndef __BIG_ENDIAN +#ifndef __LITTLE_ENDIAN +#error One of __LITTLE_ENDIAN or __BIG_ENDIAN must be #defined +#endif +#else +#ifdef __LITTLE_ENDIAN +#error Exactly one of __LITTLE_ENDIAN or __BIG_ENDIAN must be #defined +#endif +#endif + +#ifndef SCC_CALLBACK_SIZE +/*! The number of function pointers which can be stored in #scc_callbacks. + * Defaults to 4, can be overridden with compile-line argument. + */ +#define SCC_CALLBACK_SIZE 4 +#endif + +/*! Initial CRC value for CCITT-CRC calculation. */ +#define CRC_CCITT_START 0xFFFF + +/*! Number of times to spin between polling of SCC while waiting for cipher + * or zeroizing function to complete. See also #SCC_CIPHER_MAX_POLL_COUNT. */ +#define SCC_SPIN_COUNT 1000 + +/*! Number of times to polling SCC while waiting for cipher + * or zeroizing function to complete. See also #SCC_SPIN_COUNT. */ +#define SCC_CIPHER_MAX_POLL_COUNT 100 + +/*! + * @def SCC_READ_REGISTER + * Read a 32-bit value from an SCC register. Macro which depends upon + * #scc_base. Linux __raw_readl()/__raw_writel() macros operate on 32-bit quantities, as + * do SCC register reads/writes. + * + * @param offset Register offset within SCC. + * + * @return The value from the SCC's register. + */ +#ifndef SCC_REGISTER_DEBUG +#define SCC_READ_REGISTER(offset) __raw_readl(scc_base+(offset)) +#else +#define SCC_READ_REGISTER(offset) dbg_scc_read_register(offset) +#endif + +/*! + * Write a 32-bit value to an SCC register. Macro depends upon #scc_base. + * Linux __raw_readl()/__raw_writel() macros operate on 32-bit quantities, as do SCC + * register reads/writes. + * + * @param offset Register offset within SCC. + * @param value 32-bit value to store into the register + * + * @return (void) + */ +#ifndef SCC_REGISTER_DEBUG +#define SCC_WRITE_REGISTER(offset,value) (void)__raw_writel(value, scc_base+(offset)) +#else +#define SCC_WRITE_REGISTER(offset,value) dbg_scc_write_register(offset, value) +#endif + +/*! + * Calculates the byte offset into a word + * @param bp The byte (char*) pointer + * @return The offset (0, 1, 2, or 3) + */ +#define SCC_BYTE_OFFSET(bp) ((uint32_t)(bp) % sizeof(uint32_t)) + +/*! + * Converts (by rounding down) a byte pointer into a word pointer + * @param bp The byte (char*) pointer + * @return The word (uint32_t) as though it were an aligned (uint32_t*) + */ +#define SCC_WORD_PTR(bp) (((uint32_t)(bp)) & ~(sizeof(uint32_t)-1)) + +/*! + * Determine number of bytes in an SCC block + * + * @return Bytes / block + */ +#define SCC_BLOCK_SIZE_BYTES() scc_configuration.block_size_bytes + +/*! + * Maximum number of additional bytes which may be added in CRC+padding mode. + */ +#define PADDING_BUFFER_MAX_BYTES (CRC_SIZE_BYTES + sizeof(scc_block_padding)) + +/*! + * Shorthand (clearer, anyway) for number of bytes in a CRC. + */ +#define CRC_SIZE_BYTES (sizeof(crc_t)) + +/*! + * The polynomial used in CCITT-CRC calculation + */ +#define CRC_POLYNOMIAL 0x1021 + +/*! + * Calculate CRC on one byte of data + * + * @param[in,out] running_crc A value of type crc_t where CRC is kept. This + * must be an rvalue and an lvalue. + * @param[in] byte_value The byte (uint8_t, char) to be put in the CRC + * + * @return none + */ +#define CALC_CRC(byte_value,running_crc) { \ + uint8_t data; \ + data = (0xff&(byte_value)) ^ (running_crc >> 8); \ + running_crc = scc_crc_lookup_table[data] ^ (running_crc << 8); \ +} + +/*! Value of 'beginning of padding' marker in driver-provided padding */ +#define SCC_DRIVER_PAD_CHAR 0x80 + +/*! Name of the driver. Used (on Linux, anyway) when registering interrupts */ +#define SCC_DRIVER_NAME "scc" + +/* These are nice to have around */ +#ifndef FALSE +#define FALSE 0 +#endif +#ifndef TRUE +#define TRUE 1 +#endif + +/*! Provide a typedef for the CRC which can be used in encrypt/decrypt */ +typedef uint16_t crc_t; + +/*! Gives high-level view of state of the SCC */ +enum scc_status { + SCC_STATUS_INITIAL, /*!< State of driver before ever checking */ + SCC_STATUS_CHECKING, /*!< Transient state while driver loading */ + SCC_STATUS_UNIMPLEMENTED, /*!< SCC is non-existent or unuseable */ + SCC_STATUS_OK, /*!< SCC is in Secure or Default state */ + SCC_STATUS_FAILED /*!< In Failed state */ +}; + +/*! + * Information about a key slot. + */ +struct scc_key_slot { + uint64_t owner_id; /**< Access control value. */ + uint32_t length; /**< Length of value in slot. */ + uint32_t offset; /**< Offset of value from start of each RAM. */ + uint32_t status; /**< 0 = unassigned, 1 = assigned. */ +}; + +/* Forward-declare a number routines which are not part of user api */ +static int scc_init(void); +static void scc_cleanup(void); + +/* Forward defines of internal functions */ +static irqreturn_t scc_irq(int irq, void *dev_id); +static void scc_perform_callbacks(void); +static uint32_t copy_to_scc(const uint8_t * from, uint32_t to, + unsigned long count_bytes, uint16_t * crc); +static uint32_t copy_from_scc(const uint32_t from, uint8_t * to, + unsigned long count_bytes, uint16_t * crc); +static scc_return_t scc_strip_padding(uint8_t * from, + unsigned *count_bytes_stripped); +static uint32_t scc_update_state(void); +static void scc_init_ccitt_crc(void); +static uint32_t scc_grab_config_values(void); +static int setup_interrupt_handling(void); +static scc_return_t scc_encrypt(uint32_t count_in_bytes, uint8_t * data_in, + uint32_t scm_control, uint8_t * data_out, + int add_crc, unsigned long *count_out_bytes); +static scc_return_t scc_decrypt(uint32_t count_in_bytes, uint8_t * data_in, + uint32_t scm_control, uint8_t * data_out, + int verify_crc, unsigned long *count_out_bytes); +static void scc_wait_completion(void); +static int is_cipher_done(void); +static scc_return_t check_register_accessible(uint32_t offset, + uint32_t smn_status, + uint32_t scm_status); +static scc_return_t check_register_offset(uint32_t offset); +uint8_t make_vpu_partition(void); + +#ifdef SCC_REGISTER_DEBUG +static uint32_t dbg_scc_read_register(uint32_t offset); +static void dbg_scc_write_register(uint32_t offset, uint32_t value); +#endif + +/* For Linux kernel, export the API functions to other kernel modules */ +EXPORT_SYMBOL(scc_get_configuration); +EXPORT_SYMBOL(scc_zeroize_memories); +EXPORT_SYMBOL(scc_crypt); +EXPORT_SYMBOL(scc_set_sw_alarm); +EXPORT_SYMBOL(scc_monitor_security_failure); +EXPORT_SYMBOL(scc_stop_monitoring_security_failure); +EXPORT_SYMBOL(scc_read_register); +EXPORT_SYMBOL(scc_write_register); +EXPORT_SYMBOL(scc_alloc_slot); +EXPORT_SYMBOL(scc_dealloc_slot); +EXPORT_SYMBOL(scc_load_slot); +EXPORT_SYMBOL(scc_encrypt_slot); +EXPORT_SYMBOL(scc_decrypt_slot); +EXPORT_SYMBOL(scc_get_slot_info); +EXPORT_SYMBOL(make_vpu_partition); + +/* Tell Linux where to invoke driver at boot/module load time */ +module_init(scc_init); +/* Tell Linux where to invoke driver on module unload */ +module_exit(scc_cleanup); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("Device Driver for SCC (SMN/SCM)"); + +#endif /* __MXC_SCC_INTERNALS_H__ */ diff --git a/drivers/mxc/security/mxc_sec_mod.c b/drivers/mxc/security/mxc_sec_mod.c new file mode 100644 index 000000000000..eeb5cb415600 --- /dev/null +++ b/drivers/mxc/security/mxc_sec_mod.c @@ -0,0 +1,67 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include <asm/arch/mxc_security_api.h> + +static int __init mxc_sec_mod_init(void) +{ + printk(KERN_INFO "SEC: mxc_sec_mod_init() called \n"); + return 0; +} + +static void __exit mxc_sec_mod_cleanup(void) +{ + printk(KERN_INFO "SEC: mxc_sec_mod_cleanup() called \n"); +} + +#ifndef CONFIG_ARCH_MX3 +#ifdef CONFIG_MXC_SECURITY_HAC +/* Export Symbol of HACC */ +EXPORT_SYMBOL(hac_hash_data); +EXPORT_SYMBOL(hac_hashing_status); +EXPORT_SYMBOL(hac_get_status); +EXPORT_SYMBOL(hac_stop); +EXPORT_SYMBOL(hac_hash_result); +EXPORT_SYMBOL(hac_swrst); +EXPORT_SYMBOL(hac_burst_mode); +EXPORT_SYMBOL(hac_burst_read); +#ifdef CONFIG_PM +EXPORT_SYMBOL(hac_suspend); +EXPORT_SYMBOL(hac_resume); +#endif +#endif +#endif + +#ifdef CONFIG_MXC_SECURITY_RTIC +/* Export Symbol of RTIC */ +EXPORT_SYMBOL(rtic_init); +EXPORT_SYMBOL(rtic_configure_mode); +EXPORT_SYMBOL(rtic_configure_mem_blk); +EXPORT_SYMBOL(rtic_start_hash); +EXPORT_SYMBOL(rtic_get_status); +EXPORT_SYMBOL(rtic_get_control); +EXPORT_SYMBOL(rtic_configure_interrupt); +EXPORT_SYMBOL(rtic_hash_result); +EXPORT_SYMBOL(rtic_hash_write); +EXPORT_SYMBOL(rtic_get_faultaddress); +EXPORT_SYMBOL(rtic_dma_burst_read); +EXPORT_SYMBOL(rtic_hash_once_dma_throttle); +EXPORT_SYMBOL(rtic_dma_delay); +EXPORT_SYMBOL(rtic_wd_timer); +EXPORT_SYMBOL(rtic_sw_reset); +EXPORT_SYMBOL(rtic_clr_irq); +#endif + +module_init(mxc_sec_mod_init); +module_exit(mxc_sec_mod_cleanup); diff --git a/drivers/mxc/security/rng/Makefile b/drivers/mxc/security/rng/Makefile new file mode 100644 index 000000000000..7365e7d3d382 --- /dev/null +++ b/drivers/mxc/security/rng/Makefile @@ -0,0 +1,29 @@ +# Makefile for the Linux RNG Driver +# +# This makefile works within a kernel driver tree + + # Makefile for rng_driver + + +# Possible configurable paramters +CFG_RNG += -DRNGA_MAX_REQUEST_SIZE=32 + +#DBG_RNGA = -DRNGA_DEBUG +#DBG_RNGA += -DRNGA_REGISTER_DEBUG +#DBG_RNGA += -DRNGA_ENTROPY_DEBUG + +EXTRA_CFLAGS = -DLINUX_KERNEL $(CFG_RNG) $(DBG_RNG) + + +ifeq ($(CONFIG_MXC_RNG_TEST_DRIVER),y) +EXTRA_CFLAGS += -DRNG_REGISTER_PEEK_POKE +endif +ifeq ($(CONFIG_RNG_DEBUG),y) +EXTRA_CFLAGS += -DDEBUG +endif + + +EXTRA_CFLAGS += -Idrivers/mxc/security/rng/include -Idrivers/mxc/security/sahara2/include + +obj-$(CONFIG_MXC_SECURITY_RNG) += rng.o +rng-objs := rng_driver.o shw_driver.o diff --git a/drivers/mxc/security/rng/include/rng_driver.h b/drivers/mxc/security/rng/include/rng_driver.h new file mode 100644 index 000000000000..5089a08f18bf --- /dev/null +++ b/drivers/mxc/security/rng/include/rng_driver.h @@ -0,0 +1,135 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU Lesser General + * Public License. You may obtain a copy of the GNU Lesser General + * Public License Version 2.1 or later at the following locations: + * + * http://www.opensource.org/licenses/lgpl-license.html + * http://www.gnu.org/copyleft/lgpl.html + */ + +#ifndef RNG_DRIVER_H +#define RNG_DRIVER_H + +#include "shw_driver.h" + +/* This is a Linux flag meaning 'compiling kernel code'... */ +#ifndef __KERNEL__ +#include <inttypes.h> +#include <stdlib.h> +#include <memory.h> +#else +#include "../../sahara2/include/portable_os.h" +#endif + +#include "../../sahara2/include/fsl_platform.h" + +/*! @file rng_driver.h + * + * @brief Header file to use the RNG driver. + * + * @ingroup RNG + */ + +#if defined(FSL_HAVE_RNGA) + +#include "rng_rnga.h" + +#elif defined(FSL_HAVE_RNGC) + +#include "rng_rngc.h" + +#else /* neither RNGA nor RNGC */ + +#error NO_RNG_TYPE_IDENTIFIED + +#endif + +/*!**************************************************************************** + * Enumerations + *****************************************************************************/ + +/*! Values from Version ID register */ +enum rng_type { + /*! Type RNGA. Really is unused bits from the Control Register of the + RNGA. */ + RNG_TYPE_RNGA = 0, + /*! Type B. Unsupported by this driver. */ + RNG_TYPE_RNGB = 1, + /*! Type RNGC */ + RNG_TYPE_RNGC = 2 +}; + +/*! + * Return values (error codes) for kernel register interface functions + */ +typedef enum rng_return { + RNG_RET_OK = 0, /*!< Function succeeded */ + RNG_RET_FAIL /*!< Non-specific failure */ +} rng_return_t; + +/*!**************************************************************************** + * Data Structures + *****************************************************************************/ +/*! + * An entry in the RNG Work Queue. Based upon standard SHW queue entry. + * + * This entry also gets saved (for non-blocking requests) in the user's result + * pool. When the user picks up the request, the final processing (copy from + * data_local to data_user) will get made if status was good. + */ +typedef struct rng_work_entry { + struct shw_queue_entry hdr; /*!< Standards SHW queue info. */ + uint32_t length; /*!< Number of bytes still needed to satisfy request. */ + uint32_t *data_local; /*!< Where data from RNG FIFO gets placed. */ + uint8_t *data_user; /*!< Ultimate target of data. */ + unsigned completed; /*!< Non-zero if job is done. */ +} rng_work_entry_t; + +/*!**************************************************************************** + * Function Prototypes + *****************************************************************************/ + +#ifdef RNG_REGISTER_PEEK_POKE +/*! + * Read value from an RNG register. + * The offset will be checked for validity as well as whether it is + * accessible at the time of the call. + * + * This routine cannot be used to read the RNG's Output FIFO if the RNG is in + * High Assurance mode. + * + * @param[in] register_offset The (byte) offset within the RNG block + * of the register to be queried. See + * RNG(A, C) registers for meanings. + * @param[out] value Pointer to where value from the register + * should be placed. + * + * @return See #rng_return_t. + */ +/* REQ-FSLSHW-PINTFC-API-LLF-001 */ +extern rng_return_t rng_read_register(uint32_t register_offset, + uint32_t * value); + +/*! + * Write a new value into an RNG register. + * + * The offset will be checked for validity as well as whether it is + * accessible at the time of the call. + * + * @param[in] register_offset The (byte) offset within the RNG block + * of the register to be modified. See + * RNG(A, C) registers for meanings. + * @param[in] value The value to store into the register. + * + * @return See #rng_return_t. + */ +/* REQ-FSLSHW-PINTFC-API-LLF-002 */ +extern rng_return_t rng_write_register(uint32_t register_offset, + uint32_t value); +#endif /* RNG_REGISTER_PEEK_POKE */ + +#endif /* RNG_DRIVER_H */ diff --git a/drivers/mxc/security/rng/include/rng_internals.h b/drivers/mxc/security/rng/include/rng_internals.h new file mode 100644 index 000000000000..73251e9cc282 --- /dev/null +++ b/drivers/mxc/security/rng/include/rng_internals.h @@ -0,0 +1,613 @@ +/* + * Copyright 2005-2007777777 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#ifndef RNG_INTERNALS_H +#define RNG_INTERNALS_H + +/*! @file rng_internals.h + * + * This file contains definitions which are internal to the RNG driver. + * + * This header file should only ever be needed by rng_driver.c + * + * Compile-time flags minimally needed: + * + * @li Some sort of platform flag. (FSL_HAVE_RNGA or FSL_HAVE_RNGC) + * + * @ingroup RNG + */ + +#include "portable_os.h" +#include "shw_driver.h" +#include "rng_driver.h" + +#include <asm/arch/mxc_scc_driver.h> + +/*! @defgroup rngcompileflags RNG Compile Flags + * + * These are flags which are used to configure the RNG driver at compilation + * time. + * + * Most of them default to good values for normal operation, but some + * (#INT_RNG and #RNG_BASE_ADDR) need to be provided. + * + * The terms 'defined' and 'undefined' refer to whether a @c \#define (or -D on + * a compile command) has defined a given preprocessor symbol. If a given + * symbol is defined, then @c \#ifdef \<symbol\> will succeed. Some symbols + * described below default to not having a definition, i.e. they are undefined. + * + */ + +/*! @addtogroup rngcompileflags */ +/*! @{ */ + +/*! + * This is the maximum number of times the driver will loop waiting for the + * RNG hardware to say that it has generated random data. It prevents the + * driver from stalling forever should there be a hardware problem. + * + * Default value is 100. It should be revisited as CPU clocks speed up. + */ +#ifndef RNG_MAX_TRIES +#define RNG_MAX_TRIES 100 +#endif + +/* Temporarily define compile-time flags to make Doxygen happy and allow them + to get into the documentation. */ +#ifdef DOXYGEN_HACK + +/*! + * This symbol is the base address of the RNG in the CPU memory map. It may + * come from some included header file, or it may come from the compile command + * line. This symbol has no default, and the driver will not compile without + * it. + */ +#define RNG_BASE_ADDR +#undef RNG_BASE_ADDR + +/*! + * This symbol is the Interrupt Number of the RNG in the CPU. It may come + * from some included header file, or it may come from the compile command + * line. This symbol has no default, and the driver will not compile without + * it. + */ +#define INT_RNG +#undef INT_RNG + +/*! + * Defining this symbol will allow other kernel programs to call the + * #rng_read_register() and #rng_write_register() functions. If this symbol is + * not defined, those functions will not be present in the driver. + */ +#define RNG_REGISTER_PEEK_POKE +#undef RNG_REGISTER_PEEK_POKE + +/*! + * Turn on compilation of run-time operational, debug, and error messages. + * + * This flag is undefined by default. + */ +/* REQ-FSLSHW-DEBUG-001 */ + +/*! + * Turn on compilation of run-time logging of access to the RNG registers, + * except for the RNG's Output FIFO register. See #RNG_ENTROPY_DEBUG. + * + * This flag is undefined by default + */ +#define RNG_REGISTER_DEBUG +#undef RNG_REGISTER_DEBUG + +/*! + * Turn on compilation of run-time logging of reading of the RNG's Output FIFO + * register. This flag does nothing if #RNG_REGISTER_DEBUG is not defined. + * + * This flag is undefined by default + */ +#define RNG_ENTROPY_DEBUG +#undef RNG_ENTROPY_DEBUG + +/*! + * If this flag is defined, the driver will not attempt to put the RNG into + * High Assurance mode. + + * If it is undefined, the driver will attempt to put the RNG into High + * Assurance mode. If RNG fails to go into High Assurance mode, the driver + * will fail to initialize. + + * In either case, if the RNG is already in this mode, the driver will operate + * normally. + * + * This flag is undefined by default. + */ +#define RNG_NO_FORCE_HIGH_ASSURANCE +#undef RNG_NO_FORCE_HIGH_ASSURANCE + +/*! + * If this flag is defined, the driver will put the RNG into low power mode + * every opportunity. + * + * This flag is undefined by default. + */ +#define RNG_USE_LOW_POWER_MODE +#undef RNG_USE_LOW_POWER_MODE + +/*! @} */ +#endif /* end DOXYGEN_HACK */ + +/*! + * Read a 32-bit value from an RNG register. This macro depends upon + * #rng_base. The os_read32() macro operates on 32-bit quantities, as do + * all RNG register reads. + * + * @param offset Register byte offset within RNG. + * + * @return The value from the RNG's register. + */ +#ifndef RNG_REGISTER_DEBUG +#define RNG_READ_REGISTER(offset) os_read32(rng_base+(offset)) +#else +#define RNG_READ_REGISTER(offset) dbg_rng_read_register(offset) +#endif + +/*! + * Write a 32-bit value to an RNG register. This macro depends upon + * #rng_base. The os_write32() macro operates on 32-bit quantities, as do + * all RNG register writes. + * + * @param offset Register byte offset within RNG. + * @param value 32-bit value to store into the register + * + * @return (void) + */ +#ifndef RNG_REGISTER_DEBUG +#define RNG_WRITE_REGISTER(offset,value) \ + (void)os_write32(rng_base+(offset), value) +#else +#define RNG_WRITE_REGISTER(offset,value) dbg_rng_write_register(offset,value) +#endif + +#ifndef RNG_DRIVER_NAME +/*! @addtogroup rngcompileflags */ +/*! @{ */ +/*! Name the driver will use to register itself to the kernel as the driver. */ +#define RNG_DRIVER_NAME "rng" +/*! @} */ +#endif + +/*! + * Calculate number of words needed to hold the given number of bytes. + * + * @param byte_count Number of bytes + * + * @return Number of words + */ +#define BYTES_TO_WORDS(byte_count) \ + (((byte_count)+sizeof(uint32_t)-1)/sizeof(uint32_t)) + +/*! Gives high-level view of state of the RNG */ +typedef enum rng_status { + RNG_STATUS_INITIAL, /*!< Driver status before ever starting. */ + RNG_STATUS_CHECKING, /*!< During driver initialization. */ + RNG_STATUS_UNIMPLEMENTED, /*!< Hardware is non-existent / unreachable. */ + RNG_STATUS_OK, /*!< Hardware is In Secure or Default state. */ + RNG_STATUS_FAILED /*!< Hardware is In Failed state / other fatal + problem. Driver is still able to read/write + some registers, but cannot get Random + data. */ +} rng_status_t; + +static shw_queue_t rng_work_queue; + +/*!**************************************************************************** + * + * Function Declarations + * + *****************************************************************************/ + +/* kernel interface functions */ +OS_DEV_INIT_DCL(rng_init); +OS_DEV_TASK_DCL(rng_entropy_task); +OS_DEV_SHUTDOWN_DCL(rng_shutdown); +OS_DEV_ISR_DCL(rng_irq); + +#define RNG_ADD_QUEUE_ENTRY(pool, entry) \ + SHW_ADD_QUEUE_ENTRY(pool, (shw_queue_entry_t*)entry) + +#define RNG_REMOVE_QUEUE_ENTRY(pool, entry) \ + SHW_REMOVE_QUEUE_ENTRY(pool, (shw_queue_entry_t*)entry) +#define RNG_GET_WORK_ENTRY() \ + (rng_work_entry_t*)SHW_POP_FIRST_ENTRY(&rng_work_queue) + +/*! + * Add an work item to a work list. Item will be marked incomplete. + * + * @param work Work entry to place at tail of list. + * + * @return none + */ +inline static void RNG_ADD_WORK_ENTRY(rng_work_entry_t * work) +{ + work->completed = FALSE; + + SHW_ADD_QUEUE_ENTRY(&rng_work_queue, (shw_queue_entry_t *) work); + + os_dev_schedule_task(rng_entropy_task); +} + +/*! + * For #rng_check_register_accessible(), check read permission on given + * register. + */ +#define RNG_CHECK_READ 0 + +/*! + * For #rng_check_register_accessible(), check write permission on given + * register. + */ +#define RNG_CHECK_WRITE 1 + +/* Define different helper symbols based on RNG type */ +#ifdef FSL_HAVE_RNGA + +/*! Interrupt number for driver. */ +#define INT_RNG INT_RNGA + +/*! Base (bus?) address of RNG component. */ +#define RNG_BASE_ADDR RNGA_BASE_ADDR + +/*! Read and return the status register. */ +#define RNG_GET_STATUS() \ + RNG_READ_REGISTER(RNGA_STATUS) +/*! Configure RNG for Auto seeding */ +#define RNG_AUTO_SEED() +/* Put RNG for Seed Generation */ +#define RNG_SEED_GEN() +/*! + * Return RNG Type value. Should be RNG_TYPE_RNGA or RNG_TYPE_RNGC. + */ +#define RNG_GET_RNG_TYPE() \ + ((RNG_READ_REGISTER(RNGA_CONTROL) & RNGA_CONTROL_RNG_TYPE_MASK) \ + >> RNGA_CONTROL_RNG_TYPE_SHIFT) + +/*! + * Verify Type value of RNG. + * + * Returns true of OK, false if not. + */ +#define RNG_VERIFY_TYPE(type) \ + ((type) == RNG_TYPE_RNGA) + +/*! Returns non-zero if RNG device is reporting an error. */ +#define RNG_HAS_ERROR() \ + (RNG_READ_REGISTER(RNGA_STATUS) & RNGA_STATUS_ERROR_INTERRUPT) +/*! Returns non-zero if Bad Key is selected */ +#define RNG_HAS_BAD_KEY() 0 +/*! Return non-zero if Self Test Done */ +#define RNG_SELF_TEST_DONE() 0 +/*! Returns non-zero if RNG ring oscillators have failed. */ +#define RNG_OSCILLATOR_FAILED() \ + (RNG_READ_REGISTER(RNGA_STATUS) & RNGA_STATUS_OSCILLATOR_DEAD) + +/*! Returns maximum number of 32-bit words in the RNG's output fifo. */ +#define RNG_GET_FIFO_SIZE() \ + ((RNG_READ_REGISTER(RNGA_STATUS) & RNGA_STATUS_OUTPUT_FIFO_SIZE_MASK) \ + >> RNGA_STATUS_OUTPUT_FIFO_SIZE_SHIFT) + +/*! Returns number of 32-bit words currently in the RNG's output fifo. */ +#define RNG_GET_WORDS_IN_FIFO() \ + ((RNG_READ_REGISTER(RNGA_STATUS) & RNGA_STATUS_OUTPUT_FIFO_LEVEL_MASK) \ + >> RNGA_STATUS_OUTPUT_FIFO_LEVEL_SHIFT) +/* Configuring RNG for Self Test */ +#define RNG_SELF_TEST() +/*! Get a random value from the RNG's output FIFO. */ +#define RNG_READ_FIFO() \ + RNG_READ_REGISTER(RNGA_OUTPUT_FIFO) + +/*! Put entropy into the RNG's algorithm. + * @param value 32-bit value to add to RNG's entropy. + **/ +#define RNG_ADD_ENTROPY(value) \ + RNG_WRITE_REGISTER(RNGA_ENTROPY, (value)) +/*! Return non-zero in case of Error during Self Test */ +#define RNG_CHECK_SELF_ERR() 0 +/*! Return non-zero in case of Error during Seed Generation */ +#define RNG_CHECK_SEED_ERR() 0 +/*! Get the RNG started at generating output. */ +#define RNG_GO() \ +{ \ + register uint32_t control = RNG_READ_REGISTER(RNGA_CONTROL); \ + RNG_WRITE_REGISTER(RNGA_CONTROL, control | RNGA_CONTROL_GO); \ +} +/*! To clear all Error Bits in Error Status Register */ +#define RNG_CLEAR_ERR() +/*! Put RNG into High Assurance mode */ +#define RNG_SET_HIGH_ASSURANCE() \ +{ \ + register uint32_t control = RNG_READ_REGISTER(RNGA_CONTROL); \ + RNG_WRITE_REGISTER(RNGA_CONTROL, control | RNGA_CONTROL_HIGH_ASSURANCE); \ +} + +/*! Return non-zero if the RNG is in High Assurance mode. */ +#define RNG_GET_HIGH_ASSURANCE() \ + (RNG_READ_REGISTER(RNGA_CONTROL) & RNGA_CONTROL_HIGH_ASSURANCE) + +/*! Clear all status, error and otherwise. */ +#define RNG_CLEAR_ALL_STATUS() \ +{ \ + register uint32_t control = RNG_READ_REGISTER(RNGA_CONTROL); \ + RNG_WRITE_REGISTER(RNGA_CONTROL, control | RNGA_CONTROL_CLEAR_INTERRUPT); \ +} +/* Return non-zero if RESEED Required */ +#define RNG_RESEED() 1 + + +/*! Return non-zero if Seeding is done */ +#define RNG_SEED_DONE() 1 + +/*! Return non-zero if everything seems OK with the RNG. */ +#define RNG_WORKING() \ + ((RNG_READ_REGISTER(RNGA_STATUS) \ + & (RNGA_STATUS_SLEEP | RNGA_STATUS_SECURITY_VIOLATION \ + | RNGA_STATUS_ERROR_INTERRUPT | RNGA_STATUS_FIFO_UNDERFLOW \ + | RNGA_STATUS_LAST_READ_STATUS )) == 0) + +/*! Put the RNG into sleep (low-power) mode. */ +#define RNG_SLEEP() \ +{ \ + register uint32_t control = RNG_READ_REGISTER(RNGA_CONTROL); \ + RNG_WRITE_REGISTER(RNGA_CONTROL, control | RNGA_CONTROL_SLEEP); \ +} + +/*! Wake the RNG from sleep (low-power) mode. */ +#define RNG_WAKE() \ +{ \ + uint32_t control = RNG_READ_REGISTER(RNGA_CONTROL); \ + RNG_WRITE_REGISTER(RNGA_CONTROL, control & ~RNGA_CONTROL_SLEEP); \ +} + +/*! Mask interrupts so that the driver/OS will not see them. */ +#define RNG_MASK_ALL_INTERRUPTS() \ +{ \ + register uint32_t control = RNG_READ_REGISTER(RNGA_CONTROL); \ + RNG_WRITE_REGISTER(RNGA_CONTROL, control | RNGA_CONTROL_MASK_INTERRUPTS); \ +} + +/*! Unmask interrupts so that the driver/OS will see them. */ +#define RNG_UNMASK_ALL_INTERRUPTS() \ +{ \ + register uint32_t control = RNG_READ_REGISTER(RNGA_CONTROL); \ + RNG_WRITE_REGISTER(RNGA_CONTROL, control & ~RNGA_CONTROL_MASK_INTERRUPTS);\ +} + +/*! + * @def RNG_PUT_RNG_TO_SLEEP() + * + * If compiled with #RNG_USE_LOW_POWER_MODE, this routine will put the RNG + * to sleep (low power mode). + * + * @return none + */ +/*! + * @def RNG_WAKE_RNG_FROM_SLEEP() + * + * If compiled with #RNG_USE_LOW_POWER_MODE, this routine will wake the RNG + * from sleep (low power mode). + * + * @return none + */ +#ifdef RNG_USE_LOW_POWER_MODE + +#define RNG_PUT_RNG_TO_SLEEP() \ + RNG_SLEEP() + +#define RNG_WAKE_FROM_SLEEP() \ + RNG_WAKE() 1 + +#else /* not low power mode */ + +#define RNG_PUT_RNG_TO_SLEEP() + +#define RNG_WAKE_FROM_SLEEP() + +#endif /* Use low-power mode */ + +#else /* FSL_HAVE_RNGC */ + +/*! Interrupt number for driver. */ +#define INT_RNG INT_RNGC +/*! Base (bus?) address of RNG component. */ +#define RNG_BASE_ADDR RNGC_BASE_ADDR + +/*! Read and return the status register. */ +#define RNG_GET_STATUS() \ + RNG_READ_REGISTER(RNGC_ERROR) + +/*! + * Return RNG Type value. Should be RNG_TYPE_RNGA or RNG_TYPE_RNGC. + */ +#define RNG_GET_RNG_TYPE() \ + ((RNG_READ_REGISTER(RNGC_VERSION_ID) & RNGC_VERID_RNG_TYPE_MASK) \ + >> RNGC_VERID_RNG_TYPE_SHIFT) + +/*! + * Verify Type value of RNG. + * + * Returns true of OK, false if not. + */ +#define RNG_VERIFY_TYPE(type) \ + ((type) == RNG_TYPE_RNGC) + +/*! Returns non-zero if RNG device is reporting an error. */ +#define RNG_HAS_ERROR() \ + (RNG_READ_REGISTER(RNGC_STATUS) & RNGC_STATUS_ERROR) +/*! Returns non-zero if Bad Key is selected */ +#define RNG_HAS_BAD_KEY() \ + (RNG_READ_REGISTER(RNGC_ERROR) & RNGC_ERROR_STATUS_BAD_KEY) +/*! Returns non-zero if RNG ring oscillators have failed. */ +#define RNG_OSCILLATOR_FAILED() \ + (RNG_READ_REGISTER(RNGC_ERROR) & RNGC_ERROR_STATUS_OSC_ERR) + +/*! Returns maximum number of 32-bit words in the RNG's output fifo. */ +#define RNG_GET_FIFO_SIZE() \ + ((RNG_READ_REGISTER(RNGC_STATUS) & RNGC_STATUS_FIFO_SIZE_MASK) \ + >> RNGC_STATUS_FIFO_SIZE_SHIFT) + +/*! Returns number of 32-bit words currently in the RNG's output fifo. */ +#define RNG_GET_WORDS_IN_FIFO() \ + ((RNG_READ_REGISTER(RNGC_STATUS) & RNGC_STATUS_FIFO_LEVEL_MASK) \ + >> RNGC_STATUS_FIFO_LEVEL_SHIFT) + +/*! Get a random value from the RNG's output FIFO. */ +#define RNG_READ_FIFO() \ + RNG_READ_REGISTER(RNGC_FIFO) + +/*! Put entropy into the RNG's algorithm. + * @param value 32-bit value to add to RNG's entropy. + **/ +#define RNG_ADD_ENTROPY(value) +/*! Wake the RNG from sleep (low-power) mode. */ +#define RNG_WAKE() 1 +/*! Get the RNG started at generating output. */ +#define RNG_GO() +/*! Put RNG into High Assurance mode. */ +#define RNG_SET_HIGH_ASSURANCE() +/*! Returns non-zero in case of Error during Self Test */ +#define RNG_CHECK_SELF_ERR() \ + (RNG_READ_REGISTER(RNGC_ERROR) & RNGC_ERROR_STATUS_ST_ERR) +/*! Return non-zero in case of Error during Seed Generation */ +#define RNG_CHECK_SEED_ERR() \ + (RNG_READ_REGISTER(RNGC_ERROR) & RNGC_ERROR_STATUS_STAT_ERR) + +/*! Configure RNG for Self Test */ +#define RNG_SELF_TEST() \ +{ \ + register uint32_t command = RNG_READ_REGISTER(RNGC_COMMAND); \ + RNG_WRITE_REGISTER(RNGC_COMMAND, command \ + | RNGC_COMMAND_SELF_TEST); \ +} +/*! Clearing the Error bits in Error Status Register */ +#define RNG_CLEAR_ERR() \ +{ \ + register uint32_t command = RNG_READ_REGISTER(RNGC_COMMAND); \ + RNG_WRITE_REGISTER(RNGC_COMMAND, command \ + | RNGC_COMMAND_CLEAR_ERROR); \ +} + + +/*! Return non-zero if Self Test Done */ +#define RNG_SELF_TEST_DONE() \ + (RNG_READ_REGISTER(RNGC_STATUS) & RNGC_STATUS_ST_DONE) +/* Put RNG for SEED Generation */ +#define RNG_SEED_GEN() \ +{ \ + register uint32_t command = RNG_READ_REGISTER(RNGC_COMMAND); \ + RNG_WRITE_REGISTER(RNGC_COMMAND, command \ + | RNGC_COMMAND_SEED); \ +} +/* Return non-zero if RESEED Required */ +#define RNG_RESEED() \ + (RNG_READ_REGISTER(RNGC_STATUS) & RNGC_STATUS_RESEED) + +/*! Return non-zero if the RNG is in High Assurance mode. */ +#define RNG_GET_HIGH_ASSURANCE() 1 + +/*! Clear all status, error and otherwise. */ +#define RNG_CLEAR_ALL_STATUS() \ + RNG_WRITE_REGISTER(RNGC_COMMAND, \ + RNGC_COMMAND_CLEAR_INTERRUPT \ + | RNGC_COMMAND_CLEAR_ERROR) + +/*! Return non-zero if everything seems OK with the RNG. */ +#define RNG_WORKING() \ + ((RNG_READ_REGISTER(RNGC_ERROR) \ + & (RNGC_ERROR_STATUS_STAT_ERR | RNGC_ERROR_STATUS_RAND_ERR \ + | RNGC_ERROR_STATUS_FIFO_ERR | RNGC_ERROR_STATUS_ST_ERR | \ + RNGC_ERROR_STATUS_OSC_ERR | RNGC_ERROR_STATUS_LFSR_ERR )) == 0) +/*! Return Non zero if SEEDING is DONE */ +#define RNG_SEED_DONE() \ + ((RNG_READ_REGISTER(RNGC_STATUS) & RNGC_STATUS_SEED_DONE) != 0) + +/*! Put the RNG into sleep (low-power) mode. */ +#define RNG_SLEEP() + +/*! Wake the RNG from sleep (low-power) mode. */ + +/*! Mask interrupts so that the driver/OS will not see them. */ +#define RNG_MASK_ALL_INTERRUPTS() \ +{ \ + register uint32_t control = RNG_READ_REGISTER(RNGC_CONTROL); \ + RNG_WRITE_REGISTER(RNGC_CONTROL, control \ + | RNGC_CONTROL_MASK_DONE \ + | RNGC_CONTROL_MASK_ERROR); \ +} +/*! Configuring RNGC for self Test. */ + +#define RNG_AUTO_SEED() \ +{ \ + register uint32_t control = RNG_READ_REGISTER(RNGC_CONTROL); \ + RNG_WRITE_REGISTER(RNGC_CONTROL, control \ + | RNGC_CONTROL_AUTO_SEED); \ +} + + +/*! Unmask interrupts so that the driver/OS will see them. */ +#define RNG_UNMASK_ALL_INTERRUPTS() \ +{ \ + register uint32_t control = RNG_READ_REGISTER(RNGC_CONTROL); \ + RNG_WRITE_REGISTER(RNGC_CONTROL, control & ~(RNGC_CONTROL_MASK_DONE|RNGC_CONTROL_MASK_ERROR));\ +} + +/*! Put RNG to sleep if appropriate. */ +#define RNG_PUT_RNG_TO_SLEEP() + +/*! Wake RNG from sleep if necessary. */ +#define RNG_WAKE_FROM_SLEEP() + +#endif /* RNG TYPE */ + +/* internal functions */ +static os_error_code rng_map_RNG_memory(void); +static os_error_code rng_setup_interrupt_handling(void); +#ifdef RNG_REGISTER_PEEK_POKE +inline static int rng_check_register_offset(uint32_t offset); +inline static int rng_check_register_accessible(uint32_t offset, + int access_write); +#endif /* DEBUG_RNG_REGISTERS */ +static fsl_shw_return_t rng_drain_fifo(uint32_t * random_p, int count_words); +static os_error_code rng_grab_config_values(void); +static void rng_cleanup(void); +static void rng_sec_failure(void); + +#ifdef RNG_REGISTER_DEBUG +static uint32_t dbg_rng_read_register(uint32_t offset); +static void dbg_rng_write_register(uint32_t offset, uint32_t value); +#endif + +#if defined(LINUX_VERSION_CODE) + +EXPORT_SYMBOL(fsl_shw_add_entropy); +EXPORT_SYMBOL(fsl_shw_get_random); + +#ifdef RNG_REGISTER_PEEK_POKE +/* For Linux kernel, export the API functions to other kernel modules */ +EXPORT_SYMBOL(rng_read_register); +EXPORT_SYMBOL(rng_write_register); +#endif /* DEBUG_RNG_REGISTERS */ + +MODULE_AUTHOR("Freescale Semiconductor"); +MODULE_DESCRIPTION("Device Driver for RNG"); + +#endif /* LINUX_VERSION_CODE */ + +#endif /* RNG_INTERNALS_H */ diff --git a/drivers/mxc/security/rng/include/rng_rnga.h b/drivers/mxc/security/rng/include/rng_rnga.h new file mode 100644 index 000000000000..971064d51522 --- /dev/null +++ b/drivers/mxc/security/rng/include/rng_rnga.h @@ -0,0 +1,181 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#ifndef RNG_RNGA_H +#define RNG_RNGA_H + +/*! @defgroup rngaregs RNGA Registers + * @ingroup RNG + * These are the definitions for the RNG registers and their offsets + * within the RNG. They are used in the @c register_offset parameter of + * #rng_read_register() and #rng_write_register(). + */ +/*! @addtogroup rngaregs */ +/*! @{ */ + +/*! Control Register. See @ref rngacontrolreg. */ +#define RNGA_CONTROL 0x00 +/*! Status Register. See @ref rngastatusreg. */ +#define RNGA_STATUS 0x04 +/*! Register for adding to the Entropy of the RNG */ +#define RNGA_ENTROPY 0x08 +/*! Register containing latest 32 bits of random value */ +#define RNGA_OUTPUT_FIFO 0x0c +/*! Mode Register. Non-secure mode access only. See @ref rngmodereg. */ +#define RNGA_MODE 0x10 +/*! Verification Control Register. Non-secure mode access only. See + * @ref rngvfctlreg. */ +#define RNGA_VERIFICATION_CONTROL 0x14 +/*! Oscillator Control Counter Register. Non-secure mode access only. + * See @ref rngosccntctlreg. */ +#define RNGA_OSCILLATOR_CONTROL_COUNTER 0x18 +/*! Oscillator 1 Counter Register. Non-secure mode access only. See + * @ref rngosccntreg. */ +#define RNGA_OSCILLATOR1_COUNTER 0x1c +/*! Oscillator 2 Counter Register. Non-secure mode access only. See + * @ref rngosccntreg. */ +#define RNGA_OSCILLATOR2_COUNTER 0x20 +/*! Oscillator Counter Status Register. Non-secure mode access only. See + * @ref rngosccntstatreg. */ +#define RNGA_OSCILLATOR_COUNTER_STATUS 0x24 +/*! @} */ + +/*! Total address space of the RNGA, in bytes */ +#define RNG_ADDRESS_RANGE 0x28 + +/*! @defgroup rngacontrolreg RNGA Control Register Definitions + * @ingroup RNG + */ +/*! @addtogroup rngacontrolreg */ +/*! @{ */ +/*! These bits are unimplemented or reserved */ +#define RNGA_CONTROL_ZEROS_MASK 0x0fffffe0 +/*! 'RNG type' - should be 0 for RNGA */ +#define RNGA_CONTROL_RNG_TYPE_MASK 0xf0000000 +/*! Number of bits to shift the type to get it to LSB */ +#define RNGA_CONTROL_RNG_TYPE_SHIFT 28 +/*! Put RNG to sleep */ +#define RNGA_CONTROL_SLEEP 0x00000010 +/*! Clear interrupt & status */ +#define RNGA_CONTROL_CLEAR_INTERRUPT 0x00000008 +/*! Mask interrupt generation */ +#define RNGA_CONTROL_MASK_INTERRUPTS 0x00000004 +/*! Enter into Secure Mode. Notify SCC of security violation should FIFO + * underflow occur. */ +#define RNGA_CONTROL_HIGH_ASSURANCE 0x00000002 +/*! Load data into FIFO */ +#define RNGA_CONTROL_GO 0x00000001 +/*! @} */ + +/*! @defgroup rngastatusreg RNGA Status Register Definitions + * @ingroup RNG + */ +/*! @addtogroup rngastatusreg */ +/*! @{ */ +/*! RNG Oscillator not working */ +#define RNGA_STATUS_OSCILLATOR_DEAD 0x80000000 +/*! These bits are undefined or reserved */ +#define RNGA_STATUS_ZEROS1_MASK 0x7f000000 +/*! How big FIFO is, in bytes */ +#define RNGA_STATUS_OUTPUT_FIFO_SIZE_MASK 0x00ff0000 +/*! How many bits right to shift fifo size to make it LSB */ +#define RNGA_STATUS_OUTPUT_FIFO_SIZE_SHIFT 16 +/*! How many bytes are currently in the FIFO */ +#define RNGA_STATUS_OUTPUT_FIFO_LEVEL_MASK 0x0000ff00 +/*! How many bits right to shift fifo level to make it LSB */ +#define RNGA_STATUS_OUTPUT_FIFO_LEVEL_SHIFT 8 +/*! These bits are undefined or reserved. */ +#define RNGA_STATUS_ZEROS2_MASK 0x000000e0 +/*! RNG is sleeping. */ +#define RNGA_STATUS_SLEEP 0x00000010 +/*! Error detected. */ +#define RNGA_STATUS_ERROR_INTERRUPT 0x00000008 +/*! FIFO was empty on some read since last status read. */ +#define RNGA_STATUS_FIFO_UNDERFLOW 0x00000004 +/*! FIFO was empty on most recent read. */ +#define RNGA_STATUS_LAST_READ_STATUS 0x00000002 +/*! Security violation occurred. Will only happen in High Assurance mode. */ +#define RNGA_STATUS_SECURITY_VIOLATION 0x00000001 +/*! @} */ + +/*! @defgroup rngmodereg RNG Mode Register Definitions + * @ingroup RNG + */ +/*! @addtogroup rngmodereg */ +/*! @{ */ +/*! These bits are undefined or reserved */ +#define RNGA_MODE_ZEROS_MASK 0xfffffffc +/*! RNG is in / put RNG in Oscillator Frequency Test Mode. */ +#define RNGA_MODE_OSCILLATOR_FREQ_TEST 0x00000002 +/*! Put RNG in verification mode / RNG is in verification mode. */ +#define RNGA_MODE_VERIFICATION 0x00000001 +/*! @} */ + +/*! @defgroup rngvfctlreg RNG Verification Control Register Definitions + * @ingroup RNG + */ +/*! @addtogroup rngvfctlreg */ +/*! @{ */ +/*! These bits are undefined or reserved. */ +#define RNGA_VFCTL_ZEROS_MASK 0xfffffff8 +/*! Reset the shift registers. */ +#define RNGA_VFCTL_RESET_SHIFT_REGISTERS 0x00000004 +/*! Drive shift registers from system clock. */ +#define RNGA_VFCTL_FORCE_SYSTEM_CLOCK 0x00000002 +/*! Turn off shift register clocks. */ +#define RNGA_VFCTL_SHIFT_CLOCK_OFF 0x00000001 +/*! @} */ + +/*! + * @defgroup rngosccntctlreg RNG Oscillator Counter Control Register Definitions + * @ingroup RNG + */ +/*! @addtogroup rngosccntctlreg */ +/*! @{ */ +/*! These bits are undefined or reserved. */ +#define RNGA_OSCCR_ZEROS_MASK 0xfffc0000 +/*! Bits containing clock cycle counter */ +#define RNGA_OSCCR_CLOCK_CYCLES_MASK 0x0003ffff +/*! Bits to shift right RNG_OSCCR_CLOCK_CYCLES_MASK */ +#define RNGA_OSCCR_CLOCK_CYCLES_SHIFT 0 +/*! @} */ + +/*! + * @defgroup rngosccntreg RNG Oscillator (1 and 2) Counter Register Definitions + * @ingroup RNG + */ +/*! @addtogroup rngosccntreg */ +/*! @{ */ +/*! These bits are undefined or reserved. */ +#define RNGA_COUNTER_ZEROS_MASK 0xfff00000 +/*! Bits containing number of clock pulses received from the oscillator. */ +#define RNGA_COUNTER_PULSES_MASK 0x000fffff +/*! Bits right to shift RNG_COUNTER_PULSES_MASK to make it LSB. */ +#define RNGA_COUNTER_PULSES_SHIFT 0 +/*! @} */ + +/*! + * @defgroup rngosccntstatreg RNG Oscillator Counter Status Register Definitions + * @ingroup RNG + */ +/*! @addtogroup rngosccntstatreg */ +/*! @{ */ +/*! These bits are undefined or reserved. */ +#define RNGA_COUNTER_STATUS_ZEROS_MASK 0xfffffffc +/*! Oscillator 2 has toggled 0x400 times */ +#define RNGA_COUNTER_STATUS_OSCILLATOR2 0x00000002 +/*! Oscillator 1 has toggled 0x400 times */ +#define RNGA_COUNTER_STATUS_OSCILLATOR1 0x00000001 +/*! @} */ + +#endif /* RNG_RNGA_H */ diff --git a/drivers/mxc/security/rng/include/rng_rngc.h b/drivers/mxc/security/rng/include/rng_rngc.h new file mode 100644 index 000000000000..0aa36d0bef67 --- /dev/null +++ b/drivers/mxc/security/rng/include/rng_rngc.h @@ -0,0 +1,203 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#ifndef RNG_RNGC_H +#define RNG_RNGC_H + +#define RNGC_VERSION_MAJOR3 3 + +/*! RNGC Version ID Register R/W */ +#define RNGC_VERSION_ID 0x0000 +/*! RNGC Command Register R/W */ +#define RNGC_COMMAND 0x0004 +/*! RNGC Control Register R/W */ +#define RNGC_CONTROL 0x0008 +/*! RNGC Status Register R */ +#define RNGC_STATUS 0x000C +/*! RNGC Error Status Register R */ +#define RNGC_ERROR 0x0010 +/*! RNGC FIFO Register W */ +#define RNGC_FIFO 0x0014 +/*! RNGC Verification Control Register1 R/W */ +#define RNGC_VERIFICATION_CONTROL 0x0020 +/*! RNGC Oscillator Counter Control Register1 R/W */ +#define RNGC_OSCILLATOR_CONTROL_COUNTER 0x0028 +/*! RNGC Oscillator Counter Register1 R */ +#define RNGC_OSC_COUNTER 0x002C +/*! RNGC Oscillator Counter Status Register1 R */ +#define RNGC_OSC_COUNTER_STATUS 0x0030 +/*! @} */ + +/*! @defgroup rngcveridreg RNGC Version ID Register Definitions + * @ingroup RNG + */ +/*! @addtogroup rngcveridreg */ +/*! @{ */ +/*! These bits are unimplemented or reserved */ +#define RNGC_VERID_ZEROS_MASK 0x0f000000 +/*! Mask for RNG TYPE */ +#define RNGC_VERID_RNG_TYPE_MASK 0xf0000000 +/*! Shift to make RNG TYPE be LSB */ +#define RNGC_VERID_RNG_TYPE_SHIFT 28 +/*! Mask for RNG Chip Version */ +#define RNGC_VERID_CHIP_VERSION_MASK 0x00ff0000 +/*! Shift to make RNG Chip version be LSB */ +#define RNGC_VERID_CHIP_VERSION_SHIFT 16 +/*! Mask for RNG Major Version */ +#define RNGC_VERID_VERSION_MAJOR_MASK 0x0000ff00 +/*! Shift to make RNG Major version be LSB */ +#define RNGC_VERID_VERSION_MAJOR_SHIFT 8 +/*! Mask for RNG Minor Version */ +#define RNGC_VERID_VERSION_MINOR_MASK 0x000000ff +/*! Shift to make RNG Minor version be LSB */ +#define RNGC_VERID_VERSION_MINOR_SHIFT 0 +/*! @} */ + +/*! @defgroup rngccommandreg RNGC Command Register Definitions + * @ingroup RNG + */ +/*! @addtogroup rngccommandreg */ +/*! @{ */ +/*! These bits are unimplemented or reserved. */ +#define RNGC_COMMAND_ZEROS_MASK 0xffffff8c +/*! Perform a software reset of the RNGC. */ +#define RNGC_COMMAND_SOFTWARE_RESET 0x00000040 +/*! Clear error from Error Status register (and interrupt). */ +#define RNGC_COMMAND_CLEAR_ERROR 0x00000020 +/*! Clear interrupt & status. */ +#define RNGC_COMMAND_CLEAR_INTERRUPT 0x00000010 +/*! Start RNGC seed generation. */ +#define RNGC_COMMAND_SEED 0x00000002 +/*! Perform a self test of (and reset) the RNGC. */ +#define RNGC_COMMAND_SELF_TEST 0x00000001 +/*! @} */ + +/*! @defgroup rngccontrolreg RNGC Control Register Definitions + * @ingroup RNG + */ +/*! @addtogroup rngccontrolreg */ +/*! @{ */ +/*! These bits are unimplemented or reserved */ +#define RNGC_CONTROL_ZEROS_MASK 0xfffffc8c +/*! Allow access to verification registers. */ +#define RNGC_CONTROL_CTL_ACC 0x00000200 +/*! Put RNGC into deterministic verifcation mode. */ +#define RNGC_CONTROL_VERIF_MODE 0x00000100 +/*! Prevent RNGC from generating interrupts caused by errors. */ +#define RNGC_CONTROL_MASK_ERROR 0x00000040 + +/*! + * Prevent RNGC from generating interrupts after Seed Done or Self Test Mode + * completion. + */ +#define RNGC_CONTROL_MASK_DONE 0x00000020 +/*! Allow RNGC to generate a new seed whenever it is needed. */ +#define RNGC_CONTROL_AUTO_SEED 0x00000010 +/*! Set FIFO Underflow Response.*/ +#define RNGC_CONTROL_FIFO_UFLOW_MASK 0x00000003 +/*! Shift value to make FIFO Underflow Response be LSB. */ +#define RNGC_CONTROL_FIFO_UFLOW_SHIFT 0 + +/*! @} */ + +/*! @{ */ +/*! FIFO Underflow should cause ... */ +#define RNGC_CONTROL_FIFO_UFLOW_ZEROS_ERROR 0 +/*! FIFO Underflow should cause ... */ +#define RNGC_CONTROL_FIFO_UFLOW_ZEROS_ERROR2 1 +/*! FIFO Underflow should cause ... */ +#define RNGC_CONTROL_FIFO_UFLOW_BUS_XFR 2 +/*! FIFO Underflow should cause ... */ +#define RNGC_CONTROL_FIFO_UFLOW_ZEROS_INTR 3 +/*! @} */ + +/*! @defgroup rngcstatusreg RNGC Status Register Definitions + * @ingroup RNG + */ +/*! @addtogroup rngcstatusreg */ +/*! @{ */ +/*! Unused or MBZ. */ +#define RNGC_STATUS_ZEROS_MASK 0x003e0080 +/*! + * Statistical tests pass-fail. Individual bits on indicate failure of a + * particular test. + */ +#define RNGC_STATUS_STAT_TEST_PF_MASK 0xff000000 +/*! Mask to get Statistical PF to be LSB. */ +#define RNGC_STATUS_STAT_TEST_PF_SHIFT 24 +/*! + * Self tests pass-fail. Individual bits on indicate failure of a + * particular test. + */ +#define RNGC_STATUS_ST_PF_MASK 0x00c00000 +/*! Shift value to get Self Test PF field to be LSB. */ +#define RNGC_STATUS_ST_PF_SHIFT 22 +/*! Error detected in RNGC. See Error Status register. */ +#define RNGC_STATUS_ERROR 0x00010000 +/*! Size of the internal FIFO in 32-bit words. */ +#define RNGC_STATUS_FIFO_SIZE_MASK 0x0000f000 +/*! Shift value to get FIFO Size to be LSB. */ +#define RNGC_STATUS_FIFO_SIZE_SHIFT 12 +/*! The level (available data) of the internal FIFO in 32-bit words. */ +#define RNGC_STATUS_FIFO_LEVEL_MASK 0x00000f00 +/*! Shift value to get FIFO Level to be LSB. */ +#define RNGC_STATUS_FIFO_LEVEL_SHIFT 8 +/*! A new seed is ready for use. */ +#define RNGC_STATUS_NEXT_SEED_DONE 0x00000040 +/*! The first seed has been generated. */ +#define RNGC_STATUS_SEED_DONE 0x00000020 +/*! Self Test has been completed. */ +#define RNGC_STATUS_ST_DONE 0x00000010 +/*! Reseed is necessary. */ +#define RNGC_STATUS_RESEED 0x00000008 +/*! RNGC is sleeping. */ +#define RNGC_STATUS_SLEEP 0x00000004 +/*! RNGC is currently generating numbers, seeding, generating next seed, or + performing a self test. */ +#define RNGC_STATUS_BUSY 0x00000002 +/*! RNGC is in secure state. */ +#define RNGC_STATUS_SEC_STATE 0x00000001 + +/*! @} */ + +/*! @defgroup rngcerrstatusreg RNGC Error Status Register Definitions + * @ingroup RNG + */ +/*! @addtogroup rngcerrstatusreg */ +/*! @{ */ +/*! Unused or MBZ. */ +#define RNGC_ERROR_STATUS_ZEROS_MASK 0xffffffc0 +/*! Bad Key Error Status */ +#define RNGC_ERROR_STATUS_BAD_KEY 0x00000040 +/*! Random Compare Error. Previous number matched the current number. */ +#define RNGC_ERROR_STATUS_RAND_ERR 0x00000020 +/*! FIFO Underflow. FIFO was read while empty. */ +#define RNGC_ERROR_STATUS_FIFO_ERR 0x00000010 +/*! Statistic Error Statistic Test failed for the last seed. */ +#define RNGC_ERROR_STATUS_STAT_ERR 0x00000008 +/*! Self-test error. Some self test has failed. */ +#define RNGC_ERROR_STATUS_ST_ERR 0x00000004 +/*! + * Oscillator Error. The oscillator may be broken. Clear by hard or soft + * reset. + */ +#define RNGC_ERROR_STATUS_OSC_ERR 0x00000002 +/*! LFSR Error. Clear by hard or soft reset. */ +#define RNGC_ERROR_STATUS_LFSR_ERR 0x00000001 + +/*! @} */ + +/*! Total address space of the RNGC, in bytes */ +#define RNG_ADDRESS_RANGE 0x34 + +#endif diff --git a/drivers/mxc/security/rng/include/shw_driver.h b/drivers/mxc/security/rng/include/shw_driver.h new file mode 100644 index 000000000000..06f3f186466a --- /dev/null +++ b/drivers/mxc/security/rng/include/shw_driver.h @@ -0,0 +1,2141 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU Lesser General + * Public License. You may obtain a copy of the GNU Lesser General + * Public License Version 2.1 or later at the following locations: + * + * http://www.opensource.org/licenses/lgpl-license.html + * http://www.gnu.org/copyleft/lgpl.html + */ + +#ifndef SHW_DRIVER_H +#define SHW_DRIVER_H + +/* This is a Linux flag meaning 'compiling kernel code'... */ +#ifndef __KERNEL__ +#include <inttypes.h> +#include <stdlib.h> +#include <sys/ioctl.h> +#include <memory.h> +#include <stdio.h> +#else +#include "../../sahara2/include/portable_os.h" +#endif /* __KERNEL__ */ + +#include "../../sahara2/include/fsl_platform.h" + +/*! @file shw_driver.h + + * @brief Header file to use the SHW driver. + * + * The SHW driver is used in two modes: By a user, from the FSL SHW API in user + * space, which goes through /dev/fsl_shw to make open(), ioctl(), and close() + * calls; and by other kernel modules/drivers, which use the FSL SHW API, parts + * of which are supported directly by the SHW driver. + * + * Testing is performed by using the apitest and kernel api test routines + * developed for the Sahara2 driver. + * @ingroup RNG + */ + +/*! Perform a security function. */ +#define SHW_IOCTL_REQUEST 21 + +/*! + * This is part of the IOCTL request type passed between kernel and user space. + * It is added to #SHW_IOCTL_REQUEST to generate the actual value. + */ +typedef enum shw_user_request_type { + SHW_USER_REQ_REGISTER_USER, /*!< Initialize user-kernel discussion. */ + SHW_USER_REQ_DEREGISTER_USER, /*!< Terminate user-kernel discussion. */ + SHW_USER_REQ_GET_RESULTS, /*!< Get information on outstanding + results. */ + SHW_USER_REQ_GET_CAPABILITIES, /*!< Get information on hardware support. */ + SHW_USER_REQ_GET_RANDOM, /*!< Get random data from RNG. */ + SHW_USER_REQ_ADD_ENTROPY, /*!< Add entropy to hardware RNG. */ +} shw_user_request_t; + +/*!**************************************************************************** + * Enumerations + *****************************************************************************/ +/*! + * Flags for the state of the User Context Object (#fsl_shw_uco_t). + */ +typedef enum fsl_shw_user_ctx_flags { + FSL_UCO_BLOCKING_MODE = 0x01, /*!< API will block the caller until operation + completes. The result will be available in the + return code. If this is not set, user will have + to get results using #fsl_shw_get_results(). */ + FSL_UCO_CALLBACK_MODE = 0x02, /*!< User wants callback (at the function + specified with #fsl_shw_uco_set_callback()) when + the operation completes. This flag is valid + only if #FSL_UCO_BLOCKING_MODE is not set. */ + FSL_UCO_SAVE_DESC_CHAIN = 0x04, /*!< Do not free descriptor chain after + driver (adaptor) finishes */ + FSL_UCO_CALLBACK_SETUP_COMPLETE = 0x08, /*!< User has made at least one + request with callbacks requested, so + API is ready to handle others. */ + + FSL_UCO_CHAIN_PREPHYSICALIZED = 0x10, /*!< (virtual) pointer to descriptor + chain is completely linked with physical + (DMA) addresses, ready for the hardware. + This flag should not be used by FSL SHW API + programs. */ + FSL_UCO_CONTEXT_CHANGED = 0x20, /*!< The user has changed the context but + the changes have not been copied to the + kernel driver. */ + FSL_UCO_USERMODE_USER = 0x40, /*!< Internal use. This context belongs to a + user-mode API user. */ +} fsl_shw_user_ctx_flags_t; + +/*! + * Return code for FSL_SHW library. + * + * These codes may be returned from a function call. In non-blocking mode, + * they will appear as the status in a Result Object. + */ +/* REQ-FSLSHW-ERR-001 */ +typedef enum fsl_shw_return_t { + FSL_RETURN_OK_S = 0, /*!< No error. As a function return code in + Non-blocking mode, this may simply mean that + the operation was accepted for eventual + execution. */ + FSL_RETURN_ERROR_S, /*!< Failure for non-specific reason. */ + FSL_RETURN_NO_RESOURCE_S, /*!< Operation failed because some resource was + not able to be allocated. */ + FSL_RETURN_BAD_ALGORITHM_S, /*!< Crypto algorithm unrecognized or + improper. */ + FSL_RETURN_BAD_MODE_S, /*!< Crypto mode unrecognized or improper. */ + FSL_RETURN_BAD_FLAG_S, /*!< Flag setting unrecognized or + inconsistent. */ + FSL_RETURN_BAD_KEY_LENGTH_S, /*!< Improper or unsupported key length for + algorithm. */ + FSL_RETURN_BAD_KEY_PARITY_S, /*!< Improper parity in a (DES, TDES) key. */ + FSL_RETURN_BAD_DATA_LENGTH_S, /*!< Improper or unsupported data length for + algorithm or internal buffer. */ + FSL_RETURN_AUTH_FAILED_S, /*!< Authentication failed in + authenticate-decrypt operation. */ + FSL_RETURN_MEMORY_ERROR_S, /*!< A memory error occurred. */ + FSL_RETURN_INTERNAL_ERROR_S /*!< An error internal to the hardware + occurred. */ +} fsl_shw_return_t; + +/*! + * Algorithm Identifier. + * + * Selection of algorithm will determine how large the block size of the + * algorithm is. Context size is the same length unless otherwise specified. + * Selection of algorithm also affects the allowable key length. + */ +typedef enum fsl_shw_key_alg_t { + FSL_KEY_ALG_HMAC, /*!< Key will be used to perform an HMAC. Key + size is 1 to 64 octets. Block size is 64 + octets. */ + FSL_KEY_ALG_AES, /*!< Advanced Encryption Standard (Rijndael). + Block size is 16 octets. Key size is 16 + octets. (The single choice of key size is a + Sahara platform limitation.) */ + FSL_KEY_ALG_DES, /*!< Data Encryption Standard. Block size is + 8 octets. Key size is 8 octets. */ + FSL_KEY_ALG_TDES, /*!< 2- or 3-key Triple DES. Block size is 8 + octets. Key size is 16 octets for 2-key + Triple DES, and 24 octets for 3-key. */ + FSL_KEY_ALG_ARC4 /*!< ARC4. No block size. Context size is 259 + octets. Allowed key size is 1-16 octets. + (The choices for key size are a Sahara + platform limitation.) */ +} fsl_shw_key_alg_t; + +/*! + * Mode selector for Symmetric Ciphers. + * + * The selection of mode determines how a cryptographic algorithm will be + * used to process the plaintext or ciphertext. + * + * For all modes which are run block-by-block (that is, all but + * #FSL_SYM_MODE_STREAM), any partial operations must be performed on a text + * length which is multiple of the block size. Except for #FSL_SYM_MODE_CTR, + * these block-by-block algorithms must also be passed a total number of octets + * which is a multiple of the block size. + * + * In modes which require that the total number of octets of data be a multiple + * of the block size (#FSL_SYM_MODE_ECB and #FSL_SYM_MODE_CBC), and the user + * has a total number of octets which are not a multiple of the block size, the + * user must perform any necessary padding to get to the correct data length. + */ +typedef enum fsl_shw_sym_mode_t { + /*! + * Stream. There is no associated block size. Any request to process data + * may be of any length. This mode is only for ARC4 operations, and is + * also the only mode used for ARC4. + */ + FSL_SYM_MODE_STREAM, + + /*! + * Electronic Codebook. Each block of data is encrypted/decrypted. The + * length of the data stream must be a multiple of the block size. This + * mode may be used for DES, 3DES, and AES. The block size is determined + * by the algorithm. + */ + FSL_SYM_MODE_ECB, + /*! + * Cipher-Block Chaining. Each block of data is encrypted/decrypted and + * then "chained" with the previous block by an XOR function. Requires + * context to start the XOR (previous block). This mode may be used for + * DES, 3DES, and AES. The block size is determined by the algorithm. + */ + FSL_SYM_MODE_CBC, + /*! + * Counter. The counter is encrypted, then XORed with a block of data. + * The counter is then incremented (using modulus arithmetic) for the next + * block. The final operation may be non-multiple of block size. This mode + * may be used for AES. The block size is determined by the algorithm. + */ + FSL_SYM_MODE_CTR, +} fsl_shw_sym_mode_t; + +/*! + * Algorithm selector for Cryptographic Hash functions. + * + * Selection of algorithm determines how large the context and digest will be. + * Context is the same size as the digest (resulting hash), unless otherwise + * specified. + */ +typedef enum fsl_shw_hash_alg { + FSL_HASH_ALG_MD5, /*!< MD5 algorithm. Digest is 16 octets. */ + FSL_HASH_ALG_SHA1, /*!< SHA-1 (aka SHA or SHA-160) algorithm. + Digest is 20 octets. */ + FSL_HASH_ALG_SHA224, /*!< SHA-224 algorithm. Digest is 28 octets, + though context is 32 octets. */ + FSL_HASH_ALG_SHA256 /*!< SHA-256 algorithm. Digest is 32 + octets. */ +} fsl_shw_hash_alg_t; + +/*! + * The type of Authentication-Cipher function which will be performed. + */ +typedef enum fsl_shw_acc_mode_t { + /*! + * CBC-MAC for Counter. Requires context and modulus. Final operation may + * be non-multiple of block size. This mode may be used for AES. + */ + FSL_ACC_MODE_CCM, + /*! + * SSL mode. Not supported. Combines HMAC and encrypt (or decrypt). + * Needs one key object for encryption, another for the HMAC. The usual + * hashing and symmetric encryption algorithms are supported. + */ + FSL_ACC_MODE_SSL +} fsl_shw_acc_mode_t; + +/* REQ-FSLSHW-PINTFC-COA-HCO-001 */ +/*! + * Flags which control a Hash operation. + */ +typedef enum fsl_shw_hash_ctx_flags { + FSL_HASH_FLAGS_INIT = 1, /*!< Context is empty. Hash is started + from scratch, with a message-processed + count of zero. */ + FSL_HASH_FLAGS_SAVE = 2, /*!< Retrieve context from hardware after + hashing. If used with the + #FSL_HASH_FLAGS_FINALIZE flag, the final + digest value will be saved in the + object. */ + FSL_HASH_FLAGS_LOAD = 4, /*!< Place context into hardware before + hashing. */ + FSL_HASH_FLAGS_FINALIZE = 8, /*!< PAD message and perform final digest + operation. If user message is + pre-padded, this flag should not be + used. */ +} fsl_shw_hash_ctx_flags_t; + +/*! + * Flags which control an HMAC operation. + * + * These may be combined by ORing them together. See #fsl_shw_hmco_set_flags() + * and #fsl_shw_hmco_clear_flags(). + */ +typedef enum fsl_shw_hmac_ctx_flags_t { + FSL_HMAC_FLAGS_INIT = 1, /*!< Message context is empty. HMAC is + started from scratch (with key) or from + precompute of inner hash, depending on + whether + #FSL_HMAC_FLAGS_PRECOMPUTES_PRESENT is + set. */ + FSL_HMAC_FLAGS_SAVE = 2, /*!< Retrieve ongoing context from hardware + after hashing. If used with the + #FSL_HMAC_FLAGS_FINALIZE flag, the final + digest value (HMAC) will be saved in the + object. */ + FSL_HMAC_FLAGS_LOAD = 4, /*!< Place ongoing context into hardware + before hashing. */ + FSL_HMAC_FLAGS_FINALIZE = 8, /*!< PAD message and perform final HMAC + operations of inner and outer hashes. */ + FSL_HMAC_FLAGS_PRECOMPUTES_PRESENT = 16 /*!< This means that the context + contains precomputed inner and outer + hash values. */ +} fsl_shw_hmac_ctx_flags_t; + +/*! + * Flags to control use of the #fsl_shw_scco_t. + * + * These may be ORed together to get the desired effect. + * See #fsl_shw_scco_set_flags() and #fsl_shw_scco_clear_flags() + */ +typedef enum fsl_shw_sym_ctx_flags_t { + /*! + * Context is empty. In ARC4, this means that the S-Box needs to be + * generated from the key. In #FSL_SYM_MODE_CBC mode, this allows an IV of + * zero to be specified. In #FSL_SYM_MODE_CTR mode, it means that an + * initial CTR value of zero is desired. + */ + FSL_SYM_CTX_INIT = 1, + /*! + * Load context from object into hardware before running cipher. In + * #FSL_SYM_MODE_CTR mode, this would refer to the Counter Value. + */ + FSL_SYM_CTX_LOAD = 2, + /*! + * Save context from hardware into object after running cipher. In + * #FSL_SYM_MODE_CTR mode, this would refer to the Counter Value. + */ + FSL_SYM_CTX_SAVE = 4, + /*! + * Context (SBox) is to be unwrapped and wrapped on each use. + * This flag is unsupported. + * */ + FSL_SYM_CTX_PROTECT = 8, +} fsl_shw_sym_ctx_flags_t; + +/*! + * Flags which describe the state of the #fsl_shw_sko_t. + * + * These may be ORed together to get the desired effect. + * See #fsl_shw_sko_set_flags() and #fsl_shw_sko_clear_flags() + */ +typedef enum fsl_shw_key_flags_t { + FSL_SKO_KEY_IGNORE_PARITY = 1, /*!< If algorithm is DES or 3DES, do not + validate the key parity bits. */ + FSL_SKO_KEY_PRESENT = 2, /*!< Clear key is present in the object. */ + FSL_SKO_KEY_ESTABLISHED = 4, /*!< Key has been established for use. This + feature is not available for all + platforms, nor for all algorithms and + modes. */ + FSL_SKO_USE_SECRET_KEY = 8, /*!< Use device-unique key. Not always + available. */ +} fsl_shw_key_flags_t; + +/*! + * Type of value which is associated with an established key. + */ +typedef uint64_t key_userid_t; + +/*! + * Flags which describe the state of the #fsl_shw_acco_t. + * + * The @a FSL_ACCO_CTX_INIT and @a FSL_ACCO_CTX_FINALIZE flags, when used + * together, provide for a one-shot operation. + */ +typedef enum fsl_shw_auth_ctx_flags_t { + FSL_ACCO_CTX_INIT = 1, /*!< Initialize Context(s) */ + FSL_ACCO_CTX_LOAD = 2, /*!< Load intermediate context(s). + This flag is unsupported. */ + FSL_ACCO_CTX_SAVE = 4, /*!< Save intermediate context(s). + This flag is unsupported. */ + FSL_ACCO_CTX_FINALIZE = 8, /*!< Create MAC during this operation. */ + FSL_ACCO_NIST_CCM = 0x10, /*!< Formatting of CCM input data is + performed by calls to + #fsl_shw_ccm_nist_format_ctr_and_iv() and + #fsl_shw_ccm_nist_update_ctr_and_iv(). */ +} fsl_shw_auth_ctx_flags_t; + +/*! + * The operation which controls the behavior of #fsl_shw_establish_key(). + * + * These values are passed to #fsl_shw_establish_key(). + */ +typedef enum fsl_shw_key_wrap_t { + FSL_KEY_WRAP_CREATE, /*!< Generate a key from random values. */ + FSL_KEY_WRAP_ACCEPT, /*!< Use the provided clear key. */ + FSL_KEY_WRAP_UNWRAP /*!< Unwrap a previously wrapped key. */ +} fsl_shw_key_wrap_t; + +/*! + * Modulus Selector for CTR modes. + * + * The incrementing of the Counter value may be modified by a modulus. If no + * modulus is needed or desired for AES, use #FSL_CTR_MOD_128. + */ +typedef enum fsl_shw_ctr_mod { + FSL_CTR_MOD_8, /*!< Run counter with modulus of 2^8. */ + FSL_CTR_MOD_16, /*!< Run counter with modulus of 2^16. */ + FSL_CTR_MOD_24, /*!< Run counter with modulus of 2^24. */ + FSL_CTR_MOD_32, /*!< Run counter with modulus of 2^32. */ + FSL_CTR_MOD_40, /*!< Run counter with modulus of 2^40. */ + FSL_CTR_MOD_48, /*!< Run counter with modulus of 2^48. */ + FSL_CTR_MOD_56, /*!< Run counter with modulus of 2^56. */ + FSL_CTR_MOD_64, /*!< Run counter with modulus of 2^64. */ + FSL_CTR_MOD_72, /*!< Run counter with modulus of 2^72. */ + FSL_CTR_MOD_80, /*!< Run counter with modulus of 2^80. */ + FSL_CTR_MOD_88, /*!< Run counter with modulus of 2^88. */ + FSL_CTR_MOD_96, /*!< Run counter with modulus of 2^96. */ + FSL_CTR_MOD_104, /*!< Run counter with modulus of 2^104. */ + FSL_CTR_MOD_112, /*!< Run counter with modulus of 2^112. */ + FSL_CTR_MOD_120, /*!< Run counter with modulus of 2^120. */ + FSL_CTR_MOD_128 /*!< Run counter with modulus of 2^128. */ +} fsl_shw_ctr_mod_t; + +/*! + * A work type associated with a work/result queue request. + */ +typedef enum shw_work_type { + SHW_WORK_GET_RANDOM = 1, /*!< fsl_shw_get_random() request. */ + SHW_WORK_ADD_RANDOM, /*!< fsl_shw_add_entropy() request. */ +} shw_work_type_t; + +/*!**************************************************************************** + * Data Structures + *****************************************************************************/ + +/*! + * Initialization Object + */ +typedef struct fsl_sho_ibo { +} fsl_sho_ibo_t; + +/*! + * Common Entry structure for work queues, results queues. + */ +typedef struct shw_queue_entry { + struct shw_queue_entry *next; /*!< Next entry in queue. */ + struct fsl_shw_uco_t *user_ctx; /*!< Associated user context. */ + uint32_t flags; /*!< User context flags at time of request. */ + void (*callback) (struct fsl_shw_uco_t * uco); /*!< Any callback request. */ + uint32_t user_ref; /*!< User's reference for this request. */ + fsl_shw_return_t code; /*!< FSL SHW result of this operation. */ + uint32_t detail1; /*!< Any extra error info. */ + uint32_t detail2; /*!< More any extra error info. */ + void *user_mode_req; /*!< Pointer into user space. */ + uint32_t(*postprocess) (struct shw_queue_entry * q); /*!< (internal) + function to call + when this operation + completes. + */ +} shw_queue_entry_t; + +/*! + * A queue. Fields must be initialized to NULL before use. + */ +typedef struct shw_queue { + struct shw_queue_entry *head; /*!< First entry in queue. */ + struct shw_queue_entry *tail; /*!< Last entry. */ +} shw_queue_t; + +/* REQ-FSLSHW-PINTFC-COA-UCO-001 */ +/*! + * User Context Object + */ +typedef struct fsl_shw_uco_t { + int openfd; /*!< user-mode file descriptor */ + uint32_t user_ref; /*!< User's reference */ + void (*callback) (struct fsl_shw_uco_t * uco); /*!< User's callback fn */ + uint32_t flags; /*!< from fsl_shw_user_ctx_flags_t */ + unsigned pool_size; /*!< maximum size of user result pool */ +#ifdef __KERNEL__ + shw_queue_t result_pool; /*!< where non-blocking results go */ + os_process_handle_t process; /*!< remember for signalling User mode */ +#endif + struct fsl_shw_uco_t *next; /*!< To allow user-mode chaining of contexts, + for signalling and in kernel, to link user + contexts. */ +} fsl_shw_uco_t; + +/* REQ-FSLSHW-PINTFC-API-GEN-006 ?? */ +/*! + * Result object + */ +typedef struct fsl_shw_result_t { + uint32_t user_ref; /*!< User's reference at time of request. */ + fsl_shw_return_t code; /*!< Return code from request. */ + uint32_t detail1; /*!< Extra error info. Unused in SHW driver. */ + uint32_t detail2; /*!< Extra error info. Unused in SHW driver. */ + void *user_req; /*!< Pointer to original user request. */ +} fsl_shw_result_t; + +/* REQ-FSLSHW-PINTFC-COA-SKO-001 */ +/*! + * Secret Key Context Object + */ +typedef struct fsl_shw_sko_t { + uint32_t flags; /*!< Flags from #fsl_shw_sym_ctx_flags_t. */ + fsl_shw_key_alg_t algorithm; /*!< Algorithm for this key. */ + key_userid_t userid; /*!< User's identifying value for Black key. */ + uint32_t handle; /*!< Reference in SCC driver for Red key. */ + uint16_t key_length; /*!< Length of stored key, in bytes. */ + uint8_t key[64]; /*!< Bytes of stored key. */ +} fsl_shw_sko_t; + +/* REQ-FSLSHW-PINTFC-COA-CO-001 */ +/*! + * Platform Capability Object + * + * Pointer to this structure is returned by fsl_shw_get_capabilities() and + * queried with the various fsl_shw_pco_() functions. + */ +typedef struct fsl_shw_pco_t { + int api_major; /*!< Major version number for API. */ + int api_minor; /*!< Minor version number for API. */ + int driver_major; /*!< Major version of some driver. */ + int driver_minor; /*!< Minor version of some driver. */ + unsigned sym_algorithm_count; /*!< Number of sym_algorithms. */ + fsl_shw_key_alg_t *sym_algorithms; /*!< Pointer to array. */ + unsigned sym_mode_count; /*!< Number of sym_modes. */ + fsl_shw_sym_mode_t *sym_modes; /*!< Pointer to array. */ + unsigned hash_algorithm_count; /*!< Number of hash_algorithms. */ + fsl_shw_hash_alg_t *hash_algorithms; /*!< Pointer to array */ + uint8_t sym_support[5][4]; /*!< indexed by key alg then mode */ +} fsl_shw_pco_t; + +/* REQ-FSLSHW-PINTFC-COA-HCO-001 */ +/*! + * Hash Context Object + */ +typedef struct fsl_shw_hco_t { /* fsl_shw_hash_context_object */ + fsl_shw_hash_alg_t algorithm; /*!< Algorithm for this context. */ + uint32_t flags; /*!< Flags from + #fsl_shw_hash_ctx_flags_t. */ + uint8_t digest_length; /*!< hash result length in bytes */ + uint8_t context_length; /*!< Context length in bytes */ + uint8_t context_register_length; /*!< in bytes */ + uint32_t context[9]; /*!< largest digest + msg size */ +} fsl_shw_hco_t; + +/* REQ-FSLSHW-PINTFC-COA-HCO-001 */ +/*! + * HMAC Context Object + */ +typedef struct fsl_shw_hmco_t { /* fsl_shw_hmac_context_object */ + fsl_shw_hash_alg_t algorithm; /*!< Hash algorithm for the HMAC. */ + uint32_t flags; /*!< Flags from + #fsl_shw_hmac_ctx_flags_t. */ + uint8_t digest_length; /*!< in bytes */ + uint8_t context_length; /*!< in bytes */ + uint8_t context_register_length; /*!< in bytes */ + uint32_t ongoing_context[9]; /*!< largest digest + msg + size */ + uint32_t inner_precompute[9]; /*!< largest digest + msg + size */ + uint32_t outer_precompute[9]; /*!< largest digest + msg + size */ +} fsl_shw_hmco_t; + +/* REQ-FSLSHW-PINTFC-COA-SCCO-001 */ +/*! + * Symmetric Crypto Context Object Context Object + */ +typedef struct fsl_shw_scco_t { + uint32_t flags; /*!< Flags from #fsl_shw_sym_ctx_flags_t. */ + unsigned block_size_bytes; /*!< Both block and ctx size */ + fsl_shw_sym_mode_t mode; /*!< Symmetric mode for this context. */ + /* Could put modulus plus 16-octet context in union with arc4 + sbox+ptrs... */ + fsl_shw_ctr_mod_t modulus_exp; /*!< Exponent value for CTR modulus */ + uint8_t context[259]; /*!< Stored context. Large enough + for ARC4. */ +} fsl_shw_scco_t; + +/*! + * Authenticate-Cipher Context Object + + * An object for controlling the function of, and holding information about, + * data for the authenticate-cipher functions, #fsl_shw_gen_encrypt() and + * #fsl_shw_auth_decrypt(). + */ +typedef struct fsl_shw_acco_t { + uint32_t flags; /*!< See #fsl_shw_auth_ctx_flags_t for + meanings */ + fsl_shw_acc_mode_t mode; /*!< CCM only */ + uint8_t mac_length; /*!< User's value for length */ + unsigned q_length; /*!< NIST parameter - */ + fsl_shw_scco_t cipher_ctx_info; /*!< For running + encrypt/decrypt. */ + union { + fsl_shw_scco_t CCM_ctx_info; /*!< For running the CBC in + AES-CCM. */ + fsl_shw_hco_t hash_ctx_info; /*!< For running the hash */ + } auth_info; /*!< "auth" info struct */ + uint8_t unencrypted_mac[16]; /*!< max block size... */ +} fsl_shw_acco_t; + +/*! + * Common header in request structures between User-mode API and SHW driver. + */ +struct shw_req_header { + uint32_t flags; /*!< Flags - from user-mode context. */ + uint32_t user_ref; /*!< Reference - from user-mode context. */ + fsl_shw_return_t code; /*!< Result code for operation. */ +}; + +/*! + * Used by user-mode API to retrieve completed non-blocking results in + * SHW_USER_REQ_GET_RESULTS ioctl(). + */ +struct results_req { + struct shw_req_header hdr; /*!< Boilerplate. */ + unsigned requested; /*!< number of results requested, */ + unsigned actual; /*!< number of results obtained. */ + fsl_shw_result_t *results; /*!< pointer to memory to hold results. */ +}; + +/*! + * Used by user-mode API to retrieve hardware capabilities in + * SHW_USER_REQ_GET_CAPABILITIES ioctl(). + */ +struct capabilities_req { + struct shw_req_header hdr; /*!< Boilerplate. */ + unsigned size; /*!< Size, in bytes, capabilities. */ + fsl_shw_pco_t *capabilities; /*!< Place to copy out the info. */ +}; + +/*! + * Used by user-mode API to get a random number + */ +struct get_random_req { + struct shw_req_header hdr; /*!< Boilerplate. */ + unsigned size; /*!< Size, in bytes, of random. */ + uint8_t *random; /*!< Place to copy out the random number. */ +}; + +/*! + * Used by API to add entropy to a random number generator + */ +struct add_entropy_req { + struct shw_req_header hdr; /*!< Boilerplate. */ + unsigned size; /*!< Size, in bytes, of entropy. */ + uint8_t *entropy; /*!< Location of the entropy to be added. */ +}; + +/*!**************************************************************************** + * External variables + *****************************************************************************/ +#ifdef __KERNEL__ +extern os_lock_t shw_queue_lock; + +#endif + +/*!**************************************************************************** + * Access Macros for Objects + *****************************************************************************/ +/*! + * Get FSL SHW API version + * + * @param pcobject The Platform Capababilities Object to query. + * @param pcmajor A pointer to where the major version + * of the API is to be stored. + * @param pcminor A pointer to where the minor version + * of the API is to be stored. + */ +#define fsl_shw_pco_get_version(pcobject, pcmajor, pcminor) \ +do { \ + *(pcmajor) = (pcobject)->api_major; \ + *(pcminor) = (pcobject)->api_minor; \ +} while (0) + +/*! + * Get underlying driver version. + * + * @param pcobject The Platform Capababilities Object to query. + * @param pcmajor A pointer to where the major version + * of the driver is to be stored. + * @param pcminor A pointer to where the minor version + * of the driver is to be stored. + */ +#define fsl_shw_pco_get_driver_version(pcobject, pcmajor, pcminor) \ +do { \ + *(pcmajor) = (pcobject)->driver_major; \ + *(pcminor) = (pcobject)->driver_minor; \ +} while (0) + +/*! + * Get list of symmetric algorithms supported. + * + * @param pcobject The Platform Capababilities Object to query. + * @param pcalgorithms A pointer to where to store the location of + * the list of algorithms. + * @param pcacount A pointer to where to store the number of + * algorithms in the list at @a algorithms. + */ +#define fsl_shw_pco_get_sym_algorithms(pcobject, pcalgorithms, pcacount) \ +do { \ + *(pcalgorithms) = (pcobject)->sym_algorithms; \ + *(pcacount) = (pcobject)->sym_algorithm_count; \ +} while (0) + +/*! + * Get list of symmetric modes supported. + * + * @param pcobject The Platform Capababilities Object to query. + * @param gsmodes A pointer to where to store the location of + * the list of modes. + * @param gsacount A pointer to where to store the number of + * algorithms in the list at @a modes. + */ +#define fsl_shw_pco_get_sym_modes(pcobject, gsmodes, gsacount) \ +do { \ + *(gsmodes) = (pcobject)->sym_modes; \ + *(gsacount) = (pcobject)->sym_mode_count; \ +} while (0) + +/*! + * Get list of hash algorithms supported. + * + * @param pcobject The Platform Capababilities Object to query. + * @param gsalgorithms A pointer which will be set to the list of + * algorithms. + * @param gsacount The number of algorithms in the list at @a + * algorithms. + */ +#define fsl_shw_pco_get_hash_algorithms(pcobject, gsalgorithms, gsacount) \ +do { \ + *(gsalgorithms) = (pcobject)->hash_algorithms; \ + *(gsacount) = (pcobject)->hash_algorithm_count; \ +} while (0) + +/*! + * Determine whether the combination of a given symmetric algorithm and a given + * mode is supported. + * + * @param pcobject The Platform Capababilities Object to query. + * @param pcalg A Symmetric Cipher algorithm. + * @param pcmode A Symmetric Cipher mode. + * + * @return 0 if combination is not supported, non-zero if supported. + */ +#define fsl_shw_pco_check_sym_supported(pcobject, pcalg, pcmode) \ + 0 + +/*! + * Determine whether a given Encryption-Authentication mode is supported. + * + * @param pcobject The Platform Capababilities Object to query. + * @param pcmode The Authentication mode. + * + * @return 0 if mode is not supported, non-zero if supported. + */ +#define fsl_shw_pco_check_auth_supported(pcobject, pcmode) \ + 0 + +/*! + * Determine whether Black Keys (key establishment / wrapping) is supported. + * + * @param pcobject The Platform Capababilities Object to query. + * + * @return 0 if wrapping is not supported, non-zero if supported. + */ +#define fsl_shw_pco_check_black_key_supported(pcobject) \ + 0 + +/*! + * Initialize a User Context Object. + * + * This function must be called before performing any other operation with the + * Object. It sets the User Context Object to initial values, and set the size + * of the results pool. The mode will be set to a default of + * #FSL_UCO_BLOCKING_MODE. + * + * When using non-blocking operations, this sets the maximum number of + * operations which can be outstanding. This number includes the counts of + * operations waiting to start, operation(s) being performed, and results which + * have not been retrieved. + * + * Changes to this value are ignored once user registration has completed. It + * should be set to 1 if only blocking operations will ever be performed. + * + * @param ucontext The User Context object to operate on. + * @param usize The maximum number of operations which can be + * outstanding. + */ +#define fsl_shw_uco_init(ucontext, usize) \ +do { \ + fsl_shw_uco_t* uco = ucontext; \ + \ + (uco)->pool_size = usize; \ + (uco)->flags = FSL_UCO_BLOCKING_MODE | FSL_UCO_CONTEXT_CHANGED; \ + (uco)->openfd = -1; \ + (uco)->callback = NULL; \ +} while (0) + +/*! + * Set the User Reference for the User Context. + * + * @param ucontext The User Context object to operate on. + * @param uref A value which will be passed back with a result. + */ +#define fsl_shw_uco_set_reference(ucontext, uref) \ +do { \ + fsl_shw_uco_t* uco = ucontext; \ + \ + (uco)->user_ref = uref; \ + (uco)->flags |= FSL_UCO_CONTEXT_CHANGED; \ +} while (0) + +/*! + * Set the User Reference for the User Context. + * + * @param ucontext The User Context object to operate on. + * @param ucallback The function the API will invoke when an operation + * completes. + */ +#define fsl_shw_uco_set_callback(ucontext, ucallback) \ +do { \ + fsl_shw_uco_t* uco = ucontext; \ + \ + (uco)->callback = ucallback; \ + (uco)->flags |= FSL_UCO_CONTEXT_CHANGED; \ +} while (0) + +/*! + * Set flags in the User Context. + * + * Turns on the flags specified in @a flags. Other flags are untouched. + * + * @param ucontext The User Context object to operate on. + * @param uflags ORed values from #fsl_shw_user_ctx_flags_t. + */ +#define fsl_shw_uco_set_flags(ucontext, uflags) \ + (ucontext)->flags |= (uflags) | FSL_UCO_CONTEXT_CHANGED + +/*! + * Clear flags in the User Context. + * + * Turns off the flags specified in @a flags. Other flags are untouched. + * + * @param ucontext The User Context object to operate on. + * @param uflags ORed values from #fsl_shw_user_ctx_flags_t. + */ +#define fsl_shw_uco_clear_flags(ucontext, uflags) \ +do { \ + fsl_shw_uco_t* uco = ucontext; \ + \ + (uco)->flags &= ~(uflags); \ + (uco)->flags |= FSL_UCO_CONTEXT_CHANGED; \ +} while (0) + +/*! + * Retrieve the reference value from a Result Object. + * + * @param robject The result object to query. + * + * @return The reference associated with the request. + */ +#define fsl_shw_ro_get_reference(robject) \ + (robject)->user_ref + +/*! + * Retrieve the status code from a Result Object. + * + * @param robject The result object to query. + * + * @return The status of the request. + */ +#define fsl_shw_ro_get_status(robject) \ + (robject)->code + +/*! + * Initialize a Secret Key Object. + * + * This function must be called before performing any other operation with + * the Object. + * + * @param skobject The Secret Key Object to be initialized. + * @param skalgorithm DES, AES, etc. + * + */ +#define fsl_shw_sko_init(skobject,skalgorithm) \ +do { \ + (skobject)->algorithm = skalgorithm; \ + (skobject)->flags = 0; \ +} while (0) + +/*! + * Store a cleartext key in the key object. + * + * This has the side effect of setting the #FSL_SKO_KEY_PRESENT flag and + * resetting the #FSL_SKO_KEY_ESTABLISHED flag. + * + * @param skobject A variable of type #fsl_shw_sko_t. + * @param skkey A pointer to the beginning of the key. + * @param skkeylen The length, in octets, of the key. The value should be + * appropriate to the key size supported by the algorithm. + * 64 octets is the absolute maximum value allowed for this + * call. + */ +#define fsl_shw_sko_set_key(skobject, skkey, skkeylen) \ +do { \ + (skobject)->key_length = skkeylen; \ + memcpy((skobject)->key, skkey, skkeylen); \ + (skobject)->flags |= FSL_SKO_KEY_PRESENT; \ + (skobject)->flags &= ~FSL_SKO_KEY_ESTABLISHED; \ +} while (0) + +/*! + * Set a size for the key. + * + * This function would normally be used when the user wants the key to be + * generated from a random source. + * + * @param skobject A variable of type #fsl_shw_sko_t. + * @param skkeylen The length, in octets, of the key. The value should be + * appropriate to the key size supported by the algorithm. + * 64 octets is the absolute maximum value allowed for this + * call. + */ +#define fsl_shw_sko_set_key_length(skobject, skkeylen) \ + (skobject)->key_length = skkeylen; + +/*! + * Set the User ID associated with the key. + * + * @param skobject A variable of type #fsl_shw_sko_t. + * @param skuserid The User ID to identify authorized users of the key. + */ +#define fsl_shw_sko_set_user_id(skobject, skuserid) \ + (skobject)->userid = (skuserid) + +/*! + * Set the establish key handle into a key object. + * + * The @a userid field will be used to validate the access to the unwrapped + * key. This feature is not available for all platforms, nor for all + * algorithms and modes. + * + * The #FSL_SKO_KEY_ESTABLISHED will be set (and the #FSL_SKO_KEY_PRESENT flag + * will be cleared). + * + * @param skobject A variable of type #fsl_shw_sko_t. + * @param skuserid The User ID to verify this user is an authorized user of + * the key. + * @param skhandle A @a handle from #fsl_shw_sko_get_established_info. + */ +#define fsl_shw_sko_set_established_info(skobject, skuserid, skhandle) \ +do { \ + (skobject)->userid = (skuserid); \ + (skobject)->handle = (skhandle); \ + (skobject)->flags |= FSL_SKO_KEY_ESTABLISHED; \ + (skobject)->flags &= \ + ~(FSL_SKO_KEY_PRESENT); \ +} while (0) + +/*! + * Retrieve the established-key handle from a key object. + * + * @param skobject A variable of type #fsl_shw_sko_t. + * @param skhandle The location to store the @a handle of the unwrapped + * key. + */ +#define fsl_shw_sko_get_established_info(skobject, skhandle) \ + *(skhandle) = (skobject)->handle + +/*! + * Extract the algorithm from a key object. + * + * @param skobject The Key Object to be queried. + * @param skalgorithm A pointer to the location to store the algorithm. + */ +#define fsl_shw_sko_get_algorithm(skobject, skalgorithm) \ + *(skalgorithm) = (skobject)->algorithm + +/*! + * Determine the size of a wrapped key based upon the cleartext key's length. + * + * This function can be used to calculate the number of octets that + * #fsl_shw_extract_key() will write into the location at @a covered_key. + * + * If zero is returned at @a length, this means that the key length in + * @a key_info is not supported. + * + * @param wkeyinfo Information about a key to be wrapped. + * @param wkeylen Location to store the length of a wrapped + * version of the key in @a key_info. + */ +#define fsl_shw_sko_calculate_wrapped_size(wkeyinfo, wkeylen) \ +do { \ + if ((wkeyinfo)->key_length > 32) { \ + *(wkeylen) = 0; \ + } else { \ + *(wkeylen) = 66; \ + } \ +} while (0) + +/*! + * Set some flags in the key object. + * + * Turns on the flags specified in @a flags. Other flags are untouched. + * + * @param skobject A variable of type #fsl_shw_sko_t. + * @param skflags (One or more) ORed members of #fsl_shw_key_flags_t which + * are to be set. + */ +#define fsl_shw_sko_set_flags(skobject, skflags) \ + (skobject)->flags |= (skflags) + +/*! + * Clear some flags in the key object. + * + * Turns off the flags specified in @a flags. Other flags are untouched. + * + * @param skobject A variable of type #fsl_shw_sko_t. + * @param skflags (One or more) ORed members of #fsl_shw_key_flags_t + * which are to be reset. + */ +#define fsl_shw_sko_clear_flags(skobject, skflags) \ + (skobject)->flags &= ~(skflags) + +/* REQ-FSL-SHW-PINTFC-API-BASIC-HASH-004 */ +/*! + * Initialize a Hash Context Object. + * + * This function must be called before performing any other operation with the + * Object. It sets the current message length and hash algorithm in the hash + * context object. + * + * @param hcobject The hash context to operate upon. + * @param hcalgorithm The hash algorithm to be used (#FSL_HASH_ALG_MD5, + * #FSL_HASH_ALG_SHA256, etc). + * + */ +#define fsl_shw_hco_init(hcobject, hcalgorithm) \ +do { \ + (hcobject)->algorithm = hcalgorithm; \ + (hcobject)->flags = 0; \ + switch (hcalgorithm) { \ + case FSL_HASH_ALG_MD5: \ + (hcobject)->digest_length = 16; \ + (hcobject)->context_length = 16; \ + (hcobject)->context_register_length = 24; \ + break; \ + case FSL_HASH_ALG_SHA1: \ + (hcobject)->digest_length = 20; \ + (hcobject)->context_length = 20; \ + (hcobject)->context_register_length = 24; \ + break; \ + case FSL_HASH_ALG_SHA224: \ + (hcobject)->digest_length = 28; \ + (hcobject)->context_length = 32; \ + (hcobject)->context_register_length = 36; \ + break; \ + case FSL_HASH_ALG_SHA256: \ + (hcobject)->digest_length = 32; \ + (hcobject)->context_length = 32; \ + (hcobject)->context_register_length = 36; \ + break; \ + default: \ + /* error ! */ \ + (hcobject)->digest_length = 1; \ + (hcobject)->context_length = 1; \ + (hcobject)->context_register_length = 1; \ + break; \ + } \ +} while (0) + +/* REQ-FSL-SHW-PINTFC-API-BASIC-HASH-001 */ +/*! + * Get the current hash value and message length from the hash context object. + * + * The algorithm must have already been specified. See #fsl_shw_hco_init(). + * + * @param hcobject The hash context to query. + * @param hccontext Pointer to the location of @a length octets where to + * store a copy of the current value of the digest. + * @param hcclength Number of octets of hash value to copy. + * @param hcmsglen Pointer to the location to store the number of octets + * already hashed. + */ +#define fsl_shw_hco_get_digest(hcobject, hccontext, hcclength, hcmsglen) \ +do { \ + memcpy(hccontext, (hcobject)->context, hcclength); \ + if ((hcobject)->algorithm == FSL_HASH_ALG_SHA224 \ + || (hcobject)->algorithm == FSL_HASH_ALG_SHA256) { \ + *(hcmsglen) = (hcobject)->context[8]; \ + } else { \ + *(hcmsglen) = (hcobject)->context[5]; \ + } \ +} while (0) + +/* REQ-FSL-SHW-PINTFC-API-BASIC-HASH-002 */ +/*! + * Get the hash algorithm from the hash context object. + * + * @param hcobject The hash context to query. + * @param hcalgorithm Pointer to where the algorithm is to be stored. + */ +#define fsl_shw_hco_get_info(hcobject, hcalgorithm) \ +do { \ + *(hcalgorithm) = (hcobject)->algorithm; \ +} while (0) + +/* REQ-FSL-SHW-PINTFC-API-BASIC-HASH-003 */ +/* REQ-FSL-SHW-PINTFC-API-BASIC-HASH-004 */ +/*! + * Set the current hash value and message length in the hash context object. + * + * The algorithm must have already been specified. See #fsl_shw_hco_init(). + * + * @param hcobject The hash context to operate upon. + * @param hccontext Pointer to buffer of appropriate length to copy into + * the hash context object. + * @param hcmsglen The number of octets of the message which have + * already been hashed. + * + */ +#define fsl_shw_hco_set_digest(hcobject, hccontext, hcmsglen) \ +do { \ + memcpy((hcobject)->context, hccontext, (hcobject)->context_length); \ + if (((hcobject)->algorithm == FSL_HASH_ALG_SHA224) \ + || ((hcobject)->algorithm == FSL_HASH_ALG_SHA256)) { \ + (hcobject)->context[8] = hcmsglen; \ + } else { \ + (hcobject)->context[5] = hcmsglen; \ + } \ +} while (0) + +/*! + * Set flags in a Hash Context Object. + * + * Turns on the flags specified in @a flags. Other flags are untouched. + * + * @param hcobject The hash context to be operated on. + * @param hcflags The flags to be set in the context. These can be ORed + * members of #fsl_shw_hash_ctx_flags_t. + */ +#define fsl_shw_hco_set_flags(hcobject, hcflags) \ + (hcobject)->flags |= (hcflags) + +/*! + * Clear flags in a Hash Context Object. + * + * Turns off the flags specified in @a flags. Other flags are untouched. + * + * @param hcobject The hash context to be operated on. + * @param hcflags The flags to be reset in the context. These can be ORed + * members of #fsl_shw_hash_ctx_flags_t. + */ +#define fsl_shw_hco_clear_flags(hcobject, hcflags) \ + (hcobject)->flags &= ~(hcflags) + +/*! + * Initialize an HMAC Context Object. + * + * This function must be called before performing any other operation with the + * Object. It sets the current message length and hash algorithm in the HMAC + * context object. + * + * @param hcobject The HMAC context to operate upon. + * @param hcalgorithm The hash algorithm to be used (#FSL_HASH_ALG_MD5, + * #FSL_HASH_ALG_SHA256, etc). + * + */ +#define fsl_shw_hmco_init(hcobject, hcalgorithm) \ + fsl_shw_hco_init(hcobject, hcalgorithm) + +/*! + * Set flags in an HMAC Context Object. + * + * Turns on the flags specified in @a flags. Other flags are untouched. + * + * @param hcobject The HMAC context to be operated on. + * @param hcflags The flags to be set in the context. These can be ORed + * members of #fsl_shw_hmac_ctx_flags_t. + */ +#define fsl_shw_hmco_set_flags(hcobject, hcflags) \ + (hcobject)->flags |= (hcflags) + +/*! + * Clear flags in an HMAC Context Object. + * + * Turns off the flags specified in @a flags. Other flags are untouched. + * + * @param hcobject The HMAC context to be operated on. + * @param hcflags The flags to be reset in the context. These can be ORed + * members of #fsl_shw_hmac_ctx_flags_t. + */ +#define fsl_shw_hmco_clear_flags(hcobject, hcflags) \ + (hcobject)->flags &= ~(hcflags) + +/*! + * Initialize a Symmetric Cipher Context Object. + * + * This function must be called before performing any other operation with the + * Object. This will set the @a mode and @a algorithm and initialize the + * Object. + * + * @param scobject The context object to operate on. + * @param scalg The cipher algorithm this context will be used with. + * @param scmode #FSL_SYM_MODE_CBC, #FSL_SYM_MODE_ECB, etc. + * + */ +#define fsl_shw_scco_init(scobject, scalg, scmode) \ +do { \ + register uint32_t bsb; /* block-size bytes */ \ + \ + switch (scalg) { \ + case FSL_KEY_ALG_AES: \ + bsb = 16; \ + break; \ + case FSL_KEY_ALG_DES: \ + /* fall through */ \ + case FSL_KEY_ALG_TDES: \ + bsb = 8; \ + break; \ + case FSL_KEY_ALG_ARC4: \ + bsb = 259; \ + break; \ + case FSL_KEY_ALG_HMAC: \ + bsb = 1; /* meaningless */ \ + break; \ + default: \ + bsb = 00; \ + } \ + (scobject)->block_size_bytes = bsb; \ + (scobject)->mode = scmode; \ + (scobject)->flags = 0; \ +} while (0) + +/*! + * Set the flags for a Symmetric Cipher Context. + * + * Turns on the flags specified in @a flags. Other flags are untouched. + * + * @param scobject The context object to operate on. + * @param scflags The flags to reset (one or more values from + * #fsl_shw_sym_ctx_flags_t ORed together). + * + */ +#define fsl_shw_scco_set_flags(scobject, scflags) \ + (scobject)->flags |= (scflags) + +/*! + * Clear some flags in a Symmetric Cipher Context Object. + * + * Turns off the flags specified in @a flags. Other flags are untouched. + * + * @param scobject The context object to operate on. + * @param scflags The flags to reset (one or more values from + * #fsl_shw_sym_ctx_flags_t ORed together). + * + */ +#define fsl_shw_scco_clear_flags(scobject, scflags) \ + (scobject)->flags &= ~(scflags) + +/*! + * Set the Context (IV) for a Symmetric Cipher Context. + * + * This is to set the context/IV for #FSL_SYM_MODE_CBC mode, or to set the + * context (the S-Box and pointers) for ARC4. The full context size will + * be copied. + * + * @param scobject The context object to operate on. + * @param sccontext A pointer to the buffer which contains the context. + * + */ +#define fsl_shw_scco_set_context(scobject, sccontext) \ + memcpy((scobject)->context, sccontext, \ + (scobject)->block_size_bytes) + +/*! + * Get the Context for a Symmetric Cipher Context. + * + * This is to retrieve the context/IV for #FSL_SYM_MODE_CBC mode, or to + * retrieve context (the S-Box and pointers) for ARC4. The full context + * will be copied. + * + * @param scobject The context object to operate on. + * @param sccontext Pointer to location where context will be stored. + */ +#define fsl_shw_scco_get_context(scobject, sccontext) \ + memcpy(sccontext, (scobject)->context, (scobject)->block_size_bytes) + +/*! + * Set the Counter Value for a Symmetric Cipher Context. + * + * This will set the Counter Value for CTR mode. + * + * @param scobject The context object to operate on. + * @param sccounter The starting counter value. The number of octets. + * copied will be the block size for the algorithm. + * @param scmodulus The modulus for controlling the incrementing of the + * counter. + * + */ +#define fsl_shw_scco_set_counter_info(scobject, sccounter, scmodulus) \ +do { \ + if ((sccounter) != NULL) { \ + memcpy((scobject)->context, sccounter, \ + (scobject)->block_size_bytes); \ + } \ + (scobject)->modulus_exp = scmodulus; \ +} while (0) + +/*! + * Get the Counter Value for a Symmetric Cipher Context. + * + * This will retrieve the Counter Value is for CTR mode. + * + * @param scobject The context object to query. + * @param sccounter Pointer to location to store the current counter + * value. The number of octets copied will be the + * block size for the algorithm. + * @param scmodulus Pointer to location to store the modulus. + * + */ +#define fsl_shw_scco_get_counter_info(scobject, sccounter, scmodulus) \ +do { \ + if ((sccounter) != NULL) { \ + memcpy(sccounter, (scobject)->context, \ + (scobject)->block_size_bytes); \ + } \ + if ((scmodulus) != NULL) { \ + *(scmodulus) = (scobject)->modulus_exp; \ + } \ +} while (0) + +/*! + * Initialize a Authentication-Cipher Context. + * + * @param acobject Pointer to object to operate on. + * @param acmode The mode for this object (only #FSL_ACC_MODE_CCM + * supported). + */ +#define fsl_shw_acco_init(acobject, acmode) \ +do { \ + (acobject)->flags = 0; \ + (acobject)->mode = (acmode); \ +} while (0) + +/*! + * Set the flags for a Authentication-Cipher Context. + * + * Turns on the flags specified in @a flags. Other flags are untouched. + * + * @param acobject Pointer to object to operate on. + * @param acflags The flags to set (one or more from + * #fsl_shw_auth_ctx_flags_t ORed together). + * + */ +#define fsl_shw_acco_set_flags(acobject, acflags) \ + (acobject)->flags |= (acflags) + +/*! + * Clear some flags in a Authentication-Cipher Context Object. + * + * Turns off the flags specified in @a flags. Other flags are untouched. + * + * @param acobject Pointer to object to operate on. + * @param acflags The flags to reset (one or more from + * #fsl_shw_auth_ctx_flags_t ORed together). + * + */ +#define fsl_shw_acco_clear_flags(acobject, acflags) \ + (acobject)->flags &= ~(acflags) + +/*! + * Set up the Authentication-Cipher Object for CCM mode. + * + * This will set the @a auth_object for CCM mode and save the @a ctr, + * and @a mac_length. This function can be called instead of + * #fsl_shw_acco_init(). + * + * The paramater @a ctr is Counter Block 0, (counter value 0), which is for the + * MAC. + * + * @param acobject Pointer to object to operate on. + * @param acalg Cipher algorithm. Only AES is supported. + * @param accounter The initial counter value. + * @param acmaclen The number of octets used for the MAC. Valid values are + * 4, 6, 8, 10, 12, 14, and 16. + */ +/* Do we need to stash the +1 value of the CTR somewhere? */ +#define fsl_shw_acco_set_ccm(acobject, acalg, accounter, acmaclen) \ + do { \ + (acobject)->flags = 0; \ + (acobject)->mode = FSL_ACC_MODE_CCM; \ + (acobject)->auth_info.CCM_ctx_info.block_size_bytes = 16; \ + (acobject)->cipher_ctx_info.block_size_bytes = 16; \ + (acobject)->mac_length = acmaclen; \ + fsl_shw_scco_set_counter_info(&(acobject)->cipher_ctx_info, accounter, \ + FSL_CTR_MOD_128); \ +} while (0) + +/*! + * Format the First Block (IV) & Initial Counter Value per NIST CCM. + * + * This function will also set the IV and CTR values per Appendix A of NIST + * Special Publication 800-38C (May 2004). It will also perform the + * #fsl_shw_acco_set_ccm() operation with information derived from this set of + * parameters. + * + * Note this function assumes the algorithm is AES. It initializes the + * @a auth_object by setting the mode to #FSL_ACC_MODE_CCM and setting the + * flags to be #FSL_ACCO_NIST_CCM. + * + * @param acobject Pointer to object to operate on. + * @param act The number of octets used for the MAC. Valid values are + * 4, 6, 8, 10, 12, 14, and 16. + * @param acad Number of octets of Associated Data (may be zero). + * @param acq A value for the size of the length of @a q field. Valid + * values are 1-8. + * @param acN The Nonce (packet number or other changing value). Must + * be (15 - @a q_length) octets long. + * @param acQ The value of Q (size of the payload in octets). + * + */ +#define fsl_shw_ccm_nist_format_ctr_and_iv(acobject, act, acad, acq, acN, acQ)\ + do { \ + uint64_t Q = acQ; \ + uint8_t bflag = ((acad)?0x40:0) | ((((act)-2)/2)<<3) | ((acq)-1); \ + unsigned i; \ + uint8_t* qptr = (acobject)->auth_info.CCM_ctx_info.context + 15; \ + (acobject)->auth_info.CCM_ctx_info.block_size_bytes = 16; \ + (acobject)->cipher_ctx_info.block_size_bytes = 16; \ + (acobject)->mode = FSL_ACC_MODE_CCM; \ + (acobject)->flags = FSL_ACCO_NIST_CCM; \ + \ + /* Store away the MAC length (after calculating actual value */ \ + (acobject)->mac_length = (act); \ + /* Set Flag field in Block 0 */ \ + *((acobject)->auth_info.CCM_ctx_info.context) = bflag; \ + /* Set Nonce field in Block 0 */ \ + memcpy((acobject)->auth_info.CCM_ctx_info.context+1, acN, \ + 15-(acq)); \ + /* Set Flag field in ctr */ \ + *((acobject)->cipher_ctx_info.context) = (acq)-1; \ + /* Update the Q (payload length) field of Block0 */ \ + (acobject)->q_length = acq; \ + for (i = 0; i < (acq); i++) { \ + *qptr-- = Q & 0xFF; \ + Q >>= 8; \ + } \ + /* Set the Nonce field of the ctr */ \ + memcpy((acobject)->cipher_ctx_info.context+1, acN, 15-(acq)); \ + /* Clear the block counter field of the ctr */ \ + memset((acobject)->cipher_ctx_info.context+16-(acq), 0, (acq)+1); \ + } while (0) + +/*! + * Update the First Block (IV) & Initial Counter Value per NIST CCM. + * + * This function will set the IV and CTR values per Appendix A of NIST Special + * Publication 800-38C (May 2004). + * + * Note this function assumes that #fsl_shw_ccm_nist_format_ctr_and_iv() has + * previously been called on the @a auth_object. + * + * @param acobject Pointer to object to operate on. + * @param acN The Nonce (packet number or other changing value). Must + * be (15 - @a q_length) octets long. + * @param acQ The value of Q (size of the payload in octets). + * + */ +/* Do we need to stash the +1 value of the CTR somewhere? */ +#define fsl_shw_ccm_nist_update_ctr_and_iv(acobject, acN, acQ) \ + do { \ + uint64_t Q = acQ; \ + unsigned i; \ + uint8_t* qptr = (acobject)->auth_info.CCM_ctx_info.context + 15; \ + \ + /* Update the Nonce field field of Block0 */ \ + memcpy((acobject)->auth_info.CCM_ctx_info.context+1, acN, \ + 15 - (acobject)->q_length); \ + /* Update the Q (payload length) field of Block0 */ \ + for (i = 0; i < (acobject)->q_length; i++) { \ + *qptr-- = Q & 0xFF; \ + Q >>= 8; \ + } \ + /* Update the Nonce field of the ctr */ \ + memcpy((acobject)->cipher_ctx_info.context+1, acN, \ + 15 - (acobject)->q_length); \ + } while (0) + +/*!**************************************************************************** + * Library functions + *****************************************************************************/ +/* REQ-FSL-SHW-PINTFC-COA-UCO */ +/* REQ-FSLSHW-PINTFC-API-GEN-003 */ +/*! + * Determine the hardware security capabilities of this platform. + * + * Though a user context object is passed into this function, it will always + * act in a non-blocking manner. + * + * @param user_ctx The user context which will be used for the query. + * + * @return A pointer to the capabilities object. + */ +extern fsl_shw_pco_t *fsl_shw_get_capabilities(fsl_shw_uco_t * user_ctx); + +/* REQ-FSL-SHW-PINTFC-COA-UCO */ +/* REQ-FSLSHW-PINTFC-API-GEN-004 */ +/*! + * Create an association between the the user and the provider of the API. + * + * @param user_ctx The user context which will be used for this association. + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_register_user(fsl_shw_uco_t * user_ctx); + +/* REQ-FSL-SHW-PINTFC-COA-UCO */ +/* REQ-FSLSHW-PINTFC-API-GEN-006 */ +/*! + * Destroy the association between the the user and the provider of the API. + * + * @param user_ctx The user context which is no longer needed. + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_deregister_user(fsl_shw_uco_t * user_ctx); + +/*! + * Retrieve results from earlier operations. + * + * @param user_ctx The user's context. + * @param result_size The number of array elements of @a results. + * @param results Pointer to first of the (array of) locations to + * store results. + * @param result_count Pointer to store the number of results which + * were returned. + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_get_results(fsl_shw_uco_t * user_ctx, + unsigned result_size, + fsl_shw_result_t results[], + unsigned *result_count); + +/*! + * Place a key into a protected location for use only by cryptographic + * algorithms. + * + * This only needs to be used to a) unwrap a key, or b) set up a key which + * could be wrapped with a later call to #fsl_shw_extract_key(). Normal + * cleartext keys can simply be placed into #fsl_shw_sko_t key objects with + * #fsl_shw_sko_set_key() and used directly. + * + * The maximum key size supported for wrapped/unwrapped keys is 32 octets. + * (This is the maximum reasonable key length on Sahara - 32 octets for an HMAC + * key based on SHA-256.) The key size is determined by the @a key_info. The + * expected length of @a key can be determined by + * #fsl_shw_sko_calculate_wrapped_size() + * + * The protected key will not be available for use until this operation + * successfully completes. + * + * This feature is not available for all platforms, nor for all algorithms and + * modes. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param key_info The information about the key to be which will + * be established. In the create case, the key + * length must be set. + * @param establish_type How @a key will be interpreted to establish a + * key for use. + * @param key If @a establish_type is #FSL_KEY_WRAP_UNWRAP, + * this is the location of a wrapped key. If + * @a establish_type is #FSL_KEY_WRAP_CREATE, this + * parameter can be @a NULL. If @a establish_type + * is #FSL_KEY_WRAP_ACCEPT, this is the location + * of a plaintext key. + */ +extern fsl_shw_return_t fsl_shw_establish_key(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + fsl_shw_key_wrap_t establish_type, + const uint8_t * key); + +/*! + * Wrap a key and retrieve the wrapped value. + * + * A wrapped key is a key that has been cryptographically obscured. It is + * only able to be used with #fsl_shw_establish_key(). + * + * This function will also release the key (see #fsl_shw_release_key()) so + * that it must be re-established before reuse. + * + * This feature is not available for all platforms, nor for all algorithms and + * modes. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param key_info The information about the key to be deleted. + * @param covered_key The location to store the wrapped key. + * (This size is based upon the maximum key size + * of 32 octets). + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_extract_key(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + uint8_t * covered_key); + +/*! + * De-establish a key so that it can no longer be accessed. + * + * The key will need to be re-established before it can again be used. + * + * This feature is not available for all platforms, nor for all algorithms and + * modes. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param key_info The information about the key to be deleted. + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_release_key(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info); + +/* REQ-FSL-SHW-PINTFC-COA-UCO */ +/* REQ-FSL-SHW-PINTFC-COA-SKO */ +/* REQ-FSL-SHW-PINTFC-COA-SCCO */ +/* REQ-FSLSHW-PINTFC-API-BASIC-SYM-001 */ +/* PINTFC-API-BASIC-SYM-ARC4-001 */ +/* PINTFC-API-BASIC-SYM-ARC4-002 */ +/*! + * Encrypt a stream of data with a symmetric-key algorithm. + * + * In ARC4, and also in #FSL_SYM_MODE_CBC and #FSL_SYM_MODE_CTR modes, the + * flags of the @a sym_ctx object will control part of the operation of this + * function. The #FSL_SYM_CTX_INIT flag means that there is no context info in + * the object. The #FSL_SYM_CTX_LOAD means to use information in the + * @a sym_ctx at the start of the operation, and the #FSL_SYM_CTX_SAVE flag + * means to update the object's context information after the operation has + * been performed. + * + * All of the data for an operation can be run through at once using the + * #FSL_SYM_CTX_INIT or #FSL_SYM_CTX_LOAD flags, as appropriate, and then using + * a @a length for the whole of the data. + * + * If a #FSL_SYM_CTX_SAVE flag were added, an additional call to the function + * would "pick up" where the previous call left off, allowing the user to + * perform the larger function in smaller steps. + * + * In #FSL_SYM_MODE_CBC and #FSL_SYM_MODE_ECB modes, the @a length must always + * be a multiple of the block size for the algorithm being used. For proper + * operation in #FSL_SYM_MODE_CTR mode, the @a length must be a multiple of the + * block size until the last operation on the total octet stream. + * + * Some users of ARC4 may want to compute the context (S-Box and pointers) from + * the key before any data is available. This may be done by running this + * function with a @a length of zero, with the init & save flags flags on in + * the @a sym_ctx. Subsequent operations would then run as normal with the + * load and save flags. Note that they key object is still required. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param key_info Key and algorithm being used for this operation. + * @param sym_ctx Info on cipher mode, state of the cipher. + * @param length Length, in octets, of the pt (and ct). + * @param pt pointer to plaintext to be encrypted. + * @param ct pointer to where to store the resulting ciphertext. + * + * @return A return code of type #fsl_shw_return_t. + * + */ +extern fsl_shw_return_t fsl_shw_symmetric_encrypt(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + fsl_shw_scco_t * sym_ctx, + uint32_t length, + const uint8_t * pt, + uint8_t * ct); + +/* REQ-FSL-SHW-PINTFC-COA-UCO */ +/* REQ-FSL-SHW-PINTFC-COA-SKO */ +/* REQ-FSL-SHW-PINTFC-COA-SCCO */ +/* PINTFC-API-BASIC-SYM-002 */ +/* PINTFC-API-BASIC-SYM-ARC4-001 */ +/* PINTFC-API-BASIC-SYM-ARC4-002 */ +/*! + * Decrypt a stream of data with a symmetric-key algorithm. + * + * In ARC4, and also in #FSL_SYM_MODE_CBC and #FSL_SYM_MODE_CTR modes, the + * flags of the @a sym_ctx object will control part of the operation of this + * function. The #FSL_SYM_CTX_INIT flag means that there is no context info in + * the object. The #FSL_SYM_CTX_LOAD means to use information in the + * @a sym_ctx at the start of the operation, and the #FSL_SYM_CTX_SAVE flag + * means to update the object's context information after the operation has + * been performed. + * + * All of the data for an operation can be run through at once using the + * #FSL_SYM_CTX_INIT or #FSL_SYM_CTX_LOAD flags, as appropriate, and then using + * a @a length for the whole of the data. + * + * If a #FSL_SYM_CTX_SAVE flag were added, an additional call to the function + * would "pick up" where the previous call left off, allowing the user to + * perform the larger function in smaller steps. + * + * In #FSL_SYM_MODE_CBC and #FSL_SYM_MODE_ECB modes, the @a length must always + * be a multiple of the block size for the algorithm being used. For proper + * operation in #FSL_SYM_MODE_CTR mode, the @a length must be a multiple of the + * block size until the last operation on the total octet stream. + * + * Some users of ARC4 may want to compute the context (S-Box and pointers) from + * the key before any data is available. This may be done by running this + * function with a @a length of zero, with the #FSL_SYM_CTX_INIT & + * #FSL_SYM_CTX_SAVE flags on in the @a sym_ctx. Subsequent operations would + * then run as normal with the load & save flags. Note that they key object is + * still required. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param key_info The key and algorithm being used in this operation. + * @param sym_ctx Info on cipher mode, state of the cipher. + * @param length Length, in octets, of the ct (and pt). + * @param ct pointer to ciphertext to be decrypted. + * @param pt pointer to where to store the resulting plaintext. + * + * @return A return code of type #fsl_shw_return_t + * + */ +extern fsl_shw_return_t fsl_shw_symmetric_decrypt(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + fsl_shw_scco_t * sym_ctx, + uint32_t length, + const uint8_t * ct, + uint8_t * pt); + +/* REQ-FSL-SHW-PINTFC-COA-UCO */ +/* REQ-FSL-SHW-PINTFC-COA-HCO */ +/* REQ-FSLSHW-PINTFC-API-BASIC-HASH-005 */ +/*! + * Hash a stream of data with a cryptographic hash algorithm. + * + * The flags in the @a hash_ctx control the operation of this function. + * + * Hashing functions work on 64 octets of message at a time. Therefore, when + * any partial hashing of a long message is performed, the message @a length of + * each segment must be a multiple of 64. When ready to + * #FSL_HASH_FLAGS_FINALIZE the hash, the @a length may be any value. + * + * With the #FSL_HASH_FLAGS_INIT and #FSL_HASH_FLAGS_FINALIZE flags on, a + * one-shot complete hash, including padding, will be performed. The @a length + * may be any value. + * + * The first octets of a data stream can be hashed by setting the + * #FSL_HASH_FLAGS_INIT and #FSL_HASH_FLAGS_SAVE flags. The @a length must be + * a multiple of 64. + * + * The flag #FSL_HASH_FLAGS_LOAD is used to load a context previously saved by + * #FSL_HASH_FLAGS_SAVE. The two in combination will allow a (multiple-of-64 + * octets) 'middle sequence' of the data stream to be hashed with the + * beginning. The @a length must again be a multiple of 64. + * + * Since the flag #FSL_HASH_FLAGS_LOAD is used to load a context previously + * saved by #FSL_HASH_FLAGS_SAVE, the #FSL_HASH_FLAGS_LOAD and + * #FSL_HASH_FLAGS_FINALIZE flags, used together, can be used to finish the + * stream. The @a length may be any value. + * + * If the user program wants to do the padding for the hash, it can leave off + * the #FSL_HASH_FLAGS_FINALIZE flag. The @a length must then be a multiple of + * 64 octets. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param hash_ctx Hashing algorithm and state of the cipher. + * @param msg Pointer to the data to be hashed. + * @param length Length, in octets, of the @a msg. + * @param result If not null, pointer to where to store the hash + * digest. + * @param result_len Number of octets to store in @a result. + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_hash(fsl_shw_uco_t * user_ctx, + fsl_shw_hco_t * hash_ctx, + const uint8_t * msg, + uint32_t length, + uint8_t * result, uint32_t result_len); + +/* REQ-FSL-SHW-PINTFC-COA-UCO */ +/* REQ-FSL-SHW-PINTFC-API-BASIC-HMAC-001 */ +/*! + * Precompute the Key hashes for an HMAC operation. + * + * This function may be used to calculate the inner and outer precomputes, + * which are the hash contexts resulting from hashing the XORed key for the + * 'inner hash' and the 'outer hash', respectively, of the HMAC function. + * + * After execution of this function, the @a hmac_ctx will contain the + * precomputed inner and outer contexts, so that they may be used by + * #fsl_shw_hmac(). The flags of @a hmac_ctx will be updated with + * #FSL_HMAC_FLAGS_PRECOMPUTES_PRESENT to mark their presence. In addtion, the + * #FSL_HMAC_FLAGS_INIT flag will be set. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param key_info The key being used in this operation. Key must be + * 1 to 64 octets long. + * @param hmac_ctx The context which controls, by its flags and + * algorithm, the operation of this function. + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_hmac_precompute(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + fsl_shw_hmco_t * hmac_ctx); + +/* REQ-FSL-SHW-PINTFC-COA-UCO */ +/* REQ-FSLSHW-PINTFC-API-BASIC-HMAC-002 */ +/*! + * Continue, finalize, or one-shot an HMAC operation. + * + * There are a number of ways to use this function. The flags in the + * @a hmac_ctx object will determine what operations occur. + * + * If #FSL_HMAC_FLAGS_INIT is set, then the hash will be started either from + * the @a key_info, or from the precomputed inner hash value in the + * @a hmac_ctx, depending on the value of #FSL_HMAC_FLAGS_PRECOMPUTES_PRESENT. + * + * If, instead, #FSL_HMAC_FLAGS_LOAD is set, then the hash will be continued + * from the ongoing inner hash computation in the @a hmac_ctx. + * + * If #FSL_HMAC_FLAGS_FINALIZE are set, then the @a msg will be padded, hashed, + * the outer hash will be performed, and the @a result will be generated. + * + * If the #FSL_HMAC_FLAGS_SAVE flag is set, then the (ongoing or final) digest + * value will be stored in the ongoing inner hash computation field of the @a + * hmac_ctx. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param key_info If #FSL_HMAC_FLAGS_INIT is set in the @a hmac_ctx, + * this is the key being used in this operation, and the + * IPAD. If #FSL_HMAC_FLAGS_INIT is set in the @a + * hmac_ctx and @a key_info is NULL, then + * #fsl_shw_hmac_precompute() has been used to populate + * the @a inner_precompute and @a outer_precompute + * contexts. If #FSL_HMAC_FLAGS_INIT is not set, this + * parameter is ignored. + + * @param hmac_ctx The context which controls, by its flags and + * algorithm, the operation of this function. + * @param msg Pointer to the message to be hashed. + * @param length Length, in octets, of the @a msg. + * @param result Pointer, of @a result_len octets, to where to + * store the HMAC. + * @param result_len Length of @a result buffer. + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_hmac(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + fsl_shw_hmco_t * hmac_ctx, + const uint8_t * msg, + uint32_t length, + uint8_t * result, uint32_t result_len); + +/* REQ-FSL-SHW-PINTFC-COA-UCO */ +/* REQ-FSLSHW-PINTFC-API-BASIC-RNG-002 */ +/*! + * Get random data. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param length The number of octets of @a data being requested. + * @param data A pointer to a location of @a length octets to where + * random data will be returned. + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_get_random(fsl_shw_uco_t * user_ctx, + uint32_t length, uint8_t * data); + +/* REQ-FSL-SHW-PINTFC-COA-UCO */ +/* REQ-FSLSHW-PINTFC-API-BASIC-RNG-003 */ +/*! + * Add entropy to random number generator. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param length Number of bytes at @a data. + * @param data Entropy to add to random number generator. + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_add_entropy(fsl_shw_uco_t * user_ctx, + uint32_t length, uint8_t * data); + +/* REQ-FSL-SHW-PINTFC-COA-UCO */ +/* REQ-FSL-SHW-PINTFC-COA-SKO */ +/*! + * Perform Generation-Encryption by doing a Cipher and a Hash. + * + * Generate the authentication value @a auth_value as well as encrypt the @a + * payload into @a ct (the ciphertext). This is a one-shot function, so all of + * the @a auth_data and the total message @a payload must passed in one call. + * This also means that the flags in the @a auth_ctx must be #FSL_ACCO_CTX_INIT + * and #FSL_ACCO_CTX_FINALIZE. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param auth_ctx Controlling object for Authenticate-decrypt. + * @param cipher_key_info The key being used for the cipher part of this + * operation. In CCM mode, this key is used for + * both parts. + * @param auth_key_info The key being used for the authentication part + * of this operation. In CCM mode, this key is + * ignored and may be NULL. + * @param auth_data_length Length, in octets, of @a auth_data. + * @param auth_data Data to be authenticated but not encrypted. + * @param payload_length Length, in octets, of @a payload. + * @param payload Pointer to the plaintext to be encrypted. + * @param ct Pointer to the where the encrypted @a payload + * will be stored. Must be @a payload_length + * octets long. + * @param auth_value Pointer to where the generated authentication + * field will be stored. Must be as many octets as + * indicated by MAC length in the @a function_ctx. + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_gen_encrypt(fsl_shw_uco_t * user_ctx, + fsl_shw_acco_t * auth_ctx, + fsl_shw_sko_t * cipher_key_info, + fsl_shw_sko_t * auth_key_info, + uint32_t auth_data_length, + const uint8_t * auth_data, + uint32_t payload_length, + const uint8_t * payload, + uint8_t * ct, uint8_t * auth_value); + +/* REQ-FSL-SHW-PINTFC-COA-UCO */ +/* REQ-FSL-SHW-PINTFC-COA-SKO */ +/*! + * Perform Authentication-Decryption in Cipher + Hash. + * + * This function will perform a one-shot decryption of a data stream as well as + * authenticate the authentication value. This is a one-shot function, so all + * of the @a auth_data and the total message @a payload must passed in one + * call. This also means that the flags in the @a auth_ctx must be + * #FSL_ACCO_CTX_INIT and #FSL_ACCO_CTX_FINALIZE. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param auth_ctx Controlling object for Authenticate-decrypt. + * @param cipher_key_info The key being used for the cipher part of this + * operation. In CCM mode, this key is used for + * both parts. + * @param auth_key_info The key being used for the authentication part + * of this operation. In CCM mode, this key is + * ignored and may be NULL. + * @param auth_data_length Length, in octets, of @a auth_data. + * @param auth_data Data to be authenticated but not decrypted. + * @param payload_length Length, in octets, of @a ct and @a pt. + * @param ct Pointer to the encrypted input stream. + * @param auth_value The (encrypted) authentication value which will + * be authenticated. This is the same data as the + * (output) @a auth_value argument to + * #fsl_shw_gen_encrypt(). + * @param payload Pointer to where the plaintext resulting from + * the decryption will be stored. + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_auth_decrypt(fsl_shw_uco_t * user_ctx, + fsl_shw_acco_t * auth_ctx, + fsl_shw_sko_t * cipher_key_info, + fsl_shw_sko_t * auth_key_info, + uint32_t auth_data_length, + const uint8_t * auth_data, + uint32_t payload_length, + const uint8_t * ct, + const uint8_t * auth_value, + uint8_t * payload); + +/*!*************************************************************************** + * + * Functions available to other SHW-family drivers. + * +*****************************************************************************/ + +#ifdef __KERNEL__ +/*! + * Add an entry to a work/result queue. + * + * @param pool Pointer to list structure + * @param entry Entry to place at tail of list + * + * @return void + */ +inline static void SHW_ADD_QUEUE_ENTRY(shw_queue_t * pool, + shw_queue_entry_t * entry) +{ + os_lock_context_t lock_context; + + entry->next = NULL; + os_lock_save_context(shw_queue_lock, lock_context); + + if (pool->tail != NULL) { + pool->tail->next = entry; + } else { + /* Queue was empty, so this is also the head. */ + pool->head = entry; + } + pool->tail = entry; + + os_unlock_restore_context(shw_queue_lock, lock_context); + + return; + +} + +/*! + * Get first entry on the queue and remove it from the queue. + * + * @return Pointer to first entry, or NULL if none. + */ +inline static shw_queue_entry_t *SHW_POP_FIRST_ENTRY(shw_queue_t * queue) +{ + shw_queue_entry_t *entry; + os_lock_context_t lock_context; + + os_lock_save_context(shw_queue_lock, lock_context); + + entry = queue->head; + + if (entry != NULL) { + queue->head = entry->next; + entry->next = NULL; + /* If this was only entry, clear the tail. */ + if (queue->tail == entry) { + queue->tail = NULL; + } + } + + os_unlock_restore_context(shw_queue_lock, lock_context); + + return entry; +} + +/*! + * Remove an entry from the list. + * + * If the entry not on the queue, no error will be returned. + * + * @param pool Pointer to work queue + * @param entry Entry to remove from queue + * + * @return void + * + */ +inline static void SHW_QUEUE_REMOVE_ENTRY(shw_queue_t * pool, + shw_queue_entry_t * entry) +{ + os_lock_context_t lock_context; + + os_lock_save_context(shw_queue_lock, lock_context); + + /* Check for quick case. */ + if (pool->head == entry) { + pool->head = entry->next; + entry->next = NULL; + if (pool->tail == entry) { + pool->tail = NULL; + } + } else { + register shw_queue_entry_t *prev = pool->head; + + /* We know it is not the head, so start looking at entry after head. */ + while (prev->next) { + if (prev->next != entry) { + prev = prev->next; /* Try another */ + continue; + } else { + /* Unlink from chain. */ + prev->next = entry->next; + entry->next = NULL; + /* If last in chain, update tail. */ + if (pool->tail == entry) { + pool->tail = prev; + } + break; + } + } /* while */ + } + + os_unlock_restore_context(shw_queue_lock, lock_context); + + return; +} +#endif /* __KERNEL__ */ + +/*!*************************************************************************** + * + * Functions available to User-Mode API functions + * + ****************************************************************************/ +#ifndef __KERNEL__ + + /*! + * Sanity checks the user context object fields to ensure that they make some + * sense before passing the uco as a parameter. + * + * @brief Verify the user context object + * + * @param uco user context object + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t validate_uco(fsl_shw_uco_t * uco); + +/*! + * Initialize a request block to go to the driver. + * + * @param hdr Pointer to request block header + * @param user_ctx Pointer to user's context + * + * @return void + */ +inline static void init_req(struct shw_req_header *hdr, + fsl_shw_uco_t * user_ctx) +{ + hdr->flags = user_ctx->flags; + hdr->user_ref = user_ctx->user_ref; + hdr->code = FSL_RETURN_ERROR_S; + + return; +} + +/*! + * Send a request block off to the driver. + * + * If this is a non-blocking request, then req will be freed. + * + * @param type The type of request being sent + * @param req Pointer to the request block + * @param ctx Pointer to user's context + * + * @return code from driver if ioctl() succeeded, otherwise + * FSL_RETURN_INTERNAL_ERROR_S. + */ +inline static fsl_shw_return_t send_req(shw_user_request_t type, + struct shw_req_header *req, + fsl_shw_uco_t * ctx) +{ + fsl_shw_return_t ret = FSL_RETURN_INTERNAL_ERROR_S; + unsigned blocking = ctx->flags & FSL_UCO_BLOCKING_MODE; + int code; + + code = ioctl(ctx->openfd, SHW_IOCTL_REQUEST + type, req); + + if (code == 0) { + if (blocking) { + ret = req->code; + } else { + ret = FSL_RETURN_OK_S; + } + } else { +#ifdef FSL_DEBUG + fprintf(stderr, "SHW: send_req failed with (%d), %s\n", code, + strerror(code)); +#endif + } + + if (blocking) { + free(req); + } + + return ret; +} + +#endif /* no __KERNEL__ */ + +#endif /* SHW_DRIVER_H */ diff --git a/drivers/mxc/security/rng/include/shw_internals.h b/drivers/mxc/security/rng/include/shw_internals.h new file mode 100644 index 000000000000..630ceb1ee377 --- /dev/null +++ b/drivers/mxc/security/rng/include/shw_internals.h @@ -0,0 +1,158 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#ifndef SHW_INTERNALS_H +#define SHW_INTERNALS_H + +/*! @file shw_internals.h + * + * This file contains definitions which are internal to the SHW driver. + * + * This header file should only ever be included by shw_driver.c + * + * Compile-time flags minimally needed: + * + * @li Some sort of platform flag. + * + * @ingroup RNG + */ + +#include "portable_os.h" +#include "shw_driver.h" + +#include <asm/arch/mxc_scc_driver.h> + +/*! @defgroup shwcompileflags SHW Compile Flags + * + * These are flags which are used to configure the SHW driver at compilation + * time. + * + * The terms 'defined' and 'undefined' refer to whether a @c \#define (or -D on + * a compile command) has defined a given preprocessor symbol. If a given + * symbol is defined, then @c \#ifdef \<symbol\> will succeed. Some symbols + * described below default to not having a definition, i.e. they are undefined. + * + */ + +/*! @addtogroup shwcompileflags */ +/*! @{ */ +#ifndef SHW_MAJOR_NODE +/*! + * This should be configured in a Makefile/compile command line. It is the + * value the driver will use to register itself as a device driver for a + * /dev/node file. Zero means allow (Linux) to assign a value. Any positive + * number will be attempted as the registration value, to allow for + * coordination with the creation/existence of a /dev/fsl_shw (for instance) + * file in the filesystem. + */ +#define SHW_MAJOR_NODE 0 +#endif + +/* Temporarily define compile-time flags to make Doxygen happy and allow them + to get into the documentation. */ +#ifdef DOXYGEN_HACK + +/*! + * Turn on compilation of run-time operational, debug, and error messages. + * + * This flag is undefined by default. + */ +/* REQ-FSLSHW-DEBUG-001 */ + +/*! @} */ +#endif /* end DOXYGEN_HACK */ + +#ifndef SHW_DRIVER_NAME +/*! @addtogroup shwcompileflags */ +/*! @{ */ +/*! Name the driver will use to register itself to the kernel as the driver for + * the #shw_major_node and interrupt handling. */ +#define SHW_DRIVER_NAME "fsl_shw" +/*! @} */ +#endif +#ifdef __KERNEL__ +static fsl_shw_uco_t *user_list; +#endif +/*! + * Add a user context onto the list of registered users. + * + * Place it at the head of the #user_list queue. + * + * @param ctx A pointer to a user context + * + * @return void + */ +inline static void SHW_ADD_USER(fsl_shw_uco_t * ctx) +{ + os_lock_context_t lock_context; + + os_lock_save_context(shw_queue_lock, lock_context); + ctx->next = user_list; + user_list = ctx; + os_unlock_restore_context(shw_queue_lock, lock_context); + +} + +/*! + * Remove a user context from the list of registered users. + * + * @param ctx A pointer to a user context + * + * @return void + * + */ +inline static void SHW_REMOVE_USER(fsl_shw_uco_t * ctx) +{ + fsl_shw_uco_t *prev_ctx = user_list; + os_lock_context_t lock_context; + + os_lock_save_context(shw_queue_lock, lock_context); + + if (prev_ctx == ctx) { + /* Found at head, so just set new head */ + user_list = ctx->next; + } else { + for (; (prev_ctx != NULL); prev_ctx = prev_ctx->next) { + if (prev_ctx->next == ctx) { + prev_ctx->next = ctx->next; + break; + } + } + } + os_unlock_restore_context(shw_queue_lock, lock_context); +} + +static void shw_user_callback(fsl_shw_uco_t * uco); + +/* internal functions */ +static os_error_code shw_setup_user_driver_interaction(void); +static void shw_cleanup(void); + +static os_error_code init_uco(fsl_shw_uco_t * user_ctx, void *user_mode_uco); +static os_error_code get_capabilities(fsl_shw_uco_t * user_ctx, + void *user_mode_pco_request); +static os_error_code get_results(fsl_shw_uco_t * user_ctx, + void *user_mode_result_req); +static os_error_code get_random(fsl_shw_uco_t * user_ctx, + void *user_mode_get_random_req); +static os_error_code add_entropy(fsl_shw_uco_t * user_ctx, + void *user_mode_add_entropy_req); + +#if defined(LINUX_VERSION_CODE) + +MODULE_AUTHOR("Freescale Semiconductor"); +MODULE_DESCRIPTION("Device Driver for FSL SHW API"); + +#endif /* LINUX_VERSION_CODE */ + +#endif /* SHW_INTERNALS_H */ diff --git a/drivers/mxc/security/rng/rng_driver.c b/drivers/mxc/security/rng/rng_driver.c new file mode 100644 index 000000000000..55fe33ea5165 --- /dev/null +++ b/drivers/mxc/security/rng/rng_driver.c @@ -0,0 +1,1067 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! @file rng_driver.c + * + * This is the driver code for the hardware Random Number Generator (RNG). + * + * It provides the following functions to callers: + * fsl_shw_return_t fsl_shw_get_random(fsl_shw_uco_t* user_ctx, + * uint32_t length, + * uint8_t* data); + * + * fsl_shw_return_t fsl_shw_add_entropy(fsl_shw_uco_t* user_ctx, + * uint32_t length, + * uint8_t* data); + * + * The life of the driver starts at boot (or module load) time, with a call by + * the kernel to #rng_init(). As part of initialization, a background task + * running #rng_entropy_task() will be created. + * + * The life of the driver ends when the kernel is shutting down (or the driver + * is being unloaded). At this time, #rng_shutdown() is called. No function + * will ever be called after that point. In the case that the driver is + * reloaded, a new copy of the driver, with fresh global values, etc., is + * loaded, and there will be a new call to #rng_init(). + * + * A call to fsl_shw_get_random() gets converted into a work entry which is + * queued and handed off to a background task for fulfilment. This provides + * for a single thread of control for reading the RNG's FIFO register, which + * might otherwise underflow if not carefully managed. + * + * A call to fsl_shw_add_entropy() will cause the additional entropy to + * be passed directly into the hardware. + * + * In a debug configuration, it provides the following kernel functions: + * rng_return_t rng_read_register(uint32_t byte_offset, uint32_t* valuep); + * rng_return_t rng_write_register(uint32_t byte_offset, uint32_t value); + * @ingroup RNG + */ + +#include "portable_os.h" +#include "fsl_shw.h" +#include "rng_internals.h" + +/* These are often handy */ +#ifndef FALSE +/*! Non-true value for arguments, return values. */ +#define FALSE 0 +#endif +#ifndef TRUE +/*! True value for arguments, return values. */ +#define TRUE 1 +#endif + +/*!**************************************************************************** + * + * Global / Static Variables + * + *****************************************************************************/ + +/*! + * This is type void* so that a) it cannot directly be dereferenced, and b) + * pointer arithmetic on it will function for the byte offsets in rng_rnga.h + * and rng_rngc.h + * + * rng_base is the location in the iomap where the RNG's registers + * (and memory) start. + * + * The referenced data is declared volatile so that the compiler will + * not make any assumptions about the value of registers in the RNG, + * and thus will always reload the register into CPU memory before + * using it (i.e. wherever it is referenced in the driver). + * + * This value should only be referenced by the #RNG_READ_REGISTER and + * #RNG_WRITE_REGISTER macros and their ilk. All dereferences must be + * 32 bits wide. + */ +static volatile void *rng_base; + +/*! + * Flag to say whether interrupt handler has been registered for RNG + * interrupt */ +static int rng_irq_set = FALSE; + +/*! + * Size of the RNG's OUTPUT_FIFO, in words. Retrieved with + * #RNG_GET_FIFO_SIZE() during driver initialization. + */ +static int rng_output_fifo_size; + +/*! Major number for device driver. */ +static int rng_major; + +/*! Registration handle for registering driver with OS. */ +os_driver_reg_t rng_reg_handle; + +/*! + * Internal flag to know whether RNG is in Failed state (and thus many + * registers are unavailable). If the value ever goes to #RNG_STATUS_FAILED, + * it will never change. + */ +static volatile rng_status_t rng_availability = RNG_STATUS_INITIAL; + +/*! + * Global lock for the RNG driver. Mainly used for entries on the RNG work + * queue. + */ +static os_lock_t rng_queue_lock = NULL; + +/*! + * Queue for the RNG task to process. + */ +static shw_queue_t rng_work_queue; + +/*! + * Flag to say whether task initialization succeeded. + */ +static unsigned task_started = FALSE; +/*! + * Waiting queue for RNG SELF TESTING + */ +static DECLARE_COMPLETION(rng_self_testing); +static DECLARE_COMPLETION(rng_seed_done); +/*! + * Object for blocking-mode callers of RNG driver to sleep. + */ +OS_WAIT_OBJECT(rng_wait_queue); + +/*!**************************************************************************** + * + * Function Implementations - Externally Accessible + * + *****************************************************************************/ + +/*!***************************************************************************/ +/* fn rng_init() */ +/*!***************************************************************************/ +/*! + * Initialize the driver. + * + * Set up the driver to have access to RNG device registers and verify that + * it appears to be a proper working device. + * + * Set up interrupt handling. Assure RNG is ready to go and (possibly) set it + * into High Assurance mode. Create a background task to run + * #rng_entropy_task(). Set up up a callback with the SCC driver should the + * security alarm go off. Tell the kernel that the driver is here. + * + * This routine is called during kernel init or module load (insmod). + * + * The function will fail in one of two ways: Returning OK to the caller so + * that kernel startup / driver initialization completes, or returning an + * error. In the success case, the function could set the rng_avaailability to + * RNG_STATUS_FAILED so that only minimal support (e.g. register peek / poke) + * is available in the driver. + * + * @return a call to os_dev_init_return() + */ +OS_DEV_INIT(rng_init) +{ + struct clk *clk; + os_error_code return_code = OS_ERROR_FAIL_S; + rng_availability = RNG_STATUS_CHECKING; + +#if defined(FSL_HAVE_RNGC) + INIT_COMPLETION(rng_self_testing); + INIT_COMPLETION(rng_seed_done); +#endif + rng_work_queue.head = NULL; + rng_work_queue.tail = NULL; + clk = clk_get(NULL, "rng_clk"); + clk_enable(clk); + + printk(KERN_INFO "RNG Driver: Loading\n"); + return_code = rng_map_RNG_memory(); + if (return_code != OS_ERROR_OK_S) { + rng_availability = RNG_STATUS_UNIMPLEMENTED; + pr_debug("RNG: Driver failed to map RNG registers. %d\n", + return_code); + goto check_err; + } + pr_debug("RNG Driver: rng_base is 0x%08x\n", (uint32_t) rng_base); + /*Check SCC keys are fused */ + if (RNG_HAS_ERROR()) { + if (RNG_HAS_BAD_KEY()) { + printk(KERN_INFO "ERROR: BAD KEYS SELECTED\n"); + rng_availability = RNG_STATUS_FAILED; + return_code = OS_ERROR_FAIL_S; + goto check_err; + } + } + /* Check RNG configuration and status */ + return_code = rng_grab_config_values(); + if (return_code != OS_ERROR_OK_S) { + rng_availability = RNG_STATUS_UNIMPLEMENTED; + goto check_err; + } + /* Masking All Interrupts */ + RNG_MASK_ALL_INTERRUPTS(); + RNG_WAKE(); + + /* Determine status of RNG */ + if (RNG_OSCILLATOR_FAILED()) { + pr_debug("RNG Driver: RNG Oscillator is dead\n"); + rng_availability = RNG_STATUS_FAILED; + goto check_err; + } + + /* Oscillator not dead. Setup interrupt code and start the RNG. */ + if ((return_code = rng_setup_interrupt_handling()) == OS_ERROR_OK_S) { +#if defined(FSL_HAVE_RNGA) + scc_return_t scc_code; +#endif + + RNG_GO(); + /* Self Testing For RNG */ + do { + RNG_CLEAR_ERR(); + RNG_SELF_TEST(); +#if defined(FSL_HAVE_RNGC) + wait_for_completion(&rng_self_testing); +#endif + } while (RNG_CHECK_SELF_ERR()); + + RNG_CLEAR_ALL_STATUS(); + /* checking for RNG SEED done */ + do { + RNG_CLEAR_ERR(); + RNG_SEED_GEN(); +#if defined(FSL_HAVE_RNGC) + wait_for_completion(&rng_seed_done); +#endif + } while (RNG_CHECK_SEED_ERR()); +#ifndef RNG_NO_FORCE_HIGH_ASSURANCE + RNG_SET_HIGH_ASSURANCE(); +#endif + if (RNG_GET_HIGH_ASSURANCE()) { + pr_debug("RNG Driver: RNG is in High Assurance mode\n"); + } else { +#ifndef RNG_NO_FORCE_HIGH_ASSURANCE + pr_debug("RNG Driver: RNG could not be put in " + "High Assurance mode\n"); +#endif + rng_availability = RNG_STATUS_FAILED; + goto check_err; + } + + /* Check that RNG is OK */ + if (!RNG_WORKING()) { + pr_debug("RNG determined to be inoperable." + " Status %08x\n", RNG_GET_STATUS()); + /* Couldn't wake it up or other problem */ + rng_availability = RNG_STATUS_FAILED; + goto check_err; + } + + rng_queue_lock = os_lock_alloc_init(); + if (rng_queue_lock == NULL) { + pr_debug("RNG: lock initialization failed\n"); + rng_availability = RNG_STATUS_FAILED; + goto check_err; + } + + return_code = os_create_task(rng_entropy_task); + if (return_code != OS_ERROR_OK_S) { + pr_debug("RNG: task initialization failed\n"); + rng_availability = RNG_STATUS_FAILED; + goto check_err; + } else { + task_started = TRUE; + } +#if defined(FSL_HAVE_RNGA) + scc_code = scc_monitor_security_failure(rng_sec_failure); + if (scc_code != SCC_RET_OK) { + pr_debug + ("RNG Driver: Failed to register SCC callback: %d\n", + scc_code); +#ifndef RNG_NO_FORCE_HIGH_ASSURANCE + return_code = OS_ERROR_FAIL_S; + goto check_err; +#endif + } +#endif + return_code = os_driver_init_registration(rng_reg_handle); + if (return_code != OS_ERROR_OK_S) { + goto check_err; + } + /* add power suspend here */ + /* add power resume here */ + return_code = + os_driver_complete_registration(rng_reg_handle, + rng_major, RNG_DRIVER_NAME); + } + /* RNG is working */ + check_err: + /* If FIFO underflow or other error occurred during drain, this will fail, + * as system will have been put into fail mode by SCC. */ + if ((return_code == OS_ERROR_OK_S) + && (rng_availability == RNG_STATUS_CHECKING)) { + RNG_PUT_RNG_TO_SLEEP(); + rng_availability = RNG_STATUS_OK; /* RNG & driver are ready */ + } else if (return_code != OS_ERROR_OK_S) { + pr_debug("RNG: Driver initialization failed. %d\n", + return_code); + rng_cleanup(); + } + + os_dev_init_return(return_code); + +} /* rng_init */ + +/*!***************************************************************************/ +/* fn rng_shutdown() */ +/*!***************************************************************************/ +/*! + * Prepare driver for exit. + * + * This is called during @c rmmod when the driver is unloading. + * Try to undo whatever was done during #rng_init(), to make the machine be + * in the same state, if possible. + * + * Calls rng_cleanup() to do all work, and then unmap device's register space. + */ +OS_DEV_SHUTDOWN(rng_shutdown) +{ + + pr_debug("RNG: shutdown called\n"); + rng_cleanup(); + + os_driver_remove_registration(rng_reg_handle); + if (rng_base != NULL) { + /* release the virtual memory map to the RNG */ + os_unmap_device((void *)rng_base, RNG_ADDRESS_RANGE); + rng_base = NULL; + } + + os_dev_shutdown_return(OS_ERROR_OK_S); +} /* rng_shutdown */ + +/*!***************************************************************************/ +/* fn rng_cleanup() */ +/*!***************************************************************************/ +/*! + * Undo everything done by rng_init() and place driver in fail mode. + * + * Deregister from SCC, stop tasklet, shutdown the RNG. Leave the register + * map in place in case other drivers call rng_read/write_register() + * + * @return void + */ +static void rng_cleanup(void) +{ + struct clk *clk; + scc_stop_monitoring_security_failure(rng_sec_failure); + clk = clk_get(NULL, "rng_clk"); + clk_disable(clk); + if (task_started) { + os_dev_stop_task(rng_entropy_task); + } + + if (rng_base != NULL) { + /* mask off RNG interrupts */ + RNG_MASK_ALL_INTERRUPTS(); + RNG_SLEEP(); + + if (rng_irq_set) { + /* unmap the interrupts from the IRQ lines */ + os_deregister_interrupt(INT_RNG); + rng_irq_set = FALSE; + } + rng_availability = RNG_STATUS_FAILED; + } else { + rng_availability = RNG_STATUS_UNIMPLEMENTED; + } + + pr_debug("RNG Driver: Cleaned up\n"); + +} /* rng_cleanup */ + +/*! + * Post-process routine for fsl_shw_get_random(). + * + * This function will copy the random data generated by the background task + * into the user's buffer and then free the local buffer. + * + * @param gen_entry The work request. + * + * @return 0 = meaning work completed, pass back result. + */ +static uint32_t finish_random(shw_queue_entry_t * gen_entry) +{ + rng_work_entry_t *work = (rng_work_entry_t *) gen_entry; + + if (work->hdr.flags & FSL_UCO_USERMODE_USER) { + os_copy_to_user(work->data_user, work->data_local, + work->length); + } else { + memcpy(work->data_user, work->data_local, work->length); + } + + os_free_memory(work->data_local); + work->data_local = NULL; + + return 0; /* means completed. */ +} + +/* REQ-FSLSHW-PINTFC-API-BASIC-RNG-002 */ +/*!***************************************************************************/ +/* fn fsl_shw_get_random() */ +/*!***************************************************************************/ +/*! + * Get random data. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param length The number of octets of @a data being requested. + * @param data A pointer to a location of @a length octets to where + * random data will be returned. + * + * @return FSL_RETURN_NO_RESOURCE_S A return code of type #fsl_shw_return_t. + * FSL_RETURN_OK_S + */ +fsl_shw_return_t fsl_shw_get_random(fsl_shw_uco_t * user_ctx, uint32_t length, + uint8_t * data) +{ + fsl_shw_return_t return_code = FSL_RETURN_NO_RESOURCE_S; + /* Boost up length to cover any 'missing' bytes at end of a word */ + uint32_t *buf = os_alloc_memory(length + 3, 0); + volatile rng_work_entry_t *work = os_alloc_memory(sizeof(*work), 0); + + if ((rng_availability != RNG_STATUS_OK) || (buf == NULL) + || (work == NULL)) { + /* Cannot perform function. Clean up and clear out. */ + if (buf != NULL) { + os_free_memory(buf); + } + if (work != NULL) { + os_free_memory((void *)work); + } + } else { + unsigned blocking = user_ctx->flags & FSL_UCO_BLOCKING_MODE; + + work->hdr.user_ctx = user_ctx; + work->hdr.flags = user_ctx->flags; + work->hdr.callback = user_ctx->callback; + work->hdr.user_ref = user_ctx->user_ref; + work->hdr.postprocess = finish_random; + work->length = length; + work->data_local = buf; + work->data_user = data; + + RNG_ADD_WORK_ENTRY((rng_work_entry_t *) work); + + if (blocking) { + os_sleep(rng_wait_queue, work->completed != FALSE, + FALSE); + finish_random((shw_queue_entry_t *) work); + return_code = work->hdr.code; + os_free_memory((void *)work); + } else { + return_code = FSL_RETURN_OK_S; + } + } + + return return_code; +} /* fsl_shw_get_entropy */ + +/*!***************************************************************************/ +/* fn fsl_shw_add_entropy() */ +/*!***************************************************************************/ +/*! + * Add entropy to random number generator. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param length Number of bytes at @a data. + * @param data Entropy to add to random number generator. + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t fsl_shw_add_entropy(fsl_shw_uco_t * user_ctx, uint32_t length, + uint8_t * data) +{ + fsl_shw_return_t return_code = FSL_RETURN_NO_RESOURCE_S; +#ifdef FSL_HAVE_RNGC + return_code = FSL_RETURN_OK_S; +#else + uint32_t *local_data = NULL; + if (rng_availability == RNG_STATUS_OK) { + /* make 32-bit aligned place to hold data */ + local_data = os_alloc_memory(length + 3, 0); + if (local_data == NULL) { + return_code = FSL_RETURN_NO_RESOURCE_S; + } else { + memcpy(local_data, data, length); + + /* Copy one word at a time to hardware */ + while (TRUE) { + register uint32_t *ptr = local_data; + + RNG_ADD_ENTROPY(*ptr++); + if (length <= 4) { + break; + } + length -= 4; + } + return_code = FSL_RETURN_OK_S; + os_free_memory(local_data); + } /* else local_data not NULL */ + + } +#endif + /* rng_availability is OK */ + return return_code; +} /* fsl_shw_add_entropy */ + +#ifdef RNG_REGISTER_PEEK_POKE +/*!***************************************************************************/ +/* fn rng_read_register() */ +/*!***************************************************************************/ +/* + * Debug routines to allow reading of RNG registers. + * + * This routine is only for accesses by other than this driver. + * + * @param register_offset The byte offset of the register to be read. + * @param value Pointer to store the value of the register. + * + * @return RNG_RET_OK or an error return. + */ +rng_return_t rng_read_register(uint32_t register_offset, uint32_t * value) +{ + rng_return_t return_code = RNG_RET_FAIL; + + if ((rng_availability == RNG_STATUS_OK) + || (rng_availability == RNG_STATUS_FAILED)) { + if ((rng_check_register_offset(register_offset) + && rng_check_register_accessible(register_offset, + RNG_CHECK_READ))) { + /* The guards let the check through */ + *value = RNG_READ_REGISTER(register_offset); + return_code = RNG_RET_OK; + } + } + + return return_code; +} /* rng_read_register */ + +/*!***************************************************************************/ +/* fn rng_write_register() */ +/*!***************************************************************************/ +/* + * Debug routines to allow writing of RNG registers. + * + * This routine is only for accesses by other than this driver. + * + * @param register_offset The byte offset of the register to be written. + * @param value Value to store in the register. + * + * @return RNG_RET_OK or an error return. + */ +rng_return_t rng_write_register(uint32_t register_offset, uint32_t value) +{ + rng_return_t return_code = RNG_RET_FAIL; + + if ((rng_availability == RNG_STATUS_OK) + || (rng_availability == RNG_STATUS_FAILED)) { + if ((rng_check_register_offset(register_offset) + && rng_check_register_accessible(register_offset, + RNG_CHECK_WRITE))) { + RNG_WRITE_REGISTER(register_offset, value); + return_code = RNG_RET_OK; + } + } + + return return_code; +} /* rng_write_register */ +#endif /* RNG_REGISTER_PEEK_POKE */ + +/*!**************************************************************************** + * + * Function Implementations - Internal + * + *****************************************************************************/ + +#ifdef RNG_REGISTER_PEEK_POKE +/*!***************************************************************************/ +/* fn check_register_offset() */ +/*!***************************************************************************/ +/*! + * Verify that the @c offset is appropriate for the RNG's register set. + * + * @param[in] offset The (byte) offset within the RNG block + * of the register to be accessed. See + * RNG(A, C) register definitions for meanings. + * + * This routine is only for checking accesses by other than this driver. + * + * @return 0 if register offset out of bounds, 1 if ok to use + */ +inline int rng_check_register_offset(uint32_t offset) +{ + int return_code = FALSE; /* invalid */ + + /* Make sure offset isn't too high and also that it is aligned to + * aa 32-bit offset (multiple of four). + */ + if ((offset < RNG_ADDRESS_RANGE) && (offset % sizeof(uint32_t) == 0)) { + return_code = TRUE; /* OK */ + } else { + pr_debug("RNG: Denied access to offset %8x\n", offset); + } + + return return_code; + +} /* rng_check_register */ + +/*!***************************************************************************/ +/* fn check_register_accessible() */ +/*!***************************************************************************/ +/*! + * Make sure that register access is legal. + * + * Verify that, if in secure mode, only safe registers are used. + * For any register access, make sure that read-only registers are not written + * and that write-only registers are not read. This check also disallows any + * access to the RNG's Output FIFO, to prevent other drivers from draining the + * FIFO and causing an underflow condition. + * + * This routine is only for checking accesses by other than this driver. + * + * @param offset The (byte) offset within the RNG block + * of the register to be accessed. See + * @ref rngregs for meanings. + * @param access_write 0 for read, anything else for write + * + * @return 0 if invalid, 1 if OK. + */ +static int rng_check_register_accessible(uint32_t offset, int access_write) +{ + int return_code = FALSE; /* invalid */ + uint32_t secure = RNG_GET_HIGH_ASSURANCE(); + + /* First check for RNG in Secure Mode -- most registers inaccessible. + * Also disallowing access to RNG_OUTPUT_FIFO except by the driver. + */ + if (! +#ifdef FSL_HAVE_RNGA + (secure && + ((offset == RNGA_OUTPUT_FIFO) || + (offset == RNGA_MODE) || + (offset == RNGA_VERIFICATION_CONTROL) || + (offset == RNGA_OSCILLATOR_CONTROL_COUNTER) || + (offset == RNGA_OSCILLATOR1_COUNTER) || + (offset == RNGA_OSCILLATOR2_COUNTER) || + (offset == RNGA_OSCILLATOR_COUNTER_STATUS))) +#else /* RNGC */ + (secure && + ((offset == RNGC_FIFO) || + (offset == RNGC_VERIFICATION_CONTROL) || + (offset == RNGC_OSCILLATOR_CONTROL_COUNTER) || + (offset == RNGC_OSC_COUNTER) || + (offset == RNGC_OSC_COUNTER_STATUS))) +#endif + ) { + + /* Passed that test. Either not in high assurance, and/or are + checking register that is always available. Now check + R/W permissions. */ + if (access_write == RNG_CHECK_READ) { /* read request */ + /* Only the entropy register is write-only */ +#ifdef FSL_HAVE_RNGA + if (!(offset == RNGA_ENTROPY)) { + return_code = TRUE; /* Let all others be read */ + } else { + pr_debug + ("RNG: Offset %04x denied read access\n", + offset); + } +#else /* else RNGC */ + /* No registers are write-only */ + return_code = TRUE; +#endif /* RNGA */ + } /* read */ + else { /* access_write means write */ + /* Check against list of non-writable registers */ + if (! +#ifdef FSL_HAVE_RNGA + ((offset == RNGA_STATUS) || + (offset == RNGA_OUTPUT_FIFO) || + (offset == RNGA_OSCILLATOR1_COUNTER) || + (offset == RNGA_OSCILLATOR2_COUNTER) || + (offset == RNGA_OSCILLATOR_COUNTER_STATUS)) +#else /* FSL_HAVE_RNGC */ + ((offset == RNGC_STATUS) || + (offset == RNGC_FIFO) || + (offset == RNGC_OSC_COUNTER) || + (offset == RNGC_OSC_COUNTER_STATUS)) +#endif + ) { + return_code = TRUE; /* can be written */ + } else { + pr_debug + ("RNG: Offset %04x denied write access\n", + offset); + } + } /* write */ + } /* not high assurance and inaccessible register... */ + else { + pr_debug("RNG: Offset %04x denied high-assurance access\n", + offset); + } + + return return_code; +} /* rng_check_register_accessible */ +#endif /* RNG_REGISTER_PEEK_POKE */ + +/*!***************************************************************************/ +/* fn rng_irq() */ +/*!***************************************************************************/ +/*! + * This is the interrupt handler for the RNG. It is only ever invoked if the + * RNG detects a FIFO Underflow error. + * + * If the error is a Security Violation, this routine will + * set the #rng_availability to #RNG_STATUS_FAILED, as the entropy pool may + * have been corrupted. The RNG will also be placed into low power mode. The + * SCC will have noticed the problem as well. + * + * The other possibility, if the RNG is not in High Assurance mode, would be + * simply a FIFO Underflow. No special action, other than to + * clear the interrupt, is taken. + */ +OS_DEV_ISR(rng_irq) +{ + int handled = FALSE; /* assume interrupt isn't from RNG */ + + pr_debug("RNG Driver: Inside the RNG Interrupt Handler\n"); + if (RNG_SEED_DONE()) { + complete(&rng_seed_done); + RNG_CLEAR_ALL_STATUS(); + handled = TRUE; + } + + if (RNG_SELF_TEST_DONE()) { + complete(&rng_self_testing); + RNG_CLEAR_ALL_STATUS(); + handled = TRUE; + } + /* Look to see whether RNG needs attention */ + if (RNG_HAS_ERROR()) { + if (RNG_GET_HIGH_ASSURANCE()) { + RNG_SLEEP(); + rng_availability = RNG_STATUS_FAILED; + RNG_MASK_ALL_INTERRUPTS(); + } + handled = TRUE; + /* Clear the interrupt */ + RNG_CLEAR_ALL_STATUS(); + + } + os_dev_isr_return(handled); +} /* rng_irq */ + +/*!***************************************************************************/ +/* fn map_RNG_memory() */ +/*!***************************************************************************/ +/*! + * Place the RNG's memory into kernel virtual space. + * + * @return OS_ERROR_OK_S on success, os_error_code on failure + */ +static os_error_code rng_map_RNG_memory(void) +{ + os_error_code error_code = OS_ERROR_FAIL_S; + + /* Map the RNG memory on the internal bus into kernel address + space */ + rng_base = os_map_device(RNG_BASE_ADDR, RNG_ADDRESS_RANGE); + if (rng_base == NULL) { + /* failure ! */ + pr_debug("RNG Driver: ioremap failed.\n"); + } else { + error_code = OS_ERROR_OK_S; + } + + return error_code; +} /* rng_map_RNG_memory */ + +/*!***************************************************************************/ +/* fn rng_setup_interrupt_handling() */ +/*!***************************************************************************/ +/*! + * Register #rng_irq() as the interrupt handler for #INT_RNG. + * + * @return OS_ERROR_OK_S on success, os_error_code on failure + */ +static os_error_code rng_setup_interrupt_handling(void) +{ + os_error_code error_code; + + /* + * Install interrupt service routine for the RNG. Ignore the + * assigned IRQ number. + */ + error_code = os_register_interrupt(RNG_DRIVER_NAME, INT_RNG, + OS_DEV_ISR_REF(rng_irq)); + if (error_code != OS_ERROR_OK_S) { + pr_debug("RNG Driver: Error installing Interrupt Handler\n"); + } else { + RNG_UNMASK_ALL_INTERRUPTS(); + } + + return error_code; +} /* rng_setup_interrupt_handling */ + +/*!***************************************************************************/ +/* fn rng_grab_config_values() */ +/*!***************************************************************************/ +/*! + * Read configuration information from the RNG. + * + * Sets #rng_output_fifo_size. + * + * @return A error code indicating whether the part is the expected one. + */ +static os_error_code rng_grab_config_values(void) +{ + enum rng_type type; + os_error_code ret = OS_ERROR_FAIL_S; + + /* Go for type, versions... */ + type = RNG_GET_RNG_TYPE(); + + /* Make sure type is the one this code has been compiled for. */ + if (RNG_VERIFY_TYPE(type)) { + rng_output_fifo_size = RNG_GET_FIFO_SIZE(); + if (rng_output_fifo_size != 0) { + ret = OS_ERROR_OK_S; + } + } + if (ret != OS_ERROR_OK_S) { + pr_debug + ("RNG: Unknown or unexpected RNG type %d (FIFO size %d)." + " Failing driver initialization\n", type, + rng_output_fifo_size); + } + + return ret; +} + + /* rng_grab_config_values */ + +/*!***************************************************************************/ +/* fn rng_drain_fifo() */ +/*!***************************************************************************/ +/*! + * This function copies words from the RNG FIFO into the caller's buffer. + * + * + * @param random_p Location to copy random data + * @param count_words Number of words to copy + * + * @return An error code. + */ +static fsl_shw_return_t rng_drain_fifo(uint32_t * random_p, int count_words) +{ + + int words_in_rng; /* Number of words available now in RNG */ + fsl_shw_return_t code = FSL_RETURN_ERROR_S; + int sequential_count = 0; /* times through big while w/empty FIFO */ + int fifo_empty_count = 0; /* number of times FIFO was empty */ + int max_sequential = 0; /* max times 0 seen in a row */ +#if defined(FSL_HAVE_RNGC) + int count_for_reseed = 0; + INIT_COMPLETION(rng_seed_done); +#endif +#if defined(FSL_HAVE_RNGC) + if (RNG_RESEED()) { + do { + RNG_CLEAR_ERR(); + RNG_SEED_GEN(); + wait_for_completion(&rng_seed_done); + if (count_for_reseed == 3) { + os_printk + ("Device could not able to enter RESEED Mode\n"); + code = FSL_RETURN_INTERNAL_ERROR_S; + } + count_for_reseed++; + } while (RNG_CHECK_SEED_ERR()); + } +#endif + /* Copy all of them in. Stop if pool fills. */ + while ((rng_availability == RNG_STATUS_OK) && (count_words > 0)) { + /* Ask RNG how many words currently in FIFO */ + words_in_rng = RNG_GET_WORDS_IN_FIFO(); + if (words_in_rng == 0) { + ++sequential_count; + fifo_empty_count++; + if (sequential_count > max_sequential) { + max_sequential = sequential_count; + } + if (sequential_count >= RNG_MAX_TRIES) { + pr_debug("RNG: FIFO staying empty (%d)\n", + words_in_rng); + code = FSL_RETURN_NO_RESOURCE_S; + break; + } + } else { + /* Found at least one word */ + sequential_count = 0; + /* Now adjust: words_in_rng = MAX(count_words, words_in_rng) */ + words_in_rng = (count_words < words_in_rng) + ? count_words : words_in_rng; + } /* else found words */ + +#ifdef RNG_FORCE_FIFO_UNDERFLOW + /* + * For unit test, force occasional extraction of more words than + * available. This should cause FIFO Underflow, and IRQ invocation. + */ + words_in_rng = count_words; +#endif + + /* Copy out all available & neeeded data */ + while (words_in_rng-- > 0) { + *random_p++ = RNG_READ_FIFO(); + count_words--; + } + } /* while words still needed */ + + if (count_words == 0) { + code = FSL_RETURN_OK_S; + } + if (fifo_empty_count != 0) { + pr_debug("RNG: FIFO empty %d times, max loop count %d\n", + fifo_empty_count, max_sequential); + } + + return code; +} /* rng_drain_fifo */ + +/*!***************************************************************************/ +/* fn rng_entropy_task() */ +/*!***************************************************************************/ +/*! + * This is the background task of the driver. It is scheduled by + * RNG_ADD_WORK_ENTRY(). + * + * This will process each entry on the #rng_work_queue. Blocking requests will + * cause sleepers to be awoken. Non-blocking requests will be placed on the + * results queue, and if appropriate, the callback function will be invoked. + */ +OS_DEV_TASK(rng_entropy_task) +{ + rng_work_entry_t *work; + + os_dev_task_begin(); + + pr_debug("RNG: entropy task starting\n"); + + while ((work = RNG_GET_WORK_ENTRY()) != NULL) { + pr_debug("RNG: found %d bytes of work at %p (%p)\n", + work->length, work, work->data_local); + work->hdr.code = rng_drain_fifo(work->data_local, + BYTES_TO_WORDS(work->length)); + work->completed = TRUE; + + if (work->hdr.flags & FSL_UCO_BLOCKING_MODE) { + pr_debug("RNG: Waking queued processes\n"); + os_wake_sleepers(rng_wait_queue); + } else { + os_lock_context_t lock_context; + + os_lock_save_context(rng_queue_lock, lock_context); + RNG_ADD_QUEUE_ENTRY(&work->hdr.user_ctx->result_pool, + work); + os_unlock_restore_context(rng_queue_lock, lock_context); + + if (work->hdr.flags & FSL_UCO_CALLBACK_MODE) { + if (work->hdr.callback != NULL) { + work->hdr.callback(work->hdr.user_ctx); + } else { + pr_debug + ("RNG: Callback ptr for %p is NULL\n", + work); + } + } + } + } /* while */ + + pr_debug("RNG: entropy task ending\n"); + + os_dev_task_return(OS_ERROR_OK_S); +} /* rng_entropy_task */ + +/*!***************************************************************************/ +/* fn rng_sec_failure() */ +/*!***************************************************************************/ +/*! + * Function to handle "Security Alarm" indication from SCC. + * + * This function is registered with the Security Monitor ans the callback + * function for the RNG driver. Upon alarm, it will shut down the driver so + * that no more random data can be retrieved. + * + * @return void + */ +static void rng_sec_failure(void) +{ + pr_debug("RNG Driver: Security Failure Alarm received.\n"); + + rng_cleanup(); + + return; +} + +#ifdef RNG_REGISTER_DEBUG +/*!***************************************************************************/ +/* fn dbg_rng_read_register() */ +/*!***************************************************************************/ +/*! + * Noisily read a 32-bit value to an RNG register. + * @param offset The address of the register to read. + * + * @return The register value + * */ +static uint32_t dbg_rng_read_register(uint32_t offset) +{ + uint32_t value; + + value = os_read32(rng_base + offset); +#ifndef RNG_ENTROPY_DEBUG + if (offset != RNG_OUTPUT_FIFO) { +#endif + pr_debug("RNG RD: 0x%4x : 0x%08x\n", offset, value); +#ifndef RNG_ENTROPY_DEBUG + } +#endif + return value; +} + +/*!***************************************************************************/ +/* fn dbg_rng_write_register() */ +/*!***************************************************************************/ +/*! + * Noisily write a 32-bit value to an RNG register. + * @param offset The address of the register to written. + * + * @param value The new register value + */ +static void dbg_rng_write_register(uint32_t offset, uint32_t value) +{ + pr_debug("RNG WR: 0x%4x : 0x%08x\n", offset, value); + os_write32(value, rng_base + offset); + return; +} + +#endif /* RNG_REGISTER_DEBUG */ diff --git a/drivers/mxc/security/rng/shw_driver.c b/drivers/mxc/security/rng/shw_driver.c new file mode 100644 index 000000000000..8619f45b615a --- /dev/null +++ b/drivers/mxc/security/rng/shw_driver.c @@ -0,0 +1,1164 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! @file shw_driver.c + * + * This is the user-mode driver code for the FSL Security Hardware (SHW) API. + * as well as the 'common' FSL SHW API code for kernel API users. + * + * Its interaction with the Linux kernel is from calls to shw_init() when the + * driver is loaded, and shw_shutdown() should the driver be unloaded. + * + * The User API (driver interface) is handled by the following functions: + * @li shw_open() - handles open() system call on FSL SHW device + * @li shw_release() - handles close() system call on FSL SHW device + * @li shw_ioctl() - handles ioctl() system call on FSL SHW device + * + * The driver also provides the following functions for kernel users of the FSL + * SHW API: + * @li fsl_shw_register_user() + * @li fsl_shw_deregister_user() + * @li fsl_shw_get_capabilities() + * @li fsl_shw_get_results() + * + * All other functions are internal to the driver. + * + * The life of the driver starts at boot (or module load) time, with a call by + * the kernel to shw_init(). + * + * The life of the driver ends when the kernel is shutting down (or the driver + * is being unloaded). At this time, shw_shutdown() is called. No function + * will ever be called after that point. + * + * In the case that the driver is reloaded, a new copy of the driver, with + * fresh global values, etc., is loaded, and there will be a new call to + * shw_init(). + * + * In user mode, the user's fsl_shw_register_user() call causes an open() event + * on the driver followed by a ioctl() with the registration information. Any + * subsequent API calls by the user are handled through the ioctl() function + * and shuffled off to the appropriate routine (or driver) for service. The + * fsl_shw_deregister_user() call by the user results in a close() function + * call on the driver. + * + * In kernel mode, the driver provides the functions fsl_shw_register_user(), + * fsl_shw_deregister_user(), fsl_shw_get_capabilities(), and + * fsl_shw_get_results(). Other parts of the API are provided by other + * drivers, if available, to support the cryptographic functions. + * @ingroup RNG + */ + +#include "portable_os.h" +#include "fsl_shw.h" + +#include "shw_internals.h" + +/*!**************************************************************************** + * + * Function Declarations + * + *****************************************************************************/ + +/* kernel interface functions */ +OS_DEV_INIT_DCL(shw_init); +OS_DEV_SHUTDOWN_DCL(shw_shutdown); +OS_DEV_IOCTL_DCL(shw_ioctl); + +/*!**************************************************************************** + * + * Global / Static Variables + * + *****************************************************************************/ + +/*! + * Major node (user/device interaction value) of this driver. + */ +static int shw_major_node = SHW_MAJOR_NODE; + +/*! + * Flag to know whether the driver has been associated with its user device + * node (e.g. /dev/shw). + */ +static int shw_device_registered = 0; + +/*! + * OS-dependent handle used for registering user interface of a driver. + */ +static os_driver_reg_t reg_handle; + +/*! + * Linked List of registered users of the API + */ + +/*! + * This is the lock for all user request pools. H/W component drivers may also + * use it for their own work queues. + */ +os_lock_t shw_queue_lock = NULL; + +#ifndef FSL_HAVE_SAHARA +/*! Empty list of supported symmetric algorithms. */ +static fsl_shw_key_alg_t pf_syms[] = { +}; + +/*! Empty list of supported symmetric modes. */ +static fsl_shw_sym_mode_t pf_modes[] = { +}; + +/*! Empty list of supported hash algorithms. */ +static fsl_shw_hash_alg_t pf_hashes[] = { +}; +#endif /* no Sahara */ + +/*! This matches SHW capabilities... */ +static fsl_shw_pco_t cap = { + 1, 1, /* api version number - major & minor */ + 1, 0, /* driver version number - major & minor */ + sizeof(pf_syms) / sizeof(fsl_shw_key_alg_t), /* key alg count */ + pf_syms, /* key alg list ptr */ + sizeof(pf_modes) / sizeof(fsl_shw_sym_mode_t), /* sym mode count */ + pf_modes, /* modes list ptr */ + sizeof(pf_hashes) / sizeof(fsl_shw_hash_alg_t), /* hash alg count */ + pf_hashes, /* hash list ptr */ + /* + * The following table must be set to handle all values of key algorithm + * and sym mode, and be in the correct order.. + */ + { /* Stream, ECB, CBC, CTR */ + {0, 0, 0, 0} + , /* HMAC */ + {0, 0, 0, 0} + , /* AES */ + {0, 0, 0, 0} + , /* DES */ + {0, 0, 0, 0} + , /* 3DES */ + {0, 0, 0, 0} /* ARC4 */ + } + , +}; + +/* These are often handy */ +#ifndef FALSE +/*! Not true. Guaranteed to be zero. */ +#define FALSE 0 +#endif +#ifndef TRUE +/*! True. Guaranteed to be non-zero. */ +#define TRUE 1 +#endif + +/*!**************************************************************************** + * + * Function Implementations - Externally Accessible + * + *****************************************************************************/ + +/*!***************************************************************************/ +/* fn shw_init() */ +/*!***************************************************************************/ +/*! + * Initialize the driver. + * + * This routine is called during kernel init or module load (insmod). + * + * @return OS_ERROR_OK_S on success, errno on failure + */ +OS_DEV_INIT(shw_init) +{ + os_error_code error_code = OS_ERROR_NO_MEMORY_S; /* assume failure */ + + pr_debug("SHW Driver: Loading\n"); + + user_list = NULL; + shw_queue_lock = os_lock_alloc_init(); + + if (shw_queue_lock != NULL) { + error_code = shw_setup_user_driver_interaction(); + if (error_code != OS_ERROR_OK_S) { + pr_debug("SHW Driver: Failed to setup user" + " i/f: %d\n", error_code); + } + } + /* queue_lock not NULL */ + if (error_code != OS_ERROR_OK_S) { + pr_debug("SHW: Driver initialization failed. %d\n", error_code); + shw_cleanup(); + } else { + pr_debug("SHW: Driver initialization complete.\n"); + } + + os_dev_init_return(error_code); +} /* shw_init */ + +/*!***************************************************************************/ +/* fn shw_shutdown() */ +/*!***************************************************************************/ +/*! + * Prepare driver for exit. + * + * This is called during @c rmmod when the driver is unloading or when the + * kernel is shutting down. + * + * Calls shw_cleanup() to do all work to undo anything that happened during + * initialization or while driver was running. + */ +OS_DEV_SHUTDOWN(shw_shutdown) +{ + + pr_debug("SHW: shutdown called\n"); + shw_cleanup(); + + os_dev_shutdown_return(OS_ERROR_OK_S); +} /* shw_shutdown */ + +/*!***************************************************************************/ +/* fn shw_cleanup() */ +/*!***************************************************************************/ +/*! + * Prepare driver for shutdown. + * + * Remove the driver registration. + * + */ +static void shw_cleanup(void) +{ + if (shw_device_registered) { + + /* Turn off the all association with OS */ + os_driver_remove_registration(reg_handle); + shw_device_registered = 0; + } + + if (shw_queue_lock != NULL) { + os_lock_deallocate(shw_queue_lock); + } + pr_debug("SHW Driver: Cleaned up\n"); +} /* shw_cleanup */ + +/*!***************************************************************************/ +/* fn shw_open() */ +/*!***************************************************************************/ +/*! + * Handle @c open() call from user. + * + * @return OS_ERROR_OK_S on success (always!) + */ +OS_DEV_OPEN(shw_open) +{ + os_error_code status = OS_ERROR_OK_S; + + os_dev_set_user_private(NULL); /* Make sure */ + + os_dev_open_return(status); +} /* shw_open */ + +/*!***************************************************************************/ +/* fn shw_ioctl() */ +/*!***************************************************************************/ +/*! + * Process an ioctl() request from user-mode API. + * + * This code determines which of the API requests the user has made and then + * sends the request off to the appropriate function. + * + * @return ioctl_return() + */ +OS_DEV_IOCTL(shw_ioctl) +{ + os_error_code code = OS_ERROR_FAIL_S; + + fsl_shw_uco_t *user_ctx = os_dev_get_user_private(); + + pr_debug("SHW: IOCTL %d received\n", os_dev_get_ioctl_op()); + switch (os_dev_get_ioctl_op()) { + + case SHW_IOCTL_REQUEST + SHW_USER_REQ_REGISTER_USER: + { + fsl_shw_uco_t *user_ctx = + os_alloc_memory(sizeof(*user_ctx), 0); + + if (user_ctx == NULL) { + code = OS_ERROR_NO_MEMORY_S; + } else { + code = init_uco(user_ctx, (fsl_shw_uco_t *) + os_dev_get_ioctl_arg()); + if (code == OS_ERROR_OK_S) { + os_dev_set_user_private(user_ctx); + } else { + os_free_memory(user_ctx); + } + } + } + break; + + case SHW_IOCTL_REQUEST + SHW_USER_REQ_DEREGISTER_USER: + break; + + case SHW_IOCTL_REQUEST + SHW_USER_REQ_GET_RESULTS: + code = get_results(user_ctx, (struct results_req *) + os_dev_get_ioctl_arg()); + break; + + case SHW_IOCTL_REQUEST + SHW_USER_REQ_GET_CAPABILITIES: + code = get_capabilities(user_ctx, (fsl_shw_pco_t *) + os_dev_get_ioctl_arg()); + break; + + case SHW_IOCTL_REQUEST + SHW_USER_REQ_GET_RANDOM: + pr_debug("SHW: get_random ioctl received\n"); + code = get_random(user_ctx, (struct get_random_req *) + os_dev_get_ioctl_arg()); + break; + + case SHW_IOCTL_REQUEST + SHW_USER_REQ_ADD_ENTROPY: + pr_debug("SHW: add_entropy ioctl received\n"); + code = add_entropy(user_ctx, (struct add_entropy_req *) + os_dev_get_ioctl_arg()); + break; + + default: + pr_debug("SHW: Unexpected ioctl %d\n", os_dev_get_ioctl_op()); + break; + } + + os_dev_ioctl_return(code); +} + +/*!***************************************************************************/ +/* fn shw_release() */ +/*!***************************************************************************/ +/*! + * Handle @c close() call from user. + * This is a Linux device driver interface routine. + * + * @return OS_ERROR_OK_S on success (always!) + */ +OS_DEV_CLOSE(shw_release) +{ + fsl_shw_uco_t *user_ctx = os_dev_get_user_private(); + os_error_code code = OS_ERROR_OK_S; + + if (user_ctx != NULL) { + + fsl_shw_deregister_user(user_ctx); + os_free_memory(user_ctx); + os_dev_set_user_private(NULL); + } + + os_dev_close_return(code); +} /* shw_release */ + +/*!***************************************************************************/ +/* fn shw_user_callback() */ +/*!***************************************************************************/ +/*! + * FSL SHW User callback function. + * + * This function is set in the kernel version of the user context as the + * callback function when the user mode user wants a callback. Its job is to + * inform the user process that results (may) be available. It does this by + * sending a SIGUSR2 signal which is then caught by the user-mode FSL SHW + * library. + * + * @param uco Kernel version of uco associated with the request. + * + * @return void + */ +static void shw_user_callback(fsl_shw_uco_t * uco) +{ + pr_debug("SHW: Signalling callback user process for context %p\n", uco); + os_send_signal(uco->process, SIGUSR2); +} + +/*!***************************************************************************/ +/* fn setup_user_driver_interaction() */ +/*!***************************************************************************/ +/*! + * Register the driver with the kernel as the driver for shw_major_node. Note + * that this value may be zero, in which case the major number will be assigned + * by the OS. shw_major_node is never modified. + * + * The open(), ioctl(), and close() handles for the driver ned to be registered + * with the kernel. Upon success, shw_device_registered will be true; + * + * @return OS_ERROR_OK_S on success, or an os err code + */ +static os_error_code shw_setup_user_driver_interaction(void) +{ + os_error_code error_code; + + os_driver_init_registration(reg_handle); + os_driver_add_registration(reg_handle, OS_FN_OPEN, + OS_DEV_OPEN_REF(shw_open)); + os_driver_add_registration(reg_handle, OS_FN_IOCTL, + OS_DEV_IOCTL_REF(shw_ioctl)); + os_driver_add_registration(reg_handle, OS_FN_CLOSE, + OS_DEV_CLOSE_REF(shw_release)); + error_code = os_driver_complete_registration(reg_handle, shw_major_node, + SHW_DRIVER_NAME); + + if (error_code != OS_ERROR_OK_S) { + /* failure ! */ + pr_debug("SHW Driver: register device driver failed: %d\n", + error_code); + } else { /* success */ + shw_device_registered = TRUE; + pr_debug("SHW Driver: Major node is %d\n", + os_driver_get_major(reg_handle)); + } + + return error_code; +} /* shw_setup_user_driver_interaction */ + +/*!****************************************************************/ +/* User Mode Support */ +/*!****************************************************************/ + +/*! + * Initialze kernel User Context Object from User-space version. + * + * Copy user UCO into kernel UCO, set flags and fields for operation + * within kernel space. Add user to driver's list of users. + * + * @param user_ctx Pointer to kernel space UCO + * @param user_mode_uco User pointer to user space version + * + * @return os_error_code + */ +static os_error_code init_uco(fsl_shw_uco_t * user_ctx, void *user_mode_uco) +{ + os_error_code code; + + code = os_copy_from_user(user_ctx, user_mode_uco, sizeof(*user_ctx)); + if (code == OS_ERROR_OK_S) { + user_ctx->flags |= FSL_UCO_USERMODE_USER; + user_ctx->result_pool.head = NULL; + user_ctx->result_pool.tail = NULL; + user_ctx->process = os_get_process_handle(); + user_ctx->callback = shw_user_callback; + SHW_ADD_USER(user_ctx); + } + pr_debug("SHW: init uco returning %d (flags %x)\n", code, + user_ctx->flags); + + return code; +} + +/*! + * Copy array from kernel to user space. + * + * This routine will check bounds before trying to copy, and return failure + * on bounds violation or error during the copy. + * + * @param userloc Location in userloc to place data. If NULL, the function + * will do nothing (except return NULL). + * @param userend Address beyond allowed copy region at @c userloc. + * @param data_start Location of data to be copied + * @param element_size sizeof() an element + * @param element_count Number of elements of size element_size to copy. + * @return New value of userloc, or NULL if there was an error. + */ +inline static void *copy_array(void *userloc, void *userend, void *data_start, + unsigned element_size, unsigned element_count) +{ + unsigned byte_count = element_size * element_count; + + if ((userloc == NULL) || (userend == NULL) + || ((userloc + byte_count) >= userend) || + (copy_to_user(userloc, data_start, byte_count) != OS_ERROR_OK_S)) { + userloc = NULL; + } else { + userloc += byte_count; + } + + return userloc; +} + +/*! + * Send an FSL SHW API return code up into the user-space request structure. + * + * @param user_header User address of request block / request header + * @param result_code The FSL SHW API code to be placed at header.code + * + * @return an os_error_code + * + * NOTE: This function assumes that the shw_req_header is at the beginning of + * each request structure. + */ +inline static os_error_code copy_fsl_code(void *user_header, + fsl_shw_return_t result_code) +{ + return os_copy_to_user(user_header + + offsetof(struct shw_req_header, code), + &result_code, sizeof(result_code)); +} + +/*! + * Handle user-mode Get Capabilities request + * + * Right now, this function can only have a failure if the user has failed to + * provide a pointer to a location in user space with enough room to hold the + * fsl_shw_pco_t structure and any associated data. It will treat this failure + * as an ioctl failure and return an ioctl error code, instead of treating it + * as an API failure. + * + * @param user_ctx The kernel version of user's context + * @param user_mode_pco_request Pointer to user-space request + * + * @return an os_error_code + */ +static os_error_code get_capabilities(fsl_shw_uco_t * user_ctx, + void *user_mode_pco_request) +{ + os_error_code code; + struct capabilities_req req; + fsl_shw_pco_t local_cap; + + memcpy(&local_cap, &cap, sizeof(cap)); + /* Initialize pointers to out-of-struct arrays */ + local_cap.sym_algorithms = NULL; + local_cap.sym_modes = NULL; + local_cap.sym_modes = NULL; + + code = os_copy_from_user(&req, user_mode_pco_request, sizeof(req)); + if (code == OS_ERROR_OK_S) { + void *endcap; + void *user_bounds; + pr_debug("SHE: Received get_cap request: 0x%p/%u/0x%x\n", + req.capabilities, req.size, sizeof(fsl_shw_pco_t)); + endcap = req.capabilities + 1; /* point to end of structure */ + user_bounds = (void *)req.capabilities + req.size; /* end of area */ + + printk(KERN_INFO "next: %p, end: %p\n", endcap, user_bounds); // + /* First verify that request is big enough for the main structure */ + if (endcap >= user_bounds) { + endcap = NULL; /* No! */ + } + + /* Copy any Symmetric Algorithm suppport */ + if (cap.sym_algorithm_count != 0) { + local_cap.sym_algorithms = endcap; + endcap = + copy_array(endcap, user_bounds, cap.sym_algorithms, + sizeof(fsl_shw_key_alg_t), + cap.sym_algorithm_count); + } + + /* Copy any Symmetric Modes suppport */ + if (cap.sym_mode_count != 0) { + local_cap.sym_modes = endcap; + endcap = copy_array(endcap, user_bounds, cap.sym_modes, + sizeof(fsl_shw_sym_mode_t), + cap.sym_mode_count); + } + + /* Copy any Hash Algorithm suppport */ + if (cap.hash_algorithm_count != 0) { + local_cap.hash_algorithms = endcap; + endcap = + copy_array(endcap, user_bounds, cap.hash_algorithms, + sizeof(fsl_shw_hash_alg_t), + cap.hash_algorithm_count); + } + + /* Now copy up the (possibly modified) main structure */ + if (endcap != NULL) { + code = + os_copy_to_user(req.capabilities, &local_cap, + sizeof(cap)); + } + + if (endcap == NULL) { + code = OS_ERROR_BAD_ADDRESS; + } + + /* And return the FSL SHW code in the request structure. */ + if (code == OS_ERROR_OK_S) { + code = + copy_fsl_code(user_mode_pco_request, + FSL_RETURN_OK_S); + } + } + + /* code may already be set to an error. This is another error case. */ + + pr_debug("SHW: get capabilities returning %d\n", code); + + return code; +} + +/*! + * Handle user-mode Get Results request + * + * Get arguments from user space into kernel space, then call + * fsl_shw_get_results, and then copy its return code and any results from + * kernel space back to user space. + * + * @param user_ctx The kernel version of user's context + * @param user_mode_results_req Pointer to user-space request + * + * @return an os_error_code + */ +static os_error_code get_results(fsl_shw_uco_t * user_ctx, + void *user_mode_results_req) +{ + os_error_code code; + struct results_req req; + fsl_shw_result_t *results = NULL; + int loop; + + code = os_copy_from_user(&req, user_mode_results_req, sizeof(req)); + loop = 0; + + if (code == OS_ERROR_OK_S) { + results = os_alloc_memory(req.requested * sizeof(*results), 0); + if (results == NULL) { + code = OS_ERROR_NO_MEMORY_S; + } + } + + if (code == OS_ERROR_OK_S) { + fsl_shw_return_t err = + fsl_shw_get_results(user_ctx, req.requested, + results, &req.actual); + + /* Send API return code up to user. */ + code = copy_fsl_code(user_mode_results_req, err); + + if ((code == OS_ERROR_OK_S) && (err == FSL_RETURN_OK_S)) { + /* Now copy up the result count */ + code = os_copy_to_user(user_mode_results_req + + offsetof(struct results_req, + actual), &req.actual, + sizeof(req.actual)); + if ((code == OS_ERROR_OK_S) && (req.actual != 0)) { + /* now copy up the results... */ + code = os_copy_to_user(req.results, results, + req.actual * + sizeof(*results)); + } + } + } + + if (results != NULL) { + os_free_memory(results); + } + + return code; +} + +/*! + * Process header of user-mode request. + * + * Mark header as User Mode request. Update UCO's flags and reference fields + * with current versions from the header. + * + * @param user_ctx Pointer to kernel version of UCO. + * @param hdr Pointer to common part of user request. + * + * @return void + */ +inline static void process_hdr(fsl_shw_uco_t * user_ctx, + struct shw_req_header *hdr) +{ + hdr->flags |= FSL_UCO_USERMODE_USER; + user_ctx->flags = hdr->flags; + user_ctx->user_ref = hdr->user_ref; + + return; +} + +/*! + * Handle user-mode Get Random request + * + * @param user_ctx The kernel version of user's context + * @param user_mode_get_random_req Pointer to user-space request + * + * @return an os_error_code + */ +static os_error_code get_random(fsl_shw_uco_t * user_ctx, + void *user_mode_get_random_req) +{ + os_error_code code; + struct get_random_req req; + + code = os_copy_from_user(&req, user_mode_get_random_req, sizeof(req)); + if (code == OS_ERROR_OK_S) { + process_hdr(user_ctx, &req.hdr); + pr_debug("SHW: get_random() for %d bytes in %sblocking mode\n", + req.size, (req.hdr.flags & FSL_UCO_BLOCKING_MODE) ? + "" : "non-"); + req.hdr.code = + fsl_shw_get_random(user_ctx, req.size, req.random); + + pr_debug("SHW: get_random() returning %d\n", req.hdr.code); + + /* Copy FSL function status back to user */ + code = copy_fsl_code(user_mode_get_random_req, req.hdr.code); + } + + return code; +} + +/*! + * Handle user-mode Add Entropy request + * + * @param user_ctx Pointer to the kernel version of user's context + * @param user_mode_add_entropy_req Address of user-space request + * + * @return an os_error_code + */ +static os_error_code add_entropy(fsl_shw_uco_t * user_ctx, + void *user_mode_add_entropy_req) +{ + os_error_code code; + struct add_entropy_req req; + uint8_t *local_buffer = NULL; + + code = os_copy_from_user(&req, user_mode_add_entropy_req, sizeof(req)); + if (code == OS_ERROR_OK_S) { + local_buffer = os_alloc_memory(req.size, 0); /* for random */ + if (local_buffer != NULL) { + code = + os_copy_from_user(local_buffer, req.entropy, + req.size); + } + if (code == OS_ERROR_OK_S) { + req.hdr.code = fsl_shw_add_entropy(user_ctx, req.size, + local_buffer); + + code = + copy_fsl_code(user_mode_add_entropy_req, + req.hdr.code); + } + } + + if (local_buffer != NULL) { + os_free_memory(local_buffer); + } + + return code; +} + +/*!****************************************************************/ +/* End User Mode Support */ +/*!****************************************************************/ + +#ifdef LINUX_VERSION_CODE +EXPORT_SYMBOL(fsl_shw_register_user); +#endif +/* REQ-S2LRD-PINTFC-API-GEN-004 */ +/* + * Handle user registration. + * + * @param user_ctx The user context for the registration. + */ +fsl_shw_return_t fsl_shw_register_user(fsl_shw_uco_t * user_ctx) +{ + fsl_shw_return_t code = FSL_RETURN_INTERNAL_ERROR_S; + + if ((user_ctx->flags & FSL_UCO_BLOCKING_MODE) && + (user_ctx->flags & FSL_UCO_CALLBACK_MODE)) { + code = FSL_RETURN_BAD_FLAG_S; + goto error_exit; + } else if (user_ctx->pool_size == 0) { + code = FSL_RETURN_NO_RESOURCE_S; + goto error_exit; + } else { + user_ctx->result_pool.head = NULL; + user_ctx->result_pool.tail = NULL; + SHW_ADD_USER(user_ctx); + code = FSL_RETURN_OK_S; + } + + error_exit: + return code; +} + +#ifdef LINUX_VERSION_CODE +EXPORT_SYMBOL(fsl_shw_deregister_user); +#endif +/* REQ-S2LRD-PINTFC-API-GEN-005 */ +/*! + * Destroy the association between the the user and the provider of the API. + * + * @param user_ctx The user context which is no longer needed. + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t fsl_shw_deregister_user(fsl_shw_uco_t * user_ctx) +{ + shw_queue_entry_t *finished_request; + + /* Clean up what we find in result pool. */ + do { + os_lock_context_t lock_context; + os_lock_save_context(shw_queue_lock, lock_context); + finished_request = user_ctx->result_pool.head; + + if (finished_request != NULL) { + SHW_QUEUE_REMOVE_ENTRY(&user_ctx->result_pool, + finished_request); + os_unlock_restore_context(shw_queue_lock, lock_context); + os_free_memory(finished_request); + } else { + os_unlock_restore_context(shw_queue_lock, lock_context); + } + } while (finished_request != NULL); + + SHW_REMOVE_USER(user_ctx); + + return FSL_RETURN_OK_S; +} + +#ifdef LINUX_VERSION_CODE +EXPORT_SYMBOL(fsl_shw_get_results); +#endif +/* REQ-S2LRD-PINTFC-API-GEN-006 */ +fsl_shw_return_t fsl_shw_get_results(fsl_shw_uco_t * user_ctx, + unsigned result_size, + fsl_shw_result_t results[], + unsigned *result_count) +{ + shw_queue_entry_t *finished_request; + unsigned loop = 0; + + do { + os_lock_context_t lock_context; + + /* Protect state of user's result pool until we have retrieved and + * remove the first entry, or determined that the pool is empty. */ + os_lock_save_context(shw_queue_lock, lock_context); + finished_request = user_ctx->result_pool.head; + + if (finished_request != NULL) { + uint32_t code = 0; + + SHW_QUEUE_REMOVE_ENTRY(&user_ctx->result_pool, + finished_request); + os_unlock_restore_context(shw_queue_lock, lock_context); + + results[loop].user_ref = finished_request->user_ref; + results[loop].code = finished_request->code; + results[loop].detail1 = 0; + results[loop].detail2 = 0; + results[loop].user_req = + finished_request->user_mode_req; + if (finished_request->postprocess != NULL) { + code = + finished_request-> + postprocess(finished_request); + } + + results[loop].code = finished_request->code; + os_free_memory(finished_request); + if (code == 0) { + loop++; + } + } else { /* finished_request is NULL */ + /* pool is empty */ + os_unlock_restore_context(shw_queue_lock, lock_context); + } + + } while ((loop < result_size) && (finished_request != NULL)); + + *result_count = loop; + + return FSL_RETURN_OK_S; +} + +#ifdef LINUX_VERSION_CODE +EXPORT_SYMBOL(fsl_shw_get_capabilities); +#endif +fsl_shw_pco_t *fsl_shw_get_capabilities(fsl_shw_uco_t * user_ctx) +{ + + /* Unused */ + (void)user_ctx; + + return ∩ +} + +#if !(defined(FSL_HAVE_SAHARA) || defined(FSL_HAVE_RNGA) \ + || defined(FSL_HAVE_RNGC)) + +fsl_shw_return_t fsl_shw_get_random(fsl_shw_uco_t * user_ctx, + uint32_t length, uint8_t * data) +{ + + /* Unused */ + (void)user_ctx; + (void)length; + (void)data; + + return FSL_RETURN_ERROR_S; +} + +fsl_shw_return_t fsl_shw_add_entropy(fsl_shw_uco_t * user_ctx, + uint32_t length, uint8_t * data) +{ + + /* Unused */ + (void)user_ctx; + (void)length; + (void)data; + + return FSL_RETURN_ERROR_S; +} + +EXPORT_SYMBOL(fsl_shw_add_entropy); +EXPORT_SYMBOL(fsl_shw_get_random); + +#endif + +#ifndef FSL_HAVE_SAHARA +#ifdef LINUX_VERSION_CODE +EXPORT_SYMBOL(fsl_shw_symmetric_decrypt); +#endif +fsl_shw_return_t fsl_shw_symmetric_decrypt(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + fsl_shw_scco_t * sym_ctx, + uint32_t length, + const uint8_t * ct, uint8_t * pt) +{ + + /* Unused */ + (void)user_ctx; + (void)key_info; + (void)sym_ctx; + (void)length; + (void)ct; + (void)pt; + + return FSL_RETURN_ERROR_S; +} + +#ifdef LINUX_VERSION_CODE +EXPORT_SYMBOL(fsl_shw_symmetric_encrypt); +#endif +fsl_shw_return_t fsl_shw_symmetric_encrypt(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + fsl_shw_scco_t * sym_ctx, + uint32_t length, + const uint8_t * pt, uint8_t * ct) +{ + + /* Unused */ + (void)user_ctx; + (void)key_info; + (void)sym_ctx; + (void)length; + (void)pt; + (void)ct; + + return FSL_RETURN_ERROR_S; +} + +#ifdef LINUX_VERSION_CODE +EXPORT_SYMBOL(fsl_shw_establish_key); +#endif +fsl_shw_return_t fsl_shw_establish_key(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + fsl_shw_key_wrap_t establish_type, + const uint8_t * key) +{ + + /* Unused */ + (void)user_ctx; + (void)key_info; + (void)establish_type; + (void)key; + + return FSL_RETURN_ERROR_S; +} + +#ifdef LINUX_VERSION_CODE +EXPORT_SYMBOL(fsl_shw_extract_key); +#endif +fsl_shw_return_t fsl_shw_extract_key(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + uint8_t * covered_key) +{ + + /* Unused */ + (void)user_ctx; + (void)key_info; + (void)covered_key; + + return FSL_RETURN_ERROR_S; +} + +#ifdef LINUX_VERSION_CODE +EXPORT_SYMBOL(fsl_shw_release_key); +#endif +fsl_shw_return_t fsl_shw_release_key(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info) +{ + + /* Unused */ + (void)user_ctx; + (void)key_info; + + return FSL_RETURN_ERROR_S; +} +#endif + +#ifdef LINUX_VERSION_CODE +EXPORT_SYMBOL(fsl_shw_hash); +#endif +#if !defined(FSL_HAVE_SAHARA) +fsl_shw_return_t fsl_shw_hash(fsl_shw_uco_t * user_ctx, + fsl_shw_hco_t * hash_ctx, + const uint8_t * msg, + uint32_t length, + uint8_t * result, uint32_t result_len) +{ + fsl_shw_return_t ret = FSL_RETURN_ERROR_S; + + /* Unused */ + (void)user_ctx; + (void)hash_ctx; + (void)msg; + (void)length; + (void)result; + (void)result_len; + + return ret; +} +#endif + +#ifndef FSL_HAVE_SAHARA +#ifdef LINUX_VERSION_CODE +EXPORT_SYMBOL(fsl_shw_hmac_precompute); +#endif + +fsl_shw_return_t fsl_shw_hmac_precompute(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + fsl_shw_hmco_t * hmac_ctx) +{ + fsl_shw_return_t status = FSL_RETURN_ERROR_S; + + /* Unused */ + (void)user_ctx; + (void)key_info; + (void)hmac_ctx; + + return status; +} + +#ifdef LINUX_VERSION_CODE +EXPORT_SYMBOL(fsl_shw_hmac); +#endif + +fsl_shw_return_t fsl_shw_hmac(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + fsl_shw_hmco_t * hmac_ctx, + const uint8_t * msg, + uint32_t length, + uint8_t * result, uint32_t result_len) +{ + fsl_shw_return_t status = FSL_RETURN_ERROR_S; + + /* Unused */ + (void)user_ctx; + (void)key_info; + (void)hmac_ctx; + (void)msg; + (void)length; + (void)result; + (void)result_len; + + return status; +} +#endif + +#ifndef FSL_HAVE_SAHARA +#ifdef LINUX_VERSION_CODE +EXPORT_SYMBOL(fsl_shw_gen_encrypt); +#endif + +fsl_shw_return_t fsl_shw_gen_encrypt(fsl_shw_uco_t * user_ctx, + fsl_shw_acco_t * auth_ctx, + fsl_shw_sko_t * cipher_key_info, + fsl_shw_sko_t * auth_key_info, + uint32_t auth_data_length, + const uint8_t * auth_data, + uint32_t payload_length, + const uint8_t * payload, + uint8_t * ct, uint8_t * auth_value) +{ + volatile fsl_shw_return_t status = FSL_RETURN_ERROR_S; + + /* Unused */ + (void)user_ctx; + (void)auth_ctx; + (void)cipher_key_info; + (void)auth_key_info; /* save compilation warning */ + (void)auth_data_length; + (void)auth_data; + (void)payload_length; + (void)payload; + (void)ct; + (void)auth_value; + + return status; +} + +#ifdef LINUX_VERSION_CODE +EXPORT_SYMBOL(fsl_shw_auth_decrypt); +#endif +/*! + * @brief Authenticate and decrypt a (CCM) stream. + * + * @param user_ctx The user's context + * @param auth_ctx Info on this Auth operation + * @param cipher_key_info Key to encrypt payload + * @param auth_key_info (unused - same key in CCM) + * @param auth_data_length Length in bytes of @a auth_data + * @param auth_data Any auth-only data + * @param payload_length Length in bytes of @a payload + * @param ct The encrypted data + * @param auth_value The authentication code to validate + * @param payload The location to store decrypted data + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t fsl_shw_auth_decrypt(fsl_shw_uco_t * user_ctx, + fsl_shw_acco_t * auth_ctx, + fsl_shw_sko_t * cipher_key_info, + fsl_shw_sko_t * auth_key_info, + uint32_t auth_data_length, + const uint8_t * auth_data, + uint32_t payload_length, + const uint8_t * ct, + const uint8_t * auth_value, + uint8_t * payload) +{ + volatile fsl_shw_return_t status = FSL_RETURN_ERROR_S; + + /* Unused */ + (void)user_ctx; + (void)auth_ctx; + (void)cipher_key_info; + (void)auth_key_info; /* save compilation warning */ + (void)auth_data_length; + (void)auth_data; + (void)payload_length; + (void)ct; + (void)auth_value; + (void)payload; + + return status; +} +#endif diff --git a/drivers/mxc/security/sahara2/Kconfig b/drivers/mxc/security/sahara2/Kconfig new file mode 100644 index 000000000000..ab4e6fdc2cfc --- /dev/null +++ b/drivers/mxc/security/sahara2/Kconfig @@ -0,0 +1,35 @@ +menu "SAHARA2 Security Hardware Support" + +config MXC_SAHARA + tristate "Security Hardware Support (FSL SHW)" + ---help--- + Provides driver and kernel mode API for using cryptographic + accelerators. + +config MXC_SAHARA_USER_MODE + tristate "User Mode API for FSL SHW" + depends on MXC_SAHARA + ---help--- + Provides kernel driver for User Mode API. + +config MXC_SAHARA_POLL_MODE + bool "Force driver to POLL for hardware completion." + depends on MXC_SAHARA + default n + ---help--- + When this flag is yes, the driver will not use interrupts to + determine when the hardware has completed a task, but instead + will hold onto the CPU and continually poll the hardware until + it completes. + +config MXC_SAHARA_POLL_MODE_TIMEOUT + hex "Poll loop timeout" + depends on MXC_SAHARA_POLL_MODE + default "0xFFFFFFFF" + help + To avoid infinite polling, a timeout is provided. Should the + timeout be reached, a fault is reported, indicating there must + be something wrong with SAHARA, and SAHARA is reset. The loop + will exit after the given number of iterations. + +endmenu diff --git a/drivers/mxc/security/sahara2/Makefile b/drivers/mxc/security/sahara2/Makefile new file mode 100644 index 000000000000..6dd609eb378d --- /dev/null +++ b/drivers/mxc/security/sahara2/Makefile @@ -0,0 +1,47 @@ +# Makefile for the Linux Sahara2 driver +# +# This makefile works within a kernel driver tree + +# Need to augment this to support optionally building user-mode support +API_SOURCES = fsl_shw_sym.c fsl_shw_user.c fsl_shw_hash.c fsl_shw_auth.c \ + fsl_shw_hmac.c fsl_shw_rand.c sf_util.c km_adaptor.c \ + fsl_shw_wrap.c \ + + +SOURCES = sah_driver_interface.c sah_hardware_interface.c \ + sah_interrupt_handler.c sah_queue.c sah_queue_manager.c \ + sah_status_manager.c sah_memory_mapper.c + + +# Turn on for mostly full debugging +# DIAGS = -DDIAG_DRV_STATUS -DDIAG_DRV_QUEUE -DDIAG_DRV_INTERRUPT -DDIAG_DRV_IF +# DIAGS += -DDIAG_DURING_INTERRUPT + +# Turn on for lint-type checking +#EXTRA_CFLAGS = -Wall -W -Wstrict-prototypes -Wmissing-prototypes +EXTRA_CFLAGS += -DLINUX_KERNEL $(DIAGS) + + +ifeq ($(CONFIG_MXC_SAHARA_POLL_MODE),y) +EXTRA_CFLAGS += -DSAHARA_POLL_MODE +EXTRA_CFLAGS += -DSAHARA_POLL_MODE_TIMEOUT=$(CONFIG_SAHARA_POLL_MODE_TIMEOUT) +endif + +ifeq ($(CONFIG_MXC_SAHARA_USER_MODE),y) +EXTRA_CFLAGS += -DSAHARA_USER_MODE +SOURCES += +endif + +ifeq ($(CONFIG_PM),y) +EXTRA_CFLAGS += -DSAHARA_POWER_MANAGMENT +endif + +EXTRA_CFLAGS += -Idrivers/mxc/security/sahara2/include + +# handle buggy BSP -- uncomment if these are undefined during build +#EXTRA_CFLAGS += -DSAHARA_BASE_ADDR=HAC_BASE_ADDR -DINT_SAHARA=INT_HAC_RTIC + + +obj-$(CONFIG_MXC_SAHARA) += sahara.o + +sahara-objs := $(SOURCES:.c=.o) $(API_SOURCES:.c=.o) diff --git a/drivers/mxc/security/sahara2/fsl_shw_auth.c b/drivers/mxc/security/sahara2/fsl_shw_auth.c new file mode 100644 index 000000000000..881cee9711cd --- /dev/null +++ b/drivers/mxc/security/sahara2/fsl_shw_auth.c @@ -0,0 +1,792 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/*! + * @file fsl_shw_auth.c + * + * This file contains the routines which do the combined encrypt+authentication + * functions. For now, only AES-CCM is supported. + */ + +#include "sahara.h" +#include "adaptor.h" +#include "sf_util.h" + +#ifdef __KERNEL__ +EXPORT_SYMBOL(fsl_shw_gen_encrypt); +EXPORT_SYMBOL(fsl_shw_auth_decrypt); +#endif + +/* Remove warning */ +#define SUPPORT_SSL 0 + + +/*! Size of buffer to repetively sink useless CBC output */ +#define CBC_BUF_LEN 4096 + +/*! + * Compute the size, in bytes, of the encoded auth length + * + * @param l The actual auth length + * + * @return The encoded length + */ +#define COMPUTE_NIST_AUTH_LEN_SIZE(l) \ +({ \ + unsigned val; \ + if ((uint32_t)(l) < 65280) { \ + val = 2; \ + } else { /* cannot handle >= 2^32 */ \ + val = 6; \ + } \ + val; \ +}) + +/*! + * Store the encoded Auth Length into the Auth Data + * + * @param l The actual Auth Length + * @param p Location to store encoding (must be uint8_t*) + * + * @return void + */ +#define STORE_NIST_AUTH_LEN(l, p) \ +{ \ + register uint32_t L = l; \ + if ((uint32_t)(l) < 65280) { \ + (p)[1] = L & 0xff; \ + L >>= 8; \ + (p)[0] = L & 0xff; \ + } else { /* cannot handle >= 2^32 */ \ + int i; \ + for (i = 5; i > 1; i--) { \ + (p)[i] = L & 0xff; \ + L >>= 8; \ + } \ + (p)[1] = 0xfe; /* Markers */ \ + (p)[0] = 0xff; \ + } \ +} + +/*! Buffer to repetively sink useless CBC output */ +static uint8_t cbc_buffer[CBC_BUF_LEN]; + +/*! + * Place to store useless output (while bumping CTR0 to CTR1, for instance. + * Must be maximum Symmetric block size + */ +static uint8_t garbage_output[16]; + +/*! + * Block of zeroes which is maximum Symmetric block size, used for + * initializing context register, etc. + */ +static uint8_t block_zeros[16] = { + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0 +}; + +/*! + * Append a descriptor which will load the key and counter values into + * Sahara. + * + * @param[in,out] desc_chain Where to append the new descriptor + * @param user_ctx Info for acquiring memory + * @param auth_ctx Location of CTR value + * @param key_info Location of the key + * + * @return A return code of type #fsl_shw_return_t. + */ +static inline fsl_shw_return_t load_ctr_key(sah_Head_Desc ** desc_chain, + fsl_shw_uco_t * user_ctx, + fsl_shw_acco_t * auth_ctx, + fsl_shw_sko_t * key_info) +{ + fsl_shw_return_t status; + + /* Assume AES */ + uint32_t header = SAH_HDR_SKHA_SET_MODE_IV_KEY + ^ sah_insert_skha_encrypt + ^ sah_insert_skha_mode_ctr ^ sah_insert_skha_modulus_128; + + /* Assume CCM-AES for now, since that is all that is supported */ + status = sah_add_in_key_desc(header, + auth_ctx->cipher_ctx_info.context, + auth_ctx->cipher_ctx_info.block_size_bytes, + key_info, user_ctx->mem_util, desc_chain); + return status; +} + +/*! + * Append a descriptor which will load zeros into the CBC CTX registers. + * It also sets the mode to CBC. + * + * @param[in,out] desc_chain Where to append the new descriptor + * @param user_ctx Info for acquiring memory + * @param encrypt 0 for decrypt, non-zero for encrypt + * @param size Octet count of @a in and @a out + * + * @return A return code of type #fsl_shw_return_t. + */ +static inline fsl_shw_return_t load_dummy_iv(sah_Head_Desc ** desc_chain, + fsl_shw_uco_t * user_ctx, + int encrypt, uint32_t size) +{ + fsl_shw_return_t status; + /* Desc. #1. in CBC mode. Assume /AES. */ + uint32_t header = SAH_HDR_SKHA_SET_MODE_IV_KEY + ^ sah_insert_skha_mode_cbc; + + if (encrypt) { + header ^= sah_insert_skha_encrypt; + } + + status = sah_add_two_in_desc(header, block_zeros, size, NULL, 0, + user_ctx->mem_util, desc_chain); + + return status; +} + +/*! + * Append a descriptor chain which will compute CBC over the + * formatted associated data blocks. + * + * @param[in,out] link1 Where to append the new link + * @param[in,out] data_len Location of current/updated auth-only data length + * @param user_ctx Info for acquiring memory + * @param auth_ctx Location of block0 value + * @param auth_data Unformatted associated data + * @param auth_data_length Length in octets of @a auth_data + * @param[in,out] temp_buf Location of in-process data. + * + * @return A return code of type #fsl_shw_return_t. + */ +static inline fsl_shw_return_t process_assoc_from_nist_params(sah_Link ** link1, + uint32_t * + data_len, + fsl_shw_uco_t * + user_ctx, + fsl_shw_acco_t * + auth_ctx, + const uint8_t * + auth_data, + uint32_t + auth_data_length, + uint8_t ** + temp_buf) +{ + fsl_shw_return_t status; + uint32_t auth_size_length = + COMPUTE_NIST_AUTH_LEN_SIZE(auth_data_length); + uint32_t auth_pad_length = + auth_ctx->auth_info.CCM_ctx_info.block_size_bytes - + (auth_data_length + + auth_size_length) % + auth_ctx->auth_info.CCM_ctx_info.block_size_bytes; + + if (auth_pad_length == + auth_ctx->auth_info.CCM_ctx_info.block_size_bytes) { + auth_pad_length = 0; + } + + /* Put in Block0 */ + status = sah_Create_Link(user_ctx->mem_util, link1, + auth_ctx->auth_info.CCM_ctx_info.context, + auth_ctx->auth_info.CCM_ctx_info. + block_size_bytes, SAH_USES_LINK_DATA); + + if (status == FSL_RETURN_OK_S) { + /* Add on length preamble to auth data */ + STORE_NIST_AUTH_LEN(auth_data_length, *temp_buf); + status = sah_Append_Link(user_ctx->mem_util, *link1, + *temp_buf, auth_size_length, + SAH_OWNS_LINK_DATA); + *temp_buf += auth_size_length; /* 2, 6, or 10 bytes */ + } + + if (status == FSL_RETURN_OK_S) { + /* Add in auth data */ + status = sah_Append_Link(user_ctx->mem_util, *link1, + (uint8_t *) auth_data, + auth_data_length, SAH_USES_LINK_DATA); + } + + if ((status == FSL_RETURN_OK_S) && (auth_pad_length > 0)) { + status = sah_Append_Link(user_ctx->mem_util, *link1, + block_zeros, auth_pad_length, + SAH_USES_LINK_DATA); + } + + *data_len = auth_ctx->auth_info.CCM_ctx_info.block_size_bytes + + auth_data_length + auth_size_length + auth_pad_length; + + return status; +} + +/*! + * Append a descriptor chain which will process the payload in + * CCM mode. + * + * @param[in,out] desc_chain Where to append the new descriptor + * @param user_ctx Info for acquiring memory + * @param encrypt 0 for decrypt, non-zero for encrypt + * @param size Length in octets of @a input and @a output + * @param input Location of source data + * @param[out] output Location to store output data + * + * @return A return code of type #fsl_shw_return_t. + */ +static inline fsl_shw_return_t process_payload(sah_Head_Desc ** desc_chain, + fsl_shw_uco_t * user_ctx, + int encrypt, + uint32_t size, + const uint8_t * input, + uint8_t * output) +{ + fsl_shw_return_t status; + /* Assume AES */ + uint32_t header = SAH_HDR_SKHA_SET_MODE_ENC_DEC + ^ sah_insert_skha_mode_ccm ^ sah_insert_skha_modulus_128; + + if (encrypt) { + header ^= sah_insert_skha_encrypt; + } + + status = sah_add_in_out_desc(header, input, size, output, size, + user_ctx->mem_util, desc_chain); + + return status; +} + +/*! + * Add a Descriptor which will process with CBC the NIST preamble data + * + * @param desc_chain Current chain + * @param user_ctx User's context + * @param auth_ctx Inf + * @param auth_data Additional auth data for this call + * @param auth_data_length Length in bytes of @a auth_data + * + * @return A return code of type #fsl_shw_return_t. + */ +static inline fsl_shw_return_t add_assoc_preamble(sah_Head_Desc ** desc_chain, + fsl_shw_uco_t * user_ctx, + fsl_shw_acco_t * auth_ctx, + const uint8_t * auth_data, + uint32_t auth_data_length) +{ + uint8_t *temp_buf; + sah_Link *link1 = NULL; + sah_Link *link2 = NULL; + fsl_shw_return_t status = FSL_RETURN_OK_S; + uint32_t cbc_data_length = 0; + /* Assume AES */ + uint32_t header = SAH_HDR_SKHA_ENC_DEC; + + /* Grab a block big enough for multiple uses so that only one allocate + * request needs to be made. + */ + temp_buf = + user_ctx->mem_util->mu_malloc(user_ctx->mem_util->mu_ref, + 3 * + auth_ctx->auth_info.CCM_ctx_info. + block_size_bytes); + + if (temp_buf == NULL) { + status = FSL_RETURN_NO_RESOURCE_S; + } else { + uint32_t temp_buf_flag; + + if (auth_ctx->flags & FSL_ACCO_NIST_CCM) { + status = process_assoc_from_nist_params(&link1, + &cbc_data_length, + user_ctx, + auth_ctx, + auth_data, + auth_data_length, + &temp_buf); + /* temp_buf has been referenced (and incremented). Only 'own' it + * once, at its first value. Since the nist routine called above + * bumps it... + */ + temp_buf_flag = SAH_USES_LINK_DATA; + } else { /* if NIST */ + if (status == FSL_RETURN_OK_S) { + status = + sah_Create_Link(user_ctx->mem_util, &link1, + (uint8_t *) auth_data, + auth_data_length, + SAH_USES_LINK_DATA); + /* for next/first use of temp_buf */ + temp_buf_flag = SAH_OWNS_LINK_DATA; + } + cbc_data_length = auth_data_length; + } /* else not NIST */ + + /* + * Auth data links have been created. Now create link for the + * useless output of the CBC calculation. + */ + if (status == FSL_RETURN_OK_S) { + status = sah_Create_Link(user_ctx->mem_util, &link2, + temp_buf, + auth_ctx->auth_info. + CCM_ctx_info.block_size_bytes, + temp_buf_flag | + SAH_OUTPUT_LINK); + } + temp_buf += auth_ctx->auth_info.CCM_ctx_info.block_size_bytes; + + cbc_data_length -= + auth_ctx->auth_info.CCM_ctx_info.block_size_bytes; + + if (cbc_data_length != 0) { + while ((status == FSL_RETURN_OK_S) + && (cbc_data_length != 0)) { + uint32_t linklen = + (cbc_data_length > + CBC_BUF_LEN) ? CBC_BUF_LEN : + cbc_data_length; + + status = + sah_Append_Link(user_ctx->mem_util, link2, + cbc_buffer, linklen, + SAH_USES_LINK_DATA | + SAH_OUTPUT_LINK); + cbc_data_length -= linklen; + } + } + + if (status != FSL_RETURN_OK_S) { + if (link1 != NULL) { + sah_Destroy_Link(user_ctx->mem_util, link1); + } + if (link2 != NULL) { + sah_Destroy_Link(user_ctx->mem_util, link2); + } + } else { + /* Header to set up crank through auth data */ + status = sah_Append_Desc(user_ctx->mem_util, desc_chain, + header, link1, link2); + } + } + + return status; +} + +/*! + * Append a descriptor chain which will pull the MAC (CBC IV) out of the + * hardware registers. + * + * @param[in,out] desc_chain Where to append the new descriptor + * @param user_ctx Info for acquiring memory + * @param size Length in octets of desired MAC + * @param[out] mac Location to store the MAC + * + * @return A return code of type #fsl_shw_return_t. + */ +static inline fsl_shw_return_t extract_mac(sah_Head_Desc ** desc_chain, + fsl_shw_uco_t * user_ctx, + uint32_t size, uint8_t * mac) +{ + fsl_shw_return_t status; + uint32_t header = SAH_HDR_SKHA_READ_CONTEXT_IV; + + /* Pull MAC out of IV register */ + status = sah_add_in_out_desc(header, NULL, 0, mac, size, + user_ctx->mem_util, desc_chain); + + return status; +} + +/*! + * Append a descriptor chain which will (encrypt) the MAC with CTR0. + * Since CTR mode works both ways, the input could be an encrypted + * MAC with the output being the decrypted version. + * + * @param[in,out] desc_chain Where to append the new descriptor + * @param user_ctx Info for acquiring memory + * @param auth_ctx Info for CTR0, size of MAC + * @param input Location of MAC + * @param output Location to store (encrypted) MAC. + * + * @return A return code of type #fsl_shw_return_t. + */ +static inline fsl_shw_return_t encrypt_mac(sah_Head_Desc ** desc_chain, + fsl_shw_uco_t * user_ctx, + fsl_shw_acco_t * auth_ctx, + const uint8_t * input, + uint8_t * output) +{ + fsl_shw_return_t status; + /* Assuming AES here... */ + uint32_t header = SAH_HDR_SKHA_SET_MODE_IV_KEY + ^ sah_insert_skha_encrypt + ^ sah_insert_skha_mode_ctr ^ sah_insert_skha_modulus_128; + + /* Load CTR0 back in */ + status = sah_add_two_in_desc(header, + auth_ctx->cipher_ctx_info.context, + auth_ctx->cipher_ctx_info.block_size_bytes, + NULL, 0, user_ctx->mem_util, desc_chain); + + if (status == FSL_RETURN_OK_S) { + /* and encrypt the input as the auth value */ + header = SAH_HDR_SKHA_ENC_DEC; /* Desc. #4 SKHA Enc/Dec */ + + status = sah_add_in_out_desc(header, + input, auth_ctx->mac_length, + output, auth_ctx->mac_length, + user_ctx->mem_util, desc_chain); + } + + return status; +} + +#if SUPPORT_SSL +/*! + * Generate an SSL value + * + * @param user_ctx Info for acquiring memory + * @param auth_ctx Info for CTR0, size of MAC + * @param cipher_key_info + * @param auth_key_info + * @param auth_data_length + * @param auth_data + * @param payload_length + * @param payload + * @param ct + * @param auth_value + * + * @return A return code of type #fsl_shw_return_t. + */ +static fsl_shw_return_t do_ssl_gen(fsl_shw_uco_t * user_ctx, + fsl_shw_acco_t * auth_ctx, + fsl_shw_sko_t * cipher_key_info, + fsl_shw_sko_t * auth_key_info, + uint32_t auth_data_length, + const uint8_t * auth_data, + uint32_t payload_length, + const uint8_t * payload, + uint8_t * ct, uint8_t * auth_value) +{ + SAH_SF_DCLS; + uint8_t *ptr1 = NULL; + + /* Assume one-shot init-finalize... no precomputes */ + header = SAH_HDR_MDHA_SET_MODE_MD_KEY ^ + sah_insert_mdha_algorithm[auth_ctx->auth_info.hash_ctx_info. + algorithm] ^ sah_insert_mdha_init ^ + sah_insert_mdha_ssl ^ sah_insert_mdha_pdata ^ + sah_insert_mdha_mac_full; + + /* set up hmac */ + DESC_IN_KEY(header, 0, NULL, auth_key_info); + + /* This is wrong -- need to find 16 extra bytes of data from + * somewhere */ + DESC_IN_OUT(SAH_HDR_MDHA_HASH, payload_length, payload, 1, auth_value); + + /* set up encrypt */ + header = SAH_HDR_SKHA_SET_MODE_IV_KEY + ^ sah_insert_skha_mode[auth_ctx->cipher_ctx_info.mode] + ^ sah_insert_skha_encrypt + ^ sah_insert_skha_algorithm[cipher_key_info->algorithm]; + + /* Honor 'no key parity checking' for DES and TDES */ + if ((cipher_key_info->flags & FSL_SKO_KEY_IGNORE_PARITY) && + ((cipher_key_info->algorithm == FSL_KEY_ALG_DES) || + (cipher_key_info->algorithm == FSL_KEY_ALG_TDES))) { + header ^= sah_insert_skha_no_key_parity; + } + + if (auth_ctx->cipher_ctx_info.mode == FSL_SYM_MODE_CTR) { + header ^= + sah_insert_skha_modulus[auth_ctx->cipher_ctx_info. + modulus_exp]; + } + + if ((auth_ctx->cipher_ctx_info.mode == FSL_SYM_MODE_ECB) + || (auth_ctx->cipher_ctx_info.flags & FSL_SYM_CTX_INIT)) { + ptr1 = block_zeros; + } else { + ptr1 = auth_ctx->cipher_ctx_info.context; + } + + DESC_IN_KEY(header, auth_ctx->cipher_ctx_info.block_size_bytes, ptr1, + cipher_key_info); + + /* This is wrong -- need to find 16 extra bytes of data from + * somewhere... + */ + if (payload_length != 0) { + DESC_IN_OUT(SAH_HDR_SKHA_ENC_DEC, + payload_length, payload, payload_length, ct); + } + + SAH_SF_EXECUTE(); + + out: + SAH_SF_DESC_CLEAN(); + + /* Eliminate compiler warnings until full implementation... */ + (void)auth_data; + (void)auth_data_length; + + return ret; +} /* do_ssl_gen() */ +#endif + +/*! + * @brief Generate a (CCM) auth code and encrypt the payload. + * + * This is a very complicated function. Seven (or eight) descriptors are + * required to perform a CCM calculation. + * + * First: Load CTR0 and key. + * + * Second: Run an octet of data through to bump to CTR1. (This could be + * done in software, but software will have to bump and later decrement - + * or copy and bump. + * + * Third: (in Virtio) Load a descriptor with data of zeros for CBC IV. + * + * Fourth: Run any (optional) "additional data" through the CBC-mode + * portion of the algorithm. + * + * Fifth: Run the payload through in CCM mode. + * + * Sixth: Extract the unencrypted MAC. + * + * Seventh: Load CTR0. + * + * Eighth: Encrypt the MAC. + * + * @param user_ctx The user's context + * @param auth_ctx Info on this Auth operation + * @param cipher_key_info Key to encrypt payload + * @param auth_key_info (unused - same key in CCM) + * @param auth_data_length Length in bytes of @a auth_data + * @param auth_data Any auth-only data + * @param payload_length Length in bytes of @a payload + * @param payload The data to encrypt + * @param[out] ct The location to store encrypted data + * @param[out] auth_value The location to store authentication code + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t fsl_shw_gen_encrypt(fsl_shw_uco_t * user_ctx, + fsl_shw_acco_t * auth_ctx, + fsl_shw_sko_t * cipher_key_info, + fsl_shw_sko_t * auth_key_info, + uint32_t auth_data_length, + const uint8_t * auth_data, + uint32_t payload_length, + const uint8_t * payload, + uint8_t * ct, uint8_t * auth_value) +{ + SAH_SF_DCLS; + + SAH_SF_USER_CHECK(); + + if (auth_ctx->mode == FSL_ACC_MODE_SSL) { +#if SUPPORT_SSL + ret = do_ssl_gen(user_ctx, auth_ctx, cipher_key_info, + auth_key_info, auth_data_length, auth_data, + payload_length, payload, ct, auth_value); +#else + ret = FSL_RETURN_BAD_MODE_S; +#endif + return ret; + } + + if (auth_ctx->mode != FSL_ACC_MODE_CCM) { + ret = FSL_RETURN_BAD_MODE_S; + goto out; + } + + /* Only support INIT and FINALIZE flags right now. */ + if ((auth_ctx->flags & (FSL_ACCO_CTX_INIT | FSL_ACCO_CTX_LOAD | + FSL_ACCO_CTX_SAVE | FSL_ACCO_CTX_FINALIZE)) + != (FSL_ACCO_CTX_INIT | FSL_ACCO_CTX_FINALIZE)) { + ret = FSL_RETURN_BAD_FLAG_S; + goto out; + } + ret = load_ctr_key(&desc_chain, user_ctx, auth_ctx, cipher_key_info); + if (ret != FSL_RETURN_OK_S) { + goto out; + } + + header = SAH_HDR_SKHA_ENC_DEC; + DESC_IN_OUT(header, auth_ctx->cipher_ctx_info.block_size_bytes, + garbage_output, auth_ctx->cipher_ctx_info.block_size_bytes, + garbage_output); + +#ifndef NO_ZERO_IV_LOAD + ret = load_dummy_iv(&desc_chain, user_ctx, + 1, + auth_ctx->auth_info.CCM_ctx_info.block_size_bytes); + if (ret != FSL_RETURN_OK_S) { + goto out; + } +#endif + + if (auth_data_length > 0) { + ret = add_assoc_preamble(&desc_chain, user_ctx, + auth_ctx, auth_data, auth_data_length); + if (ret != FSL_RETURN_OK_S) { + goto out; + } + } + /* if auth_data_length > 0 */ + ret = process_payload(&desc_chain, user_ctx, 1, + payload_length, payload, ct); + + if (ret != FSL_RETURN_OK_S) { + goto out; + } + + /* Pull out the CBC-MAC value. */ + ret = extract_mac(&desc_chain, user_ctx, + auth_ctx->mac_length, auth_ctx->unencrypted_mac); + if (ret != FSL_RETURN_OK_S) { + goto out; + } + + /* Now load CTR0 in, and encrypt the MAC */ + ret = encrypt_mac(&desc_chain, user_ctx, auth_ctx, + auth_ctx->unencrypted_mac, auth_value); + if (ret != FSL_RETURN_OK_S) { + goto out; + } + + SAH_SF_EXECUTE(); + + out: + SAH_SF_DESC_CLEAN(); + + (void)auth_key_info; + return ret; +} /* fsl_shw_gen_encrypt() */ + +/*! + * @brief Authenticate and decrypt a (CCM) stream. + * + * @param user_ctx The user's context + * @param auth_ctx Info on this Auth operation + * @param cipher_key_info Key to encrypt payload + * @param auth_key_info (unused - same key in CCM) + * @param auth_data_length Length in bytes of @a auth_data + * @param auth_data Any auth-only data + * @param payload_length Length in bytes of @a payload + * @param ct The encrypted data + * @param auth_value The authentication code to validate + * @param[out] payload The location to store decrypted data + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t fsl_shw_auth_decrypt(fsl_shw_uco_t * user_ctx, + fsl_shw_acco_t * auth_ctx, + fsl_shw_sko_t * cipher_key_info, + fsl_shw_sko_t * auth_key_info, + uint32_t auth_data_length, + const uint8_t * auth_data, + uint32_t payload_length, + const uint8_t * ct, + const uint8_t * auth_value, + uint8_t * payload) +{ + SAH_SF_DCLS; + uint8_t *calced_auth = NULL; + unsigned blocking = user_ctx->flags & FSL_UCO_BLOCKING_MODE; + + SAH_SF_USER_CHECK(); + + /* Only support INIT and FINALIZE flags right now. */ + if (auth_ctx->mode != FSL_ACC_MODE_CCM) { + ret = FSL_RETURN_BAD_MODE_S; + goto out; + } + if ((auth_ctx->flags & (FSL_ACCO_CTX_INIT | FSL_ACCO_CTX_LOAD | + FSL_ACCO_CTX_SAVE | FSL_ACCO_CTX_FINALIZE)) + != (FSL_ACCO_CTX_INIT | FSL_ACCO_CTX_FINALIZE)) { + ret = FSL_RETURN_BAD_FLAG_S; + goto out; + } + ret = load_ctr_key(&desc_chain, user_ctx, auth_ctx, cipher_key_info); + if (ret != FSL_RETURN_OK_S) { + goto out; + } + + /* Decrypt the MAC which the user passed in */ + header = SAH_HDR_SKHA_ENC_DEC; + DESC_IN_OUT(header, + auth_ctx->mac_length, auth_value, + auth_ctx->mac_length, auth_ctx->unencrypted_mac); + +#ifndef NO_ZERO_IV_LOAD + ret = load_dummy_iv(&desc_chain, user_ctx, 1, + auth_ctx->auth_info.CCM_ctx_info.block_size_bytes); +#endif + + if (auth_data_length > 0) { + ret = add_assoc_preamble(&desc_chain, user_ctx, + auth_ctx, auth_data, auth_data_length); + if (ret != FSL_RETURN_OK_S) { + goto out; + } + } + /* if auth_data_length > 0 */ + ret = process_payload(&desc_chain, user_ctx, 0, + payload_length, ct, payload); + if (ret != FSL_RETURN_OK_S) { + goto out; + } + + /* Now pull CBC context (unencrypted MAC) out for comparison. */ + /* Need to allocate a place for it, to handle non-blocking mode + * when this stack frame will disappear! + */ + calced_auth = DESC_TEMP_ALLOC(auth_ctx->mac_length); + ret = extract_mac(&desc_chain, user_ctx, + auth_ctx->mac_length, calced_auth); + if (ret != FSL_RETURN_OK_S) { + goto out; + } + + if (!blocking) { + /* get_results will need this for comparison */ + desc_chain->out1_ptr = calced_auth; + desc_chain->out2_ptr = auth_ctx->unencrypted_mac; + desc_chain->out_len = auth_ctx->mac_length; + } + + SAH_SF_EXECUTE(); + + if (blocking && (ret == FSL_RETURN_OK_S)) { + unsigned i; + /* Validate the auth code */ + for (i = 0; i < auth_ctx->mac_length; i++) { + if (calced_auth[i] != auth_ctx->unencrypted_mac[i]) { + ret = FSL_RETURN_AUTH_FAILED_S; + break; + } + } + } + + out: + SAH_SF_DESC_CLEAN(); + DESC_TEMP_FREE(calced_auth); + + (void)auth_key_info; + return ret; +} /* fsl_shw_gen_decrypt() */ diff --git a/drivers/mxc/security/sahara2/fsl_shw_hash.c b/drivers/mxc/security/sahara2/fsl_shw_hash.c new file mode 100644 index 000000000000..1551173c1c62 --- /dev/null +++ b/drivers/mxc/security/sahara2/fsl_shw_hash.c @@ -0,0 +1,186 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file fsl_shw_hash.c + * + * This file implements Cryptographic Hashing functions of the FSL SHW API + * for Sahara. This does not include HMAC. + */ + +#include "sahara.h" +#include "sf_util.h" + +#ifdef LINUX_KERNEL +EXPORT_SYMBOL(fsl_shw_hash); +#endif + +/* REQ-S2LRD-PINTFC-API-BASIC-HASH-005 */ +/*! + * Hash a stream of data with a cryptographic hash algorithm. + * + * The flags in the @a hash_ctx control the operation of this function. + * + * Hashing functions work on 64 octets of message at a time. Therefore, when + * any partial hashing of a long message is performed, the message @a length of + * each segment must be a multiple of 64. When ready to + * #FSL_HASH_FLAGS_FINALIZE the hash, the @a length may be any value. + * + * With the #FSL_HASH_FLAGS_INIT and #FSL_HASH_FLAGS_FINALIZE flags on, a + * one-shot complete hash, including padding, will be performed. The @a length + * may be any value. + * + * The first octets of a data stream can be hashed by setting the + * #FSL_HASH_FLAGS_INIT and #FSL_HASH_FLAGS_SAVE flags. The @a length must be + * a multiple of 64. + * + * The flag #FSL_HASH_FLAGS_LOAD is used to load a context previously saved by + * #FSL_HASH_FLAGS_SAVE. The two in combination will allow a (multiple-of-64 + * octets) 'middle sequence' of the data stream to be hashed with the + * beginning. The @a length must again be a multiple of 64. + * + * Since the flag #FSL_HASH_FLAGS_LOAD is used to load a context previously + * saved by #FSL_HASH_FLAGS_SAVE, the #FSL_HASH_FLAGS_LOAD and + * #FSL_HASH_FLAGS_FINALIZE flags, used together, can be used to finish the + * stream. The @a length may be any value. + * + * If the user program wants to do the padding for the hash, it can leave off + * the #FSL_HASH_FLAGS_FINALIZE flag. The @a length must then be a multiple of + * 64 octets. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param[in,out] hash_ctx Hashing algorithm and state of the cipher. + * @param msg Pointer to the data to be hashed. + * @param length Length, in octets, of the @a msg. + * @param[out] result If not null, pointer to where to store the hash + * digest. + * @param result_len Number of octets to store in @a result. + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t fsl_shw_hash(fsl_shw_uco_t * user_ctx, + fsl_shw_hco_t * hash_ctx, + const uint8_t * msg, + uint32_t length, + uint8_t * result, uint32_t result_len) +{ + SAH_SF_DCLS; + unsigned ctx_flags = (hash_ctx->flags & (FSL_HASH_FLAGS_INIT + | FSL_HASH_FLAGS_LOAD + | FSL_HASH_FLAGS_SAVE + | FSL_HASH_FLAGS_FINALIZE)); + + SAH_SF_USER_CHECK(); + + /* Reset expectations if user gets overly zealous. */ + if (result_len > hash_ctx->digest_length) { + result_len = hash_ctx->digest_length; + } + + /* Validate hash ctx flags. + * Need INIT or LOAD but not both. + * Need SAVE or digest ptr (both is ok). + */ + if (((ctx_flags & (FSL_HASH_FLAGS_INIT | FSL_HASH_FLAGS_LOAD)) + == (FSL_HASH_FLAGS_INIT | FSL_HASH_FLAGS_LOAD)) + || ((ctx_flags & (FSL_HASH_FLAGS_INIT | FSL_HASH_FLAGS_LOAD)) == 0) + || (!(ctx_flags & FSL_HASH_FLAGS_SAVE) && (result == NULL))) { + ret = FSL_RETURN_BAD_FLAG_S; + goto out; + } + + if (ctx_flags & FSL_HASH_FLAGS_INIT) { + sah_Oct_Str out_ptr; + unsigned out_len; + + /* Create desc to perform the initial hashing operation */ + /* Desc. #8 w/INIT and algorithm */ + header = SAH_HDR_MDHA_SET_MODE_HASH + ^ sah_insert_mdha_init + ^ sah_insert_mdha_algorithm[hash_ctx->algorithm]; + + /* If user wants one-shot, set padding operation. */ + if (ctx_flags & FSL_HASH_FLAGS_FINALIZE) { + header ^= sah_insert_mdha_pdata; + } + + /* Determine where Digest will go - hash_ctx or result */ + if (ctx_flags & FSL_HASH_FLAGS_SAVE) { + out_ptr = (sah_Oct_Str) hash_ctx->context; + out_len = hash_ctx->context_register_length; + } else { + out_ptr = result; + out_len = (result_len > hash_ctx->digest_length) + ? hash_ctx->digest_length : result_len; + } + + DESC_IN_OUT(header, length, (sah_Oct_Str) msg, out_len, + out_ptr); + } else { /* not doing hash INIT */ + void *out_ptr; + unsigned out_len; + + /* + * Build two descriptors -- one to load in context/set mode, the + * other to compute & retrieve hash/context value. + * + * First up - Desc. #6 to load context. + */ + /* Desc. #8 w/algorithm */ + header = SAH_HDR_MDHA_SET_MODE_MD_KEY + ^ sah_insert_mdha_algorithm[hash_ctx->algorithm]; + + if (ctx_flags & FSL_HASH_FLAGS_FINALIZE) { + header ^= sah_insert_mdha_pdata; + } + + /* Message Digest (in) */ + DESC_IN_IN(header, + hash_ctx->context_register_length, + (sah_Oct_Str) hash_ctx->context, 0, NULL); + + if (ctx_flags & FSL_HASH_FLAGS_SAVE) { + out_ptr = hash_ctx->context; + out_len = hash_ctx->context_register_length; + } else { + out_ptr = result; + out_len = result_len; + } + + /* Second -- run data through and retrieve ctx regs */ + /* Desc. #10 - no mode register with this. */ + header = SAH_HDR_MDHA_HASH; + DESC_IN_OUT(header, length, (sah_Oct_Str) msg, out_len, + out_ptr); + } /* else not INIT */ + + /* Now that execution is rejoined, we can append another descriptor + to extract the digest/context a second time, into the result. */ + if ((ctx_flags & FSL_HASH_FLAGS_SAVE) + && (result != NULL) && (result_len != 0)) { + + header = SAH_HDR_MDHA_STORE_DIGEST; + + /* Message Digest (out) */ + DESC_IN_OUT(header, 0, NULL, + (result_len > hash_ctx->digest_length) + ? hash_ctx->digest_length : result_len, result); + } + + SAH_SF_EXECUTE(); + + out: + SAH_SF_DESC_CLEAN(); + + return ret; +} /* fsl_shw_hash() */ diff --git a/drivers/mxc/security/sahara2/fsl_shw_hmac.c b/drivers/mxc/security/sahara2/fsl_shw_hmac.c new file mode 100644 index 000000000000..4184d9b280dd --- /dev/null +++ b/drivers/mxc/security/sahara2/fsl_shw_hmac.c @@ -0,0 +1,266 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file fsl_shw_hmac.c + * + * This file implements Hashed Message Authentication Code functions of the FSL + * SHW API for Sahara. + */ + +#include "sahara.h" +#include "sf_util.h" + +#ifdef __KERNEL__ +EXPORT_SYMBOL(fsl_shw_hmac_precompute); +EXPORT_SYMBOL(fsl_shw_hmac); +#endif + +/* REQ-S2LRD-PINTFC-API-BASIC-HMAC-001 */ +/*! + * Get the precompute information + * + * + * @param user_ctx + * @param key_info + * @param hmac_ctx + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t fsl_shw_hmac_precompute(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + fsl_shw_hmco_t * hmac_ctx) +{ + SAH_SF_DCLS; + + SAH_SF_USER_CHECK(); + + if ((key_info->algorithm != FSL_KEY_ALG_HMAC) || + (key_info->key_length > 64)) { + return FSL_RETURN_BAD_ALGORITHM_S; + } else if (key_info->key_length == 0) { + return FSL_RETURN_BAD_KEY_LENGTH_S; + } + + /* Set up to start the Inner Calculation */ + /* Desc. #8 w/IPAD, & INIT */ + header = SAH_HDR_MDHA_SET_MODE_HASH + ^ sah_insert_mdha_ipad + ^ sah_insert_mdha_init + ^ sah_insert_mdha_algorithm[hmac_ctx->algorithm]; + + DESC_KEY_OUT(header, key_info, + hmac_ctx->context_register_length, + (uint8_t *) hmac_ctx->inner_precompute); + + /* Set up for starting Outer calculation */ + /* exchange IPAD bit for OPAD bit */ + header ^= (sah_insert_mdha_ipad ^ sah_insert_mdha_opad); + + /* Theoretically, we can leave this link out and use the key which is + * already in the register... however, if we do, the resulting opad + * hash does not have the correct value when using the model. */ + DESC_KEY_OUT(header, key_info, + hmac_ctx->context_register_length, + (uint8_t *) hmac_ctx->outer_precompute); + + SAH_SF_EXECUTE(); + if (ret == FSL_RETURN_OK_S) { + /* flag that precomputes have been entered in this hco + * assume it'll first be used for initilizing */ + hmac_ctx->flags |= (FSL_HMAC_FLAGS_INIT | + FSL_HMAC_FLAGS_PRECOMPUTES_PRESENT); + } + + out: + SAH_SF_DESC_CLEAN(); + + return ret; +} + +/* REQ-S2LRD-PINTFC-API-BASIC-HMAC-002 */ +/*! + * Get the hmac + * + * + * @param user_ctx Info for acquiring memory + * @param key_info + * @param hmac_ctx + * @param msg + * @param length + * @param result + * @param result_len + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t fsl_shw_hmac(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + fsl_shw_hmco_t * hmac_ctx, + const uint8_t * msg, + uint32_t length, + uint8_t * result, uint32_t result_len) +{ + SAH_SF_DCLS; + + SAH_SF_USER_CHECK(); + + /* check flag consistency */ + /* Note that Final, Init, and Save are an illegal combination when a key + * is being used. Because of the logic flow of this routine, that is + * taken care of without being explict */ + if ( + /* nothing to work on */ + (((hmac_ctx->flags & FSL_HMAC_FLAGS_INIT) == 0) && + ((hmac_ctx->flags & FSL_HMAC_FLAGS_LOAD) == 0)) || + /* can't do both */ + ((hmac_ctx->flags & FSL_HMAC_FLAGS_INIT) && + (hmac_ctx->flags & FSL_HMAC_FLAGS_LOAD)) || + /* must be some output */ + (((hmac_ctx->flags & FSL_HMAC_FLAGS_SAVE) == 0) && + ((hmac_ctx->flags & FSL_HMAC_FLAGS_FINALIZE) == 0))) { + ret = FSL_RETURN_BAD_FLAG_S; + goto out; + } + + /* build descriptor #6 */ + + /* start building descriptor header */ + header = SAH_HDR_MDHA_SET_MODE_MD_KEY ^ + sah_insert_mdha_algorithm[hmac_ctx->algorithm] ^ + sah_insert_mdha_init; + + /* if this is to finalize the digest, mark to pad last block */ + if (hmac_ctx->flags & FSL_HMAC_FLAGS_FINALIZE) { + header ^= sah_insert_mdha_pdata; + } + + /* Check if this is a one shot */ + if ((hmac_ctx->flags & FSL_HMAC_FLAGS_INIT) && + (hmac_ctx->flags & FSL_HMAC_FLAGS_FINALIZE)) { + + header ^= sah_insert_mdha_hmac; + + /* See if this uses Precomputes */ + if (hmac_ctx->flags & FSL_HMAC_FLAGS_PRECOMPUTES_PRESENT) { + DESC_IN_IN(header, + hmac_ctx->context_register_length, + (uint8_t *) hmac_ctx->inner_precompute, + hmac_ctx->context_length, + (uint8_t *) hmac_ctx->outer_precompute); + } else { /* Precomputes not requested, try using Key */ + if (key_info->key != NULL) { + /* first, validate the key fields and related flag */ + if ((key_info->key_length == 0) + || (key_info->key_length > 64)) { + ret = FSL_RETURN_BAD_KEY_LENGTH_S; + goto out; + } else { + if (key_info->algorithm != + FSL_KEY_ALG_HMAC) { + ret = + FSL_RETURN_BAD_ALGORITHM_S; + goto out; + } + } + + /* finish building descriptor header (Key specific) */ + header ^= sah_insert_mdha_mac_full; + DESC_IN_KEY(header, 0, NULL, key_info); + } else { /* not using Key or Precomputes, so die */ + ret = FSL_RETURN_BAD_FLAG_S; + goto out; + } + } + } else { /* it's not a one shot, must be multi-step */ + /* this the last chunk? */ + if (hmac_ctx->flags & FSL_HMAC_FLAGS_FINALIZE) { + header ^= sah_insert_mdha_hmac; + DESC_IN_IN(header, + hmac_ctx->context_register_length, + (uint8_t *) hmac_ctx->ongoing_context, + hmac_ctx->context_length, + (uint8_t *) hmac_ctx->outer_precompute); + } else { /* not last chunk */ + uint8_t *ptr1; + + if (hmac_ctx->flags & FSL_HMAC_FLAGS_INIT) { + /* must be using precomputes, cannot 'chunk' message + * starting with a key */ + if (hmac_ctx-> + flags & FSL_HMAC_FLAGS_PRECOMPUTES_PRESENT) + { + ptr1 = + (uint8_t *) hmac_ctx-> + inner_precompute; + } else { + ret = FSL_RETURN_NO_RESOURCE_S; + goto out; + } + } else { + ptr1 = (uint8_t *) hmac_ctx->ongoing_context; + } + + if (ret != FSL_RETURN_OK_S) { + goto out; + } + DESC_IN_IN(header, + hmac_ctx->context_register_length, ptr1, + 0, NULL); + } + } /* multi-step */ + + /* build descriptor #10 & maybe 11 */ + header = SAH_HDR_MDHA_HASH; + + if (hmac_ctx->flags & FSL_HMAC_FLAGS_FINALIZE) { + /* check that the results parameters seem reasonable */ + if ((result_len != 0) && (result != NULL)) { + if (result_len > hmac_ctx->context_register_length) { + result_len = hmac_ctx->context_register_length; + } + + /* message in / digest out (descriptor #10) */ + DESC_IN_OUT(header, length, msg, result_len, result); + + /* see if descriptor #11 needs to be built */ + if (hmac_ctx->flags & FSL_HMAC_FLAGS_SAVE) { + header = SAH_HDR_MDHA_STORE_DIGEST; + /* nothing in / context out */ + DESC_IN_IN(header, 0, NULL, + hmac_ctx->context_register_length, + (uint8_t *) hmac_ctx-> + ongoing_context); + } + } else { + /* something wrong with result or its length */ + ret = FSL_RETURN_BAD_DATA_LENGTH_S; + } + } else { /* finalize not set, so store in ongoing context field */ + if ((length % 64) == 0) { /* this will change for 384/512 support */ + /* message in / context out */ + DESC_IN_OUT(header, length, msg, + hmac_ctx->context_register_length, + (uint8_t *) hmac_ctx->ongoing_context); + } else { + /* not final data, and not multiple of block length */ + ret = FSL_RETURN_BAD_DATA_LENGTH_S; + } + } + + SAH_SF_EXECUTE(); + + out: + SAH_SF_DESC_CLEAN(); + + return ret; +} /* fsl_shw_hmac() */ diff --git a/drivers/mxc/security/sahara2/fsl_shw_rand.c b/drivers/mxc/security/sahara2/fsl_shw_rand.c new file mode 100644 index 000000000000..566c9e5c1e0f --- /dev/null +++ b/drivers/mxc/security/sahara2/fsl_shw_rand.c @@ -0,0 +1,96 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file fsl_shw_rand.c + * + * This file implements Random Number Generation functions of the FSL SHW API + * for Sahara. + */ + +#include "sahara.h" +#include "sf_util.h" + +#ifdef __KERNEL__ +EXPORT_SYMBOL(fsl_shw_get_random); +#endif + +/* REQ-S2LRD-PINTFC-API-BASIC-RNG-002 */ +/*! + * Get a random number + * + * + * @param user_ctx + * @param length + * @param data + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t fsl_shw_get_random(fsl_shw_uco_t * user_ctx, + uint32_t length, uint8_t * data) +{ + SAH_SF_DCLS; + + /* perform a sanity check on the uco */ + ret = sah_validate_uco(user_ctx); + if (ret != FSL_RETURN_OK_S) { + goto out; + } + + header = SAH_HDR_RNG_GENERATE; /* Desc. #18 */ + DESC_OUT_OUT(header, length, data, 0, NULL); + + SAH_SF_EXECUTE(); + + out: + SAH_SF_DESC_CLEAN(); + + return ret; +} + +#ifdef __KERNEL__ +EXPORT_SYMBOL(fsl_shw_add_entropy); +#endif + +/*! + * Add entropy to a random number generator + + * @param user_ctx + * @param length + * @param data + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t fsl_shw_add_entropy(fsl_shw_uco_t * user_ctx, + uint32_t length, uint8_t * data) +{ + SAH_SF_DCLS; + + /* perform a sanity check on the uco */ + ret = sah_validate_uco(user_ctx); + if (ret != FSL_RETURN_OK_S) { + goto out; + } + + header = SAH_HDR_RNG_GENERATE; /* Desc. #18 */ + + /* create descriptor #18. Generate random data */ + DESC_IN_IN(header, 0, NULL, length, data) + + SAH_SF_EXECUTE(); + + out: + SAH_SF_DESC_CLEAN(); + + return ret; +} diff --git a/drivers/mxc/security/sahara2/fsl_shw_sym.c b/drivers/mxc/security/sahara2/fsl_shw_sym.c new file mode 100644 index 000000000000..454905bbcf68 --- /dev/null +++ b/drivers/mxc/security/sahara2/fsl_shw_sym.c @@ -0,0 +1,281 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file fsl_shw_sym.c + * + * This file implements Symmetric Cipher functions of the FSL SHW API for + * Sahara. This does not include CCM. + */ + +#include "sahara.h" +#include "fsl_platform.h" + +#include "sf_util.h" +#include "adaptor.h" + +#ifdef LINUX_KERNEL +EXPORT_SYMBOL(fsl_shw_symmetric_encrypt); +EXPORT_SYMBOL(fsl_shw_symmetric_decrypt); +#endif + +#if defined(NEED_CTR_WORKAROUND) +/* CTR mode needs block-multiple data in/out */ +#define LENGTH_PATCH (sym_ctx->block_size_bytes) +#define LENGTH_PATCH_MASK (sym_ctx->block_size_bytes-1) +#else +#define LENGTH_PATCH 0 +#define LENGTH_PATCH_MASK 0 /* du not use! */ +#endif + +/*! + * Block of zeroes which is maximum Symmetric block size, used for + * initializing context register, etc. + */ +static uint32_t block_zeros[4] = { + 0, 0, 0, 0 +}; + +typedef enum cipher_direction { + SYM_DECRYPT, + SYM_ENCRYPT +} cipher_direction_t; + +/*! + * Create and run the chain for a symmetric-key operation. + * + * @param user_ctx Who the user is + * @param key_info What key is to be used + * @param sym_ctx Info details about algorithm + * @param encrypt 0 = decrypt, non-zero = encrypt + * @param length Number of octets at @a in and @a out + * @param in Pointer to input data + * @param out Location to store output data + * + * @return The status of handing chain to driver, + * or an earlier argument/flag or allocation + * error. + */ +static fsl_shw_return_t do_symmetric(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + fsl_shw_scco_t * sym_ctx, + cipher_direction_t encrypt, + uint32_t length, + const uint8_t * in, uint8_t * out) +{ + SAH_SF_DCLS; + uint8_t *sink = NULL; + sah_Link *link1 = NULL; + sah_Link *link2 = NULL; + sah_Oct_Str ptr1; + uint32_t size1 = sym_ctx->block_size_bytes; + + SAH_SF_USER_CHECK(); + + /* Two different sets of chains, depending on algorithm */ + if (key_info->algorithm == FSL_KEY_ALG_ARC4) { + if (sym_ctx->flags & FSL_SYM_CTX_INIT) { + /* Desc. #35 w/ARC4 - start from key */ + header = SAH_HDR_ARC4_SET_MODE_KEY + ^ sah_insert_skha_algorithm_arc4; + + DESC_IN_KEY(header, 0, NULL, key_info); + } else { /* load SBox */ + /* Desc. #33 w/ARC4 and NO PERMUTE */ + header = SAH_HDR_ARC4_SET_MODE_SBOX + ^ sah_insert_skha_no_permute + ^ sah_insert_skha_algorithm_arc4; + DESC_IN_IN(header, 256, sym_ctx->context, + 3, sym_ctx->context + 256); + } /* load SBox */ + + /* Add in-out data descriptor to process the data */ + if (length != 0) { + DESC_IN_OUT(SAH_HDR_SKHA_ENC_DEC, length, in, length, + out); + } + + /* Operation is done ... save what came out? */ + if (sym_ctx->flags & FSL_SYM_CTX_SAVE) { + /* Desc. #34 - Read SBox, pointers */ + header = SAH_HDR_ARC4_READ_SBOX; + DESC_OUT_OUT(header, 256, sym_ctx->context, + 3, sym_ctx->context + 256); + } + } else { /* not ARC4 */ + /* Doing 1- or 2- descriptor chain. */ + /* Desc. #1 and algorithm and mode */ + header = SAH_HDR_SKHA_SET_MODE_IV_KEY + ^ sah_insert_skha_mode[sym_ctx->mode] + ^ sah_insert_skha_algorithm[key_info->algorithm]; + + /* Honor 'no key parity checking' for DES and TDES */ + if ((key_info->flags & FSL_SKO_KEY_IGNORE_PARITY) && + ((key_info->algorithm == FSL_KEY_ALG_DES) || + (key_info->algorithm == FSL_KEY_ALG_TDES))) { + header ^= sah_insert_skha_no_key_parity; + } + + /* Header by default is decrypting, so... */ + if (encrypt == SYM_ENCRYPT) { + header ^= sah_insert_skha_encrypt; + } + + if (sym_ctx->mode == FSL_SYM_MODE_CTR) { + header ^= sah_insert_skha_modulus[sym_ctx->modulus_exp]; + } + + if (sym_ctx->mode == FSL_SYM_MODE_ECB) { + ptr1 = NULL; + size1 = 0; + } else if (sym_ctx->flags & FSL_SYM_CTX_INIT) { + ptr1 = (uint8_t *) block_zeros; + } else { + ptr1 = sym_ctx->context; + } + + DESC_IN_KEY(header, sym_ctx->block_size_bytes, ptr1, key_info); + + /* Add in-out data descriptor */ + if (length != 0) { + header = SAH_HDR_SKHA_ENC_DEC; + if (LENGTH_PATCH && (sym_ctx->mode == FSL_SYM_MODE_CTR) + && ((length & LENGTH_PATCH_MASK) != 0)) { + sink = DESC_TEMP_ALLOC(LENGTH_PATCH); + ret = + sah_Create_Link(user_ctx->mem_util, &link1, + (uint8_t *) in, length, + SAH_USES_LINK_DATA); + ret = + sah_Append_Link(user_ctx->mem_util, link1, + (uint8_t *) sink, + LENGTH_PATCH - + (length & + LENGTH_PATCH_MASK), + SAH_USES_LINK_DATA); + if (ret != FSL_RETURN_OK_S) { + goto out; + } + ret = + sah_Create_Link(user_ctx->mem_util, &link2, + out, length, + SAH_USES_LINK_DATA | + SAH_OUTPUT_LINK); + if (ret != FSL_RETURN_OK_S) { + goto out; + } + ret = sah_Append_Link(user_ctx->mem_util, link2, + sink, + LENGTH_PATCH - + (length & + LENGTH_PATCH_MASK), + SAH_USES_LINK_DATA); + if (ret != FSL_RETURN_OK_S) { + goto out; + } + ret = + sah_Append_Desc(user_ctx->mem_util, + &desc_chain, header, link1, + link2); + if (ret != FSL_RETURN_OK_S) { + goto out; + } + link1 = link2 = NULL; + } else { + DESC_IN_OUT(header, length, in, length, out); + } + } + + /* Unload any desired context */ + if (sym_ctx->flags & FSL_SYM_CTX_SAVE) { + DESC_OUT_OUT(SAH_HDR_SKHA_READ_CONTEXT_IV, 0, NULL, + sym_ctx->block_size_bytes, + sym_ctx->context); + } + + } /* not ARC4 */ + + SAH_SF_EXECUTE(); + + out: + SAH_SF_DESC_CLEAN(); + DESC_TEMP_FREE(sink); + if (LENGTH_PATCH) { + sah_Destroy_Link(user_ctx->mem_util, link1); + sah_Destroy_Link(user_ctx->mem_util, link2); + } + + return ret; +} + +/* REQ-S2LRD-PINTFC-API-BASIC-SYM-002 */ +/* PINTFC-API-BASIC-SYM-ARC4-001 */ +/* PINTFC-API-BASIC-SYM-ARC4-002 */ + +/*! + * Compute symmetric encryption + * + * + * @param user_ctx + * @param key_info + * @param sym_ctx + * @param length + * @param pt + * @param ct + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t fsl_shw_symmetric_encrypt(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + fsl_shw_scco_t * sym_ctx, + uint32_t length, + const uint8_t * pt, uint8_t * ct) +{ + fsl_shw_return_t ret; + + ret = do_symmetric(user_ctx, key_info, sym_ctx, SYM_ENCRYPT, + length, pt, ct); + + return ret; +} + +/* PINTFC-API-BASIC-SYM-002 */ +/* PINTFC-API-BASIC-SYM-ARC4-001 */ +/* PINTFC-API-BASIC-SYM-ARC4-002 */ + +/*! + * Compute symmetric decryption + * + * + * @param user_ctx + * @param key_info + * @param sym_ctx + * @param length + * @param pt + * @param ct + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t fsl_shw_symmetric_decrypt(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + fsl_shw_scco_t * sym_ctx, + uint32_t length, + const uint8_t * ct, uint8_t * pt) +{ + fsl_shw_return_t ret; + + ret = do_symmetric(user_ctx, key_info, sym_ctx, SYM_DECRYPT, + length, ct, pt); + + return ret; +} diff --git a/drivers/mxc/security/sahara2/fsl_shw_user.c b/drivers/mxc/security/sahara2/fsl_shw_user.c new file mode 100644 index 000000000000..fc56e4a9e33e --- /dev/null +++ b/drivers/mxc/security/sahara2/fsl_shw_user.c @@ -0,0 +1,158 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file fsl_shw_user.c + * + * This file implements user and platform capabilities functions of the FSL SHW + * API for Sahara + */ +#include "sahara.h" +#include <adaptor.h> +#include <sf_util.h> + +#ifdef __KERNEL__ +EXPORT_SYMBOL(fsl_shw_get_capabilities); +EXPORT_SYMBOL(fsl_shw_register_user); +EXPORT_SYMBOL(fsl_shw_deregister_user); +EXPORT_SYMBOL(fsl_shw_get_results); +#endif /* __KERNEL__ */ + +/*! This matches Sahara2 capabilities... */ +fsl_shw_pco_t sahara2_capabilities = { + 1, 1, /* api version number - major & minor */ + 1, 2, /* driver version number - major & minor */ + { + FSL_KEY_ALG_AES, + FSL_KEY_ALG_DES, + FSL_KEY_ALG_TDES, + FSL_KEY_ALG_ARC4} + , + { + FSL_SYM_MODE_STREAM, + FSL_SYM_MODE_ECB, + FSL_SYM_MODE_CBC, + FSL_SYM_MODE_CTR} + , + { + FSL_HASH_ALG_MD5, + FSL_HASH_ALG_SHA1, + FSL_HASH_ALG_SHA224, + FSL_HASH_ALG_SHA256} + , + /* + * The following table must be set to handle all values of key algorithm + * and sym mode, and be in the correct order.. + */ + { /* Stream, ECB, CBC, CTR */ + {0, 0, 0, 0} + , /* HMAC */ + {0, 1, 1, 1} + , /* AES */ + {0, 1, 1, 0} + , /* DES */ + {0, 1, 1, 0} + , /* 3DES */ + {1, 0, 0, 0} /* ARC4 */ + } +}; + +/* REQ-S2LRD-PINTFC-API-GEN-003 */ +/*! + * Determine the hardware security capabilities of this platform. + * + * Though a user context object is passed into this function, it will always + * act in a non-blocking manner. + * + * @param user_ctx The user context which will be used for the query. + * + * @return A pointer to the capabilities object. + */ +fsl_shw_pco_t *fsl_shw_get_capabilities(fsl_shw_uco_t * user_ctx) +{ + /* + * Need to get the driver/hardware versions populated... + * which is why the user_ctx is here. + */ + user_ctx = 0; + return &sahara2_capabilities; +} + +/* REQ-S2LRD-PINTFC-API-GEN-004 */ + +/*! + * Create an association between the the user and the provider of the API. + * + * @param user_ctx The user context which will be used for this association. + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t fsl_shw_register_user(fsl_shw_uco_t * user_ctx) +{ + return sah_register(user_ctx); +} + +/* REQ-S2LRD-PINTFC-API-GEN-005 */ + +/*! + * Destroy the association between the the user and the provider of the API. + * + * @param user_ctx The user context which is no longer needed. + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t fsl_shw_deregister_user(fsl_shw_uco_t * user_ctx) +{ + return sah_deregister(user_ctx); +} + +/* REQ-S2LRD-PINTFC-API-GEN-006 */ + +/*! + * Retrieve results from earlier operations. + * + * @param user_ctx The user's context. + * @param result_size The number of array elements of @a results. + * @param[in,out] results Pointer to first of the (array of) locations to + * store results. + * @param[out] result_count Pointer to store the number of results which + * were returned. + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t fsl_shw_get_results(fsl_shw_uco_t * user_ctx, + unsigned result_size, + fsl_shw_result_t results[], + unsigned *result_count) +{ + fsl_shw_return_t status; + + /* perform a sanity check on the uco */ + status = sah_validate_uco(user_ctx); + + /* if uco appears ok, build structure and pass to get results */ + if (status == FSL_RETURN_OK_S) { + sah_results arg; + + /* if requested is zero, it's done before it started */ + if (result_size > 0) { + arg.requested = result_size; + arg.actual = result_count; + arg.results = results; + /* get the results */ + status = sah_get_results(&arg, user_ctx); + } + } + + return status; +} diff --git a/drivers/mxc/security/sahara2/fsl_shw_wrap.c b/drivers/mxc/security/sahara2/fsl_shw_wrap.c new file mode 100644 index 000000000000..28c5f013dc5c --- /dev/null +++ b/drivers/mxc/security/sahara2/fsl_shw_wrap.c @@ -0,0 +1,732 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file fsl_shw_wrap.c + * + * This file implements Key-Wrap (Black Key) functions of the FSL SHW API for + * Sahara. + * + * - Ownerid is an 8-byte, user-supplied, value to keep KEY confidential. + * - KEY is a 1-32 byte value which starts in SCC RED RAM before + * wrapping, and ends up there on unwrap. Length is limited because of + * size of SCC1 RAM. + * - KEY' is the encrypted KEY + * - LEN is a 1-byte (for now) byte-length of KEY + * - ALG is a 1-byte value for the algorithm which which the key is + * associated. Values are defined by the FSL SHW API + * - Ownerid, LEN, and ALG come from the user's "key_info" object, as does the + * slot number where KEY already is/will be. + * - T is a Nonce + * - T' is the encrypted T + * - KEK is a Key-Encryption Key for the user's Key + * - ICV is the "Integrity Check Value" for the wrapped key + * - Black Key is the string of bytes returned as the wrapped key +<table> +<tr><TD align="right">BLACK_KEY <TD width="3">=<TD>ICV | T' | LEN | ALG | + KEY'</td></tr> +<tr><td> </td></tr> + +<tr><th>To Wrap</th></tr> +<tr><TD align="right">T</td> <TD width="3">=</td> <TD>RND()<sub>16</sub> + </td></tr> +<tr><TD align="right">KEK</td><TD width="3">=</td><TD>HASH<sub>sha1</sub>(T | + Ownerid)<sub>16</sub></td></tr> +<tr><TD align="right">KEY'<TD width="3">=</td><TD> + AES<sub>ctr-enc</sub>(Key=KEK, CTR=0, Data=KEY)</td></tr> +<tr><TD align="right">ICV</td><TD width="3">=</td><td>HMAC<sub>sha1</sub> + (Key=T, Data=Ownerid | LEN | ALG | KEY')<sub>16</sub></td></tr> +<tr><TD align="right">T'</td><TD width="3">=</td><TD>TDES<sub>cbc-enc</sub> + (Key=SLID, IV=Ownerid, Data=T)</td></tr> + +<tr><td> </td></tr> + +<tr><th>To Unwrap</th></tr> +<tr><TD align="right">T</td><TD width="3">=</td><TD>TDES<sub>ecb-dec</sub> + (Key=SLID, IV=Ownerid, Data=T')</sub></td></tr> +<tr><TD align="right">ICV</td><TD width="3">=</td><td>HMAC<sub>sha1</sub> + (Key=T, Data=Ownerid | LEN | ALG | KEY')<sub>16</sub></td></tr> +<tr><TD align="right">KEK</td><TD width="3">=</td><td>HASH<sub>sha1</sub> + (T | Ownerid)<sub>16</sub></td></tr> +<tr><TD align="right">KEY<TD width="3">=</td><TD>AES<sub>ctr-dec</sub> + (Key=KEK, CTR=0, Data=KEY')</td></tr> +</table> + + */ + +#include "sahara.h" +#include "fsl_platform.h" + +#include "sf_util.h" +#include "adaptor.h" + +#if defined(DIAG_SECURITY_FUNC) +#include <diagnostic.h> +#endif + +#if defined(NEED_CTR_WORKAROUND) +/* CTR mode needs block-multiple data in/out */ +#define LENGTH_PATCH 16 +#define LENGTH_PATCH_MASK 0xF +#else +#define LENGTH_PATCH 4 +#define LENGTH_PATCH_MASK 3 +#endif + +#if LENGTH_PATCH +#define ROUND_LENGTH(len) \ +({ \ + uint32_t orig_len = len; \ + uint32_t new_len; \ + \ + if ((orig_len & LENGTH_PATCH_MASK) != 0) { \ + new_len = (orig_len + LENGTH_PATCH \ + - (orig_len & LENGTH_PATCH_MASK)); \ + } \ + else { \ + new_len = orig_len; \ + } \ + new_len; \ +}) +#else +#define ROUND_LENGTH(len) (len) +#endif + +#ifdef __KERNEL__ +EXPORT_SYMBOL(fsl_shw_establish_key); +EXPORT_SYMBOL(fsl_shw_extract_key); +EXPORT_SYMBOL(fsl_shw_release_key); +#endif + +#define ICV_LENGTH 16 +#define T_LENGTH 16 +#define KEK_LENGTH 16 +#define LENGTH_LENGTH 1 +#define ALGORITHM_LENGTH 1 + +/* ICV | T' | LEN | ALG | KEY' */ +#define ICV_OFFSET 0 +#define T_PRIME_OFFSET (ICV_OFFSET + ICV_LENGTH) +#define LENGTH_OFFSET (T_PRIME_OFFSET + T_LENGTH) +#define ALGORITHM_OFFSET (LENGTH_OFFSET + LENGTH_LENGTH) +#define KEY_PRIME_OFFSET (ALGORITHM_OFFSET + ALGORITHM_LENGTH) + +/* + * For testing of the algorithm implementation,, the DO_REPEATABLE_WRAP flag + * causes the T_block to go into the T field during a wrap operation. This + * will make the black key value repeatable (for a given SCC secret key, or + * always if the default key is in use). + * + * Normally, a random sequence is used. + */ +#ifdef DO_REPEATABLE_WRAP +/*! + * Block of zeroes which is maximum Symmetric block size, used for + * initializing context register, etc. + */ +static uint8_t T_block_[16] = { + 0x42, 0, 0, 0x42, 0x42, 0, 0, 0x42, + 0x42, 0, 0, 0x42, 0x42, 0, 0, 0x42 +}; +#endif + +/* + * Insert descriptors to calculate ICV = HMAC(key=T, data=LEN|ALG|KEY') + * + * @param user_ctx User's context for this operation + * @param desc_chain Descriptor chain to append to + * @param t_key_info T's key object + * @param black_key Beginning of Black Key region + * @param key_length Number of bytes of key' there are in @c black_key + * @param[out] hmac Location to store ICV. Will be tagged "USES" so + * sf routines will not try to free it. + * + * @return A return code of type #fsl_shw_return_t. + */ +static inline fsl_shw_return_t create_icv_calc(fsl_shw_uco_t * user_ctx, + sah_Head_Desc ** desc_chain, + fsl_shw_sko_t * t_key_info, + const uint8_t * black_key, + uint32_t key_length, + uint8_t * hmac) +{ + fsl_shw_return_t sah_code; + uint32_t header; + sah_Link *link1 = NULL; + sah_Link *link2 = NULL; + + /* Load up T as key for the HMAC */ + header = (SAH_HDR_MDHA_SET_MODE_MD_KEY /* #6 */ + ^ sah_insert_mdha_algorithm_sha1 + ^ sah_insert_mdha_init ^ sah_insert_mdha_hmac ^ + sah_insert_mdha_pdata ^ sah_insert_mdha_mac_full); + sah_code = sah_add_in_key_desc(header, NULL, 0, t_key_info, /* Reference T in RED */ + user_ctx->mem_util, desc_chain); + if (sah_code != FSL_RETURN_OK_S) { + goto out; + } + + /* Previous step loaded key; Now set up to hash the data */ + header = SAH_HDR_MDHA_HASH; /* #10 */ + + /* Input - start with ownerid */ + sah_code = sah_Create_Link(user_ctx->mem_util, &link1, + (void *)&t_key_info->userid, + sizeof(t_key_info->userid), + SAH_USES_LINK_DATA); + if (sah_code != FSL_RETURN_OK_S) { + goto out; + } + + /* Still input - Append black-key fields len, alg, key' */ + sah_code = sah_Append_Link(user_ctx->mem_util, link1, + (void *)black_key + LENGTH_OFFSET, + (LENGTH_LENGTH + + ALGORITHM_LENGTH + + key_length), SAH_USES_LINK_DATA); + + if (sah_code != FSL_RETURN_OK_S) { + goto out; + } + /* Output - computed ICV/HMAC */ + sah_code = sah_Create_Link(user_ctx->mem_util, &link2, + hmac, ICV_LENGTH, + SAH_USES_LINK_DATA | SAH_OUTPUT_LINK); + if (sah_code != FSL_RETURN_OK_S) { + goto out; + } + + sah_code = sah_Append_Desc(user_ctx->mem_util, desc_chain, + header, link1, link2); + + out: + if (sah_code != FSL_RETURN_OK_S) { + (void)sah_Destroy_Link(user_ctx->mem_util, link1); + (void)sah_Destroy_Link(user_ctx->mem_util, link2); + } + + return sah_code; +} /* create_icv_calc */ + +/*! + * Perform unwrapping of a black key into a RED slot + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param[in,out] key_info The information about the key to be which will + * be unwrapped... key length, slot info, etc. + * @param black_key Encrypted key + * + * @return A return code of type #fsl_shw_return_t. + */ +static fsl_shw_return_t unwrap(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + const uint8_t * black_key) +{ + SAH_SF_DCLS; + uint8_t *hmac = NULL; + fsl_shw_sko_t t_key_info; + sah_Link *link1 = NULL; + sah_Link *link2 = NULL; + unsigned i; + unsigned rounded_key_length; + unsigned original_key_length = key_info->key_length; + + hmac = DESC_TEMP_ALLOC(ICV_LENGTH); + + /* Set up key_info for "T" - use same slot as eventual key */ + fsl_shw_sko_init(&t_key_info, FSL_KEY_ALG_AES); + t_key_info.userid = key_info->userid; + t_key_info.handle = key_info->handle; + t_key_info.flags = key_info->flags; + t_key_info.key_length = T_LENGTH; + + /* Compute T = SLID_decrypt(T'); leave in RED slot */ + ret = do_scc_slot_decrypt(user_ctx, key_info->userid, + t_key_info.handle, + T_LENGTH, black_key + T_PRIME_OFFSET); + if (ret != FSL_RETURN_OK_S) { + goto out; + } + + /* Compute ICV = HMAC(T, ownerid | len | alg | key' */ + ret = create_icv_calc(user_ctx, &desc_chain, &t_key_info, + black_key, original_key_length, hmac); + if (ret != FSL_RETURN_OK_S) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("Creation of sah_Key_Link failed due to bad key" + " flag!\n"); +#endif /*DIAG_SECURITY_FUNC */ + goto out; + } +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("Validating MAC of wrapped key"); +#endif + SAH_SF_EXECUTE(); + if (ret != FSL_RETURN_OK_S) { + goto out; + } + SAH_SF_DESC_CLEAN(); + + /* Check computed ICV against value in Black Key */ + for (i = 0; i < ICV_LENGTH; i++) { + if (black_key[ICV_OFFSET + i] != hmac[i]) { + ret = FSL_RETURN_AUTH_FAILED_S; + goto out; + } + } + + /* This is no longer needed. */ + DESC_TEMP_FREE(hmac); + + /* Compute KEK = SHA1(T | ownerid). Rewrite slot with value */ + header = (SAH_HDR_MDHA_SET_MODE_HASH /* #8 */ + ^ sah_insert_mdha_init + ^ sah_insert_mdha_algorithm_sha1 ^ sah_insert_mdha_pdata); + + /* Input - Start with T */ + ret = sah_Create_Key_Link(user_ctx->mem_util, &link1, &t_key_info); + if (ret != FSL_RETURN_OK_S) { + goto out; + } + + /* Still input - append ownerid */ + ret = sah_Append_Link(user_ctx->mem_util, link1, + (void *)&key_info->userid, + sizeof(key_info->userid), SAH_USES_LINK_DATA); + if (ret != FSL_RETURN_OK_S) { + goto out; + } + + /* Output - KEK goes into RED slot */ + ret = sah_Create_Key_Link(user_ctx->mem_util, &link2, &t_key_info); + if (ret != FSL_RETURN_OK_S) { + goto out; + } + + /* Put the Hash calculation into the chain. */ + ret = sah_Append_Desc(user_ctx->mem_util, &desc_chain, + header, link1, link2); + if (ret != FSL_RETURN_OK_S) { + goto out; + } + + /* Compute KEY = AES-decrypt(KEK, KEY') */ + header = (SAH_HDR_SKHA_SET_MODE_IV_KEY /* #1 */ + ^ sah_insert_skha_mode_ctr + ^ sah_insert_skha_algorithm_aes + ^ sah_insert_skha_modulus_128); + /* Load KEK in as the key to use */ + DESC_IN_KEY(header, 0, NULL, &t_key_info); + + rounded_key_length = ROUND_LENGTH(original_key_length); + key_info->key_length = rounded_key_length; + + /* Now set up for computation. Result in RED */ + header = SAH_HDR_SKHA_ENC_DEC; /* #4 */ + DESC_IN_KEY(header, rounded_key_length, black_key + KEY_PRIME_OFFSET, + key_info); + + /* Perform the operation */ +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("Decrypting key with KEK"); +#endif + SAH_SF_EXECUTE(); + + out: + key_info->key_length = original_key_length; + SAH_SF_DESC_CLEAN(); + + DESC_TEMP_FREE(hmac); + + /* Erase tracks */ + t_key_info.userid = 0xdeadbeef; + t_key_info.handle = 0xdeadbeef; + + return ret; +} /* unwrap */ + +/*! + * Perform wrapping of a black key from a RED slot + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param[in,out] key_info The information about the key to be which will + * be wrapped... key length, slot info, etc. + * @param black_key Place to store encrypted key + * + * @return A return code of type #fsl_shw_return_t. + */ +static fsl_shw_return_t wrap(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, uint8_t * black_key) +{ + SAH_SF_DCLS; + unsigned slots_allocated = 0; /* boolean */ + fsl_shw_sko_t T_key_info; /* for holding T */ + fsl_shw_sko_t KEK_key_info; /* for holding KEK */ + unsigned original_key_length = key_info->key_length; + unsigned rounded_key_length; + sah_Link *link1; + sah_Link *link2; + + black_key[LENGTH_OFFSET] = key_info->key_length; + black_key[ALGORITHM_OFFSET] = key_info->algorithm; + + memcpy(&T_key_info, key_info, sizeof(T_key_info)); + fsl_shw_sko_set_key_length(&T_key_info, T_LENGTH); + T_key_info.algorithm = FSL_KEY_ALG_HMAC; + + memcpy(&KEK_key_info, &T_key_info, sizeof(KEK_key_info)); + KEK_key_info.algorithm = FSL_KEY_ALG_AES; + + ret = do_scc_slot_alloc(user_ctx, T_LENGTH, key_info->userid, + &T_key_info.handle); + if (ret != FSL_RETURN_OK_S) { + goto out; + } + + ret = do_scc_slot_alloc(user_ctx, KEK_LENGTH, key_info->userid, + &KEK_key_info.handle); + if (ret != FSL_RETURN_OK_S) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("do_scc_slot_alloc() failed"); +#endif + (void)do_scc_slot_dealloc(user_ctx, key_info->userid, + T_key_info.handle); + } else { + slots_allocated = 1; + } + + /* Set up to compute everything except T' ... */ +#ifndef DO_REPEATABLE_WRAP + /* Compute T = RND() */ + header = SAH_HDR_RNG_GENERATE; /* Desc. #18 */ + DESC_KEY_OUT(header, &T_key_info, 0, NULL); +#else + ret = do_scc_slot_load_slot(user_ctx, T_key_info.userid, + T_key_info.handle, T_block, + T_key_info.key_length); + if (ret != FSL_RETURN_OK_S) { + goto out; + } +#endif + + /* Compute KEK = SHA1(T | Ownerid) */ + header = (SAH_HDR_MDHA_SET_MODE_HASH /* #8 */ + ^ sah_insert_mdha_init + ^ sah_insert_mdha_algorithm[FSL_HASH_ALG_SHA1] + ^ sah_insert_mdha_pdata); + /* Input - Start with T */ + ret = sah_Create_Key_Link(user_ctx->mem_util, &link1, &T_key_info); + if (ret != FSL_RETURN_OK_S) { + goto out; + } + /* Still input - append ownerid */ + ret = sah_Append_Link(user_ctx->mem_util, link1, + (void *)&key_info->userid, + sizeof(key_info->userid), SAH_USES_LINK_DATA); + if (ret != FSL_RETURN_OK_S) { + goto out; + } + /* Output - KEK goes into RED slot */ + ret = sah_Create_Key_Link(user_ctx->mem_util, &link2, &KEK_key_info); + if (ret != FSL_RETURN_OK_S) { + goto out; + } + /* Put the Hash calculation into the chain. */ + ret = sah_Append_Desc(user_ctx->mem_util, &desc_chain, + header, link1, link2); + if (ret != FSL_RETURN_OK_S) { + goto out; + } +#if defined(NEED_CTR_WORKAROUND) + rounded_key_length = ROUND_LENGTH(original_key_length); + key_info->key_length = rounded_key_length; +#else + rounded_key_length = original_key_length; +#endif + /* Compute KEY' = AES-encrypt(KEK, KEY) */ + header = (SAH_HDR_SKHA_SET_MODE_IV_KEY /* #1 */ + ^ sah_insert_skha_mode[FSL_SYM_MODE_CTR] + ^ sah_insert_skha_algorithm[FSL_KEY_ALG_AES] + ^ sah_insert_skha_modulus[FSL_CTR_MOD_128]); + /* Set up KEK as key to use */ + DESC_IN_KEY(header, 0, NULL, &KEK_key_info); + header = SAH_HDR_SKHA_ENC_DEC; + DESC_KEY_OUT(header, key_info, + key_info->key_length, black_key + KEY_PRIME_OFFSET); + + /* Compute and store ICV into Black Key */ + ret = create_icv_calc(user_ctx, &desc_chain, &T_key_info, + black_key, original_key_length, + black_key + ICV_OFFSET); + if (ret != FSL_RETURN_OK_S) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("Creation of sah_Key_Link failed due to bad key" + " flag!\n"); +#endif /*DIAG_SECURITY_FUNC */ + goto out; + } + + /* Now get Sahara to do the work. */ +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("Encrypting key with KEK"); +#endif + SAH_SF_EXECUTE(); + if (ret != FSL_RETURN_OK_S) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("sah_Descriptor_Chain_Execute() failed"); +#endif + goto out; + } + + /* Compute T' = SLID_encrypt(T); Result goes to Black Key */ + ret = do_scc_slot_encrypt(user_ctx, T_key_info.userid, + T_key_info.handle, + T_LENGTH, black_key + T_PRIME_OFFSET); + if (ret != FSL_RETURN_OK_S) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("do_scc_slot_encrypt() failed"); +#endif + goto out; + } + + out: + key_info->key_length = original_key_length; + + SAH_SF_DESC_CLEAN(); + if (slots_allocated) { + do_scc_slot_dealloc(user_ctx, key_info->userid, + T_key_info.handle); + do_scc_slot_dealloc(user_ctx, key_info->userid, + KEK_key_info.handle); + } + + return ret; +} /* wrap */ + +/*! + * Place a key into a protected location for use only by cryptographic + * algorithms. + * + * This only needs to be used to a) unwrap a key, or b) set up a key which + * could be wrapped with a later call to #fsl_shw_extract_key(). Normal + * cleartext keys can simply be placed into #fsl_shw_sko_t key objects with + * #fsl_shw_sko_set_key() and used directly. + * + * The maximum key size supported for wrapped/unwrapped keys is 32 octets. + * (This is the maximum reasonable key length on Sahara - 32 octets for an HMAC + * key based on SHA-256.) The key size is determined by the @a key_info. The + * expected length of @a key can be determined by + * #fsl_shw_sko_calculate_wrapped_size() + * + * The protected key will not be available for use until this operation + * successfully completes. + * + * This feature is not available for all platforms, nor for all algorithms and + * modes. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param[in,out] key_info The information about the key to be which will + * be established. In the create case, the key + * length must be set. + * @param establish_type How @a key will be interpreted to establish a + * key for use. + * @param key If @a establish_type is #FSL_KEY_WRAP_UNWRAP, + * this is the location of a wrapped key. If + * @a establish_type is #FSL_KEY_WRAP_CREATE, this + * parameter can be @a NULL. If @a establish_type + * is #FSL_KEY_WRAP_ACCEPT, this is the location + * of a plaintext key. + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t fsl_shw_establish_key(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + fsl_shw_key_wrap_t establish_type, + const uint8_t * key) +{ + SAH_SF_DCLS; + unsigned original_key_length = key_info->key_length; + unsigned rounded_key_length; + unsigned slot_allocated = 0; + uint32_t old_flags; + + header = SAH_HDR_RNG_GENERATE; /* Desc. #18 for rand */ + + /* THIS STILL NEEDS TO BE REFACTORED */ + + /* Write operations into SCC memory require word-multiple number of + * bytes. For ACCEPT and CREATE functions, the key length may need + * to be rounded up. Calculate. */ + if (LENGTH_PATCH && (original_key_length & LENGTH_PATCH_MASK) != 0) { + rounded_key_length = original_key_length + LENGTH_PATCH + - (original_key_length & LENGTH_PATCH_MASK); + } else { + rounded_key_length = original_key_length; + } + + SAH_SF_USER_CHECK(); + + ret = + do_scc_slot_alloc(user_ctx, key_info->key_length, key_info->userid, + &key_info->handle); + if (ret != FSL_RETURN_OK_S) { + goto out; + } + slot_allocated = 1; + + key_info->flags |= FSL_SKO_KEY_ESTABLISHED; + switch (establish_type) { + case FSL_KEY_WRAP_CREATE: + /* Use safe version of key length */ + key_info->key_length = rounded_key_length; + /* Generate descriptor to put random value into */ + DESC_KEY_OUT(header, key_info, 0, NULL); + /* Restore actual, desired key length */ + key_info->key_length = original_key_length; + + old_flags = user_ctx->flags; + /* Now put random value into key */ +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("Creating random key"); +#endif + SAH_SF_EXECUTE(); + /* Restore user's old flag value */ + user_ctx->flags = old_flags; + break; + + case FSL_KEY_WRAP_ACCEPT: + if (key == NULL) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("ACCEPT: Red Key is NULL"); +#endif + ret = FSL_RETURN_ERROR_S; + goto out; + } + /* Copy in safe number of bytes of Red key */ + ret = do_scc_slot_load_slot(user_ctx, key_info->userid, + key_info->handle, key, + rounded_key_length); + break; + + case FSL_KEY_WRAP_UNWRAP: + /* For now, disallow non-blocking calls. */ + if (!(user_ctx->flags & FSL_UCO_BLOCKING_MODE)) { + ret = FSL_RETURN_BAD_FLAG_S; + } else if (key == NULL) { + ret = FSL_RETURN_ERROR_S; + } else { + ret = unwrap(user_ctx, key_info, key); + } + break; + + default: + ret = FSL_RETURN_BAD_FLAG_S; + break; + } /* switch */ + + out: + if (slot_allocated && (ret != FSL_RETURN_OK_S)) { + fsl_shw_return_t scc_err; + scc_err = do_scc_slot_dealloc(user_ctx, key_info->userid, + key_info->handle); + key_info->flags &= ~FSL_SKO_KEY_ESTABLISHED; + } + + SAH_SF_DESC_CLEAN(); + + return ret; +} /* fsl_shw_establish_key() */ + +/*! + * Wrap a key and retrieve the wrapped value. + * + * A wrapped key is a key that has been cryptographically obscured. It is + * only able to be used with #fsl_shw_establish_key(). + * + * This function will also release the key (see #fsl_shw_release_key()) so + * that it must be re-established before reuse. + * + * This feature is not available for all platforms, nor for all algorithms and + * modes. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param key_info The information about the key to be deleted. + * @param[out] covered_key The location to store the 48-octet wrapped key. + * (This size is based upon the maximum key size + * of 32 octets). + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t fsl_shw_extract_key(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + uint8_t * covered_key) +{ + SAH_SF_DCLS; + + SAH_SF_USER_CHECK(); + + /* For now, only blocking mode calls are supported */ + if (user_ctx->flags & FSL_UCO_BLOCKING_MODE) { + if (key_info->flags & FSL_SKO_KEY_ESTABLISHED) { + ret = wrap(user_ctx, key_info, covered_key); + if (ret != FSL_RETURN_OK_S) { + goto out; + } + + /* Need to deallocate on successful extraction */ + do_scc_slot_dealloc(user_ctx, key_info->userid, + key_info->handle); + /* Mark key not available in the flags */ + key_info->flags &= + ~(FSL_SKO_KEY_ESTABLISHED | FSL_SKO_KEY_PRESENT); + } + } + + out: + SAH_SF_DESC_CLEAN(); + + return ret; +} + +/*! + * De-establish a key so that it can no longer be accessed. + * + * The key will need to be re-established before it can again be used. + * + * This feature is not available for all platforms, nor for all algorithms and + * modes. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param key_info The information about the key to be deleted. + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t fsl_shw_release_key(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info) +{ + SAH_SF_DCLS; + + SAH_SF_USER_CHECK(); + + if (key_info->flags & FSL_SKO_KEY_ESTABLISHED) { + ret = do_scc_slot_dealloc(user_ctx, key_info->userid, + key_info->handle); + key_info->flags &= ~(FSL_SKO_KEY_ESTABLISHED | + FSL_SKO_KEY_PRESENT); + } + + out: + SAH_SF_DESC_CLEAN(); + + return ret; +} diff --git a/drivers/mxc/security/sahara2/include/adaptor.h b/drivers/mxc/security/sahara2/include/adaptor.h new file mode 100644 index 000000000000..2b39805d72f1 --- /dev/null +++ b/drivers/mxc/security/sahara2/include/adaptor.h @@ -0,0 +1,95 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! +* @file adaptor.h +* +* @brief The Adaptor component provides an interface to the device +* driver. +* +* Intended to be used by the FSL SHW API, this can also be called directly +*/ + +#ifndef ADAPTOR_H +#define ADAPTOR_H + +#include <sahara.h> + +/*! + * Structure passed during user ioctl() call to submit request. + */ +typedef struct sah_dar { + sah_Desc *desc_addr; /*!< head of descriptor chain */ + uint32_t uco_flags; /*!< copy of fsl_shw_uco flags field */ + uint32_t uco_user_ref; /*!< copy of fsl_shw_uco user_ref */ + uint32_t result; /*!< result of descriptor chain request */ + struct sah_dar *next; /*!< for driver use */ +} sah_dar_t; + +/*! + * Structure passed during user ioctl() call to Register a user + */ +typedef struct sah_register { + uint32_t pool_size; /*!< max number of outstanding requests possible */ + uint32_t result; /*!< result of registration request */ +} sah_register_t; + +/*! + * Structure passed during ioctl() call to request SCC operation + */ +typedef struct scc_data { + uint32_t length; /*!< length of data */ + uint8_t *in; /*!< input data */ + uint8_t *out; /*!< output data */ + unsigned direction; /*!< encrypt or decrypt */ + fsl_shw_sym_mode_t crypto_mode; /*!< CBC or EBC */ + uint8_t *init_vector; /*!< initialization vector or NULL */ +} scc_data_t; + +/*! + * Structure passed during user ioctl() calls to manage stored keys and + * stored-key slots. + */ +typedef struct scc_slot_info { + uint64_t ownerid; /*!< Owner's id to check/set permissions */ + uint32_t key_length; /*!< Length of key */ + uint32_t slot; /*!< Slot to operation on, or returned slot + number. */ + uint8_t *key; /*!< User-memory pointer to key value */ + fsl_shw_return_t code; /*!< API return code from operation */ +} scc_slot_t; + +fsl_shw_return_t adaptor_Exec_Descriptor_Chain(sah_Head_Desc * dar, + fsl_shw_uco_t * uco); +fsl_shw_return_t sah_get_results(sah_results * arg, fsl_shw_uco_t * uco); +fsl_shw_return_t sah_register(fsl_shw_uco_t * user_ctx); +fsl_shw_return_t sah_deregister(fsl_shw_uco_t * user_ctx); +fsl_shw_return_t do_scc_slot_alloc(fsl_shw_uco_t * user_ctx, uint32_t key_len, + uint64_t ownerid, uint32_t * slot); +fsl_shw_return_t do_scc_slot_dealloc(fsl_shw_uco_t * user_ctx, uint64_t ownerid, + uint32_t slot); +fsl_shw_return_t do_scc_slot_load_slot(fsl_shw_uco_t * user_ctx, + uint64_t ownerid, uint32_t slot, + const uint8_t * key, + uint32_t key_length); +fsl_shw_return_t do_scc_slot_encrypt(fsl_shw_uco_t * user_ctx, uint64_t ownerid, + uint32_t slot, uint32_t key_length, + uint8_t * black_data); + +fsl_shw_return_t do_scc_slot_decrypt(fsl_shw_uco_t * user_ctx, uint64_t ownerid, + uint32_t slot, uint32_t key_length, + const uint8_t * black_data); + +#endif /* ADAPTOR_H */ + +/* End of adaptor.h */ diff --git a/drivers/mxc/security/sahara2/include/diagnostic.h b/drivers/mxc/security/sahara2/include/diagnostic.h new file mode 100644 index 000000000000..cbd15357b754 --- /dev/null +++ b/drivers/mxc/security/sahara2/include/diagnostic.h @@ -0,0 +1,78 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! +* @file diagnostic.h +* +* @brief Macros for outputting kernel and user space diagnostics. +*/ + +#ifndef DIAGNOSTIC_H +#define DIAGNOSTIC_H + +#ifndef __KERNEL__ /* linux flag */ +#include <stdio.h> +#endif + +/*! +******************************************************************** +* @brief This macro logs diagnostic messages to stderr. +* +* @param diag String that must be logged, char *. +* +* @return void +* +*/ +#if defined DIAG_SECURITY_FUNC || defined DIAG_ADAPTOR +#define LOG_DIAG(diag) \ +({ \ + char* fname = strrchr(__FILE__, '/'); \ + \ + sah_Log_Diag (fname ? fname+1 : __FILE__, __LINE__, diag); \ +}) + +#ifndef __KERNEL__ +void sah_Log_Diag(char *source_name, int source_line, char *diag); +#endif +#endif + +#ifdef __KERNEL__ +/*! +******************************************************************** +* @brief This macro logs kernel diagnostic messages to the kernel +* log. +* +* @param diag String that must be logged, char *. +* +* @return As for printf() +*/ + +#if defined(DIAG_DRV_IF) || defined(DIAG_DRV_QUEUE) || \ + defined(DIAG_DRV_STATUS) || defined(DIAG_DRV_INTERRUPT) || \ + defined(DIAG_MEM) || defined(DIAG_SECURITY_FUNC) || defined(DIAG_ADAPTOR) +#define LOG_KDIAG(diag) \ + os_printk (KERN_ALERT "sahara (%s:%i): %s\n", \ + strrchr(__FILE__, '/')+1, __LINE__, diag); + +#define sah_Log_Diag(n, l, d) \ + os_printk("%s:%i: %s\n", n, l, d) +#endif + +#else /* not KERNEL */ + +#define sah_Log_Diag(n, l, d) \ + printf("%s:%i: %s\n", n, l, d) + +#endif /* __KERNEL__ */ + +#endif /* DIAGNOSTIC_H */ diff --git a/drivers/mxc/security/sahara2/include/fsl_platform.h b/drivers/mxc/security/sahara2/include/fsl_platform.h new file mode 100644 index 000000000000..1673b8ce6a1d --- /dev/null +++ b/drivers/mxc/security/sahara2/include/fsl_platform.h @@ -0,0 +1,115 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file fsl_platform.h + * + * Header file to isolate code which might be platform-dependent + */ + +#ifndef FSL_PLATFORM_H +#define FSL_PLATFORM_H + +#ifdef __KERNEL__ +#include "portable_os.h" +#endif + +#if defined(FSL_PLATFORM_OTHER) + +/* Have Makefile or other method of setting FSL_HAVE_* flags */ + +#elif defined(CONFIG_ARCH_MX3) /* i.MX31 */ + +#define FSL_HAVE_SCC +#define FSL_HAVE_RTIC +#define FSL_HAVE_RNGA + +#elif defined(CONFIG_ARCH_MX21) + +#define FSL_HAVE_HAC +#define FSL_HAVE_RNGA +#define FSL_HAVE_SCC + +#elif defined(CONFIG_ARCH_MX27) + +#define FSL_HAVE_SAHARA2 +#define SUBMIT_MULTIPLE_DARS +#define FSL_HAVE_RTIC +#define FSL_HAVE_SCC +#define USE_OLD_PTRS + +#elif defined(CONFIG_ARCH_MXC91131) + +#define FSL_HAVE_SCC +#define FSL_HAVE_RNGA +#define FSL_HAVE_HAC + +#elif defined(CONFIG_ARCH_MXC91221) + +#define FSL_HAVE_SCC +#define FSL_HAVE_RNGC +#define FSL_HAVE_RTIC2 + +#elif defined(CONFIG_ARCH_MXC91231) + +#define FSL_HAVE_SAHARA2 +#define USE_OLD_PTRS +#define FSL_HAVE_RTIC +#define FSL_HAVE_SCC +#define NO_OUTPUT_1K_CROSSING + +#elif defined(CONFIG_ARCH_MXC91311) + +#define FSL_HAVE_SCC +#define FSL_HAVE_RNGC + +#elif defined(CONFIG_ARCH_MXC91321) + +#define FSL_HAVE_SAHARA2 +#define FSL_HAVE_RTIC +#define FSL_HAVE_SCC +#define NO_OUTPUT_1K_CROSSING +#define USE_OLD_PTRS + +#elif defined(CONFIG_ARCH_MXC92323) + +#define FSL_HAVE_SCC2 +#define FSL_HAVE_SAHARA4 +#define FSL_HAVE_RTIC2 +#define NO_1K_CROSSING +#define NO_RESEED_WORKAROUND +#define NEED_CTR_WORKAROUND +#define USE_3WORD_BURST + +#elif defined(CONFIG_ARCH_MXC91331) + +#define FSL_HAVE_SCC +#define FSL_HAVE_RNGA +#define FSL_HAVE_HAC +#define FSL_HAVE_RTIC + +#elif defined(CONFIG_8548) + +#define FSL_HAVE_SEC2x + +#elif defined(CONFIG_MPC8374) + +#define FSL_HAVE_SEC3x + +#else + +#error UNKNOWN_PLATFORM + +#endif /* platform checks */ + +#endif /* FSL_PLATFORM_H */ diff --git a/drivers/mxc/security/sahara2/include/fsl_shw.h b/drivers/mxc/security/sahara2/include/fsl_shw.h new file mode 100644 index 000000000000..ef3311d90abc --- /dev/null +++ b/drivers/mxc/security/sahara2/include/fsl_shw.h @@ -0,0 +1,1964 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/* + * NOTE TO MAINTAINERS: Although this header file is *the* header file to be + * #include'd by FSL SHW programs, it does not itself make any definitions for + * the API. Instead, it use te fsl_platform.h file and / or compiler + * environment variables to determine which actual driver header file to + * include. This allows different implementations to contain different + * implementations of the various objects, macros, etc., or even to change + * which functions are macros and which are not. + */ + +/*! + * @file fsl_shw.h + * + * @brief Definition of the Freescale Security Hardware API. + * + * See @ref index for an overview of the API. + */ + +/*! + * @if USE_MAINPAGE + * @mainpage Common API for Freescale Security Hardware (FSL SHW API) + * @endif + * + * @section intro_sec Introduction + * + * This is the interface definition for the Freescale Security Hardware API + * (FSL SHW API) for User Mode and Kernel Mode to access Freescale Security + * Hardware components for cryptographic acceleration. The API is intended to + * provide cross-platform access to security hardware components of Freescale. + * + * This documentation has not been approved, and should not be taken to + * mean anything definite about future direction. + * + * Some example code is provided to give some idea of usage of this API. + * + * Note: This first version has been defined around the capabilities of the + * Sahara2 cryptographic accelerator, and may be expanded in the future to + * provide support for other platforms. The Platform Capabilities Object is + * intended as a way to allow programs to adapt to different platforms. + * + * + * @section usr_ctx The User Context + * + * The User Context Object (#fsl_shw_uco_t) controls the interaction between + * the user program and the API. It is initialized as part of user + * registration (#fsl_shw_register_user()), and is part of every interaction + * thereafter. + * + * @section pf_sec Platform Capababilities + * + * Since this API is not tied to one specific type of hardware or even one + * given version of a given type of hardware, the platform capabilities object + * could be used by a portable program to make choices about using software + * instead of hardware for certain operations. + * + * See the #fsl_shw_pco_t, returned by #fsl_shw_get_capabilities(). + * + * @ref pcoops are provided to query its contents. + * + * + * @section sym_sec Symmetric-Key Encryption and Decryption + * + * Symmetric-Key encryption support is provided for the block cipher algorithms + * AES, DES, and Triple DES. Modes supported are #FSL_SYM_MODE_ECB, + * #FSL_SYM_MODE_CBC, and #FSL_SYM_MODE_CTR, though not necessarily all modes + * for all algorithms. There is also support for the stream cipher algorithm + * commonly known as ARC4. + * + * Encryption and decryption are performed by using the functions + * #fsl_shw_symmetric_encrypt() and #fsl_shw_symmetric_decrypt(), respectively. + * There are two objects which provide information about the operation of these + * functions. They are the #fsl_shw_sko_t, to provide key and algorithm + * information; and the #fsl_shw_scco_t, to provide (and store) initial context + * or counter value information. + * + * CCM is not supported by these functions. For information CCM support, see + * @ref cmb_sec. + * + * + * @section hash_sec Cryptographic Hashing + * + * Hashing is performed by fsl_shw_hash(). Control of the function is through + * flags in the #fsl_shw_hco_t. The algorithms which are + * supported are listed in #fsl_shw_hash_alg_t. + * + * The hashing function works on octet streams. If a user application needs to + * hash a bitstream, it will need to do its own padding of the last block. + * + * + * @section hmac_sec Hashed Message Authentication Codes + * + * An HMAC is a method of combining a hash and a key so that a message cannot + * be faked by a third party. + * + * The #fsl_shw_hmac() can be used by itself for one-shot or multi-step + * operations, or in combination with #fsl_shw_hmac_precompute() to provide the + * ability to compute and save the beginning hashes from a key one time, and + * then use #fsl_shw_hmac() to calculate an HMAC on each message as it is + * processed. + * + * The maximum key length which is directly supported by this API is 64 octets. + * If a longer key size is needed for HMAC, the user will have to hash the key + * and present the digest value as the key to be used by the HMAC functions. + * + * + * @section rnd_sec Random Numbers + * + * Support is available for acquiring random values from a + * cryptographically-strong random number generator. See + * #fsl_shw_get_random(). The function #fsl_shw_add_entropy() may be used to + * add entropy to the random number generaator. + * + * + * @section cmb_sec Combined Cipher and Authentication + * + * Some schemes require that messages be encrypted and that they also have an + * authentication code associated with the message. The function + * #fsl_shw_gen_encrypt() will generate the authentication code and encrypt the + * message. + * + * Upon receipt of such a message, the message must be decrypted and the + * authentication code validated. The function + * #fsl_shw_auth_decrypt() will perform these steps. + * + * Only AES-CCM is supported. + * + * + * @section Wrapped Keys + * + * On platforms with a Secure Memory, the function #fsl_shw_establish_key() can + * be used to place a key into the Secure Memory. This key then be used + * directly by the cryptographic hardware. It later then be wrapped + * (cryptographically obscured) by #fsl_shw_extract_key() and stored for later + * use. + * + * The wrapping and unwrapping functions provide security against unauthorized + * use and detection of tampering. + */ + +/*! @defgroup glossary Glossary + * + * @li @b AES - Advanced Encryption Standard - An NIST-created block cipher + * originally knowns as Rijndael. + * @li @b ARC4 - ARCFOUR - An S-Box-based OFB mode stream cipher. + * @li @b CBC - Cipher-Block Chaining - Each encrypted block is XORed with the + * result of the previous block's encryption. + * @li @b CCM - A way of combining CBC and CTR to perform cipher and + * authentication. + * @li @b ciphertext - @a plaintext which has been encrypted in some fashion. + * @li @b context - Information on the state of a cryptographic operation, + * excluding any key. This could include IV, Counter Value, or SBox. + * @li @b CTR - A mode where a counter value is encrypted and then XORed with + * the data. After each block, the counter value is incremented. + * @li @b DES - Data Encryption Standard - An 8-octet-block cipher. + * @li @b ECB - Electronic Codebook - A straight encryption/decryption of the + * data. + * @li @b hash - A cryptographically strong one-way function peformed on data. + * @li @b HMAC - Hashed Message Authentication Code - A key-dependent one-way + * hash result, used to verify authenticity of a message. The equation + * for an HMAC is hash((K + A) || hash((K + B) || msg)), where K is the + * key, A is the constant for the outer hash, B is the constant for the + * inner hash, and hash is the hashing function (MD5, SHA256, etc). + * @li @b IPAD - In an HMAC operation, the context generated by XORing the key + * with a constant and then hashing that value as the first block of the + * inner hash. + * @li @b IV - An "Initial Vector" or @a context for modes like CBC. + * @li @b MAC - A Message Authentication Code. HMAC, hashing, and CCM all + * produce a MAC. + * @li @b mode - A way of using a cryptographic algorithm. See ECB, CBC, etc. + * @li @b MD5 - Message Digest 5 - A one-way hash function. + * @li @b plaintext - Data which has not been encrypted, or has been decrypted + * from @a ciphertext. + * @li @b OPAD - In an HMAC operation, the context generated by XORing the key + * with a constant and then hashing that value as the first block of the + * outer hash. + * @li @b SHA - Secure Hash Algorithm - A one-way hash function. + * @li @b TDES - AKA @b 3DES - Triple Data Encryption Standard - A method of + * using two or three keys and DES to perform three operations (encrypt + * decrypt encrypt) to create a new algorithm. + * @li @b XOR - Exclusive-OR. A Boolean arithmetic function. + * @li @b Wrapped value - A (key) which has been encrypted into an opaque datum + * which cannot be unwrapped (decrypted) for use except by an authorized + * user. Once created, the key is never visible, but may be used for + * other cryptographic operations. + */ + +#ifndef FSL_SHW_H +#define FSL_SHW_H + +/* Set FSL_HAVE_* flags */ + +#include "fsl_platform.h" + +#ifndef API_DOC + +#if defined(FSL_HAVE_SAHARA2) || defined(FSL_HAVE_SAHARA4) + +#include "sahara.h" + +#else + +#if defined(FSL_HAVE_RNGA) || defined(FSL_HAVE_RNGC) + +#include "rng_driver.h" + +#else + +#error FSL_SHW_API_platform_not_recognized + +#endif + +#endif /* HAVE_SAHARA2 */ + +#else /* API_DOC */ + +#include <inttypes.h> /* for uint32_t, etc. */ +#include <stdio.h> /* Mainly for definition of NULL !! */ + +/* These groups will appear in the order in which they are defined. */ + +/*! + * @defgroup strgrp Objects + * + * These objects are used to pass information into and out of the API. Through + * flags and other settings, they control the behavior of the @ref opfuns. + * + * They are maninpulated and queried by use of the various access functions. + * There are different sets defined for each object. See @ref objman. + */ + +/*! + * @defgroup consgrp Enumerations and other Constants + * + * This collection of symbols comprise the values which can be passed into + * various functions to control how the API will work. + */ + +/*! @defgroup opfuns Operational Functions + * + * These functions request that the underlying hardware perform cryptographic + * operations. They are the heart of the API. + */ + +/****** Organization the Object Operations under one group ! **********/ +/*! @defgroup objman Object-Manipulation Operations + * + */ +/*! @addtogroup objman + @{ */ +/*! + * @defgroup pcoops Platform Context Object Operations + * + * The Platform Context object is "read-only", so only query operations are + * provided for it. It is returned by the #fsl_shw_get_capabilities() + * function. + */ + +/*! @defgroup ucoops User Context Operations + * + * These operations should be the only access to the #fsl_shw_uco_t + * type/struct, as the internal members of the object are subject to change. + * The #fsl_shw_uco_init() function must be called before any other use of the + * object. + */ + +/*! + * @defgroup rops Result Object Operations + * + * As the Result Object contains the result of one of the @ref opfuns. The + * manipulations provided are query-only. No initialization is needed for this + * object. + */ + +/*! + * @defgroup skoops Secret Key Object Operations + * + * These operations should be the only access to the #fsl_shw_sko_t + * type/struct, as the internal members of that object are subject to change. + */ + +/*! + * @defgroup hcops Hash Context Object Operations + * + * These operations should be the only access to the #fsl_shw_hco_t + * type/struct, as the internal members of that object are subject to change. + */ + +/*! + * @defgroup hmcops HMAC Context Object Operations + * + * These operations should be the only access to the #fsl_shw_hmco_t + * type/struct, as the internal members of that object are subject to change. + */ + +/*! + * @defgroup sccops Symmetric Cipher Context Operations + * + * These operations should be the only access to the #fsl_shw_scco_t + * type/struct, as the internal members of that object are subject to change + */ + +/*! @defgroup accoops Authentication-Cipher Context Object Operations + * + * These functions operate on a #fsl_shw_acco_t. Their purpose is to set + * flags, fields, etc., in order to control the operation of + * #fsl_shw_gen_encrypt() and #fsl_shw_auth_decrypt(). + */ + + /* @} *//************ END GROUPING of Object Manipulations *****************/ + +/*! @defgroup miscfuns Miscellaneous Functions + * + * These functions are neither @ref opfuns nor @ref objman. Their behavior + * does not depend upon the flags in the #fsl_shw_uco_t, yet they may involve + * more interaction with the library and the kernel than simply querying an + * object. + */ + +/****************************************************************************** + * Enumerations + *****************************************************************************/ +/*! @addtogroup consgrp + @} */ + +/*! + * Flags for the state of the User Context Object (#fsl_shw_uco_t). + * + * These flags describe how the @ref opfuns will operate. + */ +typedef enum fsl_shw_user_ctx_flags { + FSL_UCO_BLOCKING_MODE, /*!< API will block the caller until operation + completes. The result will be available in the + return code. If this is not set, user will have + to get results using #fsl_shw_get_results(). */ + FSL_UCO_CALLBACK_MODE, /*!< User wants callback (at the function specified + with #fsl_shw_uco_set_callback()) when the + operation completes. This flag is valid only if + #FSL_UCO_BLOCKING_MODE is not set. */ +} fsl_shw_user_ctx_flags_t; + +/*! + * Return code for FSL_SHW library. + * + * These codes may be returned from a function call. In non-blocking mode, + * they will appear as the status in a Result Object. + */ +typedef enum fsl_shw_return { + FSL_RETURN_OK_S = 0, /*!< No error. As a function return code in + Non-blocking mode, this may simply mean that + the operation was accepted for eventual + execution. */ + FSL_RETURN_ERROR_S, /*!< Failure for non-specific reason. */ + FSL_RETURN_NO_RESOURCE_S, /*!< Operation failed because some resource was + not able to be allocated. */ + FSL_RETURN_BAD_ALGORITHM_S, /*!< Crypto algorithm unrecognized or + improper. */ + FSL_RETURN_BAD_MODE_S, /*!< Crypto mode unrecognized or improper. */ + FSL_RETURN_BAD_FLAG_S, /*!< Flag setting unrecognized or + inconsistent. */ + FSL_RETURN_BAD_KEY_LENGTH_S, /*!< Improper or unsupported key length for + algorithm. */ + FSL_RETURN_BAD_KEY_PARITY_S, /*!< Improper parity in a (DES, TDES) key. */ + FSL_RETURN_BAD_DATA_LENGTH_S, /*!< Improper or unsupported data length for + algorithm or internal buffer. */ + FSL_RETURN_AUTH_FAILED_S, /*!< Authentication failed in + authenticate-decrypt operation. */ + FSL_RETURN_MEMORY_ERROR_S, /*!< A memory error occurred. */ + FSL_RETURN_INTERNAL_ERROR_S /*!< An error internal to the hardware + occurred. */ +} fsl_shw_return_t; + +/*! + * Algorithm Identifier. + * + * Selection of algorithm will determine how large the block size of the + * algorithm is. Context size is the same length unless otherwise specified. + * Selection of algorithm also affects the allowable key length. + */ +typedef enum fsl_shw_key_alg { + FSL_KEY_ALG_HMAC, /*!< Key will be used to perform an HMAC. Key + size is 1 to 64 octets. Block size is 64 + octets. */ + FSL_KEY_ALG_AES, /*!< Advanced Encryption Standard (Rijndael). + Block size is 16 octets. Key size is 16 + octets. (The single choice of key size is a + Sahara platform limitation.) */ + FSL_KEY_ALG_DES, /*!< Data Encryption Standard. Block size is + 8 octets. Key size is 8 octets. */ + FSL_KEY_ALG_TDES, /*!< 2- or 3-key Triple DES. Block size is 8 + octets. Key size is 16 octets for 2-key + Triple DES, and 24 octets for 3-key. */ + FSL_KEY_ALG_ARC4 /*!< ARC4. No block size. Context size is 259 + octets. Allowed key size is 1-16 octets. + (The choices for key size are a Sahara + platform limitation.) */ +} fsl_shw_key_alg_t; + +/*! + * Mode selector for Symmetric Ciphers. + * + * The selection of mode determines how a cryptographic algorithm will be + * used to process the plaintext or ciphertext. + * + * For all modes which are run block-by-block (that is, all but + * #FSL_SYM_MODE_STREAM), any partial operations must be performed on a text + * length which is multiple of the block size. Except for #FSL_SYM_MODE_CTR, + * these block-by-block algorithms must also be passed a total number of octets + * which is a multiple of the block size. + * + * In modes which require that the total number of octets of data be a multiple + * of the block size (#FSL_SYM_MODE_ECB and #FSL_SYM_MODE_CBC), and the user + * has a total number of octets which are not a multiple of the block size, the + * user must perform any necessary padding to get to the correct data length. + */ +typedef enum fsl_shw_sym_mode { + /*! + * Stream. There is no associated block size. Any request to process data + * may be of any length. This mode is only for ARC4 operations, and is + * also the only mode used for ARC4. + */ + FSL_SYM_MODE_STREAM, + + /*! + * Electronic Codebook. Each block of data is encrypted/decrypted. The + * length of the data stream must be a multiple of the block size. This + * mode may be used for DES, 3DES, and AES. The block size is determined + * by the algorithm. + */ + FSL_SYM_MODE_ECB, + /*! + * Cipher-Block Chaining. Each block of data is encrypted/decrypted and + * then "chained" with the previous block by an XOR function. Requires + * context to start the XOR (previous block). This mode may be used for + * DES, 3DES, and AES. The block size is determined by the algorithm. + */ + FSL_SYM_MODE_CBC, + /*! + * Counter. The counter is encrypted, then XORed with a block of data. + * The counter is then incremented (using modulus arithmetic) for the next + * block. The final operation may be non-multiple of block size. This mode + * may be used for AES. The block size is determined by the algorithm. + */ + FSL_SYM_MODE_CTR, +} fsl_shw_sym_mode_t; + +/*! + * Algorithm selector for Cryptographic Hash functions. + * + * Selection of algorithm determines how large the context and digest will be. + * Context is the same size as the digest (resulting hash), unless otherwise + * specified. + */ +typedef enum fsl_shw_hash_alg { + FSL_HASH_ALG_MD5, /*!< MD5 algorithm. Digest is 16 octets. */ + FSL_HASH_ALG_SHA1, /*!< SHA-1 (aka SHA or SHA-160) algorithm. + Digest is 20 octets. */ + FSL_HASH_ALG_SHA224, /*!< SHA-224 algorithm. Digest is 28 octets, + though context is 32 octets. */ + FSL_HASH_ALG_SHA256 /*!< SHA-256 algorithm. Digest is 32 + octets. */ +} fsl_shw_hash_alg_t; + +/*! + * The type of Authentication-Cipher function which will be performed. + */ +typedef enum fsl_shw_acc_mode { + /*! + * CBC-MAC for Counter. Requires context and modulus. Final operation may + * be non-multiple of block size. This mode may be used for AES. + */ + FSL_ACC_MODE_CCM +} fsl_shw_acc_mode_t; + +/*! + * The operation which controls the behavior of #fsl_shw_establish_key(). + * + * These values are passed to #fsl_shw_establish_key(). + */ +typedef enum fsl_shw_key_wrap { + FSL_KEY_WRAP_CREATE, /*!< Generate a key from random values. */ + FSL_KEY_WRAP_ACCEPT, /*!< Use the provided clear key. */ + FSL_KEY_WRAP_UNWRAP /*!< Unwrap a previously wrapped key. */ +} fsl_shw_key_wrap_t; + +/* REQ-S2LRD-PINTFC-COA-HCO-001 */ +/*! + * Flags which control a Hash operation. + * + * These may be combined by ORing them together. See #fsl_shw_hco_set_flags() + * and #fsl_shw_hco_clear_flags(). + */ +typedef enum fsl_shw_hash_ctx_flags { + FSL_HASH_FLAGS_INIT = 1, /*!< Context is empty. Hash is started + from scratch, with a message-processed + count of zero. */ + FSL_HASH_FLAGS_SAVE = 2, /*!< Retrieve context from hardware after + hashing. If used with the + #FSL_HASH_FLAGS_FINALIZE flag, the final + digest value will be saved in the + object. */ + FSL_HASH_FLAGS_LOAD = 4, /*!< Place context into hardware before + hashing. */ + FSL_HASH_FLAGS_FINALIZE = 8, /*!< PAD message and perform final digest + operation. If user message is + pre-padded, this flag should not be + used. */ +} fsl_shw_hash_ctx_flags_t; + +/*! + * Flags which control an HMAC operation. + * + * These may be combined by ORing them together. See #fsl_shw_hmco_set_flags() + * and #fsl_shw_hmco_clear_flags(). + */ +typedef enum fsl_shw_hmac_ctx_flags { + FSL_HMAC_FLAGS_INIT = 1, /*!< Message context is empty. HMAC is + started from scratch (with key) or from + precompute of inner hash, depending on + whether + #FSL_HMAC_FLAGS_PRECOMPUTES_PRESENT is + set. */ + FSL_HMAC_FLAGS_SAVE = 2, /*!< Retrieve ongoing context from hardware + after hashing. If used with the + #FSL_HMAC_FLAGS_FINALIZE flag, the final + digest value (HMAC) will be saved in the + object. */ + FSL_HMAC_FLAGS_LOAD = 4, /*!< Place ongoing context into hardware + before hashing. */ + FSL_HMAC_FLAGS_FINALIZE = 8, /*!< PAD message and perform final HMAC + operations of inner and outer hashes. */ + FSL_HMAC_FLAGS_PRECOMPUTES_PRESENT = 16 /*!< This means that the context + contains precomputed inner and outer + hash values. */ +} fsl_shw_hmac_ctx_flags_t; + +/*! + * Flags to control use of the #fsl_shw_scco_t. + * + * These may be ORed together to get the desired effect. + * See #fsl_shw_scco_set_flags() and #fsl_shw_scco_clear_flags() + */ +typedef enum fsl_shw_sym_ctx_flags { + /*! + * Context is empty. In ARC4, this means that the S-Box needs to be + * generated from the key. In #FSL_SYM_MODE_CBC mode, this allows an IV of + * zero to be specified. In #FSL_SYM_MODE_CTR mode, it means that an + * initial CTR value of zero is desired. + */ + FSL_SYM_CTX_INIT = 1, + /*! + * Load context from object into hardware before running cipher. In + * #FSL_SYM_MODE_CTR mode, this would refer to the Counter Value. + */ + FSL_SYM_CTX_LOAD = 2, + /*! + * Save context from hardware into object after running cipher. In + * #FSL_SYM_MODE_CTR mode, this would refer to the Counter Value. + */ + FSL_SYM_CTX_SAVE = 4, + /*! + * Context (SBox) is to be unwrapped and wrapped on each use. + * This flag is unsupported. + * */ + FSL_SYM_CTX_PROTECT = 8, +} fsl_shw_sym_ctx_flags_t; + +/*! + * Flags which describe the state of the #fsl_shw_sko_t. + * + * These may be ORed together to get the desired effect. + * See #fsl_shw_sko_set_flags() and #fsl_shw_sko_clear_flags() + */ +typedef enum fsl_shw_key_flags { + FSL_SKO_KEY_IGNORE_PARITY = 1, /*!< If algorithm is DES or 3DES, do not + validate the key parity bits. */ + FSL_SKO_KEY_PRESENT = 2, /*!< Clear key is present in the object. */ + FSL_SKO_KEY_ESTABLISHED = 4, /*!< Key has been established for use. This + feature is not available for all + platforms, nor for all algorithms and + modes. */ +} fsl_shw_key_flags_t; + +/*! + * Type of value which is associated with an established key. + */ +typedef uint64_t key_userid_t; + +/*! + * Flags which describe the state of the #fsl_shw_acco_t. + * + * The @a FSL_ACCO_CTX_INIT and @a FSL_ACCO_CTX_FINALIZE flags, when used + * together, provide for a one-shot operation. + */ +typedef enum fsl_shw_auth_ctx_flags { + FSL_ACCO_CTX_INIT = 1, /*!< Initialize Context(s) */ + FSL_ACCO_CTX_LOAD = 2, /*!< Load intermediate context(s). + This flag is unsupported. */ + FSL_ACCO_CTX_SAVE = 4, /*!< Save intermediate context(s). + This flag is unsupported. */ + FSL_ACCO_CTX_FINALIZE = 8, /*!< Create MAC during this operation. */ + FSL_ACCO_NIST_CCM = 0x10, /*!< Formatting of CCM input data is + performed by calls to + #fsl_shw_ccm_nist_format_ctr_and_iv() and + #fsl_shw_ccm_nist_update_ctr_and_iv(). */ +} fsl_shw_auth_ctx_flags_t; + +/*! + * Modulus Selector for CTR modes. + * + * The incrementing of the Counter value may be modified by a modulus. If no + * modulus is needed or desired for AES, use #FSL_CTR_MOD_128. + */ +typedef enum fsl_shw_ctr_mod { + FSL_CTR_MOD_8, /*!< Run counter with modulus of 2^8. */ + FSL_CTR_MOD_16, /*!< Run counter with modulus of 2^16. */ + FSL_CTR_MOD_24, /*!< Run counter with modulus of 2^24. */ + FSL_CTR_MOD_32, /*!< Run counter with modulus of 2^32. */ + FSL_CTR_MOD_40, /*!< Run counter with modulus of 2^40. */ + FSL_CTR_MOD_48, /*!< Run counter with modulus of 2^48. */ + FSL_CTR_MOD_56, /*!< Run counter with modulus of 2^56. */ + FSL_CTR_MOD_64, /*!< Run counter with modulus of 2^64. */ + FSL_CTR_MOD_72, /*!< Run counter with modulus of 2^72. */ + FSL_CTR_MOD_80, /*!< Run counter with modulus of 2^80. */ + FSL_CTR_MOD_88, /*!< Run counter with modulus of 2^88. */ + FSL_CTR_MOD_96, /*!< Run counter with modulus of 2^96. */ + FSL_CTR_MOD_104, /*!< Run counter with modulus of 2^104. */ + FSL_CTR_MOD_112, /*!< Run counter with modulus of 2^112. */ + FSL_CTR_MOD_120, /*!< Run counter with modulus of 2^120. */ + FSL_CTR_MOD_128 /*!< Run counter with modulus of 2^128. */ +} fsl_shw_ctr_mod_t; + + /*! @} *//* consgrp */ + +/****************************************************************************** + * Data Structures + *****************************************************************************/ +/*! @addtogroup strgrp + @{ */ + +/* REQ-S2LRD-PINTFC-COA-IBO-001 */ +/*! + * Application Initialization Object + * + * This object, the operations on it, and its interaction with the driver are + * TBD. + */ +typedef struct fsl_sho_ibo { +} fsl_sho_ibo_t; + +/* REQ-S2LRD-PINTFC-COA-UCO-001 */ +/*! + * User Context Object + * + * This object must be initialized by a call to #fsl_shw_uco_init(). It must + * then be passed to #fsl_shw_register_user() before it can be used in any + * calls besides those in @ref ucoops. + * + * It contains the user's configuration for the API, for instance whether an + * operation should block, or instead should call back the user upon completion + * of the operation. + * + * See @ref ucoops for further information. + */ +typedef struct fsl_shw_uco { /* fsl_shw_user_context_object */ +} fsl_shw_uco_t; + +/* REQ-S2LRD-PINTFC-API-GEN-006 ?? */ +/*! + * Result Object + * + * This object will contain success and failure information about a specific + * cryptographic request which has been made. + * + * No direct access to its members should be made by programs. Instead, the + * object should be manipulated using the provided functions. See @ref rops. + */ +typedef struct fsl_shw_result { /* fsl_shw_result */ +} fsl_shw_result_t; + +/* REQ-S2LRD-PINTFC-COA-SKO-001 */ +/*! + * Secret Key Object + * + * This object contains a key for a cryptographic operation, and information + * about its current state, its intended usage, etc. It may instead contain + * information about a protected key, or an indication to use a platform- + * specific secret key. + * + * No direct access to its members should be made by programs. Instead, the + * object should be manipulated using the provided functions. See @ref skoops. + */ +typedef struct fsl_shw_sko { /* fsl_shw_secret_key_object */ +} fsl_shw_sko_t; + +/* REQ-S2LRD-PINTFC-COA-CO-001 */ +/*! + * Platform Capabilities Object + * + * This object will contain information about the cryptographic features of the + * platform which the program is running on. + * + * No direct access to its members should be made by programs. Instead, the + * object should be manipulated using the provided functions. + * + * See @ref pcoops. + */ +typedef struct fsl_shw_pco { /* fsl_shw_platform_capabilities_object */ +} fsl_shw_pco_t; + +/* REQ-S2LRD-PINTFC-COA-HCO-001 */ +/*! + * Hash Context Object + * + * This object contains information to control hashing functions. + + * No direct access to its members should be made by programs. Instead, the + * object should be manipulated using the provided functions. See @ref hcops. + */ +typedef struct fsl_shw_hco { /* fsl_shw_hash_context_object */ +} fsl_shw_hco_t; + +/*! + * HMAC Context Object + * + * This object contains information to control HMAC functions. + + * No direct access to its members should be made by programs. Instead, the + * object should be manipulated using the provided functions. See @ref hmcops. + */ +typedef struct fsl_shw_hmco { /* fsl_shw_hmac_context_object */ +} fsl_shw_hmco_t; + +/* REQ-S2LRD-PINTFC-COA-SCCO-001 */ +/*! + * Symmetric Cipher Context Object + * + * This object contains information to control Symmetric Ciphering encrypt and + * decrypt functions in #FSL_SYM_MODE_STREAM (ARC4), #FSL_SYM_MODE_ECB, + * #FSL_SYM_MODE_CBC, and #FSL_SYM_MODE_CTR modes and the + * #fsl_shw_symmetric_encrypt() and #fsl_shw_symmetric_decrypt() functions. + * CCM mode is controlled with the #fsl_shw_acco_t object. + * + * No direct access to its members should be made by programs. Instead, the + * object should be manipulated using the provided functions. See @ref sccops. + */ +typedef struct fsl_shw_scco { /* fsl_shw_symmetric_cipher_context_object */ +} fsl_shw_scco_t; + +/*! + * Authenticate-Cipher Context Object + + * An object for controlling the function of, and holding information about, + * data for the authenticate-cipher functions, #fsl_shw_gen_encrypt() and + * #fsl_shw_auth_decrypt(). + * + * No direct access to its members should be made by programs. Instead, the + * object should be manipulated using the provided functions. See @ref + * accoops. + */ +typedef struct fsl_shw_acco { /* fsl_shw_authenticate_cipher_context_object */ +} fsl_shw_acco_t; + /*! @} *//* strgrp */ + +/****************************************************************************** + * Access Macros for Objects + *****************************************************************************/ +/*! @addtogroup pcoops + @{ */ + +/*! + * Get FSL SHW API version + * + * @param pc_info The Platform Capababilities Object to query. + * @param[out] major A pointer to where the major version + * of the API is to be stored. + * @param[out] minor A pointer to where the minor version + * of the API is to be stored. + */ +void fsl_shw_pco_get_version(const fsl_shw_pco_t * pc_info, + uint32_t * major, uint32_t * minor); + +/*! + * Get underlying driver version. + * + * @param pc_info The Platform Capababilities Object to query. + * @param[out] major A pointer to where the major version + * of the driver is to be stored. + * @param[out] minor A pointer to where the minor version + * of the driver is to be stored. + */ +void fsl_shw_pco_get_driver_version(const fsl_shw_pco_t * pc_info, + uint32_t * major, uint32_t * minor); + +/*! + * Get list of symmetric algorithms supported. + * + * @param pc_info The Platform Capababilities Object to query. + * @param[out] algorithms A pointer to where to store the location of + * the list of algorithms. + * @param[out] algorithm_count A pointer to where to store the number of + * algorithms in the list at @a algorithms. + */ +void fsl_shw_pco_get_sym_algorithms(const fsl_shw_pco_t * pc_info, + fsl_shw_key_alg_t * algorithms[], + uint8_t * algorithm_count); + +/*! + * Get list of symmetric modes supported. + * + * @param pc_info The Platform Capababilities Object to query. + * @param[out] modes A pointer to where to store the location of + * the list of modes. + * @param[out] mode_count A pointer to where to store the number of + * algorithms in the list at @a modes. + */ +void fsl_shw_pco_get_sym_modes(const fsl_shw_pco_t * pc_info, + fsl_shw_sym_mode_t * modes[], + uint8_t * mode_count); + +/*! + * Get list of hash algorithms supported. + * + * @param pc_info The Platform Capababilities Object to query. + * @param[out] algorithms A pointer which will be set to the list of + * algorithms. + * @param[out] algorithm_count The number of algorithms in the list at @a + * algorithms. + */ +void fsl_shw_pco_get_hash_algorithms(const fsl_shw_pco_t * pc_info, + fsl_shw_hash_alg_t * algorithms[], + uint8_t * algorithm_count); + +/*! + * Determine whether the combination of a given symmetric algorithm and a given + * mode is supported. + * + * @param pc_info The Platform Capababilities Object to query. + * @param algorithm A Symmetric Cipher algorithm. + * @param mode A Symmetric Cipher mode. + * + * @return 0 if combination is not supported, non-zero if supported. + */ +int fsl_shw_pco_check_sym_supported(const fsl_shw_pco_t * pc_info, + fsl_shw_key_alg_t algorithm, + fsl_shw_sym_mode_t mode); + +/*! + * Determine whether a given Encryption-Authentication mode is supported. + * + * @param pc_info The Platform Capababilities Object to query. + * @param mode The Authentication mode. + * + * @return 0 if mode is not supported, non-zero if supported. + */ +int fsl_shw_pco_check_auth_supported(const fsl_shw_pco_t * pc_info, + fsl_shw_acc_mode_t mode); + +/*! + * Determine whether Black Keys (key establishment / wrapping) is supported. + * + * @param pc_info The Platform Capababilities Object to query. + * + * @return 0 if wrapping is not supported, non-zero if supported. + */ +int fsl_shw_pco_check_black_key_supported(const fsl_shw_pco_t * pc_info); + + /*! @} *//* pcoops */ + +/*! @addtogroup ucoops + @{ */ + +/*! + * Initialize a User Context Object. + * + * This function must be called before performing any other operation with the + * Object. It sets the User Context Object to initial values, and set the size + * of the results pool. The mode will be set to a default of + * #FSL_UCO_BLOCKING_MODE. + * + * When using non-blocking operations, this sets the maximum number of + * operations which can be outstanding. This number includes the counts of + * operations waiting to start, operation(s) being performed, and results which + * have not been retrieved. + * + * Changes to this value are ignored once user registration has completed. It + * should be set to 1 if only blocking operations will ever be performed. + * + * @param user_ctx The User Context object to operate on. + * @param pool_size The maximum number of operations which can be + * outstanding. + */ +void fsl_shw_uco_init(fsl_shw_uco_t * user_ctx, uint16_t pool_size); + +/*! + * Set the User Reference for the User Context. + * + * @param user_ctx The User Context object to operate on. + * @param reference A value which will be passed back with a result. + */ +void fsl_shw_uco_set_reference(fsl_shw_uco_t * user_ctx, uint32_t reference); + +/*! + * Set the callback routine for the User Context. + * + * Note that the callback routine may be called when no results are available, + * and possibly even when no requests are oustanding. + * + * + * @param user_ctx The User Context object to operate on. + * @param callback_fn The function the API will invoke when an operation + * completes. + */ +void fsl_shw_uco_set_callback(fsl_shw_uco_t * user_ctx, + void (*callback_fn) (fsl_shw_uco_t * uco)); + +/*! + * Set flags in the User Context. + * + * Turns on the flags specified in @a flags. Other flags are untouched. + * + * @param user_ctx The User Context object to operate on. + * @param flags ORed values from #fsl_shw_user_ctx_flags_t. + */ +void fsl_shw_uco_set_flags(fsl_shw_uco_t * user_ctx, uint32_t flags); + +/*! + * Clear flags in the User Context. + * + * Turns off the flags specified in @a flags. Other flags are untouched. + * + * @param user_ctx The User Context object to operate on. + * @param flags ORed values from #fsl_shw_user_ctx_flags_t. + */ +void fsl_shw_uco_clear_flags(fsl_shw_uco_t * user_ctx, uint32_t flags); + + /*! @} *//* ucoops */ + +/*! @addtogroup rops + @{ */ + +/*! + * Retrieve the status code from a Result Object. + * + * @param result The result object to query. + * + * @return The status of the request. + */ +fsl_shw_return_t fsl_shw_ro_get_status(fsl_shw_result_t * result); + +/*! + * Retrieve the reference value from a Result Object. + * + * @param result The result object to query. + * + * @return The reference associated with the request. + */ +uint32_t fsl_shw_ro_get_reference(fsl_shw_result_t * result); + + /* @} *//* rops */ + +/*! @addtogroup skoops + @{ */ + +/*! + * Initialize a Secret Key Object. + * + * This function must be called before performing any other operation with + * the Object. + * + * @param key_info The Secret Key Object to be initialized. + * @param algorithm DES, AES, etc. + * + */ +void fsl_shw_sko_init(fsl_shw_sko_t * key_info, fsl_shw_key_alg_t algorithm); + +/*! + * Store a cleartext key in the key object. + * + * This has the side effect of setting the #FSL_SKO_KEY_PRESENT flag and + * resetting the #FSL_SKO_KEY_ESTABLISHED flag. + * + * @param key_object A variable of type #fsl_shw_sko_t. + * @param key A pointer to the beginning of the key. + * @param key_length The length, in octets, of the key. The value should be + * appropriate to the key size supported by the algorithm. + * 64 octets is the absolute maximum value allowed for this + * call. + */ +void fsl_shw_sko_set_key(fsl_shw_sko_t * key_object, + const uint8_t * key, uint16_t key_length); + +/*! + * Set a size for the key. + * + * This function would normally be used when the user wants the key to be + * generated from a random source. + * + * @param key_object A variable of type #fsl_shw_sko_t. + * @param key_length The length, in octets, of the key. The value should be + * appropriate to the key size supported by the algorithm. + * 64 octets is the absolute maximum value allowed for this + * call. + */ +void fsl_shw_sko_set_key_length(fsl_shw_sko_t * key_object, + uint16_t key_length); + +/*! + * Set the User ID associated with the key. + * + * @param key_object A variable of type #fsl_shw_sko_t. + * @param userid The User ID to identify authorized users of the key. + */ +void fsl_shw_sko_set_user_id(fsl_shw_sko_t * key_object, key_userid_t userid); + +/*! + * Set the establish key handle into a key object. + * + * The @a userid field will be used to validate the access to the unwrapped + * key. This feature is not available for all platforms, nor for all + * algorithms and modes. + * + * The #FSL_SKO_KEY_ESTABLISHED will be set (and the #FSL_SKO_KEY_PRESENT + * flag will be cleared). + * + * @param key_object A variable of type #fsl_shw_sko_t. + * @param userid The User ID to verify this user is an authorized user of + * the key. + * @param handle A @a handle from #fsl_shw_sko_get_established_info. + */ +void fsl_shw_sko_set_established_info(fsl_shw_sko_t * key_object, + key_userid_t userid, uint32_t handle); + +/*! + * Extract the algorithm from a key object. + * + * @param key_info The Key Object to be queried. + * @param[out] algorithm A pointer to the location to store the algorithm. + */ +void fsl_shw_sko_get_algorithm(const fsl_shw_sko_t * key_info, + fsl_shw_key_alg_t * algorithm); + +/*! + * Retrieve the established-key handle from a key object. + * + * @param key_object A variable of type #fsl_shw_sko_t. + * @param handle The location to store the @a handle of the unwrapped + * key. + */ +void fsl_shw_sko_get_established_info(fsl_shw_sko_t * key_object, + uint32_t * handle); + +/*! + * Determine the size of a wrapped key based upon the cleartext key's length. + * + * This function can be used to calculate the number of octets that + * #fsl_shw_extract_key() will write into the location at @a covered_key. + * + * If zero is returned at @a length, this means that the key length in + * @a key_info is not supported. + * + * @param key_info Information about a key to be wrapped. + * @param length Location to store the length of a wrapped + * version of the key in @a key_info. + */ +void fsl_shw_sko_calculate_wrapped_size(const fsl_shw_sko_t * key_info, + uint32_t * length); + +/*! + * Set some flags in the key object. + * + * Turns on the flags specified in @a flags. Other flags are untouched. + * + * @param key_object A variable of type #fsl_shw_sko_t. + * @param flags (One or more) ORed members of #fsl_shw_key_flags_t which + * are to be set. + */ +void fsl_shw_sko_set_flags(fsl_shw_sko_t * key_object, uint32_t flags); + +/*! + * Clear some flags in the key object. + * + * Turns off the flags specified in @a flags. Other flags are untouched. + * + * @param key_object A variable of type #fsl_shw_sko_t. + * @param flags (One or more) ORed members of #fsl_shw_key_flags_t which + * are to be reset. + */ +void fsl_shw_sko_clear_flags(fsl_shw_sko_t * key_object, uint32_t flags); + + /*! @} *//* end skoops */ + +/*****************************************************************************/ + +/*! @addtogroup hcops + @{ */ + +/*****************************************************************************/ +/* REQ-S2LRD-PINTFC-API-BASIC-HASH-004 - partially */ +/*! + * Initialize a Hash Context Object. + * + * This function must be called before performing any other operation with the + * Object. It sets the current message length and hash algorithm in the hash + * context object. + * + * @param hash_ctx The hash context to operate upon. + * @param algorithm The hash algorithm to be used (#FSL_HASH_ALG_MD5, + * #FSL_HASH_ALG_SHA256, etc). + * + */ +void fsl_shw_hco_init(fsl_shw_hco_t * hash_ctx, fsl_shw_hash_alg_t algorithm); + +/*****************************************************************************/ +/* REQ-S2LRD-PINTFC-API-BASIC-HASH-001 */ +/* REQ-S2LRD-PINTFC-API-BASIC-HASH-002 */ +/*! + * Get the current hash value and message length from the hash context object. + * + * The algorithm must have already been specified. See #fsl_shw_hco_init(). + * + * @param hash_ctx The hash context to query. + * @param[out] digest Pointer to the location of @a length octets where to + * store a copy of the current value of the digest. + * @param length Number of octets of hash value to copy. + * @param[out] msg_length Pointer to the location to store the number of octets + * already hashed. + */ +void fsl_shw_hco_get_digest(const fsl_shw_hco_t * hash_ctx, uint8_t * digest, + uint8_t length, uint32_t * msg_length); + +/*****************************************************************************/ +/* REQ-S2LRD-PINTFC-API-BASIC-HASH-002 - partially */ +/*! + * Get the hash algorithm from the hash context object. + * + * @param hash_ctx The hash context to query. + * @param[out] algorithm Pointer to where the algorithm is to be stored. + */ +void fsl_shw_hco_get_info(const fsl_shw_hco_t * hash_ctx, + fsl_shw_hash_alg_t * algorithm); + +/*****************************************************************************/ +/* REQ-S2LRD-PINTFC-API-BASIC-HASH-003 */ +/* REQ-S2LRD-PINTFC-API-BASIC-HASH-004 */ +/*! + * Set the current hash value and message length in the hash context object. + * + * The algorithm must have already been specified. See #fsl_shw_hco_init(). + * + * @param hash_ctx The hash context to operate upon. + * @param context Pointer to buffer of appropriate length to copy into + * the hash context object. + * @param msg_length The number of octets of the message which have + * already been hashed. + * + */ +void fsl_shw_hco_set_digest(fsl_shw_hco_t * hash_ctx, const uint8_t * context, + uint32_t msg_length); + +/*! + * Set flags in a Hash Context Object. + * + * Turns on the flags specified in @a flags. Other flags are untouched. + * + * @param hash_ctx The hash context to be operated on. + * @param flags The flags to be set in the context. These can be ORed + * members of #fsl_shw_hash_ctx_flags_t. + */ +void fsl_shw_hco_set_flags(fsl_shw_hco_t * hash_ctx, uint32_t flags); + +/*! + * Clear flags in a Hash Context Object. + * + * Turns off the flags specified in @a flags. Other flags are untouched. + * + * @param hash_ctx The hash context to be operated on. + * @param flags The flags to be reset in the context. These can be ORed + * members of #fsl_shw_hash_ctx_flags_t. + */ +void fsl_shw_hco_clear_flags(fsl_shw_hco_t * hash_ctx, uint32_t flags); + + /*! @} *//* end hcops */ + +/*****************************************************************************/ + +/*! @addtogroup hmcops + @{ */ + +/*! + * Initialize an HMAC Context Object. + * + * This function must be called before performing any other operation with the + * Object. It sets the current message length and hash algorithm in the HMAC + * context object. + * + * @param hmac_ctx The HMAC context to operate upon. + * @param algorithm The hash algorithm to be used (#FSL_HASH_ALG_MD5, + * #FSL_HASH_ALG_SHA256, etc). + * + */ +void fsl_shw_hmco_init(fsl_shw_hmco_t * hmac_ctx, fsl_shw_hash_alg_t algorithm); + +/*! + * Set flags in an HMAC Context Object. + * + * Turns on the flags specified in @a flags. Other flags are untouched. + * + * @param hmac_ctx The HMAC context to be operated on. + * @param flags The flags to be set in the context. These can be ORed + * members of #fsl_shw_hmac_ctx_flags_t. + */ +void fsl_shw_hmco_set_flags(fsl_shw_hmco_t * hmac_ctx, uint32_t flags); + +/*! + * Clear flags in an HMAC Context Object. + * + * Turns off the flags specified in @a flags. Other flags are untouched. + * + * @param hmac_ctx The HMAC context to be operated on. + * @param flags The flags to be reset in the context. These can be ORed + * members of #fsl_shw_hmac_ctx_flags_t. + */ +void fsl_shw_hmco_clear_flags(fsl_shw_hmco_t * hmac_ctx, uint32_t flags); + +/*! @} */ + +/*****************************************************************************/ + +/*! @addtogroup sccops + @{ */ + +/*! + * Initialize a Symmetric Cipher Context Object. + * + * This function must be called before performing any other operation with the + * Object. This will set the @a mode and @a algorithm and initialize the + * Object. + * + * @param sym_ctx The context object to operate on. + * @param algorithm The cipher algorithm this context will be used with. + * @param mode #FSL_SYM_MODE_CBC, #FSL_SYM_MODE_ECB, etc. + * + */ +void fsl_shw_scco_init(fsl_shw_scco_t * sym_ctx, + fsl_shw_key_alg_t algorithm, fsl_shw_sym_mode_t mode); + +/*! + * Set the flags for a Symmetric Cipher Context. + * + * Turns on the flags specified in @a flags. Other flags are untouched. + * + * @param sym_ctx The context object to operate on. + * @param flags The flags to reset (one or more values from + * #fsl_shw_sym_ctx_flags_t ORed together). + * + */ +void fsl_shw_scco_set_flags(fsl_shw_scco_t * sym_ctx, uint32_t flags); + +/*! + * Clear some flags in a Symmetric Cipher Context Object. + * + * Turns off the flags specified in @a flags. Other flags are untouched. + * + * @param sym_ctx The context object to operate on. + * @param flags The flags to reset (one or more values from + * #fsl_shw_sym_ctx_flags_t ORed together). + * + */ +void fsl_shw_scco_clear_flags(fsl_shw_scco_t * sym_ctx, uint32_t flags); + +/*! + * Set the Context (IV) for a Symmetric Cipher Context. + * + * This is to set the context/IV for #FSL_SYM_MODE_CBC mode, or to set the + * context (the S-Box and pointers) for ARC4. The full context size will + * be copied. + * + * @param sym_ctx The context object to operate on. + * @param context A pointer to the buffer which contains the context. + * + */ +void fsl_shw_scco_set_context(fsl_shw_scco_t * sym_ctx, uint8_t * context); + +/*! + * Get the Context for a Symmetric Cipher Context. + * + * This is to retrieve the context/IV for #FSL_SYM_MODE_CBC mode, or to + * retrieve context (the S-Box and pointers) for ARC4. The full context + * will be copied. + * + * @param sym_ctx The context object to operate on. + * @param[out] context Pointer to location where context will be stored. + */ +void fsl_shw_scco_get_context(const fsl_shw_scco_t * sym_ctx, + uint8_t * context); + +/*! + * Set the Counter Value for a Symmetric Cipher Context. + * + * This will set the Counter Value for CTR mode. + * + * @param sym_ctx The context object to operate on. + * @param counter The starting counter value. The number of octets. + * copied will be the block size for the algorithm. + * @param modulus The modulus for controlling the incrementing of the counter. + * + */ +void fsl_shw_scco_set_counter_info(fsl_shw_scco_t * sym_ctx, + const uint8_t * counter, + fsl_shw_ctr_mod_t modulus); + +/*! + * Get the Counter Value for a Symmetric Cipher Context. + * + * This will retrieve the Counter Value is for CTR mode. + * + * @param sym_ctx The context object to query. + * @param[out] counter Pointer to location to store the current counter + * value. The number of octets copied will be the + * block size for the algorithm. + * @param[out] modulus Pointer to location to store the modulus. + * + */ +void fsl_shw_scco_get_counter_info(const fsl_shw_scco_t * sym_ctx, + uint8_t * counter, + fsl_shw_ctr_mod_t * modulus); + + /*! @} *//* end sccops */ + +/*****************************************************************************/ + +/*! @addtogroup accoops + @{ */ + +/*! + * Initialize a Authentication-Cipher Context. + * + * @param auth_object Pointer to object to operate on. + * @param mode The mode for this object (only #FSL_ACC_MODE_CCM + * supported). + */ +void fsl_shw_acco_init(fsl_shw_acco_t * auth_object, fsl_shw_acc_mode_t mode); + +/*! + * Set the flags for a Authentication-Cipher Context. + * + * Turns on the flags specified in @a flags. Other flags are untouched. + * + * @param auth_object Pointer to object to operate on. + * @param flags The flags to set (one or more from + * #fsl_shw_auth_ctx_flags_t ORed together). + * + */ +void fsl_shw_acco_set_flags(fsl_shw_acco_t * auth_object, uint32_t flags); + +/*! + * Clear some flags in a Authentication-Cipher Context Object. + * + * Turns off the flags specified in @a flags. Other flags are untouched. + * + * @param auth_object Pointer to object to operate on. + * @param flags The flags to reset (one or more from + * #fsl_shw_auth_ctx_flags_t ORed together). + * + */ +void fsl_shw_acco_clear_flags(fsl_shw_acco_t * auth_object, uint32_t flags); + +/*! + * Set up the Authentication-Cipher Object for CCM mode. + * + * This will set the @a auth_object for CCM mode and save the @a ctr, + * and @a mac_length. This function can be called instead of + * #fsl_shw_acco_init(). + * + * The paramater @a ctr is Counter Block 0, (counter value 0), which is for the + * MAC. + * + * @param auth_object Pointer to object to operate on. + * @param algorithm Cipher algorithm. Only AES is supported. + * @param ctr The initial counter value. + * @param mac_length The number of octets used for the MAC. Valid values are + * 4, 6, 8, 10, 12, 14, and 16. + */ +void fsl_shw_acco_set_ccm(fsl_shw_acco_t * auth_object, + fsl_shw_key_alg_t algorithm, + const uint8_t * ctr, uint8_t mac_length); + +/*! + * Format the First Block (IV) & Initial Counter Value per NIST CCM. + * + * This function will also set the IV and CTR values per Appendix A of NIST + * Special Publication 800-38C (May 2004). It will also perform the + * #fsl_shw_acco_set_ccm() operation with information derived from this set of + * parameters. + * + * Note this function assumes the algorithm is AES. It initializes the + * @a auth_object by setting the mode to #FSL_ACC_MODE_CCM and setting the + * flags to be #FSL_ACCO_NIST_CCM. + * + * @param auth_object Pointer to object to operate on. + * @param t_length The number of octets used for the MAC. Valid values are + * 4, 6, 8, 10, 12, 14, and 16. + * @param ad_length Number of octets of Associated Data (may be zero). + * @param q_length A value for the size of the length of @a q field. Valid + * values are 1-8. + * @param n The Nonce (packet number or other changing value). Must + * be (15 - @a q_length) octets long. + * @param q The value of Q (size of the payload in octets). + * + */ +void fsl_shw_ccm_nist_format_ctr_and_iv(fsl_shw_acco_t * auth_object, + uint8_t t_length, + uint32_t ad_length, + uint8_t q_length, + const uint8_t * n, uint32_t q); + +/*! + * Update the First Block (IV) & Initial Counter Value per NIST CCM. + * + * This function will set the IV and CTR values per Appendix A of NIST Special + * Publication 800-38C (May 2004). + * + * Note this function assumes that #fsl_shw_ccm_nist_format_ctr_and_iv() has + * previously been called on the @a auth_object. + * + * @param auth_object Pointer to object to operate on. + * @param n The Nonce (packet number or other changing value). Must + * be (15 - @a q_length) octets long. + * @param q The value of Q (size of the payload in octets). + * + */ +void fsl_shw_ccm_nist_update_ctr_and_iv(fsl_shw_acco_t * auth_object, + const uint8_t * n, uint32_t q); + + /* @} *//* accoops */ + +/****************************************************************************** + * Library functions + *****************************************************************************/ + +/*! @addtogroup miscfuns + @{ */ + +/* REQ-S2LRD-PINTFC-API-GEN-003 */ +/*! + * Determine the hardware security capabilities of this platform. + * + * Though a user context object is passed into this function, it will always + * act in a non-blocking manner. + * + * @param user_ctx The user context which will be used for the query. + * + * @return A pointer to the capabilities object. + */ +extern fsl_shw_pco_t *fsl_shw_get_capabilities(fsl_shw_uco_t * user_ctx); + +/* REQ-S2LRD-PINTFC-API-GEN-004 */ +/*! + * Create an association between the the user and the provider of the API. + * + * @param user_ctx The user context which will be used for this association. + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_register_user(fsl_shw_uco_t * user_ctx); + +/* REQ-S2LRD-PINTFC-API-GEN-005 */ +/*! + * Destroy the association between the the user and the provider of the API. + * + * @param user_ctx The user context which is no longer needed. + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_deregister_user(fsl_shw_uco_t * user_ctx); + +/* REQ-S2LRD-PINTFC-API-GEN-006 */ +/*! + * Retrieve results from earlier operations. + * + * @param user_ctx The user's context. + * @param result_size The number of array elements of @a results. + * @param[in,out] results Pointer to first of the (array of) locations to + * store results. + * @param[out] result_count Pointer to store the number of results which + * were returned. + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_get_results(fsl_shw_uco_t * user_ctx, + uint16_t result_size, + fsl_shw_result_t results[], + uint16_t * result_count); + + /*! @} *//* miscfuns */ + +/*! @addtogroup opfuns + @{ */ + +/* REQ-S2LRD-PINTFC-API-BASIC-SYM-002 */ +/* PINTFC-API-BASIC-SYM-ARC4-001 */ +/* PINTFC-API-BASIC-SYM-ARC4-002 */ +/*! + * Encrypt a stream of data with a symmetric-key algorithm. + * + * In ARC4, and also in #FSL_SYM_MODE_CBC and #FSL_SYM_MODE_CTR modes, the + * flags of the @a sym_ctx object will control part of the operation of this + * function. The #FSL_SYM_CTX_INIT flag means that there is no context info in + * the object. The #FSL_SYM_CTX_LOAD means to use information in the + * @a sym_ctx at the start of the operation, and the #FSL_SYM_CTX_SAVE flag + * means to update the object's context information after the operation has + * been performed. + * + * All of the data for an operation can be run through at once using the + * #FSL_SYM_CTX_INIT or #FSL_SYM_CTX_LOAD flags, as appropriate, and then using + * a @a length for the whole of the data. + * + * If a #FSL_SYM_CTX_SAVE flag were added, an additional call to the function + * would "pick up" where the previous call left off, allowing the user to + * perform the larger function in smaller steps. + * + * In #FSL_SYM_MODE_CBC and #FSL_SYM_MODE_ECB modes, the @a length must always + * be a multiple of the block size for the algorithm being used. For proper + * operation in #FSL_SYM_MODE_CTR mode, the @a length must be a multiple of the + * block size until the last operation on the total octet stream. + * + * Some users of ARC4 may want to compute the context (S-Box and pointers) from + * the key before any data is available. This may be done by running this + * function with a @a length of zero, with the init & save flags flags on in + * the @a sym_ctx. Subsequent operations would then run as normal with the + * load and save flags. Note that they key object is still required. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param key_info Key and algorithm being used for this operation. + * @param[in,out] sym_ctx Info on cipher mode, state of the cipher. + * @param length Length, in octets, of the pt (and ct). + * @param pt pointer to plaintext to be encrypted. + * @param[out] ct pointer to where to store the resulting ciphertext. + * + * @return A return code of type #fsl_shw_return_t. + * + */ +extern fsl_shw_return_t fsl_shw_symmetric_encrypt(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + fsl_shw_scco_t * sym_ctx, + uint32_t length, + const uint8_t * pt, + uint8_t * ct); + +/* PINTFC-API-BASIC-SYM-002 */ +/* PINTFC-API-BASIC-SYM-ARC4-001 */ +/* PINTFC-API-BASIC-SYM-ARC4-002 */ +/*! + * Decrypt a stream of data with a symmetric-key algorithm. + * + * In ARC4, and also in #FSL_SYM_MODE_CBC and #FSL_SYM_MODE_CTR modes, the + * flags of the @a sym_ctx object will control part of the operation of this + * function. The #FSL_SYM_CTX_INIT flag means that there is no context info in + * the object. The #FSL_SYM_CTX_LOAD means to use information in the + * @a sym_ctx at the start of the operation, and the #FSL_SYM_CTX_SAVE flag + * means to update the object's context information after the operation has + * been performed. + * + * All of the data for an operation can be run through at once using the + * #FSL_SYM_CTX_INIT or #FSL_SYM_CTX_LOAD flags, as appropriate, and then using + * a @a length for the whole of the data. + * + * If a #FSL_SYM_CTX_SAVE flag were added, an additional call to the function + * would "pick up" where the previous call left off, allowing the user to + * perform the larger function in smaller steps. + * + * In #FSL_SYM_MODE_CBC and #FSL_SYM_MODE_ECB modes, the @a length must always + * be a multiple of the block size for the algorithm being used. For proper + * operation in #FSL_SYM_MODE_CTR mode, the @a length must be a multiple of the + * block size until the last operation on the total octet stream. + * + * Some users of ARC4 may want to compute the context (S-Box and pointers) from + * the key before any data is available. This may be done by running this + * function with a @a length of zero, with the #FSL_SYM_CTX_INIT & + * #FSL_SYM_CTX_SAVE flags on in the @a sym_ctx. Subsequent operations would + * then run as normal with the load & save flags. Note that they key object is + * still required. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param key_info The key and algorithm being used in this operation. + * @param[in,out] sym_ctx Info on cipher mode, state of the cipher. + * @param length Length, in octets, of the ct (and pt). + * @param ct pointer to ciphertext to be decrypted. + * @param[out] pt pointer to where to store the resulting plaintext. + * + * @return A return code of type #fsl_shw_return_t + * + */ +extern fsl_shw_return_t fsl_shw_symmetric_decrypt(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + fsl_shw_scco_t * sym_ctx, + uint32_t length, + const uint8_t * ct, + uint8_t * pt); + +/* REQ-S2LRD-PINTFC-API-BASIC-HASH-005 */ +/*! + * Hash a stream of data with a cryptographic hash algorithm. + * + * The flags in the @a hash_ctx control the operation of this function. + * + * Hashing functions work on 64 octets of message at a time. Therefore, when + * any partial hashing of a long message is performed, the message @a length of + * each segment must be a multiple of 64. When ready to + * #FSL_HASH_FLAGS_FINALIZE the hash, the @a length may be any value. + * + * With the #FSL_HASH_FLAGS_INIT and #FSL_HASH_FLAGS_FINALIZE flags on, a + * one-shot complete hash, including padding, will be performed. The @a length + * may be any value. + * + * The first octets of a data stream can be hashed by setting the + * #FSL_HASH_FLAGS_INIT and #FSL_HASH_FLAGS_SAVE flags. The @a length must be + * a multiple of 64. + * + * The flag #FSL_HASH_FLAGS_LOAD is used to load a context previously saved by + * #FSL_HASH_FLAGS_SAVE. The two in combination will allow a (multiple-of-64 + * octets) 'middle sequence' of the data stream to be hashed with the + * beginning. The @a length must again be a multiple of 64. + * + * Since the flag #FSL_HASH_FLAGS_LOAD is used to load a context previously + * saved by #FSL_HASH_FLAGS_SAVE, the #FSL_HASH_FLAGS_LOAD and + * #FSL_HASH_FLAGS_FINALIZE flags, used together, can be used to finish the + * stream. The @a length may be any value. + * + * If the user program wants to do the padding for the hash, it can leave off + * the #FSL_HASH_FLAGS_FINALIZE flag. The @a length must then be a multiple of + * 64 octets. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param[in,out] hash_ctx Hashing algorithm and state of the cipher. + * @param msg Pointer to the data to be hashed. + * @param length Length, in octets, of the @a msg. + * @param[out] result If not null, pointer to where to store the hash + * digest. + * @param result_len Number of octets to store in @a result. + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_hash(fsl_shw_uco_t * user_ctx, + fsl_shw_hco_t * hash_ctx, + const uint8_t * msg, + uint32_t length, + uint8_t * result, uint32_t result_len); + +/* REQ-S2LRD-PINTFC-API-BASIC-HMAC-001 */ +/*! + * Precompute the Key hashes for an HMAC operation. + * + * This function may be used to calculate the inner and outer precomputes, + * which are the hash contexts resulting from hashing the XORed key for the + * 'inner hash' and the 'outer hash', respectively, of the HMAC function. + * + * After execution of this function, the @a hmac_ctx will contain the + * precomputed inner and outer contexts, so that they may be used by + * #fsl_shw_hmac(). The flags of @a hmac_ctx will be updated with + * #FSL_HMAC_FLAGS_PRECOMPUTES_PRESENT to mark their presence. In addtion, the + * #FSL_HMAC_FLAGS_INIT flag will be set. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param key_info The key being used in this operation. Key must be + * 1 to 64 octets long. + * @param[in,out] hmac_ctx The context which controls, by its flags and + * algorithm, the operation of this function. + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_hmac_precompute(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + fsl_shw_hmco_t * hmac_ctx); + +/* REQ-S2LRD-PINTFC-API-BASIC-HMAC-002 */ +/*! + * Continue, finalize, or one-shot an HMAC operation. + * + * There are a number of ways to use this function. The flags in the + * @a hmac_ctx object will determine what operations occur. + * + * If #FSL_HMAC_FLAGS_INIT is set, then the hash will be started either from + * the @a key_info, or from the precomputed inner hash value in the + * @a hmac_ctx, depending on the value of #FSL_HMAC_FLAGS_PRECOMPUTES_PRESENT. + * + * If, instead, #FSL_HMAC_FLAGS_LOAD is set, then the hash will be continued + * from the ongoing inner hash computation in the @a hmac_ctx. + * + * If #FSL_HMAC_FLAGS_FINALIZE are set, then the @a msg will be padded, hashed, + * the outer hash will be performed, and the @a result will be generated. + * + * If the #FSL_HMAC_FLAGS_SAVE flag is set, then the (ongoing or final) digest + * value will be stored in the ongoing inner hash computation field of the @a + * hmac_ctx. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param key_info If #FSL_HMAC_FLAGS_INIT is set in the @a hmac_ctx, + * this is the key being used in this operation, and the + * IPAD. If #FSL_HMAC_FLAGS_INIT is set in the @a + * hmac_ctx and @a key_info is NULL, then + * #fsl_shw_hmac_precompute() has been used to populate + * the @a inner_precompute and @a outer_precompute + * contexts. If #FSL_HMAC_FLAGS_INIT is not set, this + * parameter is ignored. + + * @param[in,out] hmac_ctx The context which controls, by its flags and + * algorithm, the operation of this function. + * @param msg Pointer to the message to be hashed. + * @param length Length, in octets, of the @a msg. + * @param[out] result Pointer, of @a result_len octets, to where to + * store the HMAC. + * @param result_len Length of @a result buffer. + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_hmac(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + fsl_shw_hmco_t * hmac_ctx, + const uint8_t * msg, + uint32_t length, + uint8_t * result, uint32_t result_len); + +/* REQ-S2LRD-PINTFC-API-BASIC-RNG-002 */ +/*! + * Get random data. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param length The number of octets of @a data being requested. + * @param[out] data A pointer to a location of @a length octets to where + * random data will be returned. + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_get_random(fsl_shw_uco_t * user_ctx, + uint32_t length, uint8_t * data); + +/* REQ-S2LRD-PINTFC-API-BASIC-RNG-002 */ +/*! + * Add entropy to random number generator. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param length Number of bytes at @a data. + * @param data Entropy to add to random number generator. + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_add_entropy(fsl_shw_uco_t * user_ctx, + uint32_t length, uint8_t * data); + +/*! + * Perform Generation-Encryption by doing a Cipher and a Hash. + * + * Generate the authentication value @a auth_value as well as encrypt the @a + * payload into @a ct (the ciphertext). This is a one-shot function, so all of + * the @a auth_data and the total message @a payload must passed in one call. + * This also means that the flags in the @a auth_ctx must be #FSL_ACCO_CTX_INIT + * and #FSL_ACCO_CTX_FINALIZE. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param auth_ctx Controlling object for Authenticate-decrypt. + * @param cipher_key_info The key being used for the cipher part of this + * operation. In CCM mode, this key is used for + * both parts. + * @param auth_key_info The key being used for the authentication part + * of this operation. In CCM mode, this key is + * ignored and may be NULL. + * @param auth_data_length Length, in octets, of @a auth_data. + * @param auth_data Data to be authenticated but not encrypted. + * @param payload_length Length, in octets, of @a payload. + * @param payload Pointer to the plaintext to be encrypted. + * @param[out] ct Pointer to the where the encrypted @a payload + * will be stored. Must be @a payload_length + * octets long. + * @param[out] auth_value Pointer to where the generated authentication + * field will be stored. Must be as many octets as + * indicated by MAC length in the @a function_ctx. + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_gen_encrypt(fsl_shw_uco_t * user_ctx, + fsl_shw_acco_t * auth_ctx, + fsl_shw_sko_t * cipher_key_info, + fsl_shw_sko_t * auth_key_info, + uint32_t auth_data_length, + const uint8_t * auth_data, + uint32_t payload_length, + const uint8_t * payload, + uint8_t * ct, uint8_t * auth_value); + +/*! + * Perform Authentication-Decryption in Cipher + Hash. + * + * This function will perform a one-shot decryption of a data stream as well as + * authenticate the authentication value. This is a one-shot function, so all + * of the @a auth_data and the total message @a payload must passed in one + * call. This also means that the flags in the @a auth_ctx must be + * #FSL_ACCO_CTX_INIT and #FSL_ACCO_CTX_FINALIZE. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param auth_ctx Controlling object for Authenticate-decrypt. + * @param cipher_key_info The key being used for the cipher part of this + * operation. In CCM mode, this key is used for + * both parts. + * @param auth_key_info The key being used for the authentication part + * of this operation. In CCM mode, this key is + * ignored and may be NULL. + * @param auth_data_length Length, in octets, of @a auth_data. + * @param auth_data Data to be authenticated but not decrypted. + * @param payload_length Length, in octets, of @a ct and @a pt. + * @param ct Pointer to the encrypted input stream. + * @param auth_value The (encrypted) authentication value which will + * be authenticated. This is the same data as the + * (output) @a auth_value argument to + * #fsl_shw_gen_encrypt(). + * @param[out] payload Pointer to where the plaintext resulting from + * the decryption will be stored. + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_auth_decrypt(fsl_shw_uco_t * user_ctx, + fsl_shw_acco_t * auth_ctx, + fsl_shw_sko_t * cipher_key_info, + fsl_shw_sko_t * auth_key_info, + uint32_t auth_data_length, + const uint8_t * auth_data, + uint32_t payload_length, + const uint8_t * ct, + const uint8_t * auth_value, + uint8_t * payload); + +/*! + * Place a key into a protected location for use only by cryptographic + * algorithms. + * + * This only needs to be used to a) unwrap a key, or b) set up a key which + * could be wrapped with a later call to #fsl_shw_extract_key(). Normal + * cleartext keys can simply be placed into #fsl_shw_sko_t key objects with + * #fsl_shw_sko_set_key() and used directly. + * + * The maximum key size supported for wrapped/unwrapped keys is 32 octets. + * (This is the maximum reasonable key length on Sahara - 32 octets for an HMAC + * key based on SHA-256.) The key size is determined by the @a key_info. The + * expected length of @a key can be determined by + * #fsl_shw_sko_calculate_wrapped_size() + * + * The protected key will not be available for use until this operation + * successfully completes. + * + * This feature is not available for all platforms, nor for all algorithms and + * modes. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param[in,out] key_info The information about the key to be which will + * be established. In the create case, the key + * length must be set. + * @param establish_type How @a key will be interpreted to establish a + * key for use. + * @param key If @a establish_type is #FSL_KEY_WRAP_UNWRAP, + * this is the location of a wrapped key. If + * @a establish_type is #FSL_KEY_WRAP_CREATE, this + * parameter can be @a NULL. If @a establish_type + * is #FSL_KEY_WRAP_ACCEPT, this is the location + * of a plaintext key. + */ +extern fsl_shw_return_t fsl_shw_establish_key(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + fsl_shw_key_wrap_t establish_type, + const uint8_t * key); + +/*! + * Wrap a key and retrieve the wrapped value. + * + * A wrapped key is a key that has been cryptographically obscured. It is + * only able to be used with #fsl_shw_establish_key(). + * + * This function will also release the key (see #fsl_shw_release_key()) so + * that it must be re-established before reuse. + * + * This feature is not available for all platforms, nor for all algorithms and + * modes. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param key_info The information about the key to be deleted. + * @param[out] covered_key The location to store the wrapped key. + * (This size is based upon the maximum key size + * of 32 octets). + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_extract_key(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + uint8_t * covered_key); + +/*! + * De-establish a key so that it can no longer be accessed. + * + * The key will need to be re-established before it can again be used. + * + * This feature is not available for all platforms, nor for all algorithms and + * modes. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param key_info The information about the key to be deleted. + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_release_key(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info); + + /*! @} *//* opfuns */ + +/* Insert example code into the API documentation. */ + +/*! + * @example apitest.c + */ + +/*! + * @example sym.c + */ + +/*! + * @example rand.c + */ + +/*! + * @example hash.c + */ + +/*! + * @example hmac1.c + */ + +/*! + * @example hmac2.c + */ + +/*! + * @example gen_encrypt.c + */ + +/*! + * @example auth_decrypt.c + */ + +/*! + * @example wrapped_key.c + */ + +#endif /* API_DOC */ + +#endif /* FSL_SHW_H */ diff --git a/drivers/mxc/security/sahara2/include/linux_port.h b/drivers/mxc/security/sahara2/include/linux_port.h new file mode 100644 index 000000000000..66b5d9ec0295 --- /dev/null +++ b/drivers/mxc/security/sahara2/include/linux_port.h @@ -0,0 +1,1705 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file linux_port.h + * + * OS_PORT ported to Linux (2.6.9+ for now) + * + */ + + /*! + * @if USE_MAINPAGE + * @mainpage ==Linux version of== Generic OS API for STC Drivers + * @endif + * + * @section intro_sec Introduction + * + * This API / kernel programming environment blah blah. + * + * See @ref dkops "Driver-to-Kernel Operations" as a good place to start. + */ + +#ifndef LINUX_PORT_H +#define LINUX_PORT_H + +#define PORTABLE_OS_VERSION 101 + +/* Linux Kernel Includes */ +#include <linux/version.h> /* Current version Linux kernel */ + +#if defined(CONFIG_MODVERSIONS) && ! defined(MODVERSIONS) +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,11) +#include <linux/modversions.h> +#endif +#define MODVERSIONS +#endif +/*! + * __NO_VERSION__ defined due to Kernel module possibly spanning multiple + * files. + */ +#define __NO_VERSION__ + +#include <linux/module.h> /* Basic support for loadable modules, + printk */ +#include <linux/init.h> /* module_init, module_exit */ +#include <linux/kernel.h> /* General kernel system calls */ +#include <linux/sched.h> /* for interrupt.h */ +#include <linux/fs.h> /* for inode */ +#include <linux/random.h> +#include <linux/spinlock.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/slab.h> /* kmalloc */ + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,11) +#include <linux/device.h> /* used in dynamic power management */ +#else +#include <linux/platform_device.h> /* used in dynamic power management */ +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,18) +#include <asm/arch/clock.h> /* clock en/disable for DPM */ +#else +#include <linux/clk.h> /* clock en/disable for DPM */ +#endif + +#include <linux/dmapool.h> +#include <linux/dma-mapping.h> + +#include <asm/io.h> /* ioremap() */ +#include <asm/irq.h> +#include <asm/uaccess.h> /* copy_to_user(), copy_from_user() */ +#include <asm/cacheflush.h> + +#ifndef TRUE +/*! Useful symbol for unsigned values used as flags. */ +#define TRUE 1 +#endif + +#ifndef FALSE +/*! Useful symbol for unsigned values used as flags. */ +#define FALSE 0 +#endif + +/* These symbols are defined in Linux 2.6 and later. Include here for minimal + * support of 2.4 kernel. + **/ +#if !defined(LINUX_VERSION_CODE) || LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) +/*! + * Symbol defined somewhere in 2.5/2.6. It is the return signature of an ISR. + */ +#define irqreturn_t void +/*! Possible return value of 'modern' ISR routine. */ +#define IRQ_HANDLED +/*! Method of generating value of 'modern' ISR routine. */ +#define IRQ_RETVAL(x) +#endif + +/*! + * Type used for registering and deregistering interrupts. + */ +typedef int os_interrupt_id_t; + +/*! + * Type used as handle for a process + * + * See #os_get_process_handle() and #os_send_signal(). + */ +/* + * The following should be defined this way, but it gets compiler errors + * on the current tool chain. + * + * typedef task_t *os_process_handle_t; + */ + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,11) +typedef task_t *os_process_handle_t; +#else +typedef struct task_struct *os_process_handle_t; +#endif + +/*! + * Generic return code for functions which need such a thing. + * + * No knowledge should be assumed of the value of any of these symbols except + * that @c OS_ERROR_OK_S is guaranteed to be zero. + */ +typedef enum { + OS_ERROR_OK_S = 0, /*!< Success */ + OS_ERROR_FAIL_S = -EIO, /*!< Generic driver failure */ + OS_ERROR_NO_MEMORY_S = -ENOMEM, /*!< Failure to acquire/use memory */ + OS_ERROR_BAD_ADDRESS = -EFAULT /*!< Bad address */ +} os_error_code; + +/*! + * Handle to a lock. + */ +#ifdef CONFIG_PREEMPT_RT +typedef raw_spinlock_t *os_lock_t; +#else +typedef spinlock_t *os_lock_t; +#endif + +/*! + * Context while locking. + */ +typedef unsigned long os_lock_context_t; + +/*! + * Declare a wait object for sleeping/waking processes. + */ +#define OS_WAIT_OBJECT(name) \ + DECLARE_WAIT_QUEUE_HEAD(name##_qh) + +/*! + * Driver registration handle + * + * Used with #os_driver_init_registration(), #os_driver_add_registration(), + * and #os_driver_complete_registration(). + */ +typedef struct { + unsigned reg_complete; /*!< TRUE if next inits succeeded. */ + dev_t dev; /*!< dev_t for register_chrdev() */ + struct file_operations fops; /*!< struct for register_chrdev() */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,13) + struct class_simple *cs; /*!< results of class_simple_create() */ +#else + struct class *cs; /*!< results of class_create() */ +#endif + struct class_device *cd; /*!< Result of class_simple_device_add() */ + unsigned power_complete; /*!< TRUE if next inits succeeded */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,11) + struct device_driver dd; /*!< struct for register_driver() */ +#else + struct platform_driver dd; /*!< struct for register_driver() */ +#endif + struct platform_device pd; /*!< struct for platform_register_device() */ +} os_driver_reg_t; + +/* + * Function types which can be associated with driver entry points. + * + * Note that init and shutdown are absent. + */ +/*! @{ */ +/*! Keyword for registering open() operation handler. */ +#define OS_FN_OPEN open +/*! Keyword for registering close() operation handler. */ +#define OS_FN_CLOSE release +/*! Keyword for registering read() operation handler. */ +#define OS_FN_READ read +/*! Keyword for registering write() operation handler. */ +#define OS_FN_WRITE write +/*! Keyword for registering ioctl() operation handler. */ +#define OS_FN_IOCTL ioctl +/*! Keyword for registering mmap() operation handler. */ +#define OS_FN_MMAP mmap +/*! @} */ + +/*! + * Function signature for the portable interrupt handler + * + * While it would be nice to know which interrupt is being serviced, the + * Least Common Denominator rule says that no arguments get passed in. + * + * @return Zero if not handled, non-zero if handled. + */ +typedef int (*os_interrupt_handler_t) (int, void *); + +/*! + * @defgroup dkops Driver-to-Kernel Operations + * + * These are the operations which drivers should call to get the OS to perform + * services. + */ + +/*! @addtogroup dkops */ +/*! @{ */ + +/*! + * Register an interrupt handler. + * + * @param driver_name The name of the driver + * @param interrupt_id The interrupt line to monitor (type + * #os_interrupt_id_t) + * @param function The function to be called to handle an interrupt + * + * @return #os_error_code + */ +#define os_register_interrupt(driver_name, interrupt_id, function) \ + request_irq(interrupt_id, function, 0, driver_name, NULL) + +/*! + * Deregister an interrupt handler. + * + * @param interrupt_id The interrupt line to stop monitoring + * + * @return #os_error_code + */ +#define os_deregister_interrupt(interrupt_id) \ + free_irq(interrupt_id, NULL) + +/*! + * INTERNAL implementation of os_driver_init_registration() + * + * @return An os error code. + */ +inline static int os_drv_do_init_reg(os_driver_reg_t * handle) +{ + memset(handle, sizeof(*handle), 0); + handle->fops.owner = THIS_MODULE; + handle->power_complete = FALSE; + handle->reg_complete = FALSE; +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,11) + handle->dd.name = NULL; +#else + handle->dd.driver.name = NULL; +#endif + + return OS_ERROR_OK_S; +} + +/*! + * Initialize driver registration. + * + * If the driver handles open(), close(), ioctl(), read(), write(), or mmap() + * calls, then it needs to register their location with the kernel so that they + * get associated with the device. + * + * @param handle The handle object to be used with this registration. The + * object must live (be in memory somewhere) at least until + * os_driver_remove_registration() is called. + * + * @return A handle for further driver registration, or NULL if failed. + */ +#define os_driver_init_registration(handle) \ + os_drv_do_init_reg(&handle) + +/*! + * Add a function registration to driver registration. + * + * @param handle A handle initialized by #os_driver_init_registration(). + * @param name Which function is being supported. + * @param function The result of a call to a @c _REF version of one of the + * driver function signature macros + * @return void + */ +#define os_driver_add_registration(handle, name, function) \ + do {handle.fops.name = (void*)(function); } while (0) + +/*! + * Record 'power suspend' function for the device. + * + * @param handle A handle initialized by #os_driver_init_registration(). + * @param function Name of function to call on power suspend request + * + * Status: Provisional + * + * @return void + */ +#define os_driver_register_power_suspend(handle, function) \ + handle.dd.suspend = function + +/*! + * Record 'power resume' function for the device. + * + * @param handle A handle initialized by #os_driver_init_registration(). + * @param function Name of function to call on power resume request + * + * Status: Provisional + * + * @return void + */ +#define os_driver_register_resume(handle, function) \ + handle.dd.resume = function + +/*! + * INTERNAL function of the Linux port of the OS API. Implements the + * os_driver_complete_registration() function. + * + * @param handle The handle used with #os_driver_init_registration(). + * @param major The major device number to be associated with the driver. + * If this value is zero, a major number may be assigned. + * See #os_driver_get_major() to determine final value. + * #os_driver_remove_registration(). + * @param driver_name The driver name. Can be used as part of 'device node' + * name on platforms which support such a feature. + * + * @return An error code + */ +inline static int os_drv_do_reg(os_driver_reg_t * handle, + unsigned major, char *driver_name) +{ + os_error_code code = OS_ERROR_NO_MEMORY_S; + char *name = kmalloc(strlen(driver_name) + 1, 0); + + if (name != NULL) { + memcpy(name, driver_name, strlen(driver_name) + 1); + code = OS_ERROR_OK_S; /* OK so far */ + /* If any chardev/POSIX routines were added, then do chrdev part */ + if (handle->fops.open || handle->fops.release + || handle->fops.read || handle->fops.write + || handle->fops.ioctl || handle->fops.mmap) { + code = + register_chrdev(major, driver_name, &handle->fops); + if (code < 0) { + code = OS_ERROR_FAIL_S; + } else { + if (code != 0) { + /* Zero was passed in for major; code is actual value */ + handle->dev = MKDEV(code, 0); + } else { + handle->dev = MKDEV(major, 0); + } + code = OS_ERROR_OK_S; +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,13) + handle->cs = + class_simple_create(THIS_MODULE, + driver_name); + if (IS_ERR(handle->cs)) { + code = (os_error_code) handle->cs; + handle->cs = NULL; + } else { + handle->cd = + class_simple_device_add(handle->cs, + handle->dev, + NULL, + driver_name); + if (IS_ERR(handle->cd)) { + class_simple_device_remove + (handle->dev); + unregister_chrdev(MAJOR + (handle->dev), + driver_name); + code = + (os_error_code) handle->cs; + handle->cs = NULL; + } else { + handle->reg_complete = TRUE; + } + } +#else + handle->cs = + class_create(THIS_MODULE, driver_name); + if (IS_ERR(handle->cs)) { + code = (os_error_code) handle->cs; + handle->cs = NULL; + } else { + handle->cd = + class_device_create(handle->cs, + NULL, + handle->dev, + NULL, + driver_name); + if (IS_ERR(handle->cd)) { + class_device_destroy(handle->cs, + handle-> + dev); + class_destroy(handle->cs); + unregister_chrdev(MAJOR + (handle->dev), + driver_name); + code = + (os_error_code) handle->cs; + handle->cs = NULL; + } else { + handle->reg_complete = TRUE; + } + } +#endif + } + } + /* ... fops routine registered */ + /* Handle power management fns through separate interface */ + if ((code == OS_ERROR_OK_S) && + (handle->dd.suspend || handle->dd.resume)) { +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,11) + handle->dd.name = name; + handle->dd.bus = &platform_bus_type; + code = driver_register(&handle->dd); +#else + handle->dd.driver.name = name; + handle->dd.driver.bus = &platform_bus_type; + code = driver_register(&handle->dd.driver); +#endif + if (code == OS_ERROR_OK_S) { + handle->pd.name = name; + handle->pd.id = 0; + code = platform_device_register(&handle->pd); + if (code != OS_ERROR_OK_S) { +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,11) + driver_unregister(&handle->dd); +#else + driver_unregister(&handle->dd.driver); +#endif + } else { + handle->power_complete = TRUE; + } + } + } /* ... suspend or resume */ + } /* name != NULL */ + return code; +} + +/*! + * Finalize the driver registration with the kernel. + * + * Upon return from this call, the driver may begin receiving calls at the + * defined entry points. + * + * @param handle The handle used with #os_driver_init_registration(). + * @param major The major device number to be associated with the driver. + * If this value is zero, a major number may be assigned. + * See #os_driver_get_major() to determine final value. + * #os_driver_remove_registration(). + * @param driver_name The driver name. Can be used as part of 'device node' + * name on platforms which support such a feature. + * + * @return An error code + */ +#define os_driver_complete_registration(handle, major, driver_name) \ + os_drv_do_reg(&handle, major, driver_name) + +/*! + * Get driver Major Number from handle after a successful registration. + * + * @param handle A handle which has completed registration. + * + * @return The major number (if any) associated with the handle. + */ +#define os_driver_get_major(handle) \ + (handle.reg_complete ? MAJOR(handle.dev) : -1) + +/*! + * INTERNAL implemention of os_driver_remove_registration. + * + * @param handle A handle initialized by #os_driver_init_registration(). + * + * @return An error code. + */ +inline static int os_drv_rmv_reg(os_driver_reg_t * handle) +{ + if (handle->reg_complete) { +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,13) + if (handle->cd != NULL) { + class_simple_device_remove(handle->dev); + handle->cd = NULL; + } + if (handle->cs != NULL) { + class_simple_destroy(handle->cs); + handle->cs = NULL; + } + unregister_chrdev(MAJOR(handle->dev), handle->dd.name); +#else + if (handle->cd != NULL) { + class_device_destroy(handle->cs, handle->dev); + handle->cd = NULL; + } + if (handle->cs != NULL) { + class_destroy(handle->cs); + handle->cs = NULL; + } + unregister_chrdev(MAJOR(handle->dev), handle->dd.driver.name); +#endif + handle->reg_complete = FALSE; + } + if (handle->power_complete) { + platform_device_unregister(&handle->pd); +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,11) + driver_unregister(&handle->dd); +#else + driver_unregister(&handle->dd.driver); +#endif + handle->power_complete = FALSE; + } +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,11) + if (handle->dd.name != NULL) { + kfree(handle->dd.name); + handle->dd.name = NULL; + } +#else + if (handle->dd.driver.name != NULL) { + kfree(handle->dd.driver.name); + handle->dd.driver.name = NULL; + } +#endif + return OS_ERROR_OK_S; +} + +/*! + * Remove the driver's registration with the kernel. + * + * Upon return from this call, the driver not receive any more calls at the + * defined entry points (other than ISR and shutdown). + * + * @param handle A handle initialized by #os_driver_init_registration(). + * + * @return An error code. + */ +#define os_driver_remove_registration(handle) \ + os_drv_rmv_reg(&handle) + +/*! + * Register a driver with the Linux Device Model. + * + * @param driver_information The device_driver structure information + * + * @return An error code. + * + * Status: denigrated in favor of #os_driver_complete_registration() + */ +#define os_register_to_driver(driver_information) \ + driver_register(driver_information) + +/*! + * Unregister a driver from the Linux Device Model + * + * this routine unregisters from the Linux Device Model + * + * @param driver_information The device_driver structure information + * + * @return An error code. + * + * Status: Denigrated. See #os_register_to_driver(). + */ +#define os_unregister_from_driver(driver_information) \ + driver_unregister(driver_information) + +/*! + * register a device to a driver + * + * this routine registers a drivers devices to the Linux Device Model + * + * @param device_information The platform_device structure information + * + * @return An error code. + * + * Status: denigrated in favor of #os_driver_complete_registration() + */ +#define os_register_a_device(device_information) \ + platform_device_register(device_information) + +/*! + * unregister a device from a driver + * + * this routine unregisters a drivers devices from the Linux Device Model + * + * @param device_information The platform_device structure information + * + * @return An error code. + * + * Status: Denigrated. See #os_register_a_device(). + */ +#define os_unregister_a_device(device_information) \ + platform_device_unregister(device_information) + +/*! + * Print a message to console / into log file. After the @c msg argument a + * number of printf-style arguments may be added. Types should be limited to + * printf string, char, octal, decimal, and hexadecimal types. (This excludes + * pointers, and floating point). + * + * @param msg The main text of the message to be logged + * @param s The printf-style arguments which go with msg, if any + * + * @return (void) + */ +/* This may be a GCC-ism which needs to be ported to ANSI */ +#define os_printk(msg, s...) \ + (void) printk(msg, ## s) + +/*! + * Prepare a task to execute the given function. This should only be done once + * per function,, during the driver's initialization routine. + * + * @param task_fn Name of the OS_DEV_TASK() function to be created. + * + * @return an OS ERROR code. + */ +#define os_create_task(function_name) \ + OS_ERROR_OK_S + +/*! + * Schedule execution of a task. + * + * @param function_name The function associated with the task. + * + * @return (void) + */ +#define os_dev_schedule_task(function_name) \ + tasklet_schedule(&(function_name ## let)) + +/*! + * Make sure that task is no longer running and will no longer run. + * + * This function will not return until both are true. This is useful when + * shutting down a driver. + */ +#define os_dev_stop_task(function_name) \ +do { \ + tasklet_disable(&(function_name ## let)); \ + tasklet_kill(&(function_name ## let)); \ +} while (0) + +/*! + * Allocate some kernel memory + * + * @param amount Number of 8-bit bytes to allocate + * @param flags Some indication of purpose of memory (needs definition) + * + * @return Pointer to allocated memory, or NULL if failed. + */ +#define os_alloc_memory(amount, flags) \ + (void*)kmalloc(amount, flags) + +/*! + * Free some kernel memory + * + * @param location The beginning of the region to be freed. + * + * Do some OSes have separate free() functions which should be + * distinguished by passing in @c flags here, too? Don't some also require the + * size of the buffer being freed? + */ +#define os_free_memory(location) \ + kfree(location) + +/*! + * Allocate cache-coherent memory + * + * @param amount Number of bytes to allocate + * @param[out] dma_addrp Location to store physical address of allocated + * memory. + * @param flags Some indication of purpose of memory (needs + * definition). + * + * @return (virtual space) pointer to allocated memory, or NULL if failed. + * + */ +#define os_alloc_coherent(amount, dma_addrp, flags) \ + (void*)dma_alloc_coherent(NULL, amount, dma_addrp, flags) + +/*! + * Free cache-coherent memory + * + * @param size Number of bytes which were allocated. + * @param virt_addr Virtual(kernel) address of memory.to be freed, as + * returned by #os_alloc_coherent(). + * @param dma_addr Physical address of memory.to be freed, as returned + * by #os_alloc_coherent(). + * + * @return void + * + */ +#define os_free_coherent(size, virt_addr, dma_addr) \ + dma_free_coherent(NULL, size, virt_addr, dma_addr + +/*! + * Map an I/O space into kernel memory space + * + * @param start The starting address of the (physical / io space) region + * @param range_bytes The number of bytes to map + * + * @return A pointer to the mapped area, or NULL on failure + */ +#define os_map_device(start, range_bytes) \ + (void*)ioremap_nocache((start), range_bytes) + +/*! + * Unmap an I/O space from kernel memory space + * + * @param start The starting address of the (virtual) region + * @param range_bytes The number of bytes to unmap + * + * @return None + */ +#define os_unmap_device(start, range_bytes) \ + iounmap((void*)(start)) + +/*! + * Copy data from Kernel space to User space + * + * @param to The target location in user memory + * @param from The source location in kernel memory + * @param size The number of bytes to be copied + * + * @return #os_error_code + */ +#define os_copy_to_user(to, from, size) \ + ((copy_to_user(to, from, size) == 0) ? 0 : OS_ERROR_BAD_ADDRESS) + +/*! + * Copy data from User space to Kernel space + * + * @param to The target location in kernel memory + * @param from The source location in user memory + * @param size The number of bytes to be copied + * + * @return #os_error_code + */ +#define os_copy_from_user(to, from, size) \ + ((copy_from_user(to, from, size) == 0) ? 0 : OS_ERROR_BAD_ADDRESS) + +/*! + * Read a 8-bit device register + * + * @param register_address The (bus) address of the register to write to + * @return The value in the register + */ +#define os_read8(register_address) \ + __raw_readb(register_address) + +/*! + * Write a 8-bit device register + * + * @param register_address The (bus) address of the register to write to + * @param value The value to write into the register + */ +#define os_write8(register_address, value) \ + __raw_writeb(value, register_address) + +/*! + * Read a 16-bit device register + * + * @param register_address The (bus) address of the register to write to + * @return The value in the register + */ +#define os_read16(register_address) \ + __raw_readw(register_address) + +/*! + * Write a 16-bit device register + * + * @param register_address The (bus) address of the register to write to + * @param value The value to write into the register + */ +#define os_write16(register_address, value) \ + __raw_writew(value, (uint32_t*)(register_address)) + +/*! + * Read a 32-bit device register + * + * @param register_address The (bus) address of the register to write to + * @return The value in the register + */ +#define os_read32(register_address) \ + __raw_readl((uint32_t*)(register_address)) + +/*! + * Write a 32-bit device register + * + * @param register_address The (bus) address of the register to write to + * @param value The value to write into the register + */ +#define os_write32(register_address, value) \ + __raw_writel(value, register_address) + +/*! + * Read a 64-bit device register + * + * @param register_address The (bus) address of the register to write to + * @return The value in the register + */ +#define os_read64(register_address) \ + ERROR_UNIMPLEMENTED + +/*! + * Write a 64-bit device register + * + * @param register_address The (bus) address of the register to write to + * @param value The value to write into the register + */ +#define os_write64(register_address, value) \ + ERROR_UNIMPLEMENTED + +/*! + * Delay some number of microseconds + * + * Note that this is a busy-loop, not a suspension of the task/process. + * + * @param msecs The number of microseconds to delay + * + * @return void + */ +#define os_mdelay mdelay + +/*! + * Calculate virtual address from physical address + * + * @param pa Physical address + * + * @return virtual address + * + * @note this assumes that addresses are 32 bits wide + */ +#define os_va __va + +/*! + * Calculate physical address from virtual address + * + * + * @param va Virtual address + * + * @return physical address + * + * @note this assumes that addresses are 32 bits wide + */ +#define os_pa __pa + +#ifdef CONFIG_PREEMPT_RT +/*! + * Allocate and initialize a lock, returning a lock handle. + * + * The lock state will be initialized to 'unlocked'. + * + * @return A lock handle, or NULL if an error occurred. + */ +inline static os_lock_t os_lock_alloc_init(void) +{ + raw_spinlock_t *lockp; + lockp = (raw_spinlock_t *) kmalloc(sizeof(raw_spinlock_t), 0); + if (lockp) { + _raw_spin_lock_init(lockp); + } else { + printk("OS: lock init failedn"); + } + + return lockp; +} +#else +/*! + * Allocate and initialize a lock, returning a lock handle. + * + * The lock state will be initialized to 'unlocked'. + * + * @return A lock handle, or NULL if an error occurred. + */ +inline static os_lock_t os_lock_alloc_init(void) +{ + spinlock_t *lockp; + lockp = (spinlock_t *) kmalloc(sizeof(spinlock_t), 0); + if (lockp) { + spin_lock_init(lockp); + } else { + printk("OS: lock init failedn"); + } + + return lockp; +} +#endif /* CONFIG_PREEMPT_RT */ + +/*! + * Acquire a lock. + * + * This function should only be called from an interrupt service routine. + * + * @param lock_handle A handle to the lock to acquire. + * + * @return void + */ +#define os_lock(lock_handle) \ + spin_lock(lock_handle) + +/*! + * Unlock a lock. Lock must have been acquired by #os_lock(). + * + * @param lock_handle A handle to the lock to unlock. + * + * @return void + */ +#define os_unlock(lock_handle) \ + spin_unlock(lock_handle) + +/*! + * Acquire a lock in non-ISR context + * + * This function will spin until the lock is available. + * + * @param lock_handle A handle of the lock to acquire. + * @param context Place to save the before-lock context + * + * @return void + */ +#define os_lock_save_context(lock_handle, context) \ + spin_lock_irqsave(lock_handle, context) + +/*! + * Release a lock in non-ISR context + * + * @param lock_handle A handle of the lock to release. + * @param context Place where before-lock context was saved. + * + * @return void + */ +#define os_unlock_restore_context(lock_handle, context) \ + spin_unlock_irqrestore(lock_handle, context) + +/*! + * Deallocate a lock handle. + * + * @param lock_handle An #os_lock_t that has been allocated. + * + * @return void + */ +#define os_lock_deallocate(lock_handle) \ + kfree(lock_handle) + +/*! + * Determine process handle + * + * The process handle of the current user is returned. + * + * @return A handle on the current process. + */ +#define os_get_process_handle() \ + current + +/*! + * Send a signal to a process + * + * @param proc A handle to the target process. + * @param sig The POSIX signal to send to that process. + */ +#define os_send_signal(proc, sig) \ + send_sig(sig, proc, 0); + +/*! + * Get some random bytes + * + * @param buf The location to store the random data. + * @param count The number of bytes to store. + * + * @return void + */ +#define os_get_random_bytes(buf, count) \ + get_random_bytes(buf, count) + +/*! + * Go to sleep on an object. + * + * @param object The object on which to sleep + * @param condition An expression to check for sleep completion. Must be + * coded so that it can be referenced more than once inside + * macro, i.e., no ++ or other modifying expressions. + * @param atomic Non-zero if sleep must not return until condition. + * + * @return error code -- OK or sleep interrupted?? + */ +#define os_sleep(object, condition, atomic) \ +({ \ + DEFINE_WAIT(_waitentry_); \ + os_error_code code = OS_ERROR_OK_S; \ + \ + while (!(condition)) { \ + prepare_to_wait(&(object##_qh), &_waitentry_, \ + atomic ? 0 : TASK_INTERRUPTIBLE); \ + if (!(condition)) { \ + schedule(); \ + } \ + \ + finish_wait(&(object##_qh), &_waitentry_); \ + \ + if (!atomic && signal_pending(current)) { \ + code = OS_ERROR_FAIL_S; /* NEED SOMETHING BETTER */ \ + break; \ + } \ + }; \ + \ + code; \ +}) + +/*! + * Wake up whatever is sleeping on sleep object + * + * @param object The object on which things might be sleeping + * + * @return none + */ +#define os_wake_sleepers(object) \ + wake_up_interruptible(&(object##_qh)); + + /*! @} *//* dkops */ + +/****************************************************************************** + * Function signature-generating macros + *****************************************************************************/ + +/*! + * @defgroup drsigs Driver Signatures + * + * These macros will define the entry point signatures for interrupt handlers; + * driver initialization and shutdown; device open/close; etc. + * + * There are two versions of each macro for a given Driver Entry Point. The + * first version is used to define a function and its implementation in the + * driver.c file, e.g. #OS_DEV_INIT(). + * + * The second form is used whenever a forward declaration (prototype) is + * needed. It has the letters @c _DCL appended to the name of the defintion + * function, and takes only the first two arguments (driver_name and + * function_name). These are not otherwise mentioned in this documenation. + * + * There is a third form used when a reference to a function is required, for + * instance when passing the routine as a pointer to a function. It has the + * letters @c _REF appended to it, and takes only the first two arguments + * (driver_name and function_name). These functions are not otherwise + * mentioned in this documentation. + * + * (Note that these two extra forms are required because of the + * possibility/likelihood of having a 'wrapper function' which invokes the + * generic function with expected arguments. An alternative would be to have a + * generic function which isn't able to get at any arguments directly, but + * would be equipped with macros which could get at information passed in. + * + * Example: + * + * (in a header file) + * @code + * OS_DEV_INIT_DCL(widget, widget_init); + * @endcode + * + * (in an implementation file) + * @code + * OS_DEV_INIT(widget, widget_init) + * { + * os_dev_init_return(TRUE); + * } + * @endcode + * + */ + +/*! @addtogroup drsigs */ +/*! @{ */ + +/*! + * Define a function which will handle device initialization + * + * This is tne driver initialization routine. This is normally where the + * part would be initialized; queues, locks, interrupts handlers defined; + * long-term dynamic memory allocated for driver use; etc. + * + * @param function_name The name of the portable initialization function. + * + * @return A call to #os_dev_init_return() + * + */ +#define OS_DEV_INIT(function_name) \ +module_init(function_name); \ +static int __init function_name (void) + +/*! Make declaration for driver init function. + * @param function_name foo + */ +#define OS_DEV_INIT_DCL(function_name) \ +static int __init function_name (void); + +/*! + * Generate a function reference to the driver's init function. + * @param function_name Name of the OS_DEV_INIT() function. + * + * @return A function pointer. + */ +#define OS_DEV_INIT_REF(function_name) \ +function_name + +/*! + * Define a function which will handle device shutdown + * + * This is the inverse of the #OS_DEV_INIT() routine. + * + * @param function_name The name of the portable driver shutdown routine. + * + * @return A call to #os_dev_shutdown_return() + * + */ +#define OS_DEV_SHUTDOWN(function_name) \ +module_exit(function_name); \ +static void function_name(void) + +/*! + * Generate a function reference to the driver's shutdown function. + * @param function_name Name of the OS_DEV_HUSTDOWN() function. + * + * @return A function pointer. + */ +#define OS_DEV_SHUTDOWN_DCL(function_name) \ +static void function_name(void); + +/*! + * Generate a reference to driver's shutdown function + * @param function_name Name of the OS_DEV_HUSTDOWN() function. +*/ + +#define OS_DEV_SHUTDOWN_REF(function_name) \ +function_name + +/*! + * Define a function which will open the device for a user. + * + * @param function_name The name of the driver open() function + * + * @return A call to #os_dev_open_return() + */ +#define OS_DEV_OPEN(function_name) \ +static int function_name(struct inode* inode_p_, struct file* file_p_) + +/*! + * Declare prototype for an open() function. + * + * @param function_name The name of the OS_DEV_OPEN() function. + */ +#define OS_DEV_OPEN_DCL(function_name) \ +OS_DEV_OPEN(function_name); + +/*! + * Generate a function reference to the driver's open() function. + * @param function_name Name of the OS_DEV_OPEN() function. + * + * @return A function pointer. + */ +#define OS_DEV_OPEN_REF(function_name) \ +function_name + +/*! + * Define a function which will handle a user's ioctl() request + * + * @param function_name The name of the driver ioctl() function + * + * @return A call to #os_dev_ioctl_return() + */ +#define OS_DEV_IOCTL(function_name) \ +static int function_name(struct inode* inode_p_, struct file* file_p_, \ + unsigned int cmd_, unsigned long data_) + +/*! Boo. */ +#define OS_DEV_IOCTL_DCL(function_name) \ +OS_DEV_IOCTL(function_name); + +/*! + * Generate a function reference to the driver's ioctl() function. + * @param function_name Name of the OS_DEV_IOCTL() function. + * + * @return A function pointer. + */ +#define OS_DEV_IOCTL_REF(function_name) \ +function_name + +/*! + * Define a function which will handle a user's read() request + * + * @param function_name The name of the driver read() function + * + * @return A call to #os_dev_read_return() + */ +#define OS_DEV_READ(function_name) \ +static ssize_t function_name(struct file* file_p_, char* user_buffer_, \ + size_t count_bytes_, loff_t* file_position_) + +/*! + * Declare prototype for an read() function. + * + * @param function_name The name of the driver read function. + */ +#define OS_DEV_READ_DCL(function_name) \ +OS_DEV_READ(function_name); + +/*! + * Generate a function reference to the driver's read() routine + * @param function_name Name of the OS_DEV_READ() function. + * + * @return A function pointer. + */ +#define OS_DEV_READ_REF(function_name) \ +function_name + +/*! + * Define a function which will handle a user's write() request + * + * @param function_name The name of the driver write() function + * + * @return A call to #os_dev_write_return() + */ +#define OS_DEV_WRITE(function_name) \ +static ssize_t function_name(struct file* file_p_, char* user_buffer_, \ + size_t count_bytes_, loff_t* file_position_) + +/*! + * Declare prototype for an write() function. + * + * @param function_name The name of the driver write function. + */ +#define OS_DEV_WRITE_DCL(function_name) \ +OS_DEV_WRITE(function_name); + +/*! + * Generate a function reference to the driver's write() routine + * @param function_name Name of the OS_DEV_WRITE() function. + * + * @return A function pointer. + */ +#define OS_DEV_WRITE_REF(function_name) \ +function_name + +/*! + * Define a function which will close the device - opposite of OS_DEV_OPEN() + * + * @param function_name The name of the driver close() function + * + * @return A call to #os_dev_close_return() + */ +#define OS_DEV_CLOSE(function_name) \ +static int function_name(struct inode* inode_p_, struct file* file_p_) + +/*! + * Declare prototype for an close() function + * + * @param function_name The name of the driver close() function. + */ +#define OS_DEV_CLOSE_DCL(function_name) \ +OS_DEV_CLOSE(function_name); + +/*! + * Generate a function reference to the driver's close function. + * @param function_name Name of the OS_DEV_CLOSE() function. + * + * @return A function pointer. + */ +#define OS_DEV_CLOSE_REF(function_name) \ +function_name + +/*! + * Define a function which will handle an interrupt + * + * No arguments are available to the generic function. It must not invoke any + * OS functions which are illegal in a ISR. It gets no parameters, and must + * have a call to #os_dev_isr_return() instead of any/all return statements. + * + * Example: + * @code + * OS_DEV_ISR(widget, widget_isr, WIDGET_IRQ_NUMBER) + * { + * os_dev_isr_return(1); + * } + * @endcode + * + * @param function_name The name of the driver ISR function + * + * @return A call to #os_dev_isr_return() + */ +#define OS_DEV_ISR(function_name) \ +static irqreturn_t function_name(int N1_, void* N2_) + +/*! + * Declare prototype for an ISR function. + * + * @param function_name The name of the driver ISR function. + */ +#define OS_DEV_ISR_DCL(function_name) \ +OS_DEV_ISR(function_name); + +/*! + * Generate a function reference to the driver's interrupt service routine + * @param function_name Name of the OS_DEV_ISR() function. + * + * @return A function pointer. + */ +#define OS_DEV_ISR_REF(function_name) \ +function_name + +/*! + * Define a function which will operate as a background task / bottom half. + * + * Tasklet stuff isn't strictly limited to 'Device drivers', but leave it + * this namespace anyway. + * + * @param function_name The name of this background task function + * + * @return A call to #os_dev_task_return() + */ +#define OS_DEV_TASK(function_name) \ +static void function_name(unsigned long data_) + +/*! + * Declare prototype for a background task / bottom half function + * + * @param function_name The name of this background task function + */ +#define OS_DEV_TASK_DCL(function_name) \ +OS_DEV_TASK(function_name); \ +DECLARE_TASKLET(function_name ## let, function_name, 0); + +/*! + * Generate a reference to an #OS_DEV_TASK() function + * + * @param function_name The name of the task being referenced. + */ +#define OS_DEV_TASK_REF(function_name) \ + (function_name ## let) + + /*! @} *//* drsigs */ + +/***************************************************************************** + * Functions/Macros for returning values from Driver Signature routines + *****************************************************************************/ + +/*! + * Return from the #OS_DEV_INIT() function + * + * @param code An error code to report success or failure. + * + */ +#define os_dev_init_return(code) \ + return code + +/*! + * Return from the #OS_DEV_SHUTDOWN() function + * + * @param code An error code to report success or failure. + * + */ +#define os_dev_shutdown_return(code) \ + return + +/*! + * Return from the #OS_DEV_ISR() function + * + * The function should verify that it really was supposed to be called, + * and that its device needed attention, in order to properly set the + * return code. + * + * @param code non-zero if interrupt handled, zero otherwise. + * + */ +#define os_dev_isr_return(code) \ +do { \ + /* Unused warnings */ \ + (void)N1_; \ + (void)N2_; \ + \ + return IRQ_RETVAL(code); \ +} while (0) + +/*! + * Return from the #OS_DEV_OPEN() function + * + * @param code An error code to report success or failure. + * + */ +#define os_dev_open_return(code) \ +do { \ + int retcode = code; \ + \ + /* get rid of 'unused parameter' warnings */ \ + (void)inode_p_; \ + (void)file_p_; \ + \ + return retcode; \ +} while (0) + +/*! + * Return from the #OS_DEV_IOCTL() function + * + * @param code An error code to report success or failure. + * + */ +#define os_dev_ioctl_return(code) \ +do { \ + int retcode = code; \ + \ + /* get rid of 'unused parameter' warnings */ \ + (void)inode_p_; \ + (void)file_p_; \ + (void)cmd_; \ + (void)data_; \ + \ + return retcode; \ +} while (0) + +/*! + * Return from the #OS_DEV_READ() function + * + * @param code Number of bytes read, or an error code to report failure. + * + */ +#define os_dev_read_return(code) \ +do { \ + ssize_t retcode = code; \ + \ + /* get rid of 'unused parameter' warnings */ \ + (void)file_p_; \ + (void)user_buffer_; \ + (void)count_bytes_; \ + (void)file_position_; \ + \ + return retcode; \ +} while (0) + +/*! + * Return from the #OS_DEV_WRITE() function + * + * @param code Number of bytes written, or an error code to report failure. + * + */ +#define os_dev_write_return(code) \ +do { \ + ssize_t retcode = code; \ + \ + /* get rid of 'unused parameter' warnings */ \ + (void)file_p_; \ + (void)user_buffer_; \ + (void)count_bytes_; \ + (void)file_position_; \ + \ + return retcode; \ +} while (0) + +/*! + * Return from the #OS_DEV_CLOSE() function + * + * @param code An error code to report success or failure. + * + */ +#define os_dev_close_return(code) \ +do { \ + ssize_t retcode = code; \ + \ + /* get rid of 'unused parameter' warnings */ \ + (void)inode_p_; \ + (void)file_p_; \ + \ + return retcode; \ +} while (0) + +/*! + * Start the #OS_DEV_TASK() function + * + * In some implementations, this could be turned into a label for + * the os_dev_task_return() call. + * + * @return none + */ +#define os_dev_task_begin() + +/*! + * Return from the #OS_DEV_TASK() function + * + * In some implementations, this could be turned into a sleep followed + * by a jump back to the os_dev_task_begin() call. + * + * @param code An error code to report success or failure. + * + */ +#define os_dev_task_return(code) \ +do { \ + /* Unused warnings */ \ + (void)data_; \ + \ + return; \ +} while (0) + +/***************************************************************************** + * Functions/Macros for accessing arguments from Driver Signature routines + *****************************************************************************/ + +/*! @defgroup drsigargs Functions for Getting Arguments in Signature functions + * + */ +/* @addtogroup @drsigargs */ +/*! @{ */ +/*! + * Used in #OS_DEV_OPEN(), #OS_DEV_CLOSE(), #OS_DEV_IOCTL(), #OS_DEV_READ() and + * #OS_DEV_WRITE() routines to check whether user is requesting read + * (permission) + */ +#define os_dev_is_flag_read() \ + (file_p_->f_mode & FMODE_READ) + +/*! + * Used in #OS_DEV_OPEN(), #OS_DEV_CLOSE(), #OS_DEV_IOCTL(), #OS_DEV_READ() and + * #OS_DEV_WRITE() routines to check whether user is requesting write + * (permission) + */ +#define os_dev_is_flag_write() \ + (file_p_->f_mode & FMODE_WRITE) + +/*! + * Used in #OS_DEV_OPEN(), #OS_DEV_CLOSE(), #OS_DEV_IOCTL(), #OS_DEV_READ() and + * #OS_DEV_WRITE() routines to check whether user is requesting non-blocking + * I/O. + */ +#define os_dev_is_flag_nonblock() \ + (file_p_->f_flags & (O_NONBLOCK | O_NDELAY)) + +/*! + * Used in #OS_DEV_OPEN() and #OS_DEV_CLOSE() to determine major device being + * accessed. + */ +#define os_dev_get_major() \ + (imajor(inode_p_)) + +/*! + * Used in #OS_DEV_OPEN() and #OS_DEV_CLOSE() to determine minor device being + * accessed. + */ +#define os_dev_get_minor() \ + (iminor(inode_p_)) + +/*! + * Used in #OS_DEV_IOCTL() to determine which operation the user wants + * performed. + * + * @return Value of the operation. + */ +#define os_dev_get_ioctl_op() \ + (cmd_) + +/*! + * Used in #OS_DEV_IOCTL() to return the associated argument for the desired + * operation. + * + * @return A value which can be cast to a struct pointer or used as + * int/long. + */ +#define os_dev_get_ioctl_arg() \ + (data_) + +/*! + * Used in OS_DEV_READ() and OS_DEV_WRITE() routines to access the requested + * byte count. + * + * @return (unsigned) a count of bytes + */ +#define os_dev_get_count() \ + ((unsigned)count_bytes_) + +/*! + * Used in OS_DEV_READ() and OS_DEV_WRITE() routines to return the pointer + * byte count. + * + * @return char* pointer to user buffer + */ +#define os_dev_get_user_buffer() \ + ((void*)user_buffer_) + +/*! + * Used in OS_DEV_READ(), OS_DEV_WRITE(), and OS_DEV_IOCTL() routines to + * get the POSIX flags field for the associated open file). + * + * @return The flags associated with the file. + */ +#define os_dev_get_file_flags() \ + (file_p_->f_flags) + +/*! + * Set the driver's private structure associated with this file/open. + * + * Generally used during #OS_DEV_OPEN(). See #os_dev_get_user_private(). + * + * @param struct_p The driver data structure to associate with this user. + */ +#define os_dev_set_user_private(struct_p) \ + file_p_->private_data = (void*)(struct_p) + +/*! + * Get the driver's private structure associated with this file. + * + * May be used during #OS_DEV_OPEN(), #OS_DEV_READ(), #OS_DEV_WRITE(), + * #OS_DEV_IOCTL(), and #OS_DEV_CLOSE(). See #os_dev_set_user_private(). + * + * @return The driver data structure to associate with this user. + */ +#define os_dev_get_user_private() \ + ((void*)file_p_->private_data) + /*! @} *//* drsigargs */ + +/*! + * @defgroup cacheops Cache Operations + * + * These functions are for synchronizing processor cache with RAM. + */ +/*! @addtogroup cacheops */ +/*! @{ */ + +/*! + * Flush and invalidate all cache lines. + */ +#if 0 +#define os_flush_cache_all() \ + flush_cache_all() +#else +/* Call ARM fn directly, in case L2cache=on3 not set */ +#define os_flush_cache_all() \ + v6_flush_kern_cache_all_L2() + +/*! + * ARM-routine to flush all cache. Defined here, because it exists in no + * easy-access header file. ARM-11 with L210 cache only! + */ +extern void v6_flush_kern_cache_all_L2(void); +#endif + +/* + * These macros are using part of the Linux DMA API. They rely on the + * map function to do nothing more than the equivalent clean/inv/flush + * operation at the time of the mapping, and do nothing at an unmapping + * call, which the Sahara driver code will never invoke. + */ + +/*! + * Clean a range of addresses from the cache. That is, write updates back + * to (RAM, next layer). + * + * @param start Starting virtual address + * @param len Number of bytes to flush + * + * @return void + */ +#define os_cache_clean_range(start,len) \ +{ \ + dmac_clean_range(start, (void *)((unsigned long)(start) + len)); \ + outer_clean_range(__pa(start), __pa((unsigned long)(start) + len)); \ +} + +/*! + * Invalidate a range of addresses in the cache + * + * @param start Starting virtual address + * @param len Number of bytes to flush + * + * @return void + */ +#define os_cache_inv_range(start,len) \ +{ \ + dmac_inv_range(start, (void *)((unsigned long)(start) + len)); \ + outer_inv_range(__pa(start), __pa((unsigned long)(start) + len)); \ +} + +/*! + * Flush a range of addresses from the cache. That is, perform clean + * and invalidate + * + * @param start Starting virtual address + * @param len Number of bytes to flush + * + * @return void + */ +#define os_cache_flush_range(start,len) \ +{ \ + dmac_flush_range(start, (void *)((unsigned long)(start) + len)); \ + outer_flush_range(__pa(start), __pa((unsigned long)(start) + len)); \ +} + + /*! @} *//* cacheops */ + +#endif /* LINUX_PORT_H */ diff --git a/drivers/mxc/security/sahara2/include/platform_abstractions.h b/drivers/mxc/security/sahara2/include/platform_abstractions.h new file mode 100644 index 000000000000..c2f9c489eb0b --- /dev/null +++ b/drivers/mxc/security/sahara2/include/platform_abstractions.h @@ -0,0 +1,15 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/*! + * @file platform_abstractions.h + */ diff --git a/drivers/mxc/security/sahara2/include/portable_os.h b/drivers/mxc/security/sahara2/include/portable_os.h new file mode 100644 index 000000000000..6211e119531b --- /dev/null +++ b/drivers/mxc/security/sahara2/include/portable_os.h @@ -0,0 +1,1420 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#ifndef PORTABLE_OS_H +#define PORTABLE_OS_H + +/***************************************************************************/ + +/* + * Add support for your target OS by checking appropriate flags and then + * including the appropriate file. Don't forget to document the conditions + * in the later documentation section at the beginning of the + * DOXYGEN_PORTABLE_OS_DOC. + */ + +#if defined(LINUX_KERNEL) + +#include "linux_port.h" + +#elif defined(PORTABLE_OS) + +#include "check_portability.h" + +#else + +#error Target OS unsupported or unspecified + +#endif + +/***************************************************************************/ + +/*! + * @file portable_os.h + * + * This file should be included by portable driver code in order to gain access + * to the OS-specific header files. It is the only OS-related header file that + * the writer of a portable driver should need. + * + * This file also contains the documentation for the common API. + * + * Begin reading the documentation for this file at the @ref index "main page". + * + */ + +/*! + * @if USE_MAINPAGE + * @mainpage Generic OS API for STC Drivers + * @endif + * + * @section intro_sec Introduction + * + * This defines the API / kernel programming environment for portable drivers. + * + * This API is broken up into several functional areas. It greatly limits the + * choices of a device-driver author, but at the same time should allow for + * greater portability of the resulting code. + * + * Each kernel-to-driver function (initialization function, interrupt service + * routine, etc.) has a 'portable signature' which must be used, and a specific + * function which must be called to generate the return statement. There is + * one exception, a background task or "bottom half" routine, which instead has + * a specific structure which must be followed. These signatures and function + * definitions are found in @ref drsigs. + * + * None of these kernel-to-driver functions seem to get any arguments passed to + * them. Instead, there are @ref drsigargs which allow one of these functions + * to get at fairly generic parts of its calling arguments, if there are any. + * + * Almost every driver will have some need to call the operating system + * @ref dkops is the list of services which are available to the driver. + * + * + * @subsection warn_sec Warning + * + * The specifics of the types, values of the various enumerations + * (unless specifically stated, like = 0), etc., are only here for illustrative + * purposes. No attempts should be made to make use of any specific knowledge + * gleaned from this documentation. These types are only meant to be passed in + * and out of the API, and their contents are to be handled only by the + * provided OS-specific functions. + * + * Also, note that the function may be provided as macros in some + * implementations, or vice versa. + * + * + * @section dev_sec Writing a Portable Driver + * + * First off, writing a portable driver means calling no function in an OS + * except what is available through this header file. + * + * Secondly, all OS-called functions in your driver must be invoked and + * referenced through the signature routines. + * + * Thirdly, there might be some rules which you can get away with ignoring or + * violating on one OS, yet will cause your code not to be portable to a + * different OS. + * + * + * @section limit_sec Limitations + * + * This API is not expected to handle every type of driver which may be found + * in an operating system. For example, it will not be able to handle the + * usual design for something like a UART driver, where there are multiple + * logical devices to access, because the design usually calls for some sort of + * indication to the #OS_DEV_TASK() function or OS_DEV_ISR() to indicate which + * channel is to be serviced by that instance of the task/function. This sort + * of argument is missing in this API for functions like os_schedule_task() and + * os_register_interrupt(). + * + * + * @section port_guide Porting Guidelines + * + * This section is intended for a developer who needs to port the header file + * to an operating system which is not yet supported. + * + * This interface allows for a lot of flexibility when it comes to porting to + * an operating systems device driver interface. There are three main areas to + * examine: The use of Driver Routine Signatures, the use of Driver Argument + * Access functions, the Calls to Kernel Functions, and Data Types. + * + * + * @subsection port_sig Porting Driver Routine Signatures + * + * The three macros for each function (e.g. #OS_DEV_INIT(), #OS_DEV_INIT_DCL(), + * and #OS_DEV_INIT_REF()) allow the flexibility of having a 'wrapper' function + * with the OS-required signature, which would then call the user's function + * with some different signature. + * + * The first form would lay down the wrapper function, followed by the + * signature for the user function. The second form would lay down just the + * signatures for both functions, and the last function would reference the + * wrapper function, since that is the interface function called by the OS. + * + * Note that the driver author has no visibility at all to the signature of the + * routines. The author can access arguments only through a limited set of + * functions, and must return via another function. + * + * The Return Functions allow a lot of flexibility in converting the return + * value, or not returning a value at all. These will likely be implemented as + * macros. + * + * + * @subsection port_arg Porting Driver Argument Access Functions + * + * The signatures defined by the guide will usually be replaced with macro + * definitions. + * + * + * @subsection port_dki Porting Calls to Kernel Functions + * + * The signatures defined by the guide may be replaced with macro definitions, + * if that makes more sense. + * + * Implementors are free to ignore arguments which are not applicable to their + * OS. + * + * @subsection port_datatypes Porting Data Types + * + * + */ + +/*************************************************************************** + * Compile flags + **************************************************************************/ + +/* + * This compile flag should only be turned on when running doxygen to generate + * the API documentation. + */ +#ifdef DOXYGEN_PORTABLE_OS_DOC + +/*! + * @todo module_init()/module_cleanup() for Linux need to be added to OS + * abstractions. Also need EXPORT_SYMBOL() equivalent?? + * + */ + +/* Drop OS differentation documentation here */ + +/*! + * \#define this flag to build your driver as a Linux driver + */ +#define LINUX + +/* end OS differentation documentation */ + +/*! + * Symbol to give version number of the implementation file. Earliest + * definition is in version 1.1, with value 101 (to mean version 1.1) + */ +#define PORTABLE_OS_VERSION 101 + +/* + * NOTICE: The following definitions (the rest of the file) are not meant ever + * to be compiled. Instead, they are the documentation for the portable OS + * API, to be used by driver writers. + * + * Each individual OS port will define each of these types, functions, or + * macros as appropriate to the target OS. This is why they are under the + * DOXYGEN_PORTABLE_OS_DOC flag. + */ + +/*************************************************************************** + * Type definitions + **************************************************************************/ + +/*! + * Type used for registering and deregistering interrupts. + * + * This is typically an interrupt channel number. + */ +typedef int os_interrupt_id_t; + +/*! + * Type used as handle for a process + * + * See #os_get_process_handle() and #os_send_signal(). + */ +typedef int os_process_handle_t; + +/*! + * Generic return code for functions which need such a thing. + * + * No knowledge should be assumed of the value of any of these symbols except + * that @c OS_ERROR_OK_S is guaranteed to be zero. + * + * @todo Any other named values? What about (-EAGAIN? -ERESTARTSYS? Are they + * too Linux/Unix-specific read()/write() return values) ? + */ +typedef enum { + OS_ERROR_OK_S = 0, /*!< Success */ + OS_ERROR_FAIL_S, /*!< Generic driver failure */ + OS_ERROR_NO_MEMORY_S, /*!< Failure to acquire/use memory */ + OS_ERROR_BAD_ADDRESS /*!< Bad address */ +} os_error_code; + +/*! + * Handle to a lock. + */ +typedef int *os_lock_t; + +/*! + * Context while locking. + */ +typedef int os_lock_context_t; + +/*! + * An object which can be slept on and later used to wake any/all sleepers. + */ +typedef int os_sleep_object_t; + +/*! + * Driver registration handle + */ +typedef void *os_driver_reg_t; + +/*! + * Function signature for an #OS_DEV_INIT() function. + * + * @return A call to os_dev_init_return() function. + */ +typedef void (*os_init_function_t) (void); + +/*! + * Function signature for an #OS_DEV_SHUTDOWN() function. + * + * @return A call to os_dev_shutdown_return() function. + */ +typedef void (*os_shutdown_function_t) (void); + +/*! + * Function signature for a user-driver function. + * + * @return A call to the appropriate os_dev_xxx_return() function. + */ +typedef void (*os_user_function_t) (void); + +/*! + * Function signature for the portable interrupt handler + * + * While it would be nice to know which interrupt is being serviced, the + * Least Common Denominator rule says that no arguments get passed in. + * + * @return A call to #os_dev_isr_return() + */ +typedef void (*os_interrupt_handler_t) (void); + +/*! + * Function signature for a task function + * + * Many task function definitions get some sort of generic argument so that the + * same function can run across many (queues, channels, ...) as separate task + * instances. This has been left out of this API. + * + * This function must be structured as documented by #OS_DEV_TASK(). + * + */ +typedef void (*os_task_fn_t) (void); + +/*! + * Function types which can be associated with driver entry points. These are + * used in os_driver_add_registration(). + * + * Note that init and shutdown are absent. + */ +typedef enum { + OS_FN_OPEN, /*!< open() operation handler. */ + OS_FN_CLOSE, /*!< close() operation handler. */ + OS_FN_READ, /*!< read() operation handler. */ + OS_FN_WRITE, /*!< write() operation handler. */ + OS_FN_IOCTL, /*!< ioctl() operation handler. */ + OS_FN_MMAP /*!< mmap() operation handler. */ +} os_driver_fn_t; + +/*************************************************************************** + * Driver-to-Kernel Operations + **************************************************************************/ + +/*! + * @defgroup dkops Driver-to-Kernel Operations + * + * These are the operations which drivers should call to get the OS to perform + * services. + */ + +/*! @addtogroup dkops */ +/*! @{ */ + +/*! + * Register an interrupt handler. + * + * @param driver_name The name of the driver + * @param interrupt_id The interrupt line to monitor (type + * #os_interrupt_id_t) + * @param function The function to be called to handle an interrupt + * + * @return #os_error_code + */ +os_error_code os_register_interrupt(char *driver_name, + os_interrupt_id_t interrupt_id, + os_interrupt_handler_t function); + +/*! + * Deregister an interrupt handler. + * + * @param interrupt_id The interrupt line to stop monitoring + * + * @return #os_error_code + */ +os_error_code os_deregister_interrupt(os_interrupt_id_t interrupt_id); + +/*! + * Initialize driver registration. + * + * If the driver handles open(), close(), ioctl(), read(), write(), or mmap() + * calls, then it needs to register their location with the kernel so that they + * get associated with the device. + * + * @param handle The handle object to be used with this registration. The + * object must live (be in memory somewhere) at least until + * os_driver_remove_registration() is called. + * + * @return An os error code. + */ +os_error_code os_driver_init_registration(os_driver_reg_t handle); + +/*! + * Add a function registration to driver registration. + * + * @param handle The handle used with #os_driver_init_registration(). + * @param name Which function is being supported. + * @param function The result of a call to a @c _REF version of one of the + * driver function signature macros + * driver function signature macros + * @return void + */ +void os_driver_add_registration(os_driver_reg_t handle, os_driver_fn_t name, + void *function); + +/*! + * Finalize the driver registration with the kernel. + * + * Upon return from this call, the driver may begin receiving calls at the + * defined entry points. + * + * @param handle The handle used with #os_driver_init_registration(). + * @param major The major device number to be associated with the driver. + * If this value is zero, a major number may be assigned. + * See #os_driver_get_major() to determine final value. + * #os_driver_remove_registration(). + * @param driver_name The driver name. Can be used as part of 'device node' + * name on platforms which support such a feature. + * + * @return An error code + */ +os_error_code os_driver_complete_registration(os_driver_reg_t handle, + int major, char *driver_name); + +/*! + * Get driver Major Number from handle after a successful registration. + * + * @param handle A handle which has completed registration. + * + * @return The major number (if any) associated with the handle. + */ +uint32_t os_driver_get_major(os_driver_reg_t handle); + +/*! + * Remove the driver's registration with the kernel. + * + * Upon return from this call, the driver not receive any more calls at the + * defined entry points (other than ISR and shutdown). + * + * @param major The major device number to be associated with the driver. + * @param driver_name The driver name + * + * @return An error code. + */ +os_error_code os_driver_remove_registration(int major, char *driver_name); + +/*! + * Print a message to console / into log file. After the @c msg argument a + * number of printf-style arguments may be added. Types should be limited to + * printf string, char, octal, decimal, and hexadecimal types. (This excludes + * pointers, and floating point). + * + * @param msg The message to print to console / system log + * + * @return (void) + */ +void os_printk(char *msg, ...); + +/*! + * Allocate some kernel memory + * + * @param amount Number of 8-bit bytes to allocate + * @param flags Some indication of purpose of memory (needs definition) + * + * @return Pointer to allocated memory, or NULL if failed. + */ +void *os_alloc_memory(unsigned amount, int flags); + +/*! + * Free some kernel memory + * + * @param location The beginning of the region to be freed. + * + * Do some OSes have separate free() functions which should be + * distinguished by passing in @c flags here, too? Don't some also require the + * size of the buffer being freed? Perhaps separate routines for each + * alloc/free pair (DMAable, etc.)? + */ +void os_free_memory(void *location); + +/*! + * Allocate cache-coherent memory + * + * @param amount Number of bytes to allocate + * @param[out] dma_addrp Location to store physical address of allocated + * memory. + * @param flags Some indication of purpose of memory (needs + * definition). + * + * @return (virtual space) pointer to allocated memory, or NULL if failed. + * + */ +void *os_alloc_coherent(unsigned amount, uint32_t * dma_addrp, int flags); + +/*! + * Free cache-coherent memory + * + * @param size Number of bytes which were allocated. + * @param[out] virt_addr Virtual(kernel) address of memory.to be freed, as + * returned by #os_alloc_coherent(). + * @param[out] dma_addr Physical address of memory.to be freed, as returned + * by #os_alloc_coherent(). + * + * @return void + * + */ +void os_free_coherent(unsigned size, void *virt_addr, uint32_t dma_addr); + +/*! + * Map an I/O space into kernel memory space + * + * @param start The starting address of the (physical / io space) region + * @param range_bytes The number of bytes to map + * + * @return A pointer to the mapped area, or NULL on failure + */ +void *os_map_device(uint32_t start, unsigned range_bytes); + +/*! + * Unmap an I/O space from kernel memory space + * + * @param start The starting address of the (virtual) region + * @param range_bytes The number of bytes to unmap + * + * @return None + */ +void os_unmap_device(void *start, unsigned range_bytes); + +/*! + * Copy data from Kernel space to User space + * + * @param to The target location in user memory + * @param from The source location in kernel memory + * @param size The number of bytes to be copied + * + * @return #os_error_code + */ +os_error_code os_copy_to_user(void *to, void *from, unsigned size); + +/*! + * Copy data from User space to Kernel space + * + * @param to The target location in kernel memory + * @param from The source location in user memory + * @param size The number of bytes to be copied + * + * @return #os_error_code + */ +os_error_code os_copy_from_user(void *to, void *from, unsigned size); + +/*! + * Read an 8-bit device register + * + * @param register_address The (bus) address of the register to write to + * @return The value in the register + */ +uint8_t os_read8(uint8_t * register_address); + +/*! + * Write an 8-bit device register + * + * @param register_address The (bus) address of the register to write to + * @param value The value to write into the register + */ +void os_write8(uint8_t * register_address, uint8_t value); + +/*! + * Read a 16-bit device register + * + * @param register_address The (bus) address of the register to write to + * @return The value in the register + */ +uint16_t os_read16(uint16_t * register_address); + +/*! + * Write a 16-bit device register + * + * @param register_address The (bus) address of the register to write to + * @param value The value to write into the register + */ +void os_write16(uint16_t * register_address, uint16_t value); + +/*! + * Read a 32-bit device register + * + * @param register_address The (bus) address of the register to write to + * @return The value in the register + */ +uint32_t os_read32(uint32_t * register_address); + +/*! + * Write a 32-bit device register + * + * @param register_address The (bus) address of the register to write to + * @param value The value to write into the register + */ +void os_write32(uint32_t * register_address, uint32_t value); + +/*! + * Read a 64-bit device register + * + * @param register_address The (bus) address of the register to write to + * @return The value in the register + */ +uint64_t os_read64(uint64_t * register_address); + +/*! + * Write a 64-bit device register + * + * @param register_address The (bus) address of the register to write to + * @param value The value to write into the register + */ +void os_write64(uint64_t * register_address, uint64_t value); + +/*! + * Prepare a task to execute the given function. This should only be done once + * per task, during the driver's initialization routine. + * + * @param task_fn Name of the OS_DEV_TASK() function to be created. + * + * @return an OS ERROR code. + */ +os_error os_create_task(os_task_fn_t * task_fn); + +/*! + * Run the task associated with an #OS_DEV_TASK() function + * + * The task will begin execution sometime after or during this call. + * + * @param task_fn Name of the OS_DEV_TASK() function to be scheduled. + * + * @return void + */ +void os_schedule_task(os_task_fn_t * task_fn); + +/*! + * Make sure that task is no longer running and will no longer run. + * + * This function will not return until both are true. This is useful when + * shutting down a driver. + * + * @param task_fn Name of the OS_DEV_TASK() funciton to be stopped. + * + */ +void os_stop_task(os_task_fn_t * task_fn); + +/*! + * Delay some number of microseconds + * + * Note that this is a busy-loop, not a suspension of the task/process. + * + * @param msecs The number of microseconds to delay + * + * @return void + */ +void os_mdelay(unsigned long msecs); + +/*! + * Calculate virtual address from physical address + * + * @param pa Physical address + * + * @return virtual address + * + * @note this assumes that addresses are 32 bits wide + */ +void *os_va(uint32_t pa); + +/*! + * Calculate physical address from virtual address + * + * + * @param va Virtual address + * + * @return physical address + * + * @note this assumes that addresses are 32 bits wide + */ +uint32_t os_pa(void *va); + +/*! + * Allocate and initialize a lock, returning a lock handle. + * + * The lock state will be initialized to 'unlocked'. + * + * @return A lock handle, or NULL if an error occurred. + */ +os_lock_t os_lock_alloc_init(void); + +/*! + * Acquire a lock. + * + * This function should only be called from an interrupt service routine. + * + * @param lock_handle A handle to the lock to acquire. + * + * @return void + */ +void os_lock(os_lock_t lock_handle); + +/*! + * Unlock a lock. Lock must have been acquired by #os_lock(). + * + * @param lock_handle A handle to the lock to unlock. + * + * @return void + */ +void os_unlock(os_lock_t lock_handle); + +/*! + * Acquire a lock in non-ISR context + * + * This function will spin until the lock is available. + * + * @param lock_handle A handle of the lock to acquire. + * @param context Place to save the before-lock context + * + * @return void + */ +void os_lock_save_context(os_lock_t lock_handle, os_lock_context_t context); + +/*! + * Release a lock in non-ISR context + * + * @param lock_handle A handle of the lock to release. + * @param context Place where before-lock context was saved. + * + * @return void + */ +void os_unlock_restore_context(os_lock_t lock_handle, + os_lock_context_t context); + +/*! + * Deallocate a lock handle. + * + * @param lock_handle An #os_lock_t that has been allocated. + * + * @return void + */ +void os_lock_deallocate(os_lock_t lock_handle); + +/*! + * Determine process handle + * + * The process handle of the current user is returned. + * + * @return A handle on the current process. + */ +os_process_handle_t os_get_process_handle(); + +/*! + * Send a signal to a process + * + * @param proc A handle to the target process. + * @param sig The POSIX signal to send to that process. + */ +void os_send_signal(os_process_handle_t proc, int sig); + +/*! + * Get some random bytes + * + * @param buf The location to store the random data. + * @param count The number of bytes to store. + * + * @return void + */ +void os_get_random_bytes(void *buf, unsigned count); + +/*! + * Go to sleep on an object. + * + * Example: code = os_sleep(my_queue, available_count == 0, 0); + * + * @param object The object on which to sleep + * @param condition An expression to check for sleep completion. Must be + * coded so that it can be referenced more than once inside + * macro, i.e., no ++ or other modifying expressions. + * @param atomic Non-zero if sleep must not return until condition. + * + * @return error code -- OK or sleep interrupted?? + */ +os_error_code os_sleep(os_sleep_object_t object, unsigned condition, + unsigned atomic); + +/*! + * Wake up whatever is sleeping on sleep object + * + * @param object The object on which things might be sleeping + * + * @return none + */ +void os_wake_sleepers(os_sleep_object_t object); + + /*! @} *//* dkops */ + +/***************************************************************************** + * Function-signature-generating macros + *****************************************************************************/ + +/*! + * @defgroup drsigs Driver Function Signatures + * + * These macros will define the entry point signatures for interrupt handlers; + * driver initialization and shutdown; device open/close; etc. They are to be + * used whenever the Kernel will call into the Driver. They are not + * appropriate for driver calls to other routines in the driver. + * + * There are three versions of each macro for a given Driver Entry Point. The + * first version is used to define a function and its implementation in the + * driver.c file, e.g. #OS_DEV_INIT(). + * + * The second form is used whenever a forward declaration (prototype) is + * needed. It has the letters @c _DCL appended to the name of the definition + * function. These are not otherwise mentioned in this documenation. + * + * There is a third form used when a reference to a function is required, for + * instance when passing the routine as a pointer to a function. It has the + * letters @c _REF appended to the name of the definition function + * (e.g. DEV_IOCTL_REF). + * + * Note that these two extra forms are required because of the possibility of + * having an invisible 'wrapper function' created by the os-specific header + * file which would need to be invoked by the operating system, and which in + * turn would invoke the generic function. + * + * Example: + * + * (in a header file) + * @code + * OS_DEV_INIT_DCL(widget_init); + * OS_DEV_ISR_DCL(widget_isr); + * @endcode + * + * (in an implementation file) + * @code + * OS_DEV_INIT(widget, widget_init) + * { + * + * os_register_interrupt("widget", WIDGET_IRQ, OS_DEV_ISR_REF(widget_isr)); + * + * os_dev_init_return(OS_RETURN_NO_ERROR_S); + * } + * + * OS_DEV_ISR(widget_isr) + * { + * os_dev_isr_return(TRUE); + * } + * @endcode + */ + +/*! @addtogroup drsigs */ +/*! @{ */ + +/*! + * Define a function which will handle device initialization + * + * This is tne driver initialization routine. This is normally where the + * part would be initialized; queues, locks, interrupts handlers defined; + * long-term dynamic memory allocated for driver use; etc. + * + * @param function_name The name of the portable initialization function. + * + * @return A call to #os_dev_init_return() + * + */ +#define OS_DEV_INIT(function_name) + +/*! + * Define a function which will handle device shutdown + * + * This is the reverse of the #OS_DEV_INIT() routine. + * + * @param function_name The name of the portable driver shutdown routine. + * + * @return A call to #os_dev_shutdown_return() + */ +#define OS_DEV_SHUTDOWN(function_name) + +/*! + * Define a function which will open the device for a user. + * + * @param function_name The name of the driver open() function + * + * @return A call to #os_dev_open_return() + */ +#define OS_DEV_OPEN(function_name) + +/*! + * Define a function which will handle a user's ioctl() request + * + * @param function_name The name of the driver ioctl() function + * + * @return A call to #os_dev_ioctl_return() + */ +#define OS_DEV_IOCTL(function_name) + +/*! + * Define a function which will handle a user's read() request + * + * @param function_name The name of the driver read() function + * + * @return A call to #os_dev_read_return() + */ +#define OS_DEV_READ(function_name) + +/*! + * Define a function which will handle a user's write() request + * + * @param function_name The name of the driver write() function + * + * @return A call to #os_dev_write_return() + */ +#define OS_DEV_WRITE(function_name) + +/*! + * Define a function which will handle a user's mmap() request + * + * The mmap() function requests the driver to map some memory into user space. + * + * @todo Determine what support functions are needed for mmap() handling. + * + * @param function_name The name of the driver mmap() function + * + * @return A call to #os_dev_mmap_return() + */ +#define OS_DEV_MMAP(function_name) + +/*! + * Define a function which will close the device - opposite of OS_DEV_OPEN() + * + * @param function_name The name of the driver close() function + * + * @return A call to #os_dev_close_return() + */ +#define OS_DEV_CLOSE(function_name) + +/*! + * Define a function which will handle an interrupt + * + * No arguments are available to the generic function. It must not invoke any + * OS functions which are illegal in a ISR. It gets no parameters, and must + * have a call to #os_dev_isr_return() instead of any/all return statements. + * + * Example: + * @code + * OS_DEV_ISR(widget, widget_isr, WIDGET_IRQ_NUMBER) + * { + * os_dev_isr_return(1); + * } + * @endcode + * + * @param function_name The name of the driver ISR function + * + * @return A call to #os_dev_isr_return() + */ +#define OS_DEV_ISR(function_name) + +/*! + * Define a function which will operate as a background task / bottom half. + * + * The function implementation must be structured in the following manner: + * @code + * OS_DEV_TASK(widget_task) + * { + * OS_DEV_TASK_SETUP(widget_task); + * + * while OS_DEV_TASK_CONDITION(widget_task) } + * + * }; + * } + * @endcode + * + * @todo In some systems the OS_DEV_TASK_CONDITION() will be an action which + * will cause the task to sleep on some event triggered by os_run_task(). In + * others, the macro will reference a variable laid down by + * OS_DEV_TASK_SETUP() to make sure that the loop is only performed once. + * + * @param function_name The name of this background task function + */ +#define OS_DEV_TASK(function_name) + + /*! @} *//* drsigs */ + +/*! @defgroup dclsigs Routines to declare Driver Signature routines + * + * These macros drop prototypes suitable for forward-declaration of + * @ref drsigs "function signatures". + */ + +/*! @addtogroup dclsigs */ +/*! @{ */ + +/*! + * Declare prototype for the device initialization function + * + * @param function_name The name of the portable initialization function. + */ +#define OS_DEV_INIT_DCL(function_name) + +/*! + * Declare prototype for the device shutdown function + * + * @param function_name The name of the portable driver shutdown routine. + * + * @return A call to #os_dev_shutdown_return() + */ +#define OS_DEV_SHUTDOWN_DCL(function_name) + +/*! + * Declare prototype for the open() function. + * + * @param function_name The name of the driver open() function + * + * @return A call to #os_dev_open_return() + */ +#define OS_DEV_OPEN_DCL(function_name) + +/*! + * Declare prototype for the user's ioctl() request function + * + * @param function_name The name of the driver ioctl() function + * + * @return A call to #os_dev_ioctl_return() + */ +#define OS_DEV_IOCTL_DCL(function_name) + +/*! + * Declare prototype for the function a user's read() request + * + * @param function_name The name of the driver read() function + */ +#define OS_DEV_READ_DCL(function_name) + +/*! + * Declare prototype for the user's write() request function + * + * @param function_name The name of the driver write() function + */ +#define OS_DEV_WRITE_DCL(function_name) + +/*! + * Declare prototype for the user's mmap() request function + * + * @param function_name The name of the driver mmap() function + */ +#define OS_DEV_MMAP_DCL(function_name) + +/*! + * Declare prototype for the close function + * + * @param function_name The name of the driver close() function + * + * @return A call to #os_dev_close_return() + */ +#define OS_DEV_CLOSE_DCL(function_name) + +/*! + * Declare prototype for the interrupt handling function + * + * @param function_name The name of the driver ISR function + */ +#define OS_DEV_ISR_DCL(function_name) + +/*! + * Declare prototype for a background task / bottom half function + * + * @param function_name The name of this background task function + */ +#define OS_DEV_TASK_DCL(function_name) + + /*! @} *//* dclsigs */ + +/***************************************************************************** + * Functions for Returning Values from Driver Signature routines + *****************************************************************************/ + +/*! + * @defgroup retfns Functions to Return Values from Driver Signature routines + */ + +/*! @addtogroup retfns */ +/*! @{ */ + +/*! + * Return from the #OS_DEV_INIT() function + * + * @param code An error code to report success or failure. + * + */ +void os_dev_init_return(os_error_code code); + +/*! + * Return from the #OS_DEV_SHUTDOWN() function + * + * @param code An error code to report success or failure. + * + */ +void os_dev_shutdown_return(os_error_code code); + +/*! + * Return from the #OS_DEV_ISR() function + * + * The function should verify that it really was supposed to be called, + * and that its device needed attention, in order to properly set the + * return code. + * + * @param code non-zero if interrupt handled, zero otherwise. + * + */ +void os_dev_isr_return(int code); + +/*! + * Return from the #OS_DEV_OPEN() function + * + * @param code An error code to report success or failure. + * + */ +void os_dev_open_return(os_error_code code); + +/*! + * Return from the #OS_DEV_IOCTL() function + * + * @param code An error code to report success or failure. + * + */ +void os_dev_ioctl_return(os_error_code code); + +/*! + * Return from the #OS_DEV_READ() function + * + * @param code Number of bytes read, or an error code to report failure. + * + */ +void os_dev_read_return(os_error_code code); + +/*! + * Return from the #OS_DEV_WRITE() function + * + * @param code Number of bytes written, or an error code to report failure. + * + */ +void os_dev_write_return(os_error_code code); + +/*! + * Return from the #OS_DEV_MMAP() function + * + * @param code Number of bytes written, or an error code to report failure. + * + */ +void os_dev_mmap_return(os_error_code code); + +/*! + * Return from the #OS_DEV_CLOSE() function + * + * @param code An error code to report success or failure. + * + */ +void os_dev_close_return(os_error_code code); + +/*! + * Start the #OS_DEV_TASK() function + * + * In some implementations, this could be turned into a label for + * the os_dev_task_return() call. + * + * For a more portable interface, should this take the sleep object as an + * argument??? + * + * @return none + */ +void os_dev_task_begin(void); + +/*! + * Return from the #OS_DEV_TASK() function + * + * In some implementations, this could be turned into a sleep followed + * by a jump back to the os_dev_task_begin() call. + * + * @param code An error code to report success or failure. + * + */ +void os_dev_task_return(os_error_code code); + + /*! @} *//* retfns */ + +/***************************************************************************** + * Functions/Macros for accessing arguments from Driver Signature routines + *****************************************************************************/ + +/*! @defgroup drsigargs Functions for Getting Arguments in Signature functions + * + */ +/* @addtogroup @drsigargs */ +/*! @{ */ + +/*! + * Check whether user is requesting read (permission) on the file/device. + * Usable in #OS_DEV_OPEN(), #OS_DEV_CLOSE(), #OS_DEV_IOCTL(), #OS_DEV_READ() + * and #OS_DEV_WRITE() routines. + */ +int os_dev_is_flag_read(void); + +/*! + * Check whether user is requesting write (permission) on the file/device. + * Usable in #OS_DEV_OPEN(), #OS_DEV_CLOSE(), #OS_DEV_IOCTL(), #OS_DEV_READ() + * and #OS_DEV_WRITE() routines. + */ +int os_dev_is_flag_write(void); + +/*! + * Check whether user is requesting non-blocking I/O. Usable in + * #OS_DEV_OPEN(), #OS_DEV_CLOSE(), #OS_DEV_IOCTL(), #OS_DEV_READ() and + * #OS_DEV_WRITE() routines. + * + * @todo Specify required behavior when nonblock is requested and (sufficient?) + * data are not available to fulfill the request. + * + */ +int os_dev_is_flag_nonblock(void); + +/*! + * Determine which major device is being accessed. Usable in #OS_DEV_OPEN() + * and #OS_DEV_CLOSE(). + */ +int os_dev_get_major(void); + +/*! + * Determine which minor device is being accessed. Usable in #OS_DEV_OPEN() + * and #OS_DEV_CLOSE(). + */ +int os_dev_get_minor(void); + +/*! + * Determine which operation the user wants performed. Usable in + * #OS_DEV_IOCTL(). + * + * @return Value of the operation. + * + * @todo Define some generic way to define the individual operations. + */ +unsigned os_dev_get_ioctl_op(void); + +/*! + * Retrieve the associated argument for the desired operation. Usable in + * #OS_DEV_IOCTL(). + * + * @return A value which can be cast to a struct pointer or used as + * int/long. + */ +os_dev_ioctl_arg_t os_dev_get_ioctl_arg(void); + +/*! + * Determine the requested byte count. This should be the size of buffer at + * #os_dev_get_user_buffer(). Usable in OS_DEV_READ() and OS_DEV_WRITE() + * routines. + * + * @return A count of bytes + */ +unsigned os_dev_get_count(void); + +/*! + * Get the pointer to the user's data buffer. Usable in OS_DEV_READ(), + * OS_DEV_WRITE(), and OS_DEV_MMAP() routines. + * + * @return Pointer to user buffer (in user space). See #os_copy_to_user() + * and #os_copy_from_user(). + */ +void *os_dev_get_user_buffer(void); + +/*! + * Get the POSIX flags field for the associated open file. Usable in + * OS_DEV_READ(), OS_DEV_WRITE(), and OS_DEV_IOCTL() routines. + * + * @return The flags associated with the file. + */ +unsigned os_dev_get_file_flags(void); + +/*! + * Set the driver's private structure associated with this file/open. + * + * Generally used during #OS_DEV_OPEN(). May also be used during + * #OS_DEV_READ(), #OS_DEV_WRITE(), #OS_DEV_IOCTL(), #OS_DEV_MMAP(), and + * #OS_DEV_CLOSE(). See also #os_dev_get_user_private(). + * + * @param struct_p The driver data structure to associate with this user. + */ +void os_dev_set_user_private(void *struct_p); + +/*! + * Get the driver's private structure associated with this file. + * + * May be used during #OS_DEV_OPEN(), #OS_DEV_READ(), #OS_DEV_WRITE(), + * #OS_DEV_IOCTL(), #OS_DEV_MMAP(), and #OS_DEV_CLOSE(). See + * also #os_dev_set_user_private(). + * + * @return The driver data structure to associate with this user. + */ +void *os_dev_get_user_private(void); + + /*! @} *//* drsigargs */ + +/***************************************************************************** + * Functions for Generating References to Driver Routines + *****************************************************************************/ + +/*! + * @defgroup drref Functions for Generating References to Driver Routines + * + * These functions will most likely be implemented as macros. They are a + * necessary part of the portable API to guarantee portability. The @c symbol + * type in here is the same symbol passed to the associated + * signature-generating macro. + * + * These macros must be used whenever referring to a + * @ref drsigs "driver signature function", for instance when storing or + * passing a pointer to the function. + */ + +/*! @addtogroup drref */ +/*! @{ */ + +/*! + * Generate a reference to an #OS_DEV_INIT() function + * + * @param function_name The name of the init function being referenced. + * + * @return A reference to the function + */ +os_init_function_t OS_DEV_INIT_REF(symbol function_name); + +/*! + * Generate a reference to an #OS_DEV_SHUTDOWN() function + * + * @param function_name The name of the shutdown function being referenced. + * + * @return A reference to the function + */ +os_shutdown_function_t OS_DEV_SHUTDOWN_REF(symbol function_name); + +/*! + * Generate a reference to an #OS_DEV_OPEN() function + * + * @param function_name The name of the open function being referenced. + * + * @return A reference to the function + */ +os_user_function_t OS_DEV_OPEN_REF(symbol function_name); + +/*! + * Generate a reference to an #OS_DEV_CLOSE() function + * + * @param function_name The name of the close function being referenced. + * + * @return A reference to the function + */ +os_user_function_t OS_DEV_CLOSE_REF(symbol function_name); + +/*! + * Generate a reference to an #OS_DEV_READ() function + * + * @param function_name The name of the read function being referenced. + * + * @return A reference to the function + */ +os_user_function_t OS_DEV_READ_REF(symbol function_name); + +/*! + * Generate a reference to an #OS_DEV_WRITE() function + * + * @param function_name The name of the write function being referenced. + * + * @return A reference to the function + */ +os_user_function_t OS_DEV_WRITE_REF(symbol function_name); + +/*! + * Generate a reference to an #OS_DEV_IOCTL() function + * + * @param function_name The name of the ioctl function being referenced. + * + * @return A reference to the function + */ +os_user_function_t OS_DEV_IOCTL_REF(symbol function_name); + +/*! + * Generate a reference to an #OS_DEV_MMAP() function + * + * @param function_name The name of the mmap function being referenced. + * + * @return A reference to the function + */ +os_user_function_t OS_DEV_MMAP_REF(symbol function_name); + +/*! + * Generate a reference to an #OS_DEV_ISR() function + * + * @param function_name The name of the isr being referenced. + * + * @return a reference to the function + */ +os_interrupt_handler_t OS_DEV_ISR_REF(symbol function_name); + + /*! @} *//* drref */ + +/*! + * Flush and invalidate all cache lines. + */ +void os_flush_cache_all(void); + +/*! + * Flush a range of addresses from the cache + * + * @param start Starting virtual address + * @param len Number of bytes to flush + */ +void os_cache_flush_range(void *start, uint32_t len); + +/*! + * Invalidate a range of addresses in the cache + * + * @param start Starting virtual address + * @param len Number of bytes to flush + */ +void os_cache_inv_range(void *start, uint32_t len); + +/*! + * Clean a range of addresses from the cache + * + * @param start Starting virtual address + * @param len Number of bytes to flush + */ +void os_cache_clean_range(void *start, uint32_t len); + +#endif /* DOXYGEN_PORTABLE_OS_DOC */ + +#endif /* PORTABLE_OS_H */ diff --git a/drivers/mxc/security/sahara2/include/sah_driver_common.h b/drivers/mxc/security/sahara2/include/sah_driver_common.h new file mode 100644 index 000000000000..a31eefe96a54 --- /dev/null +++ b/drivers/mxc/security/sahara2/include/sah_driver_common.h @@ -0,0 +1,98 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/** +* @file sah_driver_common.h +* +* @brief Provides types and defined values for use in the Driver Interface. +* +*/ + +#ifndef SAH_DRIVER_COMMON_H +#define SAH_DRIVER_COMMON_H + +#include "fsl_platform.h" +#include <sahara.h> +#include <adaptor.h> + +/** This specifies the permissions for the device file. It is equivalent to + * chmod 666. + */ +#define SAHARA_DEVICE_MODE S_IFCHR | S_IRUGO | S_IWUGO + +/** +* The status of entries in the Queue. +* +******************************************************************************/ +typedef enum +{ + /** This state indicates that the entry is in the queue and awaits + * execution on SAHARA. */ + SAH_STATE_PENDING, + /** This state indicates that the entry has been written to the SAHARA + * DAR. */ + SAH_STATE_ON_SAHARA, + /** This state indicates that the entry is off of SAHARA, and is awaiting + post-processing. */ + SAH_STATE_OFF_SAHARA, + /** This state indicates that the entry is successfully executed on SAHARA, + and it is finished with post-processing. */ + SAH_STATE_COMPLETE, + /** This state indicates that the entry caused an error or fault on SAHARA, + * and it is finished with post-processing. */ + SAH_STATE_FAILED, + /** This state indicates that the entry was reset via the Reset IO + * Control, and it is finished with post-processing. */ + SAH_STATE_RESET, + /** This state indicates that the entry was signalled from user-space and + * either in the DAR, IDAR or has finished executing pending Bottom Half + * processing. */ + SAH_STATE_IGNORE, + /** This state indicates that the entry was signalled from user-space and + * has been processed by the bottom half. */ + SAH_STATE_IGNORED +} sah_Queue_Status; + +/* any of these conditions being true indictes the descriptor's processing + * is complete */ +#define SAH_DESC_PROCESSED(status) \ + (((status) == SAH_STATE_COMPLETE) || \ + ((status) == SAH_STATE_FAILED ) || \ + ((status) == SAH_STATE_RESET )) + +extern os_lock_t desc_queue_lock; + +extern uint32_t dar_count; +extern uint32_t interrupt_count; +extern uint32_t done1done2_count; +extern uint32_t done1busy2_count; +extern uint32_t done1_count; + + +int sah_get_results_pointers(fsl_shw_uco_t* user_ctx, uint32_t arg); +fsl_shw_return_t sah_get_results_from_pool(volatile fsl_shw_uco_t* user_ctx, + sah_results *arg); +fsl_shw_return_t sah_handle_registration(fsl_shw_uco_t *user_cts); +fsl_shw_return_t sah_handle_deregistration(fsl_shw_uco_t *user_cts); + +int sah_Queue_Manager_Count_Entries(int ignore_state, sah_Queue_Status state); +unsigned long sah_Handle_Poll(sah_Head_Desc *entry); + +#ifdef DIAG_DRV_IF +/****************************************************************************** +* Descriptor and Link dumping functions. +******************************************************************************/ +void sah_Dump_Chain(const sah_Desc *chain, dma_addr_t addr); +#endif /* DIAG_DRV_IF */ + +#endif /* SAH_DRIVER_COMMON_H */ diff --git a/drivers/mxc/security/sahara2/include/sah_hardware_interface.h b/drivers/mxc/security/sahara2/include/sah_hardware_interface.h new file mode 100644 index 000000000000..a03e53999c4a --- /dev/null +++ b/drivers/mxc/security/sahara2/include/sah_hardware_interface.h @@ -0,0 +1,93 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/** +* @file sah_hardware_interface.h +* +* @brief Provides an interface to the SAHARA hardware registers. +* +*/ + +#ifndef SAH_HARDWARE_INTERFACE_H +#define SAH_HARDWARE_INTERFACE_H + +#include <sah_driver_common.h> +#include <sah_status_manager.h> + +/* These values can be used with sah_HW_Write_Control(). */ +#ifdef SAHARA1 +/** Define platform as Little-Endian */ +#define CTRL_LITTLE_END 0x00000002 +/** Bit to cause endian change in transfers */ +#define CTRL_INT_EN 0x00000004 +/** Set High Assurance mode */ +#define CTRL_HA 0x00000008 +#else +/** Bit to cause byte swapping in (data?) transfers */ +#define CTRL_BYTE_SWAP 0x00000001 +/** Bit to cause halfword swapping in (data?) transfers */ +#define CTRL_HALFWORD_SWAP 0x00000002 +/** Bit to cause endian change in (data?) transfers */ +#define CTRL_INT_EN 0x00000010 +/** Set High Assurance mode */ +#define CTRL_HA 0x00000020 +/** Disable High Assurance */ +#define CTRL_HA_DISABLE 0x00000040 +/** Reseed the RNG CHA */ +#define CTRL_RNG_RESEED 0x00000080 +#endif + + +/* These values can be used with sah_HW_Write_Command(). */ +/** Reset the Sahara */ +#define CMD_RESET 0x00000001 +/** Set Sahara into Batch mode. */ +#define CMD_BATCH 0x00010000 +/** Clear the Sahara interrupt */ +#define CMD_CLR_INT_BIT 0x00000100 +/** Clear the Sahara error */ +#define CMD_CLR_ERROR_BIT 0x00000200 + + +/** error status register contains error */ +#define STATUS_ERROR 0x00000010 + +/** Op status register contains op status */ +#define OP_STATUS 0x00000020 + + +/* High Level functions */ +int sah_HW_Reset(void); +fsl_shw_return_t sah_HW_Set_HA(void); +sah_Execute_Status sah_Wait_On_Sahara(void); + +/* Low Level functions */ +uint32_t sah_HW_Read_Version(void); +uint32_t sah_HW_Read_Control(void); +uint32_t sah_HW_Read_Status(void); +uint32_t sah_HW_Read_Error_Status(void); +uint32_t sah_HW_Read_Op_Status(void); +uint32_t sah_HW_Read_DAR(void); +uint32_t sah_HW_Read_CDAR(void); +uint32_t sah_HW_Read_IDAR(void); +uint32_t sah_HW_Read_Fault_Address(void); +uint32_t sah_HW_Read_MM_Status(void); +uint32_t sah_HW_Read_Config(void); +void sah_HW_Write_Command(uint32_t command); +void sah_HW_Write_Control(uint32_t control); +void sah_HW_Write_DAR(uint32_t pointer); +void sah_HW_Write_Config(uint32_t configuration); + +#endif /* SAH_HARDWARE_INTERFACE_H */ + +/* End of sah_hardware_interface.c */ diff --git a/drivers/mxc/security/sahara2/include/sah_interrupt_handler.h b/drivers/mxc/security/sahara2/include/sah_interrupt_handler.h new file mode 100644 index 000000000000..8eb8690ff093 --- /dev/null +++ b/drivers/mxc/security/sahara2/include/sah_interrupt_handler.h @@ -0,0 +1,42 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/** +* @file sah_interrupt_handler.h +* +* @brief Provides a hardware interrupt handling mechanism for device driver. +* +*/ +/****************************************************************************** +* +* CAUTION: +* +* MODIFICATION HISTORY: +* +* Date Person Change +* 30/07/03 MW Initial Creation +******************************************************************* +*/ + +#ifndef SAH_INTERRUPT_HANDLER_H +#define SAH_INTERRUPT_HANDLER_H + +#include <sah_driver_common.h> + +/****************************************************************************** +* External function declarations +******************************************************************************/ +int sah_Intr_Init (wait_queue_head_t *wait_queue); +void sah_Intr_Release (void); + +#endif /* SAH_INTERRUPT_HANDLER_H */ diff --git a/drivers/mxc/security/sahara2/include/sah_kernel.h b/drivers/mxc/security/sahara2/include/sah_kernel.h new file mode 100644 index 000000000000..1f2d072f27ac --- /dev/null +++ b/drivers/mxc/security/sahara2/include/sah_kernel.h @@ -0,0 +1,89 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/* +* @file sah_kernel.h +* +* @brief Provides definitions for items that user-space and kernel-space share. +*/ +/****************************************************************************** +* +* This file needs to be PORTED to a non-Linux platform +*/ + +#ifndef SAH_KERNEL_H +#define SAH_KERNEL_H + +#if defined(__KERNEL__) + +#if defined(CONFIG_ARCH_MXC91321) || defined(CONFIG_ARCH_MXC91231) \ + || defined(CONFIG_ARCH_MX27) || defined(CONFIG_ARCH_MX33) \ + || defined(CONFIG_ARCH_MXC92323) +#include <asm/arch/hardware.h> +#define SAHA_BASE_ADDR SAHARA_BASE_ADDR +#define SAHARA_IRQ INT_SAHARA +#else +#include <asm/arch/mx2.h> +#endif + +#endif /* KERNEL */ + +/* IO Controls */ +/* The magic number 'k' is reserved for the SPARC architecture. (See <kernel + * source root>/Documentation/ioctl-number.txt. + */ +#define SAH_IOC_MAGIC 'k' +#define SAHARA_HWRESET _IO(SAH_IOC_MAGIC, 0) +#define SAHARA_SET_HA _IO(SAH_IOC_MAGIC, 1) +#define SAHARA_CHK_TEST_MODE _IOR(SAH_IOC_MAGIC,2, int) +#define SAHARA_DAR _IO(SAH_IOC_MAGIC, 3) +#define SAHARA_GET_RESULTS _IO(SAH_IOC_MAGIC, 4) +#define SAHARA_REGISTER _IO(SAH_IOC_MAGIC, 5) +#define SAHARA_DEREGISTER _IO(SAH_IOC_MAGIC, 6) +/* 7 */ +#define SAHARA_SCC_ALLOC _IOWR(SAH_IOC_MAGIC, 8, scc_slot_t) +#define SAHARA_SCC_DEALLOC _IOWR(SAH_IOC_MAGIC, 9, scc_slot_t) +#define SAHARA_SCC_LOAD _IOWR(SAH_IOC_MAGIC, 10, scc_slot_t) +#define SAHARA_SCC_UNLOAD _IOWR(SAH_IOC_MAGIC, 11, scc_slot_t) +#define SAHARA_SCC_SLOT_ENC _IOWR(SAH_IOC_MAGIC, 12, scc_slot_t) +#define SAHARA_SCC_SLOT_DEC _IOWR(SAH_IOC_MAGIC, 13, scc_slot_t) + +/*! This is the name that will appear in /proc/interrupts */ +#define SAHARA_NAME "SAHARA" + +/*! + * SAHARA IRQ number. See page 9-239 of TLICS - Motorola Semiconductors H.K. + * TAHITI-Lite IC Specification, Rev 1.1, Feb 2003. + * + * TAHITI has two blocks of 32 interrupts. The SAHARA IRQ is number 27 + * in the second block. This means that the SAHARA IRQ is 27 + 32 = 59. + */ +#ifndef SAHARA_IRQ +#define SAHARA_IRQ (27+32) +#endif + +/*! + * Device file definition. The #ifndef is here to support the unit test code + * by allowing it to specify a different test device. + */ +#ifndef SAHARA_DEVICE_SHORT +#define SAHARA_DEVICE_SHORT "sahara" +#endif + +#ifndef SAHARA_DEVICE +#define SAHARA_DEVICE "/dev/"SAHARA_DEVICE_SHORT +#endif + +#endif /* SAH_KERNEL_H */ + +/* End of sah_kernel.h */ diff --git a/drivers/mxc/security/sahara2/include/sah_memory_mapper.h b/drivers/mxc/security/sahara2/include/sah_memory_mapper.h new file mode 100644 index 000000000000..3bca7215b359 --- /dev/null +++ b/drivers/mxc/security/sahara2/include/sah_memory_mapper.h @@ -0,0 +1,82 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/** +* @file sah_memory_mapper.h +* +* @brief Re-creates SAHARA data structures in Kernel memory such that they are +* suitable for DMA. +* +*/ + +#ifndef SAH_MEMORY_MAPPER_H +#define SAH_MEMORY_MAPPER_H + +#include <sah_driver_common.h> +#include <sah_queue_manager.h> + + +/****************************************************************************** +* External function declarations +******************************************************************************/ +sah_Head_Desc *sah_Copy_Descriptors ( + sah_Head_Desc *desc); + +sah_Link *sah_Copy_Links ( + sah_Link *ptr); + +sah_Head_Desc *sah_Physicalise_Descriptors ( + sah_Head_Desc *desc); + +sah_Link *sah_Physicalise_Links ( + sah_Link *ptr); + +sah_Head_Desc *sah_DePhysicalise_Descriptors ( + sah_Head_Desc *desc); + +sah_Link *sah_DePhysicalise_Links ( + sah_Link *ptr); + +sah_Link *sah_Make_Links ( + sah_Link *ptr, + sah_Link **tail); + +void sah_Destroy_Descriptors ( + sah_Head_Desc *desc); + +void sah_Destroy_Links ( + sah_Link *link); + +void sah_Free_Chained_Descriptors ( + sah_Head_Desc *desc); + +void sah_Free_Chained_Links ( + sah_Link *link); + +int sah_Init_Mem_Map (void); + +void sah_Stop_Mem_Map (void); + +int sah_Block_Add_Page (int big); + +sah_Desc *sah_Alloc_Descriptor (void); +sah_Head_Desc *sah_Alloc_Head_Descriptor (void); +void sah_Free_Descriptor (sah_Desc *desc); +void sah_Free_Head_Descriptor (sah_Head_Desc *desc); +sah_Link *sah_Alloc_Link (void); +void sah_Free_Link (sah_Link *link); + + +#endif /* SAH_MEMORY_MAPPER_H */ + +/* End of sah_memory_mapper.h */ diff --git a/drivers/mxc/security/sahara2/include/sah_queue_manager.h b/drivers/mxc/security/sahara2/include/sah_queue_manager.h new file mode 100644 index 000000000000..2dde5883e193 --- /dev/null +++ b/drivers/mxc/security/sahara2/include/sah_queue_manager.h @@ -0,0 +1,63 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/** +* @file sah_queue_manager.h +* +* @brief This file definitions for the Queue Manager. + +* The Queue Manager manages additions and removal from the queue and updates +* the status of queue entries. It also calls sah_HW_* functions to interact +* with the hardware. +* +*/ + +#ifndef SAH_QUEUE_MANAGER_H +#define SAH_QUEUE_MANAGER_H + +#include <sah_driver_common.h> +#include <sah_status_manager.h> + + +/************************* +* Queue Manager Functions +*************************/ +fsl_shw_return_t sah_Queue_Manager_Init(void); +void sah_Queue_Manager_Close(void); +void sah_Queue_Manager_Reset_Entries(void); +void sah_Queue_Manager_Append_Entry(sah_Head_Desc *entry); +void sah_Queue_Manager_Remove_Entry(sah_Head_Desc *entry); + + +/************************* +* Queue Functions +*************************/ +sah_Queue *sah_Queue_Construct(void); +void sah_Queue_Destroy(sah_Queue *this); +void sah_Queue_Append_Entry(sah_Queue *this, sah_Head_Desc *entry); +void sah_Queue_Remove_Entry(sah_Queue *this); +void sah_Queue_Remove_Any_Entry(sah_Queue *this, sah_Head_Desc *entry); +void sah_postprocess_queue(unsigned long reset_flag); + + +/************************* +* Misc Releated Functions +*************************/ + +int sah_blocking_mode(struct sah_Head_Desc *entry); +fsl_shw_return_t sah_convert_error_status(uint32_t error_status); + + +#endif /* SAH_QUEUE_MANAGER_H */ + +/* End of sah_queue_manager.h */ diff --git a/drivers/mxc/security/sahara2/include/sah_status_manager.h b/drivers/mxc/security/sahara2/include/sah_status_manager.h new file mode 100644 index 000000000000..e38731bf4835 --- /dev/null +++ b/drivers/mxc/security/sahara2/include/sah_status_manager.h @@ -0,0 +1,228 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/** +* @file sah_status_manager.h +* +* @brief SAHARA Status Manager Types and Function Prototypes +* +* @author Stuart Holloway (SH) +* +*/ + +#ifndef STATUS_MANAGER_H +#define STATUS_MANAGER_H +#include "sah_driver_common.h" +#include "sahara.h" + + +/****************************************************************************** +* User defined data types +******************************************************************************/ +/** +****************************************************************************** +* sah_Execute_Status +* Types read from SAHARA Status Register, with additional state for Op Status +******************************************************************************/ +typedef enum sah_Execute_Status +{ + /** Sahara is Idle. */ + SAH_EXEC_IDLE = 0, + /** SAHARA is busy performing a resest or processing a decriptor chain. */ + SAH_EXEC_BUSY = 1, + /** An error occurred while SAHARA executed the first descriptor. */ + SAH_EXEC_ERROR1 = 2, + /** SAHARA has failed internally. */ + SAH_EXEC_FAULT = 3, + /** SAHARA has finished processing a descriptor chain and is idle. */ + SAH_EXEC_DONE1 = 4, + /** SAHARA has finished processing a descriptor chain, and is processing a + * second chain. */ + SAH_EXEC_DONE1_BUSY2 = 5, + /** SAHARA has finished processing a descriptor chain, but has generated an + * error while processing a second descriptor chain. */ + SAH_EXEC_DONE1_ERROR2 = 6, + /** SAHARA has finished two descriptors. */ + SAH_EXEC_DONE1_DONE2 = 7, + /** SAHARA has stopped, and first descriptor has Op Status, not Err */ + SAH_EXEC_OPSTAT1 = 0x20, +} sah_Execute_Status; + +/** + * When this bit is on in a #sah_Execute_Status, it means that DONE1 is true. + */ +#define SAH_EXEC_DONE1_BIT 4 + +/** + * Bits which make up the Sahara State + */ +#define SAH_EXEC_STATE_MASK 0x00000007 + +/** +******************************************************************************* +* sah_Execute_Error +* Types read from SAHARA Error Status Register +******************************************************************************/ +typedef enum sah_Execute_Error +{ + /** No Error */ + SAH_ERR_NONE = 0, + /** Header is not valid. */ + SAH_ERR_HEADER = 1, + /** Descriptor length is not correct. */ + SAH_ERR_DESC_LENGTH = 2, + /** Length or pointer field is zero while the other is non-zero. */ + SAH_ERR_DESC_POINTER = 3, + /** Length of the link is not a multiple of 4 and is not the last link */ + SAH_ERR_LINK_LENGTH = 4, + /** The data pointer in a link is zero */ + SAH_ERR_LINK_POINTER = 5, + /** Input Buffer reported an overflow */ + SAH_ERR_INPUT_BUFFER = 6, + /** Output Buffer reported an underflow */ + SAH_ERR_OUTPUT_BUFFER = 7, + /** Incorrect data in output buffer after CHA's has signalled 'done'. */ + SAH_ERR_OUTPUT_BUFFER_STARVATION = 8, + /** Internal Hardware Failure. */ + SAH_ERR_INTERNAL_STATE = 9, + /** Current Descriptor was not legal, but cause is unknown. */ + SAH_ERR_GENERAL_DESCRIPTOR = 10, + /** Reserved pointer fields have been set to 1. */ + SAH_ERR_RESERVED_FIELDS = 11, + /** Descriptor address error */ + SAH_ERR_DESCRIPTOR_ADDRESS = 12, + /** Link address error */ + SAH_ERR_LINK_ADDRESS = 13, + /** Processing error in CHA module */ + SAH_ERR_CHA = 14, + /** Processing error during DMA */ + SAH_ERR_DMA = 15 +} sah_Execute_Error; + + +/** +******************************************************************************* +* sah_CHA_Error_Source +* Types read from SAHARA Error Status Register for CHA Error Source +* +******************************************************************************/ +typedef enum sah_CHA_Error_Source +{ + /** No Error indicated in Source CHA Error. */ + SAH_CHA_NO_ERROR = 0, + /** Error in SKHA module. */ + SAH_CHA_SKHA_ERROR = 1, + /** Error in MDHA module. */ + SAH_CHA_MDHA_ERROR = 2, + /** Error in RNG module. */ + SAH_CHA_RNG_ERROR = 3, + /** Error in PKHA module. */ + SAH_CHA_PKHA_ERROR = 4, +} sah_CHA_Error_Source; + +/** +****************************************************************************** +* sah_CHA_Error_Status +* Types read from SAHARA Error Status Register for CHA Error Status +* +******************************************************************************/ +typedef enum sah_CHA_Error_Status +{ + /** No CHA error detected */ + SAH_CHA_NO_ERR = 0x000, + /** Non-empty input buffer when complete. */ + SAH_CHA_IP_BUF = 0x001, + /** Illegal Address Error. */ + SAH_CHA_ADD_ERR = 0x002, + /** Illegal Mode Error. */ + SAH_CHA_MODE_ERR = 0x004, + /** Illegal Data Size Error. */ + SAH_CHA_DATA_SIZE_ERR = 0x008, + /** Illegal Key Size Error. */ + SAH_CHA_KEY_SIZE_ERR = 0x010, + /** Mode/Context/Key written during processing. */ + SAH_CHA_PROC_ERR = 0x020, + /** Context Read During Processing. */ + SAH_CHA_CTX_READ_ERR = 0x040, + /** Internal Hardware Error. */ + SAH_CHA_INTERNAL_HW_ERR = 0x080, + /** Input Buffer not enabled or underflow. */ + SAH_CHA_IP_BUFF_ERR = 0x100, + /** Output Buffer not enabled or overflow. */ + SAH_CHA_OP_BUFF_ERR = 0x200, + /** DES key parity error (SKHA) */ + SAH_CHA_DES_KEY_ERR = 0x400, + /** Reserved error code. */ + SAH_CHA_RES = 0x800 +} sah_CHA_Error_Status; + +/** +***************************************************************************** +* sah_DMA_Error_Status +* Types read from SAHARA Error Status Register for DMA Error Status +******************************************************************************/ +typedef enum sah_DMA_Error_Status +{ + /** No DMA Error Code. */ + SAH_DMA_NO_ERR = 0, + /** AHB terminated a bus cycle with an error. */ + SAH_DMA_AHB_ERR = 2, + /** Internal IP bus cycle was terminated with an error termination. */ + SAH_DMA_IP_ERR = 4, + /** Parity error detected on DMA command. */ + SAH_DMA_PARITY_ERR = 6, + /** DMA was requested to cross a 256 byte internal address boundary. */ + SAH_DMA_BOUNDRY_ERR = 8, + /** DMA controller is busy */ + SAH_DMA_BUSY_ERR = 10, + /** Memory Bounds Error */ + SAH_DMA_RESERVED_ERR = 12, + /** Internal DMA hardware error detected */ + SAH_DMA_INT_ERR = 14 +} sah_DMA_Error_Status; + +/** +***************************************************************************** +* sah_DMA_Error_Size +* Types read from SAHARA Error Status Register for DMA Error Size +* +******************************************************************************/ +typedef enum sah_DMA_Error_Size +{ + /** Error during Byte transfer. */ + SAH_DMA_SIZE_BYTE = 0, + /** Error during Half-word transfer. */ + SAH_DMA_SIZE_HALF_WORD = 1, + /** Error during Word transfer. */ + SAH_DMA_SIZE_WORD = 2, + /** Reserved DMA word size. */ + SAH_DMA_SIZE_RES = 3 +} sah_DMA_Error_Size; + + + + +extern bool sah_dpm_flag; + +/************************* +* Status Manager Functions +*************************/ + +unsigned long sah_Handle_Interrupt(sah_Execute_Status hw_status); +sah_Head_Desc *sah_Find_With_State (sah_Queue_Status status); +int sah_dpm_init(void); +void sah_dpm_close(void); +void sah_Queue_Manager_Prime (sah_Head_Desc *entry); + + +#endif /* STATUS_MANAGER_H */ diff --git a/drivers/mxc/security/sahara2/include/sahara.h b/drivers/mxc/security/sahara2/include/sahara.h new file mode 100644 index 000000000000..b7681e0c34ec --- /dev/null +++ b/drivers/mxc/security/sahara2/include/sahara.h @@ -0,0 +1,2184 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file sahara.h + * + * File which implements the FSL_SHW API when used on Sahara + */ +/*! + * @if USE_MAINPAGE + * @mainpage Sahara2 implemtation of FSL Security Hardware API + * @endif + * + */ + +#ifndef SAHARA2_API_H +#define SAHARA2_API_H + +#ifdef DIAG_SECURITY_FUNC +#include <diagnostic.h> +#endif /* DIAG_SECURITY_FUNC */ + +/* This is a Linux flag... ? */ +#ifndef __KERNEL__ +#include <inttypes.h> +#include <stdlib.h> +#include <memory.h> +#else +#include "portable_os.h" +#endif + +/* This definition may need a new name, and needs to go somewhere which + * can determine platform, kernel vs. user, os, etc. + */ +#define copy_bytes(out, in, len) memcpy(out, in, len) + +/* Does this belong here? */ +#ifndef SAHARA_DEVICE +#define SAHARA_DEVICE "/dev/sahara" +#endif + +/*! +******************************************************************************* +* @defgroup lnkflags Link Flags +* +* @brief Flags to show information about link data and link segments +* +******************************************************************************/ +/*! @addtogroup lnkflags + * @{ + */ + +/*! +******************************************************************************* +* This flag indicates that the data in a link is owned by the security +* function component and this memory will be freed by the security function +* component. To be used as part of the flag field of the sah_Link structure. +******************************************************************************/ +#define SAH_OWNS_LINK_DATA 0x01 + +/*! +******************************************************************************* +* The data in a link is not owned by the security function component and +* therefore it will not attempt to free this memory. To be used as part of the +* flag field of the sah_Link structure. +******************************************************************************/ +#define SAH_USES_LINK_DATA 0x02 + +/*! +******************************************************************************* +* The data in this link will change when the descriptor gets executed. +******************************************************************************/ +#define SAH_OUTPUT_LINK 0x04 + +/*! +******************************************************************************* +* The ptr and length in this link are really 'established key' info. They +* are to be converted to ptr/length before putting on request queue. +******************************************************************************/ +#define SAH_KEY_IS_HIDDEN 0x08 + +/*! +******************************************************************************* +* The link structure has been appended to the previous one by the driver. It +* needs to be removed before leaving the driver (and returning to API). +******************************************************************************/ +#define SAH_REWORKED_LINK 0x10 + +/*! +******************************************************************************* +* The length and data fields of this link contain the slot and user id +* used to access the SCC stored key +******************************************************************************/ +#define SAH_STORED_KEY_INFO 0x20 + +/*! +******************************************************************************* +* The Data field points to a physical address, and does not need to be +* processed by the driver. Honored only in Kernel API. +******************************************************************************/ +#define SAH_PREPHYS_DATA 0x40 + +/*! +******************************************************************************* +* The link was inserted during the Physicalise procedure. It is tagged so +* it can be removed during DePhysicalise, thereby returning to the caller an +* intact chain. +******************************************************************************/ +#define SAH_LINK_INSERTED_LINK 0x80 +/*! @} */ + +/*! +******************************************************************************* +* sah_Link_Flags +* +* Type to be used for flags associated with a Link in security function. +* These flags are used internally by the security function component only. +* +* Values defined at @ref lnkflags +* +* @brief typedef for flags field of sah_Link +******************************************************************************/ +typedef uint32_t sah_Link_Flags; + +/* +******************************************************************************* +* Security Parameters Related Structures +* +* All of structures associated with API parameters +* +******************************************************************************/ + +/* +******************************************************************************* +* Common Types +* +* All of structures used across several classes of crytography +******************************************************************************/ + +/*! +******************************************************************************* +* @brief Indefinite precision integer used for security operations on SAHARA +* accelerator. The data will always be in little Endian format. +******************************************************************************/ +typedef uint8_t *sah_Int; + +/*! +******************************************************************************* +* @brief Byte array used for block cipher and hash digest/MAC operations on +* SAHARA accelerator. The Endian format will be as specified by the function +* using the sah_Oct_Str. +******************************************************************************/ +typedef uint8_t *sah_Oct_Str; + +/*! + * A queue of descriptor heads -- used to hold requests waiting for user to + * pick up the results. */ +typedef struct sah_Queue { + int count; /*!< # entries in queue */ + struct sah_Head_Desc *head; /*!< first entry in queue */ + struct sah_Head_Desc *tail; /*!< last entry in queue */ +} sah_Queue; + +/****************************************************************************** + * Enumerations + *****************************************************************************/ +/*! + * Flags for the state of the User Context Object (#fsl_shw_uco_t). + */ +typedef enum fsl_shw_user_ctx_flags { + /*! + * API will block the caller until operation completes. The result will be + * available in the return code. If this is not set, user will have to get + * results using #fsl_shw_get_results(). + */ + FSL_UCO_BLOCKING_MODE = 1, + /*! + * User wants callback (at the function specified with + * #fsl_shw_uco_set_callback()) when the operation completes. This flag is + * valid only if #FSL_UCO_BLOCKING_MODE is not set. + */ + FSL_UCO_CALLBACK_MODE = 2, + /*! Do not free descriptor chain after driver (adaptor) finishes */ + FSL_UCO_SAVE_DESC_CHAIN = 4, + /*! + * User has made at least one request with callbacks requested, so API is + * ready to handle others. + */ + FSL_UCO_CALLBACK_SETUP_COMPLETE = 8, + /*! + * (virtual) pointer to descriptor chain is completely linked with physical + * (DMA) addresses, ready for the hardware. This flag should not be used + * by FSL SHW API programs. + */ + FSL_UCO_CHAIN_PREPHYSICALIZED = 16, +} fsl_shw_user_ctx_flags_t; + +/*! + * Return code for FSL_SHW library. + * + * These codes may be returned from a function call. In non-blocking mode, + * they will appear as the status in a Result Object. + */ +typedef enum fsl_shw_return_t { + /*! + * No error. As a function return code in Non-blocking mode, this may + * simply mean that the operation was accepted for eventual execution. + */ + FSL_RETURN_OK_S = 0, + /*! Failure for non-specific reason. */ + FSL_RETURN_ERROR_S, + /*! + * Operation failed because some resource was not able to be allocated. + */ + FSL_RETURN_NO_RESOURCE_S, + /*! Crypto algorithm unrecognized or improper. */ + FSL_RETURN_BAD_ALGORITHM_S, + /*! Crypto mode unrecognized or improper. */ + FSL_RETURN_BAD_MODE_S, + /*! Flag setting unrecognized or inconsistent. */ + FSL_RETURN_BAD_FLAG_S, + /*! Improper or unsupported key length for algorithm. */ + FSL_RETURN_BAD_KEY_LENGTH_S, + /*! Improper parity in a (DES, TDES) key. */ + FSL_RETURN_BAD_KEY_PARITY_S, + /*! + * Improper or unsupported data length for algorithm or internal buffer. + */ + FSL_RETURN_BAD_DATA_LENGTH_S, + /*! Authentication / Integrity Check code check failed. */ + FSL_RETURN_AUTH_FAILED_S, + /*! A memory error occurred. */ + FSL_RETURN_MEMORY_ERROR_S, + /*! An error internal to the hardware occurred. */ + FSL_RETURN_INTERNAL_ERROR_S, + /*! ECC detected Point at Infinity */ + FSL_RETURN_POINT_AT_INFINITY_S, + /*! ECC detected No Point at Infinity */ + FSL_RETURN_POINT_NOT_AT_INFINITY_S, + /*! GCD is One */ + FSL_RETURN_GCD_IS_ONE_S, + /*! GCD is not One */ + FSL_RETURN_GCD_IS_NOT_ONE_S, + /*! Candidate is Prime */ + FSL_RETURN_PRIME_S, + /*! Candidate is not Prime */ + FSL_RETURN_NOT_PRIME_S, + /*! N register loaded improperly with even value */ + FSL_RETURN_EVEN_MODULUS_S, +} fsl_shw_return_t; + +/*! + * Algorithm Identifier. + * + * Selection of algorithm will determine how large the block size of the + * algorithm is. Context size is the same length unless otherwise specified. + * Selection of algorithm also affects the allowable key length. + */ +typedef enum fsl_shw_key_alg_t { + /*! + * Key will be used to perform an HMAC. Key size is 1 to 64 octets. Block + * size is 64 octets. + */ + FSL_KEY_ALG_HMAC, + /*! + * Advanced Encryption Standard (Rijndael). Block size is 16 octets. Key + * size is 16 octets. (The single choice of key size is a Sahara platform + * limitation.) + */ + FSL_KEY_ALG_AES, + /*! + * Data Encryption Standard. Block size is 8 octets. Key size is 8 + * octets. + */ + FSL_KEY_ALG_DES, + /*! + * 2- or 3-key Triple DES. Block size is 8 octets. Key size is 16 octets + * for 2-key Triple DES, and 24 octets for 3-key. + */ + FSL_KEY_ALG_TDES, + /*! + * ARC4. No block size. Context size is 259 octets. Allowed key size is + * 1-16 octets. (The choices for key size are a Sahara platform + * limitation.) + */ + FSL_KEY_ALG_ARC4, +} fsl_shw_key_alg_t; + +/*! + * Mode selector for Symmetric Ciphers. + * + * The selection of mode determines how a cryptographic algorithm will be + * used to process the plaintext or ciphertext. + * + * For all modes which are run block-by-block (that is, all but + * #FSL_SYM_MODE_STREAM), any partial operations must be performed on a text + * length which is multiple of the block size. Except for #FSL_SYM_MODE_CTR, + * these block-by-block algorithms must also be passed a total number of octets + * which is a multiple of the block size. + * + * In modes which require that the total number of octets of data be a multiple + * of the block size (#FSL_SYM_MODE_ECB and #FSL_SYM_MODE_CBC), and the user + * has a total number of octets which are not a multiple of the block size, the + * user must perform any necessary padding to get to the correct data length. + */ +typedef enum fsl_shw_sym_mode_t { + /*! + * Stream. There is no associated block size. Any request to process data + * may be of any length. This mode is only for ARC4 operations, and is + * also the only mode used for ARC4. + */ + FSL_SYM_MODE_STREAM, + + /*! + * Electronic Codebook. Each block of data is encrypted/decrypted. The + * length of the data stream must be a multiple of the block size. This + * mode may be used for DES, 3DES, and AES. The block size is determined + * by the algorithm. + */ + FSL_SYM_MODE_ECB, + /*! + * Cipher-Block Chaining. Each block of data is encrypted/decrypted and + * then "chained" with the previous block by an XOR function. Requires + * context to start the XOR (previous block). This mode may be used for + * DES, 3DES, and AES. The block size is determined by the algorithm. + */ + FSL_SYM_MODE_CBC, + /*! + * Counter. The counter is encrypted, then XORed with a block of data. + * The counter is then incremented (using modulus arithmetic) for the next + * block. The final operation may be non-multiple of block size. This mode + * may be used for AES. The block size is determined by the algorithm. + */ + FSL_SYM_MODE_CTR, +} fsl_shw_sym_mode_t; + +/*! + * Algorithm selector for Cryptographic Hash functions. + * + * Selection of algorithm determines how large the context and digest will be. + * Context is the same size as the digest (resulting hash), unless otherwise + * specified. + */ +typedef enum fsl_shw_hash_alg { + /*! MD5 algorithm. Digest is 16 octets. */ + FSL_HASH_ALG_MD5, + /*! SHA-1 (aka SHA or SHA-160) algorithm. Digest is 20 octets. */ + FSL_HASH_ALG_SHA1, + /*! + * SHA-224 algorithm. Digest is 28 octets, though context is 32 octets. + */ + FSL_HASH_ALG_SHA224, + /*! SHA-256 algorithm. Digest is 32 octets. */ + FSL_HASH_ALG_SHA256 +} fsl_shw_hash_alg_t; + +/*! + * The type of Authentication-Cipher function which will be performed. + */ +typedef enum fsl_shw_acc_mode_t { + /*! + * CBC-MAC for Counter. Requires context and modulus. Final operation may + * be non-multiple of block size. This mode may be used for AES. + */ + FSL_ACC_MODE_CCM, + /*! + * SSL mode. Not supported. Combines HMAC and encrypt (or decrypt). + * Needs one key object for encryption, another for the HMAC. The usual + * hashing and symmetric encryption algorithms are supported. + */ + FSL_ACC_MODE_SSL, +} fsl_shw_acc_mode_t; + +/* REQ-S2LRD-PINTFC-COA-HCO-001 */ +/*! + * Flags which control a Hash operation. + */ +typedef enum fsl_shw_hash_ctx_flags { + /*! + * Context is empty. Hash is started from scratch, with a + * message-processed count of zero. + */ + FSL_HASH_FLAGS_INIT = 1, + /*! + * Retrieve context from hardware after hashing. If used with the + * #FSL_HASH_FLAGS_FINALIZE flag, the final digest value will be saved in + * the object. + */ + FSL_HASH_FLAGS_SAVE = 2, + /*! Place context into hardware before hashing. */ + FSL_HASH_FLAGS_LOAD = 4, + /*! + * PAD message and perform final digest operation. If user message is + * pre-padded, this flag should not be used. + */ + FSL_HASH_FLAGS_FINALIZE = 8, +} fsl_shw_hash_ctx_flags_t; + +/*! + * Flags which control an HMAC operation. + * + * These may be combined by ORing them together. See #fsl_shw_hmco_set_flags() + * and #fsl_shw_hmco_clear_flags(). + */ +typedef enum fsl_shw_hmac_ctx_flags_t { + /*! + * Message context is empty. HMAC is started from scratch (with key) or + * from precompute of inner hash, depending on whether + * #FSL_HMAC_FLAGS_PRECOMPUTES_PRESENT is set. + */ + FSL_HMAC_FLAGS_INIT = 1, + /*! + * Retrieve ongoing context from hardware after hashing. If used with the + * #FSL_HMAC_FLAGS_FINALIZE flag, the final digest value (HMAC) will be + * saved in the object. + */ + FSL_HMAC_FLAGS_SAVE = 2, + /*! Place ongoing context into hardware before hashing. */ + FSL_HMAC_FLAGS_LOAD = 4, + /*! + * PAD message and perform final HMAC operations of inner and outer + * hashes. + */ + FSL_HMAC_FLAGS_FINALIZE = 8, + /*! + * This means that the context contains precomputed inner and outer hash + * values. + */ + FSL_HMAC_FLAGS_PRECOMPUTES_PRESENT = 16, +} fsl_shw_hmac_ctx_flags_t; + +/*! + * Flags to control use of the #fsl_shw_scco_t. + * + * These may be ORed together to get the desired effect. + * See #fsl_shw_scco_set_flags() and #fsl_shw_scco_clear_flags() + */ +typedef enum fsl_shw_sym_ctx_flags_t { + /*! + * Context is empty. In ARC4, this means that the S-Box needs to be + * generated from the key. In #FSL_SYM_MODE_CBC mode, this allows an IV of + * zero to be specified. In #FSL_SYM_MODE_CTR mode, it means that an + * initial CTR value of zero is desired. + */ + FSL_SYM_CTX_INIT = 1, + /*! + * Load context from object into hardware before running cipher. In + * #FSL_SYM_MODE_CTR mode, this would refer to the Counter Value. + */ + FSL_SYM_CTX_LOAD = 2, + /*! + * Save context from hardware into object after running cipher. In + * #FSL_SYM_MODE_CTR mode, this would refer to the Counter Value. + */ + FSL_SYM_CTX_SAVE = 4, + /*! + * Context (SBox) is to be unwrapped and wrapped on each use. + * This flag is unsupported. + * */ + FSL_SYM_CTX_PROTECT = 8, +} fsl_shw_sym_ctx_flags_t; + +/*! + * Flags which describe the state of the #fsl_shw_sko_t. + * + * These may be ORed together to get the desired effect. + * See #fsl_shw_sko_set_flags() and #fsl_shw_sko_clear_flags() + */ +typedef enum fsl_shw_key_flags_t { + /*! If algorithm is DES or 3DES, do not validate the key parity bits. */ + FSL_SKO_KEY_IGNORE_PARITY = 1, + /*! Clear key is present in the object. */ + FSL_SKO_KEY_PRESENT = 2, + /*! + * Key has been established for use. This feature is not available for all + * platforms, nor for all algorithms and modes. + */ + FSL_SKO_KEY_ESTABLISHED = 4, +} fsl_shw_key_flags_t; + +/*! + * Type of value which is associated with an established key. + */ +typedef uint64_t key_userid_t; + +/*! + * Flags which describe the state of the #fsl_shw_acco_t. + * + * The @a FSL_ACCO_CTX_INIT and @a FSL_ACCO_CTX_FINALIZE flags, when used + * together, provide for a one-shot operation. + */ +typedef enum fsl_shw_auth_ctx_flags_t { + /*! Initialize Context(s) */ + FSL_ACCO_CTX_INIT = 1, + /*! Load intermediate context(s). This flag is unsupported. */ + FSL_ACCO_CTX_LOAD = 2, + /*! Save intermediate context(s). This flag is unsupported. */ + FSL_ACCO_CTX_SAVE = 4, + /*! Create MAC during this operation. */ + FSL_ACCO_CTX_FINALIZE = 8, + /*! + * Formatting of CCM input data is performed by calls to + * #fsl_shw_ccm_nist_format_ctr_and_iv() and + * #fsl_shw_ccm_nist_update_ctr_and_iv(). + */ + FSL_ACCO_NIST_CCM = 0x10, +} fsl_shw_auth_ctx_flags_t; + +/*! + * The operation which controls the behavior of #fsl_shw_establish_key(). + * + * These values are passed to #fsl_shw_establish_key(). + */ +typedef enum fsl_shw_key_wrap_t { + /*! Generate a key from random values. */ + FSL_KEY_WRAP_CREATE, + /*! Use the provided clear key. */ + FSL_KEY_WRAP_ACCEPT, + /*! Unwrap a previously wrapped key. */ + FSL_KEY_WRAP_UNWRAP +} fsl_shw_key_wrap_t; + +/*! + * Modulus Selector for CTR modes. + * + * The incrementing of the Counter value may be modified by a modulus. If no + * modulus is needed or desired for AES, use #FSL_CTR_MOD_128. + */ +typedef enum fsl_shw_ctr_mod { + FSL_CTR_MOD_8, /*!< Run counter with modulus of 2^8. */ + FSL_CTR_MOD_16, /*!< Run counter with modulus of 2^16. */ + FSL_CTR_MOD_24, /*!< Run counter with modulus of 2^24. */ + FSL_CTR_MOD_32, /*!< Run counter with modulus of 2^32. */ + FSL_CTR_MOD_40, /*!< Run counter with modulus of 2^40. */ + FSL_CTR_MOD_48, /*!< Run counter with modulus of 2^48. */ + FSL_CTR_MOD_56, /*!< Run counter with modulus of 2^56. */ + FSL_CTR_MOD_64, /*!< Run counter with modulus of 2^64. */ + FSL_CTR_MOD_72, /*!< Run counter with modulus of 2^72. */ + FSL_CTR_MOD_80, /*!< Run counter with modulus of 2^80. */ + FSL_CTR_MOD_88, /*!< Run counter with modulus of 2^88. */ + FSL_CTR_MOD_96, /*!< Run counter with modulus of 2^96. */ + FSL_CTR_MOD_104, /*!< Run counter with modulus of 2^104. */ + FSL_CTR_MOD_112, /*!< Run counter with modulus of 2^112. */ + FSL_CTR_MOD_120, /*!< Run counter with modulus of 2^120. */ + FSL_CTR_MOD_128 /*!< Run counter with modulus of 2^128. */ +} fsl_shw_ctr_mod_t; + +/****************************************************************************** + * Data Structures + *****************************************************************************/ + +/*! + * + * @brief Structure type for descriptors + * + * The first five fields are passed to the hardware. + * + *****************************************************************************/ +#ifndef USE_NEW_PTRS /* Experimental */ + +typedef struct sah_Desc { + uint32_t header; /*!< descriptor header value */ + uint32_t len1; /*!< number of data bytes in 'ptr1' buffer */ + void *ptr1; /*!< pointer to first sah_Link structure */ + uint32_t len2; /*!< number of data bytes in 'ptr2' buffer */ + void *ptr2; /*!< pointer to second sah_Link structure */ + struct sah_Desc *next; /*!< pointer to next descriptor */ +#ifdef __KERNEL__ /* This needs a better test */ + /* These two must be last. See sah_Copy_Descriptors */ + struct sah_Desc *virt_addr; /*!< Virtual (kernel) address of this + descriptor. */ + dma_addr_t dma_addr; /*!< Physical (bus) address of this + descriptor. */ + void *original_ptr1; /*!< user's pointer to ptr1 */ + void *original_ptr2; /*!< user's pointer to ptr2 */ + struct sah_Desc *original_next; /*!< user's pointer to next */ +#endif +} sah_Desc; + +#else + +typedef struct sah_Desc { + uint32_t header; /*!< descriptor header value */ + uint32_t len1; /*!< number of data bytes in 'ptr1' buffer */ + uint32_t hw_ptr1; /*!< pointer to first sah_Link structure */ + uint32_t len2; /*!< number of data bytes in 'ptr2' buffer */ + uint32_t hw_ptr2; /*!< pointer to second sah_Link structure */ + uint32_t hw_next; /*!< pointer to next descriptor */ + struct sah_Link *ptr1; /*!< (virtual) pointer to first sah_Link structure */ + struct sah_Link *ptr2; /*!< (virtual) pointer to first sah_Link structure */ + struct sah_Desc *next; /*!< (virtual) pointer to next descriptor */ +#ifdef __KERNEL__ /* This needs a better test */ + /* These two must be last. See sah_Copy_Descriptors */ + struct sah_Desc *virt_addr; /*!< Virtual (kernel) address of this + descriptor. */ + dma_addr_t dma_addr; /*!< Physical (bus) address of this + descriptor. */ +#endif +} sah_Desc; + +#endif + +/*! +******************************************************************************* +* @brief The first descriptor in a chain +******************************************************************************/ +typedef struct sah_Head_Desc { + sah_Desc desc; /*!< whole struct - must be first */ + struct fsl_shw_uco_t *user_info; /*!< where result pool lives */ + uint32_t user_ref; /*!< at time of request */ + uint32_t uco_flags; /*!< at time of request */ + uint32_t status; /*!< Status of queue entry */ + uint32_t error_status; /*!< If error, register from Sahara */ + uint32_t fault_address; /*!< If error, register from Sahara */ + uint32_t current_dar; /*!< If error, register from Sahara */ + fsl_shw_return_t result; /*!< Result of running descriptor */ + struct sah_Head_Desc *next; /*!< Next in queue */ + struct sah_Head_Desc *prev; /*!< previous in queue */ + struct sah_Head_Desc *user_desc; /*!< For API async get_results */ + void *out1_ptr; /*!< For async post-processing */ + void *out2_ptr; /*!< For async post-processing */ + uint32_t out_len; /*!< For async post-processing */ +} sah_Head_Desc; + +/*! + * @brief Structure type for links + * + * The first three fields are used by hardware. + *****************************************************************************/ +#ifndef USE_NEW_PTRS + +typedef struct sah_Link { + size_t len; /*!< len of 'data' buffer in bytes */ + uint8_t *data; /*!< buffer to store data */ + struct sah_Link *next; /*!< pointer to the next sah_Link storing + * data */ + sah_Link_Flags flags; /*!< indicates the component that created the + * data buffer. Security Function internal + * information */ + key_userid_t ownerid; /*!< Auth code for established key */ + uint32_t slot; /*!< Location of the the established key */ +#ifdef __KERNEL__ /* This needs a better test */ + /* These two elements must be last. See sah_Copy_Links() */ + struct sah_Link *virt_addr; + dma_addr_t dma_addr; +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)) + struct page *vm_info; +#endif + uint8_t *original_data; /*!< user's version of data pointer */ + struct sah_Link *original_next; /*!< user's version of next pointer */ +#ifdef SAH_COPY_DATA + uint8_t *copy_data; /*!< Virtual address of acquired buffer */ +#endif +#endif /* kernel-only */ +} sah_Link; + +#else + +typedef struct sah_Link { + /*! len of 'data' buffer in bytes */ + size_t len; + /*! buffer to store data */ + uint32_t hw_data; + /*! Physical address */ + uint32_t hw_next; + /*! + * indicates the component that created the data buffer. Security Function + * internal information + */ + sah_Link_Flags flags; + /*! (virtual) pointer to data */ + uint8_t *data; + /*! (virtual) pointer to the next sah_Link storing data */ + struct sah_Link *next; + /*! Auth code for established key */ + key_userid_t ownerid; + /*! Location of the the established key */ + uint32_t slot; +#ifdef __KERNEL__ /* This needs a better test */ + /* These two elements must be last. See sah_Copy_Links() */ + struct sah_Link *virt_addr; + dma_addr_t dma_addr; +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)) + struct page *vm_info; +#endif +#endif /* kernel-only */ +} sah_Link; + +#endif + +/*! + * Initialization Object + */ +typedef struct fsl_sho_ibo { +} fsl_sho_ibo_t; + +/* Imported from Sahara1 driver -- is it needed forever? */ +/*! +******************************************************************************* +* FIELDS +* +* void * ref - parameter to be passed into the memory function calls +* +* void * (*malloc)(void *ref, size_t n) - pointer to user's malloc function +* +* void (*free)(void *ref, void *ptr) - pointer to user's free function +* +* void * (*memcpy)(void *ref, void *dest, const void *src, size_t n) - +* pointer to user's memcpy function +* +* void * (*memset)(void *ref, void *ptr, int ch, size_t n) - pointer to +* user's memset function +* +* @brief Structure for API memory utilities +******************************************************************************/ +typedef struct sah_Mem_Util { + /*! Who knows. Vestigial. */ + void *mu_ref; + /*! Acquire buffer of size n bytes */ + void *(*mu_malloc) (void *ref, size_t n); + /*! Acquire a sah_Head_Desc */ + sah_Head_Desc *(*mu_alloc_head_desc) (void *ref); + /* Acquire a sah_Desc */ + sah_Desc *(*mu_alloc_desc) (void *ref); + /* Acquire a sah_Link */ + sah_Link *(*mu_alloc_link) (void *ref); + /*! Free buffer at ptr */ + void (*mu_free) (void *ref, void *ptr); + /*! Free sah_Head_Desc at ptr */ + void (*mu_free_head_desc) (void *ref, sah_Head_Desc * ptr); + /*! Free sah_Desc at ptr */ + void (*mu_free_desc) (void *ref, sah_Desc * ptr); + /*! Free sah_Link at ptr */ + void (*mu_free_link) (void *ref, sah_Link * ptr); + /*! Funciton which will copy n bytes from src to dest */ + void *(*mu_memcpy) (void *ref, void *dest, const void *src, size_t n); + /*! Set all n bytes of ptr to ch */ + void *(*mu_memset) (void *ref, void *ptr, int ch, size_t n); +} sah_Mem_Util; + +/* REQ-S2LRD-PINTFC-COA-UCO-001 */ +/*! + * User Context Object + */ +typedef struct fsl_shw_uco_t { + int sahara_openfd; /*!< this should be kernel-only?? */ + sah_Mem_Util *mem_util; /*!< Memory utility fns */ + uint32_t user_ref; /*!< User's reference */ + void (*callback) (struct fsl_shw_uco_t * uco); /*!< User's callback fn */ + uint32_t flags; /*!< from fsl_shw_user_ctx_flags_t */ + unsigned pool_size; /*!< maximum size of user pool */ +#ifdef __KERNEL__ + sah_Queue result_pool; /*!< where non-blocking results go */ + os_process_handle_t process; /*!< remember for signalling User mode */ +#else + struct fsl_shw_uco_t *next; /*!< To allow user-mode chaining of contexts, + for signalling. */ +#endif +} fsl_shw_uco_t; + +/* REQ-S2LRD-PINTFC-API-GEN-006 ?? */ +/*! + * Result object + */ +typedef struct fsl_shw_result_t { + uint32_t user_ref; + fsl_shw_return_t code; + uint32_t detail1; + uint32_t detail2; + sah_Head_Desc *user_desc; +} fsl_shw_result_t; + +/* REQ-S2LRD-PINTFC-COA-SKO-001 */ +/*! + * Secret Key Context Object + */ +typedef struct fsl_shw_sko_t { + uint32_t flags; + fsl_shw_key_alg_t algorithm; + key_userid_t userid; + uint32_t handle; + uint16_t key_length; + uint8_t key[64]; +} fsl_shw_sko_t; + +/* REQ-S2LRD-PINTFC-COA-CO-001 */ +/*! + * @brief Platform Capability Object + */ +typedef struct fsl_shw_pco_t { /* Consider turning these constants into symbols */ + int api_major; + int api_minor; + int driver_major; + int driver_minor; + fsl_shw_key_alg_t sym_algorithms[4]; + fsl_shw_sym_mode_t sym_modes[4]; + fsl_shw_hash_alg_t hash_algorithms[4]; + uint8_t sym_support[5][4]; /* indexed by key alg then mode */ +} fsl_shw_pco_t; + +/* REQ-S2LRD-PINTFC-COA-HCO-001 */ +/*! + * Hash Context Object + */ +typedef struct fsl_shw_hco_t { /* fsl_shw_hash_context_object */ + fsl_shw_hash_alg_t algorithm; + uint32_t flags; + uint8_t digest_length; /* in bytes */ + uint8_t context_length; /* in bytes */ + uint8_t context_register_length; /* in bytes */ + uint32_t context[9]; /* largest digest + msg size */ +} fsl_shw_hco_t; + +/*! + * HMAC Context Object + */ +typedef struct fsl_shw_hmco_t { /* fsl_shw_hmac_context_object */ + fsl_shw_hash_alg_t algorithm; + uint32_t flags; + uint8_t digest_length; /*!< in bytes */ + uint8_t context_length; /*!< in bytes */ + uint8_t context_register_length; /*!< in bytes */ + uint32_t ongoing_context[9]; /*!< largest digest + msg + size */ + uint32_t inner_precompute[9]; /*!< largest digest + msg + size */ + uint32_t outer_precompute[9]; /*!< largest digest + msg + size */ +} fsl_shw_hmco_t; + +/* REQ-S2LRD-PINTFC-COA-SCCO-001 */ +/*! + * Symmetric Crypto Context Object Context Object + */ +typedef struct fsl_shw_scco_t { + uint32_t flags; + unsigned block_size_bytes; /* double duty block&ctx size */ + fsl_shw_sym_mode_t mode; + /* Could put modulus plus 16-octet context in union with arc4 + sbox+ptrs... */ + fsl_shw_ctr_mod_t modulus_exp; + uint8_t context[259]; +} fsl_shw_scco_t; + +/*! + * Authenticate-Cipher Context Object + + * An object for controlling the function of, and holding information about, + * data for the authenticate-cipher functions, #fsl_shw_gen_encrypt() and + * #fsl_shw_auth_decrypt(). + */ +typedef struct fsl_shw_acco_t { + uint32_t flags; /*!< See #fsl_shw_auth_ctx_flags_t for + meanings */ + fsl_shw_acc_mode_t mode; /*!< CCM only */ + uint8_t mac_length; /*!< User's value for length */ + unsigned q_length; /*!< NIST parameter - */ + fsl_shw_scco_t cipher_ctx_info; /*!< For running + encrypt/decrypt. */ + union { + fsl_shw_scco_t CCM_ctx_info; /*!< For running the CBC in + AES-CCM. */ + fsl_shw_hco_t hash_ctx_info; /*!< For running the hash */ + } auth_info; /*!< "auth" info struct */ + uint8_t unencrypted_mac[16]; /*!< max block size... */ +} fsl_shw_acco_t; + +typedef struct fsl_shw_dsa_cmn_t { + unsigned p_size; /*!< byte count of P (prime modulus) and G (generator) */ + unsigned q_size; /*!< byte count of Q (divisor) */ + uint8_t data[]; /*!< P, Q, G */ +} fsl_shw_dsa_cmn_t; + +/*! Get size in bytes of P field from pointer to struct fsl_shw_dsa_cmn_t */ +#define DSA_CMN_GET_P_SIZE(parm) \ +({ \ + struct fsl_shw_dsa_cmn_t* parmp = (parm); \ + \ + parmp->p_size; \ +}) + +/*! Get pointer to P field from pointer to struct fsl_shw_dsa_cmn_t */ +#define DSA_CMN_GET_P(parm) \ +({ \ + struct fsl_shw_dsa_cmn_t* parmp = (parm); \ + \ + parmp->data; \ +}) + +/*! Get size in bytes of Q field from pointer to struct fsl_shw_dsa_cmn_t */ +#define DSA_CMN_GET_Q_SIZE(parm) \ +({ \ + struct fsl_shw_dsa_cmn_t* parmp = (parm); \ + \ + parmp->q_size; \ +}) + +/*! Get pointer to Q field from pointer to struct fsl_shw_dsa_cmn_t */ +#define DSA_CMN_GET_Q(parm) \ +({ \ + struct fsl_shw_dsa_cmn_t* parmp = (parm); \ + \ + parmp->data + parmp->p_size; \ +}) + +/*! Get size in bytes of G field from pointer to struct fsl_shw_dsa_cmn_t */ +#define DSA_CMN_GET_G_SIZE(parm) \ +({ \ + struct fsl_shw_dsa_cmn_t* parmp = (parm); \ + \ + parmp->p_size; \ +}) + +/*! Get pointer to G field from pointer to struct fsl_shw_dsa_cmn_t */ +#define DSA_CMN_GET_G(parm) \ +({ \ + struct fsl_shw_dsa_cmn_t* parmp = (parm); \ + \ + parmp->data + parmp->p_size + parmp->q_size; \ +}) + +typedef struct fsl_shw_dsa_pub_t { + unsigned y_size; /*!< byte count of y field */ + uint8_t data[]; /*!< y */ +} fsl_shw_dsa_pub_t; + +/*! Get size in bytes of Y field from pointer to struct fsl_shw_dsa_pub_t */ +#define DSA_PUB_GET_Y_SIZE(parm) \ +({ \ + struct fsl_shw_dsa_pub_t* parmp = (parm); \ + \ + parmp->y_size; \ +}) + +/*! Get pointer to P field from pointer to struct fsl_shw_dsa_pub_t */ +#define DSA_PUB_GET_Y(parm) \ +({ \ + struct fsl_shw_dsa_pub_t* parmp = (parm); \ + \ + parmp->data; \ +}) + +typedef struct fsl_shw_curve_fp_t { + unsigned p_size; /*!< byte count of mod */ + unsigned r_size; /*!< byte count of r */ + unsigned a_size; /*!< byte count of a */ + unsigned b_size; /*!< byte count of b */ + unsigned c_size; /*!< byte count of c */ + unsigned Gx_size; /*!< byte count of Gx */ + unsigned Gy_size; /*!< byte count of Gy */ + uint8_t data[]; /*!< p, r, a, b , Gx, Gy, (k),(SEED, c) */ +} fsl_shw_curve_fp_t; + +/*! Get size in bytes of P field from pointer to struct fsl_shw_curve_fp_t */ +#define FP_CRV_GET_P_SIZE(parm) \ +({ \ + struct fsl_shw_curve_fp_t* parmp = (parm); \ + \ + parmp->p_size; \ +}) + +/*! Get pointer to P field from pointer to struct fsl_shw_curve_fp_t */ +#define FP_CRV_GET_P(parm) \ +({ \ + struct fsl_shw_curve_fp_t* parmp = (parm); \ + \ + parmp->data; \ +}) + +/*! Get size in bytes of R field from pointer to struct fsl_shw_curve_fp_t */ +#define FP_CRV_GET_R_SIZE(parm) \ +({ \ + struct fsl_shw_curve_fp_t* parmp = (parm); \ + \ + parmp->r_size; \ +}) + +/*! Get pointer to R field from pointer to struct fsl_shw_curve_fp_t */ +#define FP_CRV_GET_R(parm) \ +({ \ + struct fsl_shw_curve_fp_t* parmp = (parm); \ + \ + parmp->data + parmp->p_size; \ +}) + +/*! Get size in bytes of A field from pointer to struct fsl_shw_curve_fp_t */ +#define FP_CRV_GET_A_SIZE(parm) \ +({ \ + struct fsl_shw_curve_fp_t* parmp = (parm); \ + \ + parmp->a_size; \ +}) + +/*! Get pointer to A field from pointer to struct fsl_shw_curve_fp_t */ +#define FP_CRV_GET_A(parm) \ +({ \ + struct fsl_shw_curve_fp_t* parmp = (parm); \ + \ + parmp->data + parmp->p_size + parmp->r_size; \ +}) + +/*! Get size in bytes of B field from pointer to struct fsl_shw_curve_fp_t */ +#define FP_CRV_GET_B_SIZE(parm) \ +({ \ + struct fsl_shw_curve_fp_t* parmp = (parm); \ + \ + parmp->b_size; \ +}) + +/*! Get pointer to B field from pointer to struct fsl_shw_curve_fp_t */ +#define FP_CRV_GET_B(parm) \ +({ \ + struct fsl_shw_curve_fp_t* parmp = (parm); \ + \ + parmp->data + parmp->p_size + parmp->r_size + parmp->a_size; \ +}) + +/*! Get size in bytes of Gx field from pointer to struct fsl_shw_curve_fp_t */ +#define FP_CRV_GET_GX_SIZE(parm) \ +({ \ + struct fsl_shw_curve_fp_t* parmp = (parm); \ + \ + parmp->Gx_size; \ +}) + +/*! Get pointer to Gx field from pointer to struct fsl_shw_curve_fp_t */ +#define FP_CRV_GET_GX(parm) \ +({ \ + struct fsl_shw_curve_fp_t* parmp = (parm); \ + \ + parmp->data + parmp->p_size + parmp->r_size + parmp->a_size \ + + parmp->b_size; \ +}) + +/*! Get size in bytes of Gy field from pointer to struct fsl_shw_curve_fp_t */ +#define FP_CRV_GET_GY_SIZE(parm) \ +({ \ + struct fsl_shw_curve_fp_t* parmp = (parm); \ + \ + parmp->Gx_size; \ +}) + +/*! Get pointer to Gy field from pointer to struct fsl_shw_curve_fp_t */ +#define FP_CRV_GET_GY(parm) \ +({ \ + struct fsl_shw_curve_fp_t* parmp = (parm); \ + \ + parmp->data + parmp->p_size + parmp->r_size + parmp->a_size \ + + parmp->b_size + parmp->Gx_size; \ +}) + +typedef struct fsl_shw_curve_f2m_t { + unsigned p_size; /*!< byte count of polynomial */ + unsigned r_size; /*!< byte count of r */ + unsigned a_size; /*!< byte count of a */ + unsigned c_size; /*!< byte count of c */ + unsigned Gx_size; /*!< byte count of Gx */ + unsigned Gy_size; /*!< byte count of Gy */ + unsigned b_size; /*!< byte count of b */ + uint8_t data[]; /* poly, r, a, b, Gx, Gy, b */ +} fsl_shw_curve_f2m_t; + +/*! Get size in bytes of P field from pointer to struct fsl_shw_curve_f2m_t */ +#define F2M_CRV_GET_P_SIZE(parm) \ +({ \ + struct fsl_shw_curve_f2m_t* parmp = (parm); \ + \ + parmp->p_size; \ +}) + +/*! Get pointer to P field from pointer to struct fsl_shw_curve_f2m_t */ +#define F2M_CRV_GET_P(parm) \ +({ \ + struct fsl_shw_curve_f2m_t* parmp = (parm); \ + \ + parmp->data; \ +}) + +/*! Get size in bytes of R field from pointer to struct fsl_shw_curve_f2m_t */ +#define F2M_CRV_GET_R_SIZE(parm) \ +({ \ + struct fsl_shw_curve_f2m_t* parmp = (parm); \ + \ + parmp->r_size; \ +}) + +/*! Get pointer to R field from pointer to struct fsl_shw_curve_f2m_t */ +#define F2M_CRV_GET_R(parm) \ +({ \ + struct fsl_shw_curve_f2m_t* parmp = (parm); \ + \ + parmp->data + parmp->p_size; \ +}) + +/*! Get size in bytes of A field from pointer to struct fsl_shw_curve_f2m_t */ +#define F2M_CRV_GET_A_SIZE(parm) \ +({ \ + struct fsl_shw_curve_f2m_t* parmp = (parm); \ + \ + parmp->a_size; \ +}) + +/*! Get pointer to A field from pointer to struct fsl_shw_curve_f2m_t */ +#define F2M_CRV_GET_A(parm) \ +({ \ + struct fsl_shw_curve_f2m_t* parmp = (parm); \ + \ + parmp->data + parmp->p_size + parmp->r_size; \ +}) + +/*! Get size in bytes of C field from pointer to struct fsl_shw_curve_f2m_t */ +#define F2M_CRV_GET_C_SIZE(parm) \ +({ \ + struct fsl_shw_curve_f2m_t* parmp = (parm); \ + \ + parmp->c_size; \ +}) + +/*! Get pointer to C field from pointer to struct fsl_shw_curve_f2m_t */ +#define F2M_CRV_GET_C(parm) \ +({ \ + struct fsl_shw_curve_f2m_t* parmp = (parm); \ + \ + parmp->data + parmp->p_size + parmp->r_size + parmp->a_size; \ +}) + +/*! Get size in bytes of Gx field from pointer to struct fsl_shw_curve_f2m_t */ +#define F2M_CRV_GET_GX_SIZE(parm) \ +({ \ + struct fsl_shw_curve_f2m_t* parmp = (parm); \ + \ + parmp->Gx_size; \ +}) + +/*! Get pointer to Gx field from pointer to struct fsl_shw_curve_f2m_t */ +#define F2M_CRV_GET_GX(parm) \ +({ \ + struct fsl_shw_curve_f2m_t* parmp = (parm); \ + \ + parmp->data + parmp->p_size + parmp->r_size + parmp->a_size \ + + parmp->c_size; \ +}) + +/*! Get size in bytes of Gy field from pointer to struct fsl_shw_curve_f2m_t */ +#define F2M_CRV_GET_GY_SIZE(parm) \ +({ \ + struct fsl_shw_curve_f2m_t* parmp = (parm); \ + \ + parmp->Gy_size; \ +}) + +/*! Get pointer to Gy field from pointer to struct fsl_shw_curve_f2m_t */ +#define F2M_CRV_GET_GY(parm) \ +({ \ + struct fsl_shw_curve_f2m_t* parmp = (parm); \ + \ + parmp->data + parmp->p_size + parmp->r_size + parmp->a_size \ + + parmp->c_size + parmp->Gx_size; \ +}) + +/*! Get size in bytes of b field from pointer to struct fsl_shw_curve_f2m_t */ +#define F2M_CRV_GET_B_SIZE(parm) \ +({ \ + struct fsl_shw_curve_f2m_t* parmp = (parm); \ + \ + parmp->b_size; \ +}) + +/*! Get pointer to b field from pointer to struct fsl_shw_curve_f2m_t */ +#define F2M_CRV_GET_B(parm) \ +({ \ + struct fsl_shw_curve_f2m_t* parmp = (parm); \ + \ + parmp->data + parmp->p_size + parmp->r_size + parmp->a_size \ + + parmp->c_size + parmp->Gx_size + parmp->Gy_size; \ +}) + +typedef struct fsl_shw_ecc_point_t { + unsigned x_size; + unsigned y_size; + uint8_t data[]; /* x, y */ +} fsl_shw_ecc_point_t; + +/*! Get pointer to x field from pointer to struct fsl_shw_ecc_point_t */ +#define POINT_GET_X(parm) \ +({ \ + struct fsl_shw_ecc_point_t* parmp = (parm); \ + \ + parmp->data; \ +}) + +/*! Get pointer to x field from pointer to struct fsl_shw_ecc_point_t */ +#define POINT_GET_X_SIZE(parm) \ +({ \ + struct fsl_shw_ecc_point_t* parmp = (parm); \ + \ + parmp->x_size; \ +}) + +/*! Get pointer to y field from pointer to struct fsl_shw_ecc_point_t */ +#define POINT_GET_Y(parm) \ +({ \ + struct fsl_shw_ecc_point_t* parmp = (parm); \ + \ + parmp->data + parmp->y_size; \ +}) + +/*! Get pointer to x field from pointer to struct fsl_shw_ecc_point_t */ +#define POINT_GET_Y_SIZE(parm) \ +({ \ + struct fsl_shw_ecc_point_t* parmp = (parm); \ + \ + parmp->y_size; \ +}) + +/*! + * Used by Sahara API to retrieve completed non-blocking results. + */ +typedef struct sah_results { + unsigned requested; /*!< number of results requested */ + unsigned *actual; /*!< number of results obtained */ + fsl_shw_result_t *results; /*!< pointer to memory to hold results */ +} sah_results; + +/****************************************************************************** + * Access Macros for Objects + *****************************************************************************/ +/*! + * Get FSL SHW API version + * + * @param pcobject The Platform Capababilities Object to query. + * @param[out] pcmajor A pointer to where the major version + * of the API is to be stored. + * @param[out] pcminor A pointer to where the minor version + * of the API is to be stored. + */ +#define fsl_shw_pco_get_version(pcobject, pcmajor, pcminor) \ +{ \ + *(pcmajor) = (pcobject)->api_major; \ + *(pcminor) = (pcobject)->api_minor; \ +} + +/*! + * Get underlying driver version. + * + * @param pcobject The Platform Capababilities Object to query. + * @param[out] pcmajor A pointer to where the major version + * of the driver is to be stored. + * @param[out] pcminor A pointer to where the minor version + * of the driver is to be stored. + */ +#define fsl_shw_pco_get_driver_version(pcobject, pcmajor, pcminor) \ +{ \ + *(pcmajor) = (pcobject)->driver_major; \ + *(pcminor) = (pcobject)->driver_minor; \ +} + +/*! + * Get list of symmetric algorithms supported. + * + * @param pcobject The Platform Capababilities Object to query. + * @param[out] pcalgorithms A pointer to where to store the location of + * the list of algorithms. + * @param[out] pcacount A pointer to where to store the number of + * algorithms in the list at @a algorithms. + */ +#define fsl_shw_pco_get_sym_algorithms(pcobject, pcalgorithms, pcacount) \ +{ \ + *(pcalgorithms) = (pcobject)->sym_algorithms; \ + *(pcacount) = sizeof((pcobject)->sym_algorithms)/4; \ +} + +/*! + * Get list of symmetric modes supported. + * + * @param pcobject The Platform Capababilities Object to query. + * @param[out] gsmodes A pointer to where to store the location of + * the list of modes. + * @param[out] gsacount A pointer to where to store the number of + * algorithms in the list at @a modes. + */ +#define fsl_shw_pco_get_sym_modes(pcobject, gsmodes, gsacount) \ +{ \ + *(gsmodes) = (pcobject)->sym_modes; \ + *(gsacount) = sizeof((pcobject)->sym_modes)/4; \ +} + +/*! + * Get list of hash algorithms supported. + * + * @param pcobject The Platform Capababilities Object to query. + * @param[out] gsalgorithms A pointer which will be set to the list of + * algorithms. + * @param[out] gsacount The number of algorithms in the list at @a + * algorithms. + */ +#define fsl_shw_pco_get_hash_algorithms(pcobject, gsalgorithms, gsacount) \ +{ \ + *(gsalgorithms) = (pcobject)->hash_algorithms; \ + *(gsacount) = sizeof((pcobject)->hash_algorithms)/4; \ +} + +/*! + * Determine whether the combination of a given symmetric algorithm and a given + * mode is supported. + * + * @param pcobject The Platform Capababilities Object to query. + * @param pcalg A Symmetric Cipher algorithm. + * @param pcmode A Symmetric Cipher mode. + * + * @return 0 if combination is not supported, non-zero if supported. + */ +#define fsl_shw_pco_check_sym_supported(pcobject, pcalg, pcmode) \ + ((pcobject)->sym_support[pcalg][pcmode]) + +/*! + * Determine whether a given Encryption-Authentication mode is supported. + * + * @param pcobject The Platform Capababilities Object to query. + * @param pcmode The Authentication mode. + * + * @return 0 if mode is not supported, non-zero if supported. + */ +#define fsl_shw_pco_check_auth_supported(pcobject, pcmode) \ + ((pcmode == FSL_ACC_MODE_CCM) ? 1 : 0) + +/*! + * Determine whether Black Keys (key establishment / wrapping) is supported. + * + * @param pcobject The Platform Capababilities Object to query. + * + * @return 0 if wrapping is not supported, non-zero if supported. + */ +#define fsl_shw_pco_check_black_key_supported(pcobject) \ + 1 + +/*! + * Initialize a User Context Object. + * + * This function must be called before performing any other operation with the + * Object. It sets the User Context Object to initial values, and set the size + * of the results pool. The mode will be set to a default of + * #FSL_UCO_BLOCKING_MODE. + * + * When using non-blocking operations, this sets the maximum number of + * operations which can be outstanding. This number includes the counts of + * operations waiting to start, operation(s) being performed, and results which + * have not been retrieved. + * + * Changes to this value are ignored once user registration has completed. It + * should be set to 1 if only blocking operations will ever be performed. + * + * @param ucontext The User Context object to operate on. + * @param usize The maximum number of operations which can be + * outstanding. + */ +#define fsl_shw_uco_init(ucontext, usize) \ +{ \ + (ucontext)->pool_size = usize; \ + (ucontext)->flags = FSL_UCO_BLOCKING_MODE; \ + (ucontext)->sahara_openfd = -1; \ + (ucontext)->mem_util = NULL; \ + (ucontext)->callback = NULL; \ +} + +/*! + * Set the User Reference for the User Context. + * + * @param ucontext The User Context object to operate on. + * @param uref A value which will be passed back with a result. + */ +#define fsl_shw_uco_set_reference(ucontext, uref) \ + (ucontext)->user_ref = uref + +/*! + * Set the User Reference for the User Context. + * + * @param ucontext The User Context object to operate on. + * @param ucallback The function the API will invoke when an operation + * completes. + */ +#define fsl_shw_uco_set_callback(ucontext, ucallback) \ + (ucontext)->callback = ucallback + +/*! + * Set flags in the User Context. + * + * Turns on the flags specified in @a flags. Other flags are untouched. + * + * @param ucontext The User Context object to operate on. + * @param uflags ORed values from #fsl_shw_user_ctx_flags_t. + */ +#define fsl_shw_uco_set_flags(ucontext, uflags) \ + (ucontext)->flags |= (uflags) + +/*! + * Clear flags in the User Context. + * + * Turns off the flags specified in @a flags. Other flags are untouched. + * + * @param ucontext The User Context object to operate on. + * @param uflags ORed values from #fsl_shw_user_ctx_flags_t. + */ +#define fsl_shw_uco_clear_flags(ucontext, uflags) \ + (ucontext)->flags &= ~(uflags) + +/*! + * Retrieve the reference value from a Result Object. + * + * @param robject The result object to query. + * + * @return The reference associated with the request. + */ +#define fsl_shw_ro_get_reference(robject) \ + (robject)->user_ref + +/*! + * Retrieve the status code from a Result Object. + * + * @param robject The result object to query. + * + * @return The status of the request. + */ +#define fsl_shw_ro_get_status(robject) \ + (robject)->code + +/*! + * Initialize a Secret Key Object. + * + * This function must be called before performing any other operation with + * the Object. + * + * @param skobject The Secret Key Object to be initialized. + * @param skalgorithm DES, AES, etc. + * + */ +#define fsl_shw_sko_init(skobject,skalgorithm) \ +{ \ + (skobject)->algorithm = skalgorithm; \ + (skobject)->flags = 0; \ +} + +/*! + * Store a cleartext key in the key object. + * + * This has the side effect of setting the #FSL_SKO_KEY_PRESENT flag and + * resetting the #FSL_SKO_KEY_ESTABLISHED flag. + * + * @param skobject A variable of type #fsl_shw_sko_t. + * @param skkey A pointer to the beginning of the key. + * @param skkeylen The length, in octets, of the key. The value should be + * appropriate to the key size supported by the algorithm. + * 64 octets is the absolute maximum value allowed for this + * call. + */ +#define fsl_shw_sko_set_key(skobject, skkey, skkeylen) \ +{ \ + (skobject)->key_length = skkeylen; \ + copy_bytes((skobject)->key, skkey, skkeylen); \ + (skobject)->flags |= FSL_SKO_KEY_PRESENT; \ + (skobject)->flags &= ~FSL_SKO_KEY_ESTABLISHED; \ +} + +/*! + * Set a size for the key. + * + * This function would normally be used when the user wants the key to be + * generated from a random source. + * + * @param skobject A variable of type #fsl_shw_sko_t. + * @param skkeylen The length, in octets, of the key. The value should be + * appropriate to the key size supported by the algorithm. + * 64 octets is the absolute maximum value allowed for this + * call. + */ +#define fsl_shw_sko_set_key_length(skobject, skkeylen) \ + (skobject)->key_length = skkeylen; + +/*! + * Set the User ID associated with the key. + * + * @param skobject A variable of type #fsl_shw_sko_t. + * @param skuserid The User ID to identify authorized users of the key. + */ +#define fsl_shw_sko_set_user_id(skobject, skuserid) \ + (skobject)->userid = (skuserid) + +/*! + * Set the establish key handle into a key object. + * + * The @a userid field will be used to validate the access to the unwrapped + * key. This feature is not available for all platforms, nor for all + * algorithms and modes. + * + * The #FSL_SKO_KEY_ESTABLISHED will be set (and the #FSL_SKO_KEY_PRESENT flag + * will be cleared). + * + * @param skobject A variable of type #fsl_shw_sko_t. + * @param skuserid The User ID to verify this user is an authorized user of + * the key. + * @param skhandle A @a handle from #fsl_shw_sko_get_established_info. + */ +#define fsl_shw_sko_set_established_info(skobject, skuserid, skhandle) \ +{ \ + (skobject)->userid = (skuserid); \ + (skobject)->handle = (skhandle); \ + (skobject)->flags |= FSL_SKO_KEY_ESTABLISHED; \ + (skobject)->flags &= \ + ~(FSL_SKO_KEY_PRESENT); \ +} + +/*! + * Retrieve the established-key handle from a key object. + * + * @param skobject A variable of type #fsl_shw_sko_t. + * @param skhandle The location to store the @a handle of the unwrapped + * key. + */ +#define fsl_shw_sko_get_established_info(skobject, skhandle) \ + *(skhandle) = (skobject)->handle + +/*! + * Extract the algorithm from a key object. + * + * @param skobject The Key Object to be queried. + * @param[out] skalgorithm A pointer to the location to store the algorithm. + */ +#define fsl_shw_sko_get_algorithm(skobject, skalgorithm) \ + *(skalgorithm) = (skobject)->algorithm + +/*! + * Determine the size of a wrapped key based upon the cleartext key's length. + * + * This function can be used to calculate the number of octets that + * #fsl_shw_extract_key() will write into the location at @a covered_key. + * + * If zero is returned at @a length, this means that the key length in + * @a key_info is not supported. + * + * @param wkeyinfo Information about a key to be wrapped. + * @param wkeylen Location to store the length of a wrapped + * version of the key in @a key_info. + */ +#define fsl_shw_sko_calculate_wrapped_size(wkeyinfo, wkeylen) \ +{ \ + if ((wkeyinfo)->key_length > 32) { \ + *(wkeylen) = 0; \ + } else { \ + *(wkeylen) = 66; \ + } \ +} + +/*! + * Set some flags in the key object. + * + * Turns on the flags specified in @a flags. Other flags are untouched. + * + * @param skobject A variable of type #fsl_shw_sko_t. + * @param skflags (One or more) ORed members of #fsl_shw_key_flags_t which + * are to be set. + */ +#define fsl_shw_sko_set_flags(skobject, skflags) \ + (skobject)->flags |= (skflags) + +/*! + * Clear some flags in the key object. + * + * Turns off the flags specified in @a flags. Other flags are untouched. + * + * @param skobject A variable of type #fsl_shw_sko_t. + * @param skflags (One or more) ORed members of #fsl_shw_key_flags_t + * which are to be reset. + */ +#define fsl_shw_sko_clear_flags(skobject, skflags) \ + (skobject)->flags &= ~(skflags) + +/*! + * Initialize a Hash Context Object. + * + * This function must be called before performing any other operation with the + * Object. It sets the current message length and hash algorithm in the hash + * context object. + * + * @param hcobject The hash context to operate upon. + * @param hcalgorithm The hash algorithm to be used (#FSL_HASH_ALG_MD5, + * #FSL_HASH_ALG_SHA256, etc). + * + */ +#define fsl_shw_hco_init(hcobject, hcalgorithm) \ +{ \ + (hcobject)->algorithm = hcalgorithm; \ + (hcobject)->flags = 0; \ + switch (hcalgorithm) { \ + case FSL_HASH_ALG_MD5: \ + (hcobject)->digest_length = 16; \ + (hcobject)->context_length = 16; \ + (hcobject)->context_register_length = 24; \ + break; \ + case FSL_HASH_ALG_SHA1: \ + (hcobject)->digest_length = 20; \ + (hcobject)->context_length = 20; \ + (hcobject)->context_register_length = 24; \ + break; \ + case FSL_HASH_ALG_SHA224: \ + (hcobject)->digest_length = 28; \ + (hcobject)->context_length = 32; \ + (hcobject)->context_register_length = 36; \ + break; \ + case FSL_HASH_ALG_SHA256: \ + (hcobject)->digest_length = 32; \ + (hcobject)->context_length = 32; \ + (hcobject)->context_register_length = 36; \ + break; \ + default: \ + /* error ! */ \ + (hcobject)->digest_length = 1; \ + (hcobject)->context_length = 1; \ + (hcobject)->context_register_length = 1; \ + break; \ + } \ +} + +/*! + * Get the current hash value and message length from the hash context object. + * + * The algorithm must have already been specified. See #fsl_shw_hco_init(). + * + * @param hcobject The hash context to query. + * @param[out] hccontext Pointer to the location of @a length octets where to + * store a copy of the current value of the digest. + * @param hcclength Number of octets of hash value to copy. + * @param[out] hcmsglen Pointer to the location to store the number of octets + * already hashed. + */ +#define fsl_shw_hco_get_digest(hcobject, hccontext, hcclength, hcmsglen) \ +{ \ + copy_bytes(hccontext, (hcobject)->context, hcclength); \ + if ((hcobject)->algorithm == FSL_HASH_ALG_SHA224 \ + || (hcobject)->algorithm == FSL_HASH_ALG_SHA256) { \ + *(hcmsglen) = (hcobject)->context[8]; \ + } else { \ + *(hcmsglen) = (hcobject)->context[5]; \ + } \ +} + +/*! + * Get the hash algorithm from the hash context object. + * + * @param hcobject The hash context to query. + * @param[out] hcalgorithm Pointer to where the algorithm is to be stored. + */ +#define fsl_shw_hco_get_info(hcobject, hcalgorithm) \ +{ \ + *(hcalgorithm) = (hcobject)->algorithm; \ +} + +/*! + * Set the current hash value and message length in the hash context object. + * + * The algorithm must have already been specified. See #fsl_shw_hco_init(). + * + * @param hcobject The hash context to operate upon. + * @param hccontext Pointer to buffer of appropriate length to copy into + * the hash context object. + * @param hcmsglen The number of octets of the message which have + * already been hashed. + * + */ +#define fsl_shw_hco_set_digest(hcobject, hccontext, hcmsglen) \ +{ \ + copy_bytes((hcobject)->context, hccontext, (hcobject)->context_length); \ + if (((hcobject)->algorithm == FSL_HASH_ALG_SHA224) \ + || ((hcobject)->algorithm == FSL_HASH_ALG_SHA256)) { \ + (hcobject)->context[8] = hcmsglen; \ + } else { \ + (hcobject)->context[5] = hcmsglen; \ + } \ +} + +/*! + * Set flags in a Hash Context Object. + * + * Turns on the flags specified in @a flags. Other flags are untouched. + * + * @param hcobject The hash context to be operated on. + * @param hcflags The flags to be set in the context. These can be ORed + * members of #fsl_shw_hash_ctx_flags_t. + */ +#define fsl_shw_hco_set_flags(hcobject, hcflags) \ + (hcobject)->flags |= (hcflags) + +/*! + * Clear flags in a Hash Context Object. + * + * Turns off the flags specified in @a flags. Other flags are untouched. + * + * @param hcobject The hash context to be operated on. + * @param hcflags The flags to be reset in the context. These can be ORed + * members of #fsl_shw_hash_ctx_flags_t. + */ +#define fsl_shw_hco_clear_flags(hcobject, hcflags) \ + (hcobject)->flags &= ~(hcflags) + +/*! + * Initialize an HMAC Context Object. + * + * This function must be called before performing any other operation with the + * Object. It sets the current message length and hash algorithm in the HMAC + * context object. + * + * @param hcobject The HMAC context to operate upon. + * @param hcalgorithm The hash algorithm to be used (#FSL_HASH_ALG_MD5, + * #FSL_HASH_ALG_SHA256, etc). + * + */ +#define fsl_shw_hmco_init(hcobject, hcalgorithm) \ + fsl_shw_hco_init(hcobject, hcalgorithm) + +/*! + * Set flags in an HMAC Context Object. + * + * Turns on the flags specified in @a flags. Other flags are untouched. + * + * @param hcobject The HMAC context to be operated on. + * @param hcflags The flags to be set in the context. These can be ORed + * members of #fsl_shw_hmac_ctx_flags_t. + */ +#define fsl_shw_hmco_set_flags(hcobject, hcflags) \ + (hcobject)->flags |= (hcflags) + +/*! + * Clear flags in an HMAC Context Object. + * + * Turns off the flags specified in @a flags. Other flags are untouched. + * + * @param hcobject The HMAC context to be operated on. + * @param hcflags The flags to be reset in the context. These can be ORed + * members of #fsl_shw_hmac_ctx_flags_t. + */ +#define fsl_shw_hmco_clear_flags(hcobject, hcflags) \ + (hcobject)->flags &= ~(hcflags) + +/*! + * Initialize a Symmetric Cipher Context Object. + * + * This function must be called before performing any other operation with the + * Object. This will set the @a mode and @a algorithm and initialize the + * Object. + * + * @param scobject The context object to operate on. + * @param scalg The cipher algorithm this context will be used with. + * @param scmode #FSL_SYM_MODE_CBC, #FSL_SYM_MODE_ECB, etc. + * + */ +#define fsl_shw_scco_init(scobject, scalg, scmode) \ +{ \ + register uint32_t bsb; /* block-size bytes */ \ + \ + switch (scalg) { \ + case FSL_KEY_ALG_AES: \ + bsb = 16; \ + break; \ + case FSL_KEY_ALG_DES: \ + /* fall through */ \ + case FSL_KEY_ALG_TDES: \ + bsb = 8; \ + break; \ + case FSL_KEY_ALG_ARC4: \ + bsb = 259; \ + break; \ + case FSL_KEY_ALG_HMAC: \ + bsb = 1; /* meaningless */ \ + break; \ + default: \ + bsb = 00; \ + } \ + (scobject)->block_size_bytes = bsb; \ + (scobject)->mode = scmode; \ + (scobject)->flags = 0; \ +} + +/*! + * Set the flags for a Symmetric Cipher Context. + * + * Turns on the flags specified in @a flags. Other flags are untouched. + * + * @param scobject The context object to operate on. + * @param scflags The flags to reset (one or more values from + * #fsl_shw_sym_ctx_flags_t ORed together). + * + */ +#define fsl_shw_scco_set_flags(scobject, scflags) \ + (scobject)->flags |= (scflags) + +/*! + * Clear some flags in a Symmetric Cipher Context Object. + * + * Turns off the flags specified in @a flags. Other flags are untouched. + * + * @param scobject The context object to operate on. + * @param scflags The flags to reset (one or more values from + * #fsl_shw_sym_ctx_flags_t ORed together). + * + */ +#define fsl_shw_scco_clear_flags(scobject, scflags) \ + (scobject)->flags &= ~(scflags) + +/*! + * Set the Context (IV) for a Symmetric Cipher Context. + * + * This is to set the context/IV for #FSL_SYM_MODE_CBC mode, or to set the + * context (the S-Box and pointers) for ARC4. The full context size will + * be copied. + * + * @param scobject The context object to operate on. + * @param sccontext A pointer to the buffer which contains the context. + * + */ +#define fsl_shw_scco_set_context(scobject, sccontext) \ + copy_bytes((scobject)->context, sccontext, \ + (scobject)->block_size_bytes) + +/*! + * Get the Context for a Symmetric Cipher Context. + * + * This is to retrieve the context/IV for #FSL_SYM_MODE_CBC mode, or to + * retrieve context (the S-Box and pointers) for ARC4. The full context + * will be copied. + * + * @param scobject The context object to operate on. + * @param[out] sccontext Pointer to location where context will be stored. + */ +#define fsl_shw_scco_get_context(scobject, sccontext) \ + copy_bytes(sccontext, (scobject)->context, (scobject)->block_size_bytes) + +/*! + * Set the Counter Value for a Symmetric Cipher Context. + * + * This will set the Counter Value for CTR mode. + * + * @param scobject The context object to operate on. + * @param sccounter The starting counter value. The number of octets. + * copied will be the block size for the algorithm. + * @param scmodulus The modulus for controlling the incrementing of the + * counter. + * + */ +#define fsl_shw_scco_set_counter_info(scobject, sccounter, scmodulus) \ + { \ + if ((sccounter) != NULL) { \ + copy_bytes((scobject)->context, sccounter, \ + (scobject)->block_size_bytes); \ + } \ + (scobject)->modulus_exp = scmodulus; \ + } + +/*! + * Get the Counter Value for a Symmetric Cipher Context. + * + * This will retrieve the Counter Value is for CTR mode. + * + * @param scobject The context object to query. + * @param[out] sccounter Pointer to location to store the current counter + * value. The number of octets copied will be the + * block size for the algorithm. + * @param[out] scmodulus Pointer to location to store the modulus. + * + */ +#define fsl_shw_scco_get_counter_info(scobject, sccounter, scmodulus) \ + { \ + if ((sccounter) != NULL) { \ + copy_bytes(sccounter, (scobject)->context, \ + (scobject)->block_size_bytes); \ + } \ + if ((scmodulus) != NULL) { \ + *(scmodulus) = (scobject)->modulus_exp; \ + } \ + } + +/*! + * Initialize a Authentication-Cipher Context. + * + * @param acobject Pointer to object to operate on. + * @param acmode The mode for this object (only #FSL_ACC_MODE_CCM + * supported). + */ +#define fsl_shw_acco_init(acobject, acmode) \ + { \ + (acobject)->flags = 0; \ + (acobject)->mode = (acmode); \ + } + +/*! + * Set the flags for a Authentication-Cipher Context. + * + * Turns on the flags specified in @a flags. Other flags are untouched. + * + * @param acobject Pointer to object to operate on. + * @param acflags The flags to set (one or more from + * #fsl_shw_auth_ctx_flags_t ORed together). + * + */ +#define fsl_shw_acco_set_flags(acobject, acflags) \ + (acobject)->flags |= (acflags) + +/*! + * Clear some flags in a Authentication-Cipher Context Object. + * + * Turns off the flags specified in @a flags. Other flags are untouched. + * + * @param acobject Pointer to object to operate on. + * @param acflags The flags to reset (one or more from + * #fsl_shw_auth_ctx_flags_t ORed together). + * + */ +#define fsl_shw_acco_clear_flags(acobject, acflags) \ + (acobject)->flags &= ~(acflags) + +/*! + * Set up the Authentication-Cipher Object for CCM mode. + * + * This will set the @a auth_object for CCM mode and save the @a ctr, + * and @a mac_length. This function can be called instead of + * #fsl_shw_acco_init(). + * + * The paramater @a ctr is Counter Block 0, (counter value 0), which is for the + * MAC. + * + * @param acobject Pointer to object to operate on. + * @param acalg Cipher algorithm. Only AES is supported. + * @param accounter The initial counter value. + * @param acmaclen The number of octets used for the MAC. Valid values are + * 4, 6, 8, 10, 12, 14, and 16. + */ +/* Do we need to stash the +1 value of the CTR somewhere? */ +#define fsl_shw_acco_set_ccm(acobject, acalg, accounter, acmaclen) \ +{ \ + (acobject)->flags = 0; \ + (acobject)->mode = FSL_ACC_MODE_CCM; \ + (acobject)->auth_info.CCM_ctx_info.block_size_bytes = 16; \ + (acobject)->cipher_ctx_info.block_size_bytes = 16; \ + (acobject)->mac_length = acmaclen; \ + fsl_shw_scco_set_counter_info(&(acobject)->cipher_ctx_info, accounter, \ + FSL_CTR_MOD_128); \ +} + +/*! + * Format the First Block (IV) & Initial Counter Value per NIST CCM. + * + * This function will also set the IV and CTR values per Appendix A of NIST + * Special Publication 800-38C (May 2004). It will also perform the + * #fsl_shw_acco_set_ccm() operation with information derived from this set of + * parameters. + * + * Note this function assumes the algorithm is AES. It initializes the + * @a auth_object by setting the mode to #FSL_ACC_MODE_CCM and setting the + * flags to be #FSL_ACCO_NIST_CCM. + * + * @param acobject Pointer to object to operate on. + * @param act The number of octets used for the MAC. Valid values are + * 4, 6, 8, 10, 12, 14, and 16. + * @param acad Number of octets of Associated Data (may be zero). + * @param acq A value for the size of the length of @a q field. Valid + * values are 1-8. + * @param acN The Nonce (packet number or other changing value). Must + * be (15 - @a q_length) octets long. + * @param acQ The value of Q (size of the payload in octets). + * + */ +/* Do we need to stash the +1 value of the CTR somewhere? */ +#define fsl_shw_ccm_nist_format_ctr_and_iv(acobject, act, acad, acq, acN, acQ)\ + { \ + uint64_t Q = acQ; \ + uint8_t bflag = ((acad)?0x40:0) | ((((act)-2)/2)<<3) | ((acq)-1); \ + unsigned i; \ + uint8_t* qptr = (acobject)->auth_info.CCM_ctx_info.context + 15; \ + (acobject)->auth_info.CCM_ctx_info.block_size_bytes = 16; \ + (acobject)->cipher_ctx_info.block_size_bytes = 16; \ + (acobject)->mode = FSL_ACC_MODE_CCM; \ + (acobject)->flags = FSL_ACCO_NIST_CCM; \ + \ + /* Store away the MAC length (after calculating actual value */ \ + (acobject)->mac_length = (act); \ + /* Set Flag field in Block 0 */ \ + *((acobject)->auth_info.CCM_ctx_info.context) = bflag; \ + /* Set Nonce field in Block 0 */ \ + copy_bytes((acobject)->auth_info.CCM_ctx_info.context+1, acN, \ + 15-(acq)); \ + /* Set Flag field in ctr */ \ + *((acobject)->cipher_ctx_info.context) = (acq)-1; \ + /* Update the Q (payload length) field of Block0 */ \ + (acobject)->q_length = acq; \ + for (i = 0; i < (acq); i++) { \ + *qptr-- = Q & 0xFF; \ + Q >>= 8; \ + } \ + /* Set the Nonce field of the ctr */ \ + copy_bytes((acobject)->cipher_ctx_info.context+1, acN, 15-(acq)); \ + /* Clear the block counter field of the ctr */ \ + memset((acobject)->cipher_ctx_info.context+16-(acq), 0, (acq)+1); \ + } + +/*! + * Update the First Block (IV) & Initial Counter Value per NIST CCM. + * + * This function will set the IV and CTR values per Appendix A of NIST Special + * Publication 800-38C (May 2004). + * + * Note this function assumes that #fsl_shw_ccm_nist_format_ctr_and_iv() has + * previously been called on the @a auth_object. + * + * @param acobject Pointer to object to operate on. + * @param acN The Nonce (packet number or other changing value). Must + * be (15 - @a q_length) octets long. + * @param acQ The value of Q (size of the payload in octets). + * + */ +/* Do we need to stash the +1 value of the CTR somewhere? */ +#define fsl_shw_ccm_nist_update_ctr_and_iv(acobject, acN, acQ) \ + { \ + uint64_t Q = acQ; \ + unsigned i; \ + uint8_t* qptr = (acobject)->auth_info.CCM_ctx_info.context + 15; \ + \ + /* Update the Nonce field field of Block0 */ \ + copy_bytes((acobject)->auth_info.CCM_ctx_info.context+1, acN, \ + 15 - (acobject)->q_length); \ + /* Update the Q (payload length) field of Block0 */ \ + for (i = 0; i < (acobject)->q_length; i++) { \ + *qptr-- = Q & 0xFF; \ + Q >>= 8; \ + } \ + /* Update the Nonce field of the ctr */ \ + copy_bytes((acobject)->cipher_ctx_info.context+1, acN, \ + 15 - (acobject)->q_length); \ + } + +/****************************************************************************** + * Library functions + *****************************************************************************/ +/* REQ-S2LRD-PINTFC-API-GEN-003 */ +extern fsl_shw_pco_t *fsl_shw_get_capabilities(fsl_shw_uco_t * user_ctx); + +/* REQ-S2LRD-PINTFC-API-GEN-004 */ +extern fsl_shw_return_t fsl_shw_register_user(fsl_shw_uco_t * user_ctx); + +/* REQ-S2LRD-PINTFC-API-GEN-005 */ +extern fsl_shw_return_t fsl_shw_deregister_user(fsl_shw_uco_t * user_ctx); + +/* REQ-S2LRD-PINTFC-API-GEN-006 */ +extern fsl_shw_return_t fsl_shw_get_results(fsl_shw_uco_t * user_ctx, + unsigned result_size, + fsl_shw_result_t results[], + unsigned *result_count); + +extern fsl_shw_return_t fsl_shw_establish_key(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + fsl_shw_key_wrap_t establish_type, + const uint8_t * key); + +extern fsl_shw_return_t fsl_shw_extract_key(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + uint8_t * covered_key); + +extern fsl_shw_return_t fsl_shw_release_key(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info); + +/* REQ-S2LRD-PINTFC-API-BASIC-SYM-002 */ +/* PINTFC-API-BASIC-SYM-ARC4-001 */ +/* PINTFC-API-BASIC-SYM-ARC4-002 */ +extern fsl_shw_return_t fsl_shw_symmetric_encrypt(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + fsl_shw_scco_t * sym_ctx, + uint32_t length, + const uint8_t * pt, + uint8_t * ct); + +/* PINTFC-API-BASIC-SYM-002 */ +/* PINTFC-API-BASIC-SYM-ARC4-001 */ +/* PINTFC-API-BASIC-SYM-ARC4-002 */ +extern fsl_shw_return_t fsl_shw_symmetric_decrypt(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + fsl_shw_scco_t * sym_ctx, + uint32_t length, + const uint8_t * ct, + uint8_t * pt); + +/* REQ-S2LRD-PINTFC-API-BASIC-HASH-005 */ +extern fsl_shw_return_t fsl_shw_hash(fsl_shw_uco_t * user_ctx, + fsl_shw_hco_t * hash_ctx, + const uint8_t * msg, + uint32_t length, + uint8_t * result, uint32_t result_len); + +/* REQ-S2LRD-PINTFC-API-BASIC-HMAC-001 */ +extern fsl_shw_return_t fsl_shw_hmac_precompute(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + fsl_shw_hmco_t * hmac_ctx); + +/* REQ-S2LRD-PINTFC-API-BASIC-HMAC-002 */ +extern fsl_shw_return_t fsl_shw_hmac(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + fsl_shw_hmco_t * hmac_ctx, + const uint8_t * msg, + uint32_t length, + uint8_t * result, uint32_t result_len); + +/* REQ-S2LRD-PINTFC-API-BASIC-RNG-002 */ +extern fsl_shw_return_t fsl_shw_get_random(fsl_shw_uco_t * user_ctx, + uint32_t length, uint8_t * data); + +extern fsl_shw_return_t fsl_shw_add_entropy(fsl_shw_uco_t * user_ctx, + uint32_t length, uint8_t * data); + +extern fsl_shw_return_t fsl_shw_gen_encrypt(fsl_shw_uco_t * user_ctx, + fsl_shw_acco_t * auth_ctx, + fsl_shw_sko_t * cipher_key_info, + fsl_shw_sko_t * auth_key_info, + uint32_t auth_data_length, + const uint8_t * auth_data, + uint32_t payload_length, + const uint8_t * payload, + uint8_t * ct, uint8_t * auth_value); + +extern fsl_shw_return_t fsl_shw_auth_decrypt(fsl_shw_uco_t * user_ctx, + fsl_shw_acco_t * auth_ctx, + fsl_shw_sko_t * cipher_key_info, + fsl_shw_sko_t * auth_key_info, + uint32_t auth_data_length, + const uint8_t * auth_data, + uint32_t payload_length, + const uint8_t * ct, + const uint8_t * auth_value, + uint8_t * payload); + +#if 0 +sah_Test_Status sah_Check_Test_Mode(void); +#endif + +fsl_shw_return_t sah_Append_Desc(const sah_Mem_Util * mu, + sah_Head_Desc ** desc_head, + const uint32_t header, + sah_Link * link1, sah_Link * link2); + +/* Utility Function leftover from sahara1 API */ +void sah_Descriptor_Chain_Destroy(const sah_Mem_Util * mu, + sah_Head_Desc ** desc_chain); + +/* Utility Function leftover from sahara1 API */ +fsl_shw_return_t sah_Descriptor_Chain_Execute(sah_Head_Desc * desc_chain, + fsl_shw_uco_t * user_ctx); + +fsl_shw_return_t sah_Append_Link(const sah_Mem_Util * mu, + sah_Link * link, + uint8_t * p, + const size_t length, + const sah_Link_Flags flags); + +fsl_shw_return_t sah_Create_Link(const sah_Mem_Util * mu, + sah_Link ** link, + uint8_t * p, + const size_t length, + const sah_Link_Flags flags); + +fsl_shw_return_t sah_Create_Key_Link(const sah_Mem_Util * mu, + sah_Link ** link, + fsl_shw_sko_t * key_info); + +void sah_Destroy_Link(const sah_Mem_Util * mu, sah_Link * link); + +void sah_Postprocess_Results(fsl_shw_uco_t * user_ctx, + sah_results * result_info); + +#endif /* SAHARA2_API_H */ diff --git a/drivers/mxc/security/sahara2/include/sahara2_kernel.h b/drivers/mxc/security/sahara2/include/sahara2_kernel.h new file mode 100644 index 000000000000..b833f0ab8f56 --- /dev/null +++ b/drivers/mxc/security/sahara2/include/sahara2_kernel.h @@ -0,0 +1,49 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#define DRIVER_NAME sahara2 + +#define SAHARA_MAJOR_NODE 78 + +#include "portable_os.h" + +#include "platform_abstractions.h" + +/* Forward-declare prototypes using signature macros */ + +OS_DEV_ISR_DCL(sahara2_isr); + +OS_DEV_INIT_DCL(sahara2_init); + +OS_DEV_SHUTDOWN_DCL(sahara2_shutdown); + +OS_DEV_OPEN_DCL(sahara2_open); + +OS_DEV_CLOSE_DCL(sahara2_release); + +OS_DEV_IOCTL_DCL(sahara2_ioctl); + +struct sahara2_kernel_user { + void *command_ring[32]; +}; + +struct sahara2_sym_arg { + char *key; + unsigned key_len; +}; + +/*! These need to be added to Linux / OS abstractions */ +/* +module_init(OS_DEV_INIT_REF(sahara2_init)); +module_cleanup(OS_DEV_SHUTDOWN_REF(sahara2_shutdown)); +*/ diff --git a/drivers/mxc/security/sahara2/include/sf_util.h b/drivers/mxc/security/sahara2/include/sf_util.h new file mode 100644 index 000000000000..710c94fab896 --- /dev/null +++ b/drivers/mxc/security/sahara2/include/sf_util.h @@ -0,0 +1,426 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! +* @file sf_util.h +* +* @brief Header for Sahara Descriptor-chain building Functions +*/ +#ifndef SF_UTIL_H +#define SF_UTIL_H + +#include <sahara.h> + +/*! Header value for Sahara Descriptor 1 */ +#define SAH_HDR_SKHA_SET_MODE_IV_KEY 0x10880000 +/*! Header value for Sahara Descriptor 2 */ +#define SAH_HDR_SKHA_SET_MODE_ENC_DEC 0x108D0000 +/*! Header value for Sahara Descriptor 4 */ +#define SAH_HDR_SKHA_ENC_DEC 0x90850000 +/*! Header value for Sahara Descriptor 5 */ +#define SAH_HDR_SKHA_READ_CONTEXT_IV 0x10820000 +/*! Header value for Sahara Descriptor 6 */ +#define SAH_HDR_MDHA_SET_MODE_MD_KEY 0x20880000 +/*! Header value for Sahara Descriptor 8 */ +#define SAH_HDR_MDHA_SET_MODE_HASH 0x208D0000 +/*! Header value for Sahara Descriptor 10 */ +#define SAH_HDR_MDHA_HASH 0xA0850000 +/*! Header value for Sahara Descriptor 11 */ +#define SAH_HDR_MDHA_STORE_DIGEST 0x20820000 +/*! Header value for Sahara Descriptor 18 */ +#define SAH_HDR_RNG_GENERATE 0x308C0000 +/*! Header value for Sahara Descriptor 19 */ +#define SAH_HDR_PKHA_LD_N_E 0xC0800000 +/*! Header value for Sahara Descriptor 20 */ +#define SAH_HDR_PKHA_LD_A_EX_ST_B 0x408D0000 +/*! Header value for Sahara Descriptor 21 */ +#define SAH_HDR_PKHA_LD_N_EX_ST_B 0x408E0000 +/*! Header value for Sahara Descriptor 22 */ +#define SAH_HDR_PKHA_LD_A_B 0xC0830000 +/*! Header value for Sahara Descriptor 23 */ +#define SAH_HDR_PKHA_LD_A0_A1 0x40840000 +/*! Header value for Sahara Descriptor 24 */ +#define SAH_HDR_PKHA_LD_A2_A3 0xC0850000 +/*! Header value for Sahara Descriptor 25 */ +#define SAH_HDR_PKHA_LD_B0_B1 0xC0860000 +/*! Header value for Sahara Descriptor 26 */ +#define SAH_HDR_PKHA_LD_B2_B3 0x40870000 +/*! Header value for Sahara Descriptor 27 */ +#define SAH_HDR_PKHA_ST_A_B 0x40820000 +/*! Header value for Sahara Descriptor 28 */ +#define SAH_HDR_PKHA_ST_A0_A1 0x40880000 +/*! Header value for Sahara Descriptor 29 */ +#define SAH_HDR_PKHA_ST_A2_A3 0xC0890000 +/*! Header value for Sahara Descriptor 30 */ +#define SAH_HDR_PKHA_ST_B0_B1 0xC08A0000 +/*! Header value for Sahara Descriptor 31 */ +#define SAH_HDR_PKHA_ST_B2_B3 0x408B0000 +/*! Header value for Sahara Descriptor 32 */ +#define SAH_HDR_PKHA_EX_ST_B1 0xC08C0000 +/*! Header value for Sahara Descriptor 33 */ +#define SAH_HDR_ARC4_SET_MODE_SBOX 0x90890000 +/*! Header value for Sahara Descriptor 34 */ +#define SAH_HDR_ARC4_READ_SBOX 0x90860000 +/*! Header value for Sahara Descriptor 35 */ +#define SAH_HDR_ARC4_SET_MODE_KEY 0x90830000 +/*! Header value for Sahara Descriptor 36 */ +#define SAH_HDR_PKHA_LD_A3_B0 0x40810000 +/*! Header value for Sahara Descriptor 37 */ +#define SAH_HDR_PKHA_ST_B1_B2 0xC08F0000 +/*! Header value for Sahara Descriptor 38 */ +#define SAH_HDR_SKHA_CBC_ICV 0x10840000 +/*! Header value for Sahara Descriptor 39 */ +#define SAH_HDR_MDHA_ICV_CHECK 0xA08A0000 + +/*! Header bit indicating "Link-List optimization" */ +#define SAH_HDR_LLO 0x01000000 + +#define SAH_SF_DCLS \ + fsl_shw_return_t ret; \ + unsigned sf_executed = 0; \ + sah_Head_Desc* desc_chain = NULL; \ + uint32_t header + +#define SAH_SF_USER_CHECK() \ +do { \ + ret = sah_validate_uco(user_ctx); \ + if (ret != FSL_RETURN_OK_S) { \ + goto out; \ + } \ +} while (0) + +#define SAH_SF_EXECUTE() \ +do { \ + sf_executed = 1; \ + ret = sah_Descriptor_Chain_Execute(desc_chain, user_ctx); \ +} while (0) + +#define SAH_SF_DESC_CLEAN() \ +do { \ + if (!sf_executed || (user_ctx->flags & FSL_UCO_BLOCKING_MODE)) { \ + sah_Descriptor_Chain_Destroy(user_ctx->mem_util, &desc_chain); \ + } \ + (void) header; \ +} while (0) + +/*! Add Descriptor with two inputs */ +#define DESC_IN_IN(hdr, len1, ptr1, len2, ptr2) \ +{ \ + ret = sah_add_two_in_desc(hdr, ptr1, len1, ptr2, len2, \ + user_ctx->mem_util, &desc_chain); \ + if (ret != FSL_RETURN_OK_S) { \ + goto out; \ + } \ +} + +/*! Add Descriptor with input and a key */ +#define DESC_IN_KEY(hdr, len1, ptr1, key2) \ +{ \ + ret = sah_add_in_key_desc(hdr, ptr1, len1, key2, \ + user_ctx->mem_util, &desc_chain); \ + if (ret != FSL_RETURN_OK_S) { \ + goto out; \ + } \ +} + +/*! Add Descriptor with input and an output */ +#define DESC_IN_OUT(hdr, len1, ptr1, len2, ptr2) \ +{ \ + ret = sah_add_in_out_desc(hdr, ptr1, len1, ptr2, len2, \ + user_ctx->mem_util, &desc_chain); \ + if (ret != FSL_RETURN_OK_S) { \ + goto out; \ + } \ +} + +/*! Add Descriptor with input and a key output */ +#define DESC_IN_KEYOUT(hdr, len1, ptr1, key2) \ +{ \ + ret = sah_add_in_keyout_desc(hdr, ptr1, len1, key2, \ + user_ctx->mem_util, &desc_chain); \ + if (ret != FSL_RETURN_OK_S) { \ + goto out; \ + } \ +} + +/*! Add Descriptor with a key and an output */ +#define DESC_KEY_OUT(hdr, key1, len2, ptr2) \ +{ \ + ret = sah_add_key_out_desc(hdr, key1, ptr2, len2, \ + user_ctx->mem_util, &desc_chain); \ + if (ret != FSL_RETURN_OK_S) { \ + goto out; \ + } \ +} + +/*! Add Descriptor with two outputs */ +#define DESC_OUT_OUT(hdr, len1, ptr1, len2, ptr2) \ +{ \ + ret = sah_add_two_out_desc(hdr, ptr1, len1, ptr2, len2, \ + user_ctx->mem_util, &desc_chain); \ + if (ret != FSL_RETURN_OK_S) { \ + goto out; \ + } \ +} + +/*! Add Descriptor with output then input pointers */ +#define DESC_OUT_IN(hdr, len1, ptr1, len2, ptr2) \ +{ \ + ret = sah_add_out_in_desc(hdr, ptr1, len1, ptr2, len2, \ + user_ctx->mem_util, &desc_chain); \ + if (ret != FSL_RETURN_OK_S) { \ + goto out; \ + } \ +} + +#ifdef SAH_SF_DEBUG +/*! Add Descriptor with two outputs */ +#define DBG_DESC(hdr, len1, ptr1, len2, ptr2) \ +{ \ + ret = sah_add_two_out_desc(hdr, ptr1, len1, ptr2, len2, \ + user_ctx->mem_util, &desc_chain); \ + if (ret != FSL_RETURN_OK_S) { \ + goto out; \ + } \ +} +#else +#define DBG_DESC(hdr, len1, ptr1, len2, ptr2) +#endif + +#define DESC_TEMP_ALLOC(size) \ +({ \ + uint8_t* ptr; \ + ptr = user_ctx->mem_util->mu_malloc(user_ctx->mem_util->mu_ref, \ + size); \ + if (ptr == NULL) { \ + ret = FSL_RETURN_NO_RESOURCE_S; \ + goto out; \ + } \ + \ + ptr; \ +}) + +#define DESC_TEMP_FREE(ptr) \ +({ \ + if ((ptr != NULL) && \ + (!sf_executed || (user_ctx->flags & FSL_UCO_BLOCKING_MODE))) { \ + user_ctx->mem_util-> \ + mu_free(user_ctx->mem_util->mu_ref, ptr); \ + ptr = NULL; \ + } \ +}) + +/* Temporary implementation. This needs to be in internal/secure RAM */ +#define DESC_TEMP_SECURE_ALLOC(size) \ +({ \ + uint8_t* ptr; \ + ptr = user_ctx->mem_util->mu_malloc(user_ctx->mem_util->mu_ref, \ + size); \ + if (ptr == NULL) { \ + ret = FSL_RETURN_NO_RESOURCE_S; \ + goto out; \ + } \ + \ + ptr; \ +}) + +#define DESC_TEMP_SECURE_FREE(ptr, size) \ +({ \ + if ((ptr != NULL) && \ + (!sf_executed || (user_ctx->flags & FSL_UCO_BLOCKING_MODE))) { \ + user_ctx->mem_util->mu_memset(user_ctx->mem_util->mu_ref, \ + ptr, 0, size); \ + \ + user_ctx->mem_util-> \ + mu_free(user_ctx->mem_util->mu_ref, ptr); \ + ptr = NULL; \ + } \ +}) + +extern const uint32_t sah_insert_mdha_algorithm[]; + +/*! @defgroup mdhaflags MDHA Mode Register Values + * + * These are bit fields and combinations of bit fields for setting the Mode + * Register portion of a Sahara Descriptor Header field. + * + * The parity bit has been set to ensure that these values have even parity, + * therefore using an Exclusive-OR operation against an existing header will + * preserve its parity. + * + * @addtogroup mdhaflags + @{ + */ +#define sah_insert_mdha_icv_check 0x80001000 +#define sah_insert_mdha_ssl 0x80000400 +#define sah_insert_mdha_mac_full 0x80000200 +#define sah_insert_mdha_opad 0x80000080 +#define sah_insert_mdha_ipad 0x80000040 +#define sah_insert_mdha_init 0x80000020 +#define sah_insert_mdha_hmac 0x80000008 +#define sah_insert_mdha_pdata 0x80000004 +#define sah_insert_mdha_algorithm_sha224 0x00000003 +#define sah_insert_mdha_algorithm_sha256 0x80000002 +#define sah_insert_mdha_algorithm_md5 0x80000001 +#define sah_insert_mdha_algorithm_sha1 0x00000000 +/*! @} */ + +extern const uint32_t sah_insert_skha_algorithm[]; +extern const uint32_t sah_insert_skha_mode[]; +extern const uint32_t sah_insert_skha_modulus[]; + +/*! @defgroup skhaflags SKHA Mode Register Values + * + * These are bit fields and combinations of bit fields for setting the Mode + * Register portion of a Sahara Descriptor Header field. + * + * The parity bit has been set to ensure that these values have even parity, + * therefore using an Exclusive-OR operation against an existing header will + * preserve its parity. + * + * @addtogroup skhaflags + @{ + */ +/*! */ +#define sah_insert_skha_modulus_128 0x00001e00 +#define sah_insert_skha_no_key_parity 0x80000100 +#define sah_insert_skha_ctr_last_block 0x80000020 +#define sah_insert_skha_suppress_cbc 0x80000020 +#define sah_insert_skha_no_permute 0x80000020 +#define sah_insert_skha_algorithm_arc4 0x00000003 +#define sah_insert_skha_algorithm_tdes 0x80000002 +#define sah_insert_skha_algorithm_des 0x80000001 +#define sah_insert_skha_algorithm_aes 0x00000000 +#define sah_insert_skha_mode_ctr 0x00000018 +#define sah_insert_skha_mode_ccm 0x80000010 +#define sah_insert_skha_mode_cbc 0x80000008 +#define sah_insert_skha_mode_ecb 0x00000000 +#define sah_insert_skha_encrypt 0x80000004 +#define sah_insert_skha_decrypt 0x00000000 +/*! @} */ + +/*! @defgroup pkhaflags PKHA Mode Register Values + * + */ +/*! */ +#define sah_insert_pkha_soft_err_false 0x80000200 +#define sah_insert_pkha_soft_err_true 0x80000100 + +#define sah_insert_pkha_rtn_clr_mem 0x80000001 +#define sah_insert_pkha_rtn_clr_eram 0x80000002 +#define sah_insert_pkha_rtn_mod_exp 0x00000003 +#define sah_insert_pkha_rtn_mod_r2modn 0x80000004 +#define sah_insert_pkha_rtn_mod_rrmodp 0x00000005 +#define sah_insert_pkha_rtn_ec_fp_aff_ptmult 0x00000006 +#define sah_insert_pkha_rtn_ec_f2m_aff_ptmult 0x80000007 +#define sah_insert_pkha_rtn_ec_fp_proj_ptmult 0x80000008 +#define sah_insert_pkha_rtn_ec_f2m_proj_ptmult 0x00000009 +#define sah_insert_pkha_rtn_ec_fp_add 0x0000000A +#define sah_insert_pkha_rtn_ec_fp_double 0x8000000B +#define sah_insert_pkha_rtn_ec_f2m_add 0x0000000C +#define sah_insert_pkha_rtn_ec_f2m_double 0x8000000D +#define sah_insert_pkha_rtn_f2m_r2 0x8000000E +#define sah_insert_pkha_rtn_f2m_inv 0x0000000F +#define sah_insert_pkha_rtn_mod_inv 0x80000010 +#define sah_insert_pkha_rtn_rsa_sstep 0x00000011 +#define sah_insert_pkha_rtn_mod_emodn 0x00000012 +#define sah_insert_pkha_rtn_f2m_emodn 0x80000013 +#define sah_insert_pkha_rtn_ec_fp_ptmul 0x00000014 +#define sah_insert_pkha_rtn_ec_f2m_ptmul 0x80000015 +#define sah_insert_pkha_rtn_f2m_gcd 0x80000016 +#define sah_insert_pkha_rtn_mod_gcd 0x00000017 +#define sah_insert_pkha_rtn_f2m_dbl_aff 0x00000018 +#define sah_insert_pkha_rtn_fp_dbl_aff 0x80000019 +#define sah_insert_pkha_rtn_f2m_add_aff 0x8000001A +#define sah_insert_pkha_rtn_fp_add_aff 0x0000001B +#define sah_insert_pkha_rtn_f2m_exp 0x8000001C +#define sah_insert_pkha_rtn_mod_exp_teq 0x0000001D +#define sah_insert_pkha_rtn_rsa_sstep_teq 0x0000001E +#define sah_insert_pkha_rtn_f2m_multn 0x8000001F +#define sah_insert_pkha_rtn_mod_multn 0x80000020 +#define sah_insert_pkha_rtn_mod_add 0x00000021 +#define sah_insert_pkha_rtn_mod_sub 0x00000022 +#define sah_insert_pkha_rtn_mod_mult1_mont 0x80000023 +#define sah_insert_pkha_rtn_mod_mult2_deconv 0x00000024 +#define sah_insert_pkha_rtn_f2m_add 0x80000025 +#define sah_insert_pkha_rtn_f2m_mult1_mont 0x80000026 +#define sah_insert_pkha_rtn_f2m_mult2_deconv 0x00000027 +#define sah_insert_pkha_rtn_miller_rabin 0x00000028 +/*! @} */ + +/*! Add a descriptor with two input pointers */ +fsl_shw_return_t sah_add_two_in_desc(uint32_t header, + const uint8_t * in1, + uint32_t in1_length, + const uint8_t * in2, + uint32_t in2_length, + const sah_Mem_Util * mu, + sah_Head_Desc ** desc_chain); + +/*! Add a descriptor with an input and key pointer */ +fsl_shw_return_t sah_add_in_key_desc(uint32_t header, + const uint8_t * in1, + uint32_t in1_length, + fsl_shw_sko_t * key_info, + const sah_Mem_Util * mu, + sah_Head_Desc ** desc_chain); + +/*! Add a descriptor with two key pointers */ +fsl_shw_return_t sah_add_key_key_desc(uint32_t header, + fsl_shw_sko_t * key_info1, + fsl_shw_sko_t * key_info2, + const sah_Mem_Util * mu, + sah_Head_Desc ** desc_chain); + +/*! Add a descriptor with two output pointers */ +fsl_shw_return_t sah_add_two_out_desc(uint32_t header, + uint8_t * out1, + uint32_t out1_length, + uint8_t * out2, + uint32_t out2_length, + const sah_Mem_Util * mu, + sah_Head_Desc ** desc_chain); + +/*! Add a descriptor with an input and output pointer */ +fsl_shw_return_t sah_add_in_out_desc(uint32_t header, + const uint8_t * in, uint32_t in_length, + uint8_t * out, uint32_t out_length, + const sah_Mem_Util * mu, + sah_Head_Desc ** desc_chain); + +/*! Add a descriptor with an input and key output pointer */ +fsl_shw_return_t sah_add_in_keyout_desc(uint32_t header, + const uint8_t * in, uint32_t in_length, + fsl_shw_sko_t * key_info, + const sah_Mem_Util * mu, + sah_Head_Desc ** desc_chain); + +/*! Add a descriptor with a key and an output pointer */ +fsl_shw_return_t sah_add_key_out_desc(uint32_t header, fsl_shw_sko_t * key_info, + uint8_t * out, uint32_t out_length, + const sah_Mem_Util * mu, + sah_Head_Desc ** desc_chain); + +/*! Add a descriptor with an output and input pointer */ +fsl_shw_return_t sah_add_out_in_desc(uint32_t header, + uint8_t * out, uint32_t out_length, + const uint8_t * in, uint32_t in_length, + const sah_Mem_Util * mu, + sah_Head_Desc ** desc_chain); + +/*! Verify that supplied User Context Object is valid */ +fsl_shw_return_t sah_validate_uco(fsl_shw_uco_t * uco); + +#endif /* SF_UTIL_H */ + +/* End of sf_util.h */ diff --git a/drivers/mxc/security/sahara2/km_adaptor.c b/drivers/mxc/security/sahara2/km_adaptor.c new file mode 100644 index 000000000000..89bb6a778163 --- /dev/null +++ b/drivers/mxc/security/sahara2/km_adaptor.c @@ -0,0 +1,630 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! +* @file km_adaptor.c +* +* @brief The Adaptor component provides an interface to the +* driver for a kernel user. +*/ + +#include <adaptor.h> +#include <sf_util.h> +#include <sah_queue_manager.h> +#include <sah_memory_mapper.h> +#ifdef FSL_HAVE_SCC +#include <asm/arch/mxc_scc_driver.h> +#else +#include <asm/arch/mxc_scc2_driver.h> +#endif + + +EXPORT_SYMBOL(adaptor_Exec_Descriptor_Chain); +EXPORT_SYMBOL(sah_register); +EXPORT_SYMBOL(sah_deregister); +EXPORT_SYMBOL(sah_get_results); +EXPORT_SYMBOL(do_scc_slot_alloc); +EXPORT_SYMBOL(do_scc_slot_dealloc); +EXPORT_SYMBOL(do_scc_slot_load_slot); +EXPORT_SYMBOL(do_scc_slot_encrypt); +EXPORT_SYMBOL(do_scc_slot_decrypt); + +#if defined(DIAG_DRV_IF) || defined(DIAG_MEM) || defined(DIAG_ADAPTOR) +#include <diagnostic.h> +#endif + +#if defined(DIAG_DRV_IF) || defined(DIAG_MEM) || defined(DIAG_ADAPTOR) +#define MAX_DUMP 16 + +#define DIAG_MSG_SIZE 300 +static char Diag_msg[DIAG_MSG_SIZE]; +#endif + +/* This is the wait queue to this mode of driver */ +DECLARE_WAIT_QUEUE_HEAD(Wait_queue_km); + +#ifdef DIAG_ADAPTOR +void km_Dump_Chain(const sah_Desc * chain); + +void km_Dump_Region(const char *prefix, const unsigned char *data, + unsigned length); + +static void km_Dump_Link(const char *prefix, const sah_Link * link); + +void km_Dump_Words(const char *prefix, const unsigned *data, unsigned length); +#endif + +/**** Memory routines ****/ + +static void *my_malloc(void *ref, size_t n) +{ + register void *mem; + +#ifndef DIAG_MEM_ERRORS + mem = os_alloc_memory(n, GFP_KERNEL); + +#else + { + uint32_t rand; + /* are we feeling lucky ? */ + os_get_random_bytes(&rand, sizeof(rand)); + if ((rand % DIAG_MEM_CONST) == 0) { + mem = 0; + } else { + mem = os_alloc_memory(n, GFP_ATOMIC); + } + } +#endif /* DIAG_MEM_ERRORS */ + +#ifdef DIAG_MEM + sprintf(Diag_msg, "API kmalloc: %p for %d\n", mem, n); + LOG_KDIAG(Diag_msg); +#endif + ref = 0; /* unused param warning */ + return mem; +} + +static sah_Head_Desc *my_alloc_head_desc(void *ref) +{ + register sah_Head_Desc *ptr; + +#ifndef DIAG_MEM_ERRORS + ptr = sah_Alloc_Head_Descriptor(); + +#else + { + uint32_t rand; + /* are we feeling lucky ? */ + os_get_random_bytes(&rand, sizeof(rand)); + if ((rand % DIAG_MEM_CONST) == 0) { + ptr = 0; + } else { + ptr = sah_Alloc_Head_Descriptor(); + } + } +#endif + ref = 0; + return ptr; +} + +static sah_Desc *my_alloc_desc(void *ref) +{ + register sah_Desc *ptr; + +#ifndef DIAG_MEM_ERRORS + ptr = sah_Alloc_Descriptor(); + +#else + { + uint32_t rand; + /* are we feeling lucky ? */ + os_get_random_bytes(&rand, sizeof(rand)); + if ((rand % DIAG_MEM_CONST) == 0) { + ptr = 0; + } else { + ptr = sah_Alloc_Descriptor(); + } + } +#endif + ref = 0; + return ptr; +} + +static sah_Link *my_alloc_link(void *ref) +{ + register sah_Link *ptr; + +#ifndef DIAG_MEM_ERRORS + ptr = sah_Alloc_Link(); + +#else + { + uint32_t rand; + /* are we feeling lucky ? */ + os_get_random_bytes(&rand, sizeof(rand)); + if ((rand % DIAG_MEM_CONST) == 0) { + ptr = 0; + } else { + ptr = sah_Alloc_Link(); + } + } +#endif + ref = 0; + return ptr; +} + +static void my_free(void *ref, void *ptr) +{ + ref = 0; /* unused param warning */ +#ifdef DIAG_MEM + sprintf(Diag_msg, "API kfree: %p\n", ptr); + LOG_KDIAG(Diag_msg); +#endif + os_free_memory(ptr); +} + +static void my_free_head_desc(void *ref, sah_Head_Desc * ptr) +{ + sah_Free_Head_Descriptor(ptr); +} + +static void my_free_desc(void *ref, sah_Desc * ptr) +{ + sah_Free_Descriptor(ptr); +} + +static void my_free_link(void *ref, sah_Link * ptr) +{ + sah_Free_Link(ptr); +} + +static void *my_memcpy(void *ref, void *dest, const void *src, size_t n) +{ + ref = 0; /* unused param warning */ + return memcpy(dest, src, n); +} + +static void *my_memset(void *ref, void *ptr, int ch, size_t n) +{ + ref = 0; /* unused param warning */ + return memset(ptr, ch, n); +} + +/*! Standard memory manipulation routines for kernel API. */ +static sah_Mem_Util std_kernelmode_mem_util = { + .mu_ref = 0, + .mu_malloc = my_malloc, + .mu_alloc_head_desc = my_alloc_head_desc, + .mu_alloc_desc = my_alloc_desc, + .mu_alloc_link = my_alloc_link, + .mu_free = my_free, + .mu_free_head_desc = my_free_head_desc, + .mu_free_desc = my_free_desc, + .mu_free_link = my_free_link, + .mu_memcpy = my_memcpy, + .mu_memset = my_memset +}; + +/*! + * Sends a request to register this user + * + * @brief Sends a request to register this user + * + * @param[in,out] user_ctx part of the structure contains input parameters and + * part is filled in by the driver + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t sah_register(fsl_shw_uco_t * user_ctx) +{ + fsl_shw_return_t status; + + /* this field is used in user mode to indicate a file open has occured. + * it is used here, in kernel mode, to indicate that the uco is registered + */ + user_ctx->sahara_openfd = 0; /* set to 'registered' */ + user_ctx->mem_util = &std_kernelmode_mem_util; + + /* check that uco is valid */ + status = sah_validate_uco(user_ctx); + + /* If life is good, register this user */ + if (status == FSL_RETURN_OK_S) { + status = sah_handle_registration(user_ctx); + } + + if (status != FSL_RETURN_OK_S) { + user_ctx->sahara_openfd = -1; /* set to 'not registered' */ + } + + return status; +} + +/*! + * Sends a request to deregister this user + * + * @brief Sends a request to deregister this user + * + * @param[in,out] user_ctx Info on user being deregistered. + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t sah_deregister(fsl_shw_uco_t * user_ctx) +{ + fsl_shw_return_t status = FSL_RETURN_OK_S; + + if (user_ctx->sahara_openfd == 0) { + status = sah_handle_deregistration(user_ctx); + user_ctx->sahara_openfd = -1; /* set to 'no registered */ + } + + return status; +} + +/*! + * Sends a request to get results for this user + * + * @brief Sends a request to get results for this user + * + * @param[in,out] arg Pointer to structure to collect results + * @param uco User's context + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t sah_get_results(sah_results * arg, fsl_shw_uco_t * uco) +{ + fsl_shw_return_t code = sah_get_results_from_pool(uco, arg); + + if ((code == FSL_RETURN_OK_S) && (arg->actual != 0)) { + sah_Postprocess_Results(uco, arg); + } + + return code; +} + +/*! + * This function writes the Descriptor Chain to the kernel driver. + * + * @brief Writes the Descriptor Chain to the kernel driver. + * + * @param dar A pointer to a Descriptor Chain of type sah_Head_Desc + * @param uco The user context object + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t adaptor_Exec_Descriptor_Chain(sah_Head_Desc * dar, + fsl_shw_uco_t * uco) +{ + sah_Head_Desc *kernel_space_desc = NULL; + fsl_shw_return_t code = FSL_RETURN_OK_S; + int os_error_code = 0; + unsigned blocking_mode = dar->uco_flags & FSL_UCO_BLOCKING_MODE; + +#ifdef DIAG_ADAPTOR + km_Dump_Chain(&dar->desc); +#endif + + dar->user_info = uco; + dar->user_desc = dar; + + /* This code has been shamelessly copied from sah_driver_interface.c */ + /* It needs to be moved somewhere common ... */ + kernel_space_desc = sah_Physicalise_Descriptors(dar); + + if (kernel_space_desc == NULL) { + /* We may have failed due to a -EFAULT as well, but we will return + * -ENOMEM since either way it is a memory related failure. */ + code = FSL_RETURN_NO_RESOURCE_S; +#ifdef DIAG_DRV_IF + LOG_KDIAG("sah_Physicalise_Descriptors() failed\n"); +#endif + } else { + if (blocking_mode) { +#ifdef SAHARA_POLL_MODE + os_error_code = sah_Handle_Poll(dar); +#else + os_error_code = sah_blocking_mode(dar); +#endif + if (os_error_code != 0) { + code = FSL_RETURN_ERROR_S; + } else { /* status of actual operation */ + code = dar->result; + } + } else { +#ifdef SAHARA_POLL_MODE + sah_Handle_Poll(dar); +#else + /* just put someting in the DAR */ + sah_Queue_Manager_Append_Entry(dar); +#endif /* SAHARA_POLL_MODE */ + } + } + + return code; +} + +/*! + * Allocates a slot in the SCC + * + * @brief Allocates a slot in the SCC + * + * @param user_ctx + * @param key_len + * @param ownerid + * @param slot + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t do_scc_slot_alloc(fsl_shw_uco_t * user_ctx, + uint32_t key_len, + uint64_t ownerid, uint32_t * slot) +{ + scc_return_t scc_status = scc_alloc_slot(key_len, ownerid, slot); + fsl_shw_return_t ret; + + if (scc_status == SCC_RET_OK) { + ret = FSL_RETURN_OK_S; + } else { + ret = FSL_RETURN_NO_RESOURCE_S; + } + + user_ctx = NULL; + return ret; +} + +/*! + * Deallocates a slot in the SCC + * + * @brief Deallocates a slot in the SCC + * + * @param user_ctx + * @param ownerid + * @param slot + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t do_scc_slot_dealloc(fsl_shw_uco_t * user_ctx, + uint64_t ownerid, uint32_t slot) +{ + scc_return_t scc_status = scc_dealloc_slot(ownerid, slot); + user_ctx = NULL; + return (scc_status == + SCC_RET_OK) ? FSL_RETURN_OK_S : FSL_RETURN_ERROR_S; +} + +/*! + * Populate a slot in the SCC + * + * @brief Deallocates a slot in the SCC + * + * @param user_ctx + * @param uint32_t slot + * @param key + * @param key_length + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t do_scc_slot_load_slot(fsl_shw_uco_t * user_ctx, + uint64_t ownerid, uint32_t slot, + const uint8_t * key, uint32_t key_length) +{ + scc_return_t scc_status = scc_load_slot(ownerid, slot, (void *)key, + key_length); + user_ctx = NULL; + return (scc_status == + SCC_RET_OK) ? FSL_RETURN_OK_S : FSL_RETURN_ERROR_S; +} + +/*! + * Encrypt what's in a slot on the SCC + * + * @brief Encrypt what's in a slot on the SCC + * + * @param user_ctx + * @param ownerid + * @param slot + * @param key_length + * @param black_data + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t do_scc_slot_encrypt(fsl_shw_uco_t * user_ctx, + uint64_t ownerid, + uint32_t slot, + uint32_t key_length, uint8_t * black_data) +{ + scc_return_t scc_code; + + user_ctx = NULL; /* for unused-param warning */ + scc_code = scc_encrypt_slot(ownerid, slot, key_length, black_data); + return (scc_code == SCC_RET_OK) ? FSL_RETURN_OK_S : FSL_RETURN_ERROR_S; +} + +/*! + * Deallocates a slot in the SCC + * + * @brief Deallocates a slot in the SCC + * + * @param user_ctx + * @param ownerid + * @param slot + * @param key_length + * @param black_data + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t do_scc_slot_decrypt(fsl_shw_uco_t * user_ctx, + uint64_t ownerid, + uint32_t slot, + uint32_t key_length, + const uint8_t * black_data) +{ + scc_return_t scc_code; + + user_ctx = NULL; /* for unused-param warning */ + scc_code = scc_decrypt_slot(ownerid, slot, key_length, black_data); + return (scc_code == SCC_RET_OK) ? FSL_RETURN_OK_S : FSL_RETURN_ERROR_S; +} + +#ifdef DIAG_ADAPTOR +/*! + * Dump chain of descriptors to the log. + * + * @brief Dump descriptor chain + * + * @param chain Kernel virtual address of start of chain of descriptors + * + * @return void + */ +void km_Dump_Chain(const sah_Desc * chain) +{ + while (chain != NULL) { + km_Dump_Words("Desc", (unsigned *)chain, + 6 /*sizeof(*chain)/sizeof(unsigned) */ ); + /* place this definition elsewhere */ + if (chain->ptr1) { + if (chain->header & SAH_HDR_LLO) { + km_Dump_Region(" Data1", chain->ptr1, + chain->len1); + } else { + km_Dump_Link(" Link1", chain->ptr1); + } + } + if (chain->ptr2) { + if (chain->header & SAH_HDR_LLO) { + km_Dump_Region(" Data2", chain->ptr2, + chain->len2); + } else { + km_Dump_Link(" Link2", chain->ptr2); + } + } + + chain = chain->next; + } +} + +/*! + * Dump chain of links to the log. + * + * @brief Dump chain of links + * + * @param prefix Text to put in front of dumped data + * @param link Kernel virtual address of start of chain of links + * + * @return void + */ +static void km_Dump_Link(const char *prefix, const sah_Link * link) +{ + while (link != NULL) { + km_Dump_Words(prefix, (unsigned *)link, + 3 /* # words in h/w link */ ); + if (link->flags & SAH_STORED_KEY_INFO) { +#ifdef CAN_DUMP_SCC_DATA + uint32_t len; +#endif + +#ifdef CAN_DUMP_SCC_DATA + { + char buf[50]; + + scc_get_slot_info(link->ownerid, link->slot, (uint32_t *) & link->data, /* RED key address */ + &len); /* key length */ + sprintf(buf, " SCC slot %d: ", link->slot); + km_Dump_Words(buf, + (void *)IO_ADDRESS((uint32_t) + link->data), + link->len / 4); + } +#else + sprintf(Diag_msg, " SCC slot %d", link->slot); + LOG_KDIAG(Diag_msg); +#endif + } else if (link->data != NULL) { + km_Dump_Region(" Data", link->data, link->len); + } + + link = link->next; + } +} + +/*! + * Dump given region of data to the log. + * + * @brief Dump data + * + * @param prefix Text to put in front of dumped data + * @param data Kernel virtual address of start of region to dump + * @param length Amount of data to dump + * + * @return void +*/ +void km_Dump_Region(const char *prefix, const unsigned char *data, + unsigned length) +{ + unsigned count; + char *output; + unsigned data_len; + + sprintf(Diag_msg, "%s (%08X,%u):", prefix, (uint32_t) data, length); + + /* Restrict amount of data to dump */ + if (length > MAX_DUMP) { + data_len = MAX_DUMP; + } else { + data_len = length; + } + + /* We've already printed some text in output buffer, skip over it */ + output = Diag_msg + strlen(Diag_msg); + + for (count = 0; count < data_len; count++) { + if (count % 4 == 0) { + *output++ = ' '; + } + sprintf(output, "%02X", *data++); + output += 2; + } + + LOG_KDIAG(Diag_msg); +} + +/*! + * Dump given wors of data to the log. + * + * @brief Dump data + * + * @param prefix Text to put in front of dumped data + * @param data Kernel virtual address of start of region to dump + * @param word_count Amount of data to dump + * + * @return void +*/ +void km_Dump_Words(const char *prefix, const unsigned *data, + unsigned word_count) +{ + char *output; + + sprintf(Diag_msg, "%s (%08X,%uw): ", prefix, (uint32_t) data, + word_count); + + /* We've already printed some text in output buffer, skip over it */ + output = Diag_msg + strlen(Diag_msg); + + while (word_count--) { + sprintf(output, "%08X ", *data++); + output += 9; + } + + LOG_KDIAG(Diag_msg); +} +#endif diff --git a/drivers/mxc/security/sahara2/sah_driver_interface.c b/drivers/mxc/security/sahara2/sah_driver_interface.c new file mode 100644 index 000000000000..aacb8d1cb888 --- /dev/null +++ b/drivers/mxc/security/sahara2/sah_driver_interface.c @@ -0,0 +1,1325 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! +* @file sah_driver_interface.c +* +* @brief Provides a Linux Kernel Module interface to the SAHARA h/w device. +* +*/ + +/* SAHARA Includes */ +#include <sah_driver_common.h> +#include <sah_kernel.h> +#include <sah_memory_mapper.h> +#include <sah_queue_manager.h> +#include <sah_status_manager.h> +#include <sah_interrupt_handler.h> +#include <sah_hardware_interface.h> +#include <adaptor.h> +#ifdef FSL_HAVE_SCC +#include <asm/arch/mxc_scc_driver.h> +#else +#include <asm/arch/mxc_scc2_driver.h> +#endif + +#ifdef DIAG_DRV_IF +#include <diagnostic.h> +#endif + +#if defined(CONFIG_DEVFS_FS) && (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)) +#include <linux/devfs_fs_kernel.h> +#else +#include <linux/proc_fs.h> +#endif + +#include <asm/arch/spba.h> + +#ifdef PERF_TEST +#define interruptible_sleep_on(x) sah_Handle_Interrupt() +#endif + +#define TEST_MODE_OFF 1 +#define TEST_MODE_ON 2 + +/*! Version register on first deployments */ +#define SAHARA_VERSION2 2 +/*! Version register on MX27 */ +#define SAHARA_VERSION3 3 +/*! Version register on MXC92323 */ +#define SAHARA_VERSION4 4 + +/****************************************************************************** +* Module function declarations +******************************************************************************/ + +OS_DEV_INIT_DCL(sah_init); +OS_DEV_SHUTDOWN_DCL(sah_cleanup); +OS_DEV_OPEN_DCL(sah_open); +OS_DEV_CLOSE_DCL(sah_release); +OS_DEV_IOCTL_DCL(sah_ioctl); + +static void sah_user_callback(fsl_shw_uco_t * uco); +static os_error_code sah_handle_scc_slot_alloc(uint32_t info); +static os_error_code sah_handle_scc_slot_dealloc(uint32_t info); +static os_error_code sah_handle_scc_slot_load(uint32_t info); +static os_error_code sah_handle_scc_slot_decrypt(uint32_t info); +static os_error_code sah_handle_scc_slot_encrypt(uint32_t info); + +/*! Boolean flag for whether interrupt handler needs to be released on exit */ +static unsigned interrupt_registered; + +static int handle_sah_ioctl_dar(fsl_shw_uco_t * filp, uint32_t user_space_desc); + +#if !defined(CONFIG_DEVFS_FS) || (LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0)) +static int sah_read_procfs(char *buf, + char **start, + off_t offset, int count, int *eof, void *data); + +static int sah_write_procfs(struct file *file, const char __user * buffer, + unsigned long count, void *data); +#endif + +#if defined(CONFIG_DEVFS_FS) && (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)) + +/* This is a handle to the sahara DEVFS entry. */ +static devfs_handle_t Sahara_devfs_handle; + +#else + +/* Major number assigned to our device driver */ +static int Major; + +/* This is a handle to the sahara PROCFS entry */ +static struct proc_dir_entry *Sahara_procfs_handle; + +#endif + +uint32_t sah_hw_version; + +/* This is the wait queue to this driver. Linux declaration. */ +DECLARE_WAIT_QUEUE_HEAD(Wait_queue); + +/* This is a global variable that is used to track how many times the device + * has been opened simultaneously. */ +#ifdef DIAG_DRV_IF +static int Device_in_use = 0; +#endif + +/*! + * OS-dependent handle used for registering user interface of a driver. + */ +static os_driver_reg_t reg_handle; + +#ifdef DIAG_DRV_IF +/* This is for sprintf() to use when constructing output. */ +#define DIAG_MSG_SIZE 1024 +static char Diag_msg[DIAG_MSG_SIZE]; +#endif + +/*! +******************************************************************************* +* This function gets called when the module is inserted (insmod) into the +* running kernel. +* +* @brief SAHARA device initialisation function. +* +* @return 0 on success +* @return -EBUSY if the device or proc file entry cannot be created. +* @return OS_ERROR_NO_MEMORY_S if kernel memory could not be allocated. +* @return OS_ERROR_FAIL_S if initialisation of proc entry failed +*/ +OS_DEV_INIT(sah_init) +{ + /* Status variable */ + int os_error_code = 0; +#ifdef DIAG_DRV_IF + char err_string[200]; +#endif + + interrupt_registered = 0; + + /* Enable the SAHARA Clocks */ +#ifdef DIAG_DRV_IF + LOG_KDIAG("SAHARA : Enabling the IPG and AHB clocks\n") +#endif /*DIAG_DRV_IF */ +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,18)) + mxc_clks_enable(SAHARA2_CLK); +#else + { + struct clk *clk = clk_get(NULL, "sahara_clk"); + if (clk != ERR_PTR(ENOENT)) { + clk_enable(clk); + } + } +#endif + + /* Check for SPBA need */ +#if defined(CONFIG_ARCH_MXC91231) || defined(CONFIG_ARCH_MXC91321) + /* This needs to be a PLATFORM abstraction */ + if (spba_take_ownership(SPBA_SAHARA, SPBA_MASTER_A)) { +#ifdef DIAG_DRV_IF + LOG_KDIAG("Sahara driver could not take ownership of Sahara\n"); +#endif + os_error_code = OS_ERROR_FAIL_S; + } +#endif /* SPBA */ + + if (os_error_code == 0) { + sah_hw_version = sah_HW_Read_Version(); + printk("Sahara HW Version is 0x%08x\n", sah_hw_version); + + /* verify code and hardware are version compatible */ + if ((sah_hw_version != SAHARA_VERSION2) + && (sah_hw_version != SAHARA_VERSION3)) { + if (((sah_hw_version >> 8) & 0xff) != SAHARA_VERSION4) { + printk + ("Sahara HW Version was not expected value.\n"); + os_error_code = OS_ERROR_FAIL_S; + } + } + } + + if (os_error_code == 0) { +#ifdef DIAG_DRV_IF + LOG_KDIAG("Calling sah_Init_Mem_Map to initialise " + "memory subsystem."); +#endif + /* Do any memory-routine initialization */ + os_error_code = sah_Init_Mem_Map(); + } + + if (os_error_code == 0) { +#ifdef DIAG_DRV_IF + LOG_KDIAG("Calling sah_HW_Reset() to Initialise the Hardware."); +#endif + /* Initialise the hardware */ + os_error_code = sah_HW_Reset(); + if (os_error_code != OS_ERROR_OK_S) { + os_printk + ("sah_HW_Reset() failed to Initialise the Hardware.\n"); + } + + } + + if (os_error_code == 0) { +#if defined(CONFIG_DEVFS_FS) && (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)) + /* Register the DEVFS entry */ + Sahara_devfs_handle = devfs_register(NULL, + SAHARA_DEVICE_SHORT, + DEVFS_FL_AUTO_DEVNUM, + 0, 0, + SAHARA_DEVICE_MODE, + &Fops, NULL); + if (Sahara_devfs_handle == NULL) { +#ifdef DIAG_DRV_IF + LOG_KDIAG + ("Registering the DEVFS character device failed."); +#endif /* DIAG_DRV_IF */ + os_error_code = -EBUSY; + } +#else /* CONFIG_DEVFS_FS */ + /* Create the PROCFS entry. This is used to report the assigned device + * major number back to user-space. */ +#if 1 + Sahara_procfs_handle = create_proc_entry(SAHARA_DEVICE_SHORT, 0700, /* default mode */ + NULL); /* parent dir */ + if (Sahara_procfs_handle == NULL) { +#ifdef DIAG_DRV_IF + LOG_KDIAG("Registering the PROCFS interface failed."); +#endif /* DIAG_DRV_IF */ + os_error_code = OS_ERROR_FAIL_S; + } else { + Sahara_procfs_handle->nlink = 1; + Sahara_procfs_handle->data = 0; + Sahara_procfs_handle->read_proc = sah_read_procfs; + Sahara_procfs_handle->write_proc = sah_write_procfs; + } +#endif /* #if 1 */ + } + + if (os_error_code == 0) { +#ifdef DIAG_DRV_IF + LOG_KDIAG + ("Calling sah_Queue_Manager_Init() to Initialise the Queue " + "Manager."); +#endif + /* Initialise the Queue Manager */ + if (sah_Queue_Manager_Init() != FSL_RETURN_OK_S) { + os_error_code = -ENOMEM; + } + } +#ifndef SAHARA_POLL_MODE + if (os_error_code == 0) { +#ifdef DIAG_DRV_IF + LOG_KDIAG("Calling sah_Intr_Init() to Initialise the Interrupt " + "Handler."); +#endif + /* Initialise the Interrupt Handler */ + os_error_code = sah_Intr_Init(&Wait_queue); + if (os_error_code == OS_ERROR_OK_S) { + interrupt_registered = 1; + } + } +#endif /* ifndef SAHARA_POLL_MODE */ + +#ifdef SAHARA_POWER_MANAGEMENT + if (os_error_code == 0) { + /* set up dynamic power management (dmp) */ + os_error_code = sah_dpm_init(); + } +#endif + + if (os_error_code == 0) { + os_driver_init_registration(reg_handle); + os_driver_add_registration(reg_handle, OS_FN_OPEN, + OS_DEV_OPEN_REF(sah_open)); + os_driver_add_registration(reg_handle, OS_FN_IOCTL, + OS_DEV_IOCTL_REF(sah_ioctl)); + os_driver_add_registration(reg_handle, OS_FN_CLOSE, + OS_DEV_CLOSE_REF(sah_release)); + os_error_code = + os_driver_complete_registration(reg_handle, Major, + "sahara"); + + if (os_error_code < 0) { +#ifdef DIAG_DRV_IF + snprintf(Diag_msg, DIAG_MSG_SIZE, + "Registering the regular " + "character device failed with error code: %d\n", + os_error_code); + LOG_KDIAG(Diag_msg); +#endif + } + } +#endif /* CONFIG_DEVFS_FS */ + + if (os_error_code != 0) { +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)) + cleanup_module(); +#else + sah_cleanup(); +#endif + } +#ifdef DIAG_DRV_IF + else { + sprintf(err_string, "Sahara major node is %d\n", Major); + LOG_KDIAG(err_string); + } +#endif + + os_dev_init_return(os_error_code); +} + +/*! +******************************************************************************* +* This function gets called when the module is removed (rmmod) from the running +* kernel. +* +* @brief SAHARA device clean-up function. +* +* @return void +*/ +OS_DEV_SHUTDOWN(sah_cleanup) +{ + /* Unregister the device */ +#if defined(CONFIG_DEVFS_FS) && (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)) + devfs_unregister(Sahara_devfs_handle); +#else + int ret_val = 0; + + if (Sahara_procfs_handle != NULL) { + remove_proc_entry(SAHARA_DEVICE_SHORT, NULL); + } + + if (Major >= 0) { + ret_val = os_driver_remove_registration(reg_handle); + } +#ifdef DIAG_DRV_IF + if (ret_val < 0) { + snprintf(Diag_msg, DIAG_MSG_SIZE, "Error while attempting to " + "unregister the device: %d\n", ret_val); + LOG_KDIAG(Diag_msg); + } +#endif + +#endif /* CONFIG_DEVFS_FS */ + sah_Queue_Manager_Close(); + +#ifndef SAHARA_POLL_MODE + if (interrupt_registered) { + sah_Intr_Release(); + interrupt_registered = 0; + } +#endif + sah_Stop_Mem_Map(); +#ifdef SAHARA_POWER_MANAGEMENT + sah_dpm_close(); +#endif + +#ifdef DIAG_DRV_IF + LOG_KDIAG("SAHARA : Disabling the clocks\n") +#endif /* DIAG_DRV_IF */ +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,18)) + mxc_clks_disable(SAHARA2_CLK); +#else + { + struct clk *clk = clk_get(NULL, "sahara_clk"); + if (clk != ERR_PTR(ENOENT)) { + clk_disable(clk); + } + } +#endif + + os_dev_shutdown_return(OS_ERROR_OK_S); +} + +/*! +******************************************************************************* +* This function simply increments the module usage count. +* +* @brief SAHARA device open function. +* +* @param inode Part of the kernel prototype. +* @param file Part of the kernel prototype. +* +* @return 0 - Always returns 0 since any number of calls to this function are +* allowed. +* +*/ +OS_DEV_OPEN(sah_open) +{ + +#if defined(LINUX_VERSION) && (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,10)) + MOD_INC_USE_COUNT; +#endif + +#ifdef DIAG_DRV_IF + Device_in_use++; + snprintf(Diag_msg, DIAG_MSG_SIZE, + "Incrementing module use count to: %d ", Device_in_use); + LOG_KDIAG(Diag_msg); +#endif + + os_dev_set_user_private(NULL); + + /* Return 0 to indicate success */ + os_dev_open_return(0); +} + +/*! +******************************************************************************* +* This function simply decrements the module usage count. +* +* @brief SAHARA device release function. +* +* @param inode Part of the kernel prototype. +* @param file Part of the kernel prototype. +* +* @return 0 - Always returns 0 since this function does not fail. +*/ +OS_DEV_CLOSE(sah_release) +{ + fsl_shw_uco_t *user_ctx = os_dev_get_user_private(); + +#if defined(LINUX_VERSION) && (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,10)) + MOD_DEC_USE_COUNT; +#endif + +#ifdef DIAG_DRV_IF + Device_in_use--; + snprintf(Diag_msg, DIAG_MSG_SIZE, + "Decrementing module use count to: %d ", Device_in_use); + LOG_KDIAG(Diag_msg); +#endif + + if (user_ctx != NULL) { + sah_handle_deregistration(user_ctx); + os_free_memory(user_ctx); + os_dev_set_user_private(NULL); + } + + /* Return 0 to indicate success */ + os_dev_close_return(OS_ERROR_OK_S); +} + +/*! +******************************************************************************* +* This function provides the IO Controls for the SAHARA driver. Three IO +* Controls are supported: +* +* SAHARA_HWRESET and +* SAHARA_SET_HA +* SAHARA_CHK_TEST_MODE +* +* @brief SAHARA device IO Control function. +* +* @param inode Part of the kernel prototype. +* @param filp Part of the kernel prototype. +* @param cmd Part of the kernel prototype. +* @param arg Part of the kernel prototype. +* +* @return 0 on success +* @return -EBUSY if the HA bit could not be set due to busy hardware. +* @return -ENOTTY if an unsupported IOCTL was attempted on the device. +* @return -EFAULT if put_user() fails +*/ +OS_DEV_IOCTL(sah_ioctl) +{ + int status = 0; + int test_mode; + + switch (os_dev_get_ioctl_op()) { + case SAHARA_HWRESET: +#ifdef DIAG_DRV_IF + LOG_KDIAG("SAHARA_HWRESET IOCTL."); +#endif + /* We need to reset the hardware. */ + sah_HW_Reset(); + + /* Mark all the entries in the Queue Manager's queue with state + * SAH_STATE_RESET. + */ + sah_Queue_Manager_Reset_Entries(); + + /* Wake up all sleeping write() calls. */ + wake_up_interruptible(&Wait_queue); + break; +#ifdef SAHARA_HA_ENABLED + case SAHARA_SET_HA: +#ifdef DIAG_DRV_IF + LOG_KDIAG("SAHARA_SET_HA IOCTL."); +#endif /* DIAG_DRV_IF */ + if (sah_HW_Set_HA() == ERR_INTERNAL) { + status = -EBUSY; + } + break; +#endif /* SAHARA_HA_ENABLED */ + + case SAHARA_CHK_TEST_MODE: + /* load test_mode */ + test_mode = TEST_MODE_OFF; +#ifdef DIAG_DRV_IF + LOG_KDIAG("SAHARA_CHECK_TEST_MODE IOCTL."); + test_mode = TEST_MODE_ON; +#endif /* DIAG_DRV_IF */ +#if defined(KERNEL_TEST) || defined(PERF_TEST) + test_mode = TEST_MODE_ON; +#endif /* KERNEL_TEST || PERF_TEST */ + /* copy test_mode back to user space. put_user() is Linux fn */ + /* compiler warning `register': no problem found so ignored */ + status = put_user(test_mode, (int *)os_dev_get_ioctl_arg()); + break; + + case SAHARA_DAR: +#ifdef DIAG_DRV_IF + LOG_KDIAG("SAHARA_DAR IOCTL."); +#endif /* DIAG_DRV_IF */ + { + fsl_shw_uco_t *user_ctx = os_dev_get_user_private(); + + if (user_ctx != NULL) { + status = + handle_sah_ioctl_dar(user_ctx, + os_dev_get_ioctl_arg + ()); + } else { + status = OS_ERROR_FAIL_S; + } + } + break; + + case SAHARA_GET_RESULTS: +#ifdef DIAG_DRV_IF + LOG_KDIAG("SAHARA_GET_RESULTS IOCTL."); +#endif /* DIAG_DRV_IF */ + { + fsl_shw_uco_t *user_ctx = os_dev_get_user_private(); + + if (user_ctx != NULL) { + status = + sah_get_results_pointers(user_ctx, + os_dev_get_ioctl_arg + ()); + } else { + status = OS_ERROR_FAIL_S; + } + } + break; + + case SAHARA_REGISTER: +#ifdef DIAG_DRV_IF + LOG_KDIAG("SAHARA_REGISTER IOCTL."); +#endif /* DIAG_DRV_IF */ + { + fsl_shw_uco_t *user_ctx = os_dev_get_user_private(); + + if (user_ctx != NULL) { + status = OS_ERROR_FAIL_S; /* already registered */ + } else { + user_ctx = + os_alloc_memory(sizeof(fsl_shw_uco_t), + GFP_KERNEL); + if (user_ctx == NULL) { + status = OS_ERROR_NO_MEMORY_S; + } else { + /* Copy UCO from user, but only as big as the common UCO */ + if (os_copy_from_user(user_ctx, + (void *) + os_dev_get_ioctl_arg + (), + offsetof + (fsl_shw_uco_t, + result_pool))) { + status = OS_ERROR_FAIL_S; + } else { + os_dev_set_user_private + (user_ctx); + status = + sah_handle_registration + (user_ctx); + } + } + } + } + break; + + /* This ioctl cmd should disappear in favor of a close() routine. */ + case SAHARA_DEREGISTER: +#ifdef DIAG_DRV_IF + LOG_KDIAG("SAHARA_DEREGISTER IOCTL."); +#endif /* DIAG_DRV_IF */ + { + fsl_shw_uco_t *user_ctx = os_dev_get_user_private(); + + if (user_ctx == NULL) { + status = OS_ERROR_FAIL_S; + } else { + status = sah_handle_deregistration(user_ctx); + os_free_memory(user_ctx); + os_dev_set_user_private(NULL); + } + } + break; + + case SAHARA_SCC_ALLOC: +#ifdef DIAG_DRV_IF + LOG_KDIAG("SAHARA_SCC_ALLOC IOCTL."); +#endif /* DIAG_DRV_IF */ + status = sah_handle_scc_slot_alloc(os_dev_get_ioctl_arg()); + break; + + case SAHARA_SCC_DEALLOC: +#ifdef DIAG_DRV_IF + LOG_KDIAG("SAHARA_SCC_DEALLOC IOCTL."); +#endif /* DIAG_DRV_IF */ + status = sah_handle_scc_slot_dealloc(os_dev_get_ioctl_arg()); + break; + + case SAHARA_SCC_LOAD: +#ifdef DIAG_DRV_IF + LOG_KDIAG("SAHARA_SCC_LOAD IOCTL."); +#endif /* DIAG_DRV_IF */ + status = sah_handle_scc_slot_load(os_dev_get_ioctl_arg()); + break; + + case SAHARA_SCC_SLOT_DEC: + status = sah_handle_scc_slot_decrypt(os_dev_get_ioctl_arg()); + break; + + case SAHARA_SCC_SLOT_ENC: + status = sah_handle_scc_slot_encrypt(os_dev_get_ioctl_arg()); + break; + + default: +#ifdef DIAG_DRV_IF + LOG_KDIAG("Unknown SAHARA IOCTL."); +#endif /* DIAG_DRV_IF */ + status = OS_ERROR_FAIL_S; + } + + os_dev_ioctl_return(status); +} + +/*! +******************************************************************************* +* Allocates a slot in the SCC +* +* @brief Allocates a slot in the SCC +* +* @param info slot information +* +* @return 0 if pass; non-zero on error +*/ +static os_error_code sah_handle_scc_slot_alloc(uint32_t info) +{ + scc_slot_t slot_info; + os_error_code os_err; + scc_return_t scc_ret; + + os_err = os_copy_from_user(&slot_info, (void *)info, sizeof(slot_info)); + if (os_err == OS_ERROR_OK_S) { + scc_ret = + scc_alloc_slot(slot_info.key_length, slot_info.ownerid, + &slot_info.slot); + if (scc_ret == SCC_RET_OK) { + slot_info.code = FSL_RETURN_OK_S; + } else if (scc_ret == SCC_RET_INSUFFICIENT_SPACE) { + slot_info.code = FSL_RETURN_NO_RESOURCE_S; + } else { + slot_info.code = FSL_RETURN_ERROR_S; + } + + /* Return error code and slot info */ + os_err = + os_copy_to_user((void *)info, &slot_info, + sizeof(slot_info)); + + if (os_err != OS_ERROR_OK_S) { + (void)scc_dealloc_slot(slot_info.ownerid, + slot_info.slot); + } + } + + return os_err; +} + +/*! + * Deallocate a slot in the SCC + * + * @brief Deallocate a slot in the SCC + * + * @param info slot information + * + * @return 0 if pass; non-zero on error + */ +static os_error_code sah_handle_scc_slot_dealloc(uint32_t info) +{ + fsl_shw_return_t ret = FSL_RETURN_INTERNAL_ERROR_S; + scc_slot_t slot_info; + os_error_code os_err; + scc_return_t scc_ret; + + os_err = os_copy_from_user(&slot_info, (void *)info, sizeof(slot_info)); + if (os_err == OS_ERROR_OK_S) { + scc_ret = scc_dealloc_slot(slot_info.ownerid, slot_info.slot); + + if (scc_ret == SCC_RET_OK) { + ret = FSL_RETURN_OK_S; + } else { + ret = FSL_RETURN_ERROR_S; + } + slot_info.code = ret; + os_err = + os_copy_to_user((void *)info, &slot_info, + sizeof(slot_info)); + } + + return os_err; +} + +/*! + * Populate a slot in the SCC + * + * @brief Populate a slot in the SCC + * + * @param info slot information + * + * @return 0 if pass; non-zero on error + */ +static os_error_code sah_handle_scc_slot_load(uint32_t info) +{ + fsl_shw_return_t ret = FSL_RETURN_INTERNAL_ERROR_S; + scc_slot_t slot_info; + os_error_code os_err; + scc_return_t scc_ret; + uint8_t *key = NULL; + + os_err = os_copy_from_user(&slot_info, (void *)info, sizeof(slot_info)); + + if (os_err == OS_ERROR_OK_S) { + /* Allow slop in alloc in case we are rounding up to word multiple */ + key = os_alloc_memory(slot_info.key_length + 3, GFP_KERNEL); + if (key == NULL) { + ret = FSL_RETURN_NO_RESOURCE_S; + os_err = OS_ERROR_OK_S; + } else { + os_err = os_copy_from_user(key, slot_info.key, + slot_info.key_length); + } + } + + if (os_err == OS_ERROR_OK_S) { + unsigned key_length = slot_info.key_length; + + /* Round up if necessary, as SCC call wants a multiple of 32-bit + * values for the full object being loaded. */ + if ((key_length & 3) != 0) { + key_length += 4 - (key_length & 3); + } + scc_ret = scc_load_slot(slot_info.ownerid, slot_info.slot, key, + key_length); + if (scc_ret == SCC_RET_OK) { + ret = FSL_RETURN_OK_S; + } else { + ret = FSL_RETURN_ERROR_S; + } + + slot_info.code = ret; + os_err = + os_copy_to_user((void *)info, &slot_info, + sizeof(slot_info)); + } + + if (key != NULL) { + memset(key, 0, slot_info.key_length); + os_free_memory(key); + } + + return os_err; +} + +/*! + * Decrypt data into a slot in the SCC + * + * @brief Decrypt data into a slot in the SCC + * + * @param info user-space ptr to slot and data information + * + * @return 0 if pass; non-zero on error + */ +static os_error_code sah_handle_scc_slot_decrypt(uint32_t info) +{ + fsl_shw_return_t ret = FSL_RETURN_INTERNAL_ERROR_S; + scc_slot_t slot_info; /*!< decrypt request fields */ + os_error_code os_err; + scc_return_t scc_ret; + uint8_t *key = NULL; + + os_err = os_copy_from_user(&slot_info, (void *)info, sizeof(slot_info)); + + if (os_err == OS_ERROR_OK_S) { + key = os_alloc_memory(slot_info.key_length, GFP_KERNEL); + if (key == NULL) { + ret = FSL_RETURN_NO_RESOURCE_S; + os_err = OS_ERROR_OK_S; + } else { + os_err = os_copy_from_user(key, slot_info.key, + slot_info.key_length); + } + } + + if (os_err == OS_ERROR_OK_S) { + scc_ret = scc_decrypt_slot(slot_info.ownerid, slot_info.slot, + slot_info.key_length, key); + if (scc_ret == SCC_RET_OK) { + ret = FSL_RETURN_OK_S; + } else { + ret = FSL_RETURN_ERROR_S; + } + + slot_info.code = ret; + os_err = + os_copy_to_user((void *)info, &slot_info, + sizeof(slot_info)); + } + + if (key != NULL) { + memset(key, 0, slot_info.key_length); + os_free_memory(key); + } + + return os_err; +} + +/*! + * Encrypt data in a slot in the SCC + * + * @brief Encrypt data in a slot in the SCC + * + * @param info slot data and target location information + * + * @return 0 if pass; non-zero on error + */ +static os_error_code sah_handle_scc_slot_encrypt(uint32_t info) +{ + fsl_shw_return_t ret = FSL_RETURN_INTERNAL_ERROR_S; + scc_slot_t slot_info; + os_error_code os_err; + scc_return_t scc_ret; + uint8_t *key = NULL; + + os_err = os_copy_from_user(&slot_info, (void *)info, sizeof(slot_info)); + + if (os_err == OS_ERROR_OK_S) { + key = os_alloc_memory(slot_info.key_length, GFP_KERNEL); + if (key == NULL) { + ret = FSL_RETURN_NO_RESOURCE_S; + } + } + + if (key != NULL) { + scc_ret = scc_encrypt_slot(slot_info.ownerid, slot_info.slot, + slot_info.key_length, key); + + if (scc_ret != SCC_RET_OK) { + ret = FSL_RETURN_ERROR_S; + } else { + os_err = + os_copy_to_user(slot_info.key, key, + slot_info.key_length); + if (os_err != OS_ERROR_OK_S) { + ret = FSL_RETURN_INTERNAL_ERROR_S; + } else { + ret = FSL_RETURN_OK_S; + } + } + + slot_info.code = ret; + os_err = + os_copy_to_user((void *)info, &slot_info, + sizeof(slot_info)); + + memset(key, 0, slot_info.key_length); + os_free_memory(key); + } + + return os_err; +} + +/*! + * Register a user + * + * @brief Register a user + * + * @param user_ctx information about this user + * + * @return status code + */ +fsl_shw_return_t sah_handle_registration(fsl_shw_uco_t * user_ctx) +{ + /* Initialize the user's result pool (like sah_Queue_Construct() */ + user_ctx->result_pool.head = NULL; + user_ctx->result_pool.tail = NULL; + user_ctx->result_pool.count = 0; + + return FSL_RETURN_OK_S; +} + +/*! + * Deregister a user + * + * @brief Deregister a user + * + * @param user_ctx information about this user + * + * @return status code + */ +fsl_shw_return_t sah_handle_deregistration(fsl_shw_uco_t * user_ctx) +{ + + return FSL_RETURN_OK_S; +} + +/*! + * Sets up memory to extract results from results pool + * + * @brief Sets up memory to extract results from results pool + * + * @param user_ctx information about this user + * @param[in,out] arg contains input parameters and fields that the driver + * fills in + * + * @return os error code or 0 on success + */ +int sah_get_results_pointers(fsl_shw_uco_t * user_ctx, uint32_t arg) +{ + sah_results results_arg; /* kernel mode usable version of 'arg' */ + fsl_shw_result_t *user_results; /* user mode address of results */ + unsigned *user_actual; /* user mode address of actual number of results */ + unsigned actual; /* local memory of actual number of results */ + int ret_val = OS_ERROR_FAIL_S; + sah_Head_Desc *finished_request; + unsigned int loop; + + /* copy structure from user to kernel space */ + if (!os_copy_from_user(&results_arg, (void *)arg, sizeof(sah_results))) { + /* save user space pointers */ + user_actual = results_arg.actual; /* where count goes */ + user_results = results_arg.results; /* where results goe */ + + /* Set pointer for actual value to temporary kernel memory location */ + results_arg.actual = &actual; + + /* Allocate kernel memory to hold temporary copy of the results */ + results_arg.results = + os_alloc_memory(sizeof(fsl_shw_result_t) * + results_arg.requested, GFP_KERNEL); + + /* if memory allocated, continue */ + if (results_arg.results == NULL) { + ret_val = OS_ERROR_NO_MEMORY_S; + } else { + fsl_shw_return_t get_status; + + /* get the results */ + get_status = + sah_get_results_from_pool(user_ctx, &results_arg); + + /* free the copy of the user space descriptor chain */ + for (loop = 0; loop < actual; ++loop) { + /* get sah_Head_Desc from results and put user address into + * the return structure */ + finished_request = + results_arg.results[loop].user_desc; + results_arg.results[loop].user_desc = + finished_request->user_desc; + /* return the descriptor chain memory to the block free pool */ + sah_Free_Chained_Descriptors(finished_request); + } + + /* if no errors, copy results and then the actual number of results + * back to user space + */ + if (get_status == FSL_RETURN_OK_S) { + if (os_copy_to_user + (user_results, results_arg.results, + actual * sizeof(fsl_shw_result_t)) + || os_copy_to_user(user_actual, &actual, + sizeof(user_actual))) { + ret_val = OS_ERROR_FAIL_S; + } else { + ret_val = 0; /* no error */ + } + } + /* free the allocated memory */ + os_free_memory(results_arg.results); + } + } + + return ret_val; +} + +/*! + * Extracts results from results pool + * + * @brief Extract results from results pool + * + * @param user_ctx information about this user + * @param[in,out] arg contains input parameters and fields that the + * driver fills in + * + * @return status code + */ +fsl_shw_return_t sah_get_results_from_pool(volatile fsl_shw_uco_t * user_ctx, + sah_results * arg) +{ + sah_Head_Desc *finished_request; + unsigned int loop = 0; + os_lock_context_t int_flags; + + /* Get the number of results requested, up to total number of results + * available + */ + do { + /* Protect state of user's result pool until we have retrieved and + * remove the first entry, or determined that the pool is empty. */ + os_lock_save_context(desc_queue_lock, int_flags); + finished_request = user_ctx->result_pool.head; + + if (finished_request != NULL) { + sah_Queue_Remove_Entry((sah_Queue *) & user_ctx-> + result_pool); + os_unlock_restore_context(desc_queue_lock, int_flags); + + /* Prepare to free. */ + (void)sah_DePhysicalise_Descriptors(finished_request); + + arg->results[loop].user_ref = + finished_request->user_ref; + arg->results[loop].code = finished_request->result; + arg->results[loop].detail1 = + finished_request->fault_address; + arg->results[loop].detail2 = + finished_request->current_dar; + arg->results[loop].user_desc = finished_request; + + loop++; + } else { /* finished_request is NULL */ + /* pool is empty */ + os_unlock_restore_context(desc_queue_lock, int_flags); + } + + } while ((loop < arg->requested) && (finished_request != NULL)); + + /* record number of results actually obtained */ + *arg->actual = loop; + + return FSL_RETURN_OK_S; +} + +/*! + * Converts descriptor chain to kernel space (from user space) and submits + * chain to Sahara for processing + * + * @brief Submits converted descriptor chain to sahara + * + * @param user_ctx Pointer to Kernel version of user's ctx + * @param user_space_desc user space address of descriptor chain that is + * in user space + * + * @return OS status code + */ +static int handle_sah_ioctl_dar(fsl_shw_uco_t * user_ctx, + uint32_t user_space_desc) +{ + int os_error_code = OS_ERROR_FAIL_S; + sah_Head_Desc *desc_chain_head; /* chain in kernel - virtual address */ + + /* This will re-create the linked list so that the SAHARA hardware can + * DMA on it. + */ + desc_chain_head = + sah_Copy_Descriptors((sah_Head_Desc *) user_space_desc); + + if (desc_chain_head == NULL) { + /* We may have failed due to a -EFAULT as well, but we will return + * OS_ERROR_NO_MEMORY_S since either way it is a memory related + * failure. + */ + os_error_code = OS_ERROR_NO_MEMORY_S; + } else { + fsl_shw_return_t stat; + + desc_chain_head->user_info = user_ctx; + desc_chain_head->user_desc = (sah_Head_Desc *) user_space_desc; + + if (desc_chain_head->uco_flags & FSL_UCO_BLOCKING_MODE) { +#ifdef SAHARA_POLL_MODE + sah_Handle_Poll(desc_chain_head); +#else + sah_blocking_mode(desc_chain_head); +#endif + stat = desc_chain_head->result; + /* return the descriptor chain memory to the block free pool */ + sah_Free_Chained_Descriptors(desc_chain_head); + /* Tell user how the call turned out */ + + /* Copy 'result' back up to the result member. + * + * The dereference of the different member will cause correct the + * arithmetic to occur on the user-space address because of the + * missing dma/bus locations in the user mode version of the + * sah_Desc structure. */ + os_error_code = + os_copy_to_user((void *)(user_space_desc + + offsetof(sah_Head_Desc, + uco_flags)), + &stat, sizeof(fsl_shw_return_t)); + + } else { /* not blocking mode - queue and forget */ + + if (desc_chain_head->uco_flags & FSL_UCO_CALLBACK_MODE) { + user_ctx->process = os_get_process_handle(); + user_ctx->callback = sah_user_callback; + } +#ifdef SAHARA_POLL_MODE + /* will put results in result pool */ + sah_Handle_Poll(desc_chain_head); +#else + /* just put someting in the DAR */ + sah_Queue_Manager_Append_Entry(desc_chain_head); +#endif + /* assume all went well */ + os_error_code = OS_ERROR_OK_S; + } + } + + return os_error_code; +} + +static void sah_user_callback(fsl_shw_uco_t * uco) +{ + os_send_signal(uco->process, SIGUSR2); +} + +/*! + * This function is called when a thread attempts to read from the /proc/sahara + * file. Upon read, statistics and information about the state of the driver + * are returned in nthe supplied buffer. + * + * @brief SAHARA PROCFS read function. + * + * @param buf Anything written to this buffer will be returned to the + * user-space process that is reading from this proc entry. + * @param start Part of the kernel prototype. + * @param offset Part of the kernel prototype. + * @param count The size of the buf argument. + * @param eof An integer which is set to one to tell the user-space + * process that there is no more data to read. + * @param data Part of the kernel prototype. + * + * @return The number of bytes written to the proc entry. + */ +#if !defined(CONFIG_DEVFS_FS) || (LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0)) +static int sah_read_procfs(char *buf, + char **start, + off_t offset, int count, int *eof, void *data) +{ + int output_bytes = 0; + int in_queue_count = 0; + os_lock_context_t lock_context; + + os_lock_save_context(desc_queue_lock, lock_context); + in_queue_count = sah_Queue_Manager_Count_Entries(TRUE, 0); + os_unlock_restore_context(desc_queue_lock, lock_context); + output_bytes += snprintf(buf, count - output_bytes, "queued: %d\n", + in_queue_count); + output_bytes += snprintf(buf + output_bytes, count - output_bytes, + "Descriptors: %d, " + "Interrupts %d (%d Done1Done2, %d Done1Busy2, " + " %d Done1)\n", + dar_count, interrupt_count, done1done2_count, + done1busy2_count, done1_count); + output_bytes += snprintf(buf + output_bytes, count - output_bytes, + "Control: %08x\n", sah_HW_Read_Control()); +#if !defined(FSL_HAVE_SAHARA4) || defined(SAHARA4_NO_USE_SQUIB) + output_bytes += snprintf(buf + output_bytes, count - output_bytes, + "IDAR: %08x; CDAR: %08x\n", + sah_HW_Read_IDAR(), sah_HW_Read_CDAR()); +#endif +#ifdef DIAG_DRV_STATUS + output_bytes += snprintf(buf + output_bytes, count - output_bytes, + "Status: %08x; Error Status: %08x; Op Status: %08x\n", + sah_HW_Read_Status(), + sah_HW_Read_Error_Status(), + sah_HW_Read_Op_Status()); +#endif +#ifdef FSL_HAVE_SAHARA4 + output_bytes += snprintf(buf + output_bytes, count - output_bytes, + "MMStat: %08x; Config: %08x\n", + sah_HW_Read_MM_Status(), sah_HW_Read_Config()); +#endif + + /* Signal the end of the file */ + *eof = 1; + + /* To get rid of the unused parameter warnings */ + (void)start; + (void)data; + (void)offset; + + return output_bytes; +} + +static int sah_write_procfs(struct file *file, const char __user * buffer, + unsigned long count, void *data) +{ + + /* Any write to this file will reset all counts. */ + dar_count = interrupt_count = done1done2_count = + done1busy2_count = done1_count = 0; + + (void)file; + (void)buffer; + (void)data; + + return count; +} + +#endif + +#ifndef SAHARA_POLL_MODE +/*! + * Block user call until processing is complete. + * + * @param entry The user's request. + * + * @return An OS error code, or 0 if no error + */ +int sah_blocking_mode(sah_Head_Desc * entry) +{ + int os_error_code = 0; + sah_Queue_Status status; + + /* queue entry, put someting in the DAR, if nothing is there currently */ + sah_Queue_Manager_Append_Entry(entry); + + /* get this descriptor chain's current status */ + status = ((volatile sah_Head_Desc *)entry)->status; + + while (!SAH_DESC_PROCESSED(status)) { + extern sah_Queue *main_queue; + + DEFINE_WAIT(sahara_wait); /* create a wait queue entry. Linux */ + + /* enter the wait queue entry into the queue */ + prepare_to_wait(&Wait_queue, &sahara_wait, TASK_INTERRUPTIBLE); + + /* check if this entry has been processed */ + status = ((volatile sah_Head_Desc *)entry)->status; + + if (!SAH_DESC_PROCESSED(status)) { + /* go to sleep - Linux */ + schedule(); + } + + /* un-queue the 'prepare to wait' queue? - Linux */ + finish_wait(&Wait_queue, &sahara_wait); + + /* signal belongs to this thread? */ + if (signal_pending(current)) { /* Linux */ + os_lock_context_t lock_flags; + + /* don't allow access during this check and operation */ + os_lock_save_context(desc_queue_lock, lock_flags); + status = ((volatile sah_Head_Desc *)entry)->status; + if (status == SAH_STATE_PENDING) { + sah_Queue_Remove_Any_Entry(main_queue, entry); + entry->result = FSL_RETURN_INTERNAL_ERROR_S; + ((volatile sah_Head_Desc *)entry)->status = + SAH_STATE_FAILED; + } + os_unlock_restore_context(desc_queue_lock, lock_flags); + } + + status = ((volatile sah_Head_Desc *)entry)->status; + } /* while ... */ + + /* Do this so that caller can free */ + (void)sah_DePhysicalise_Descriptors(entry); + + return os_error_code; +} + +/*! + * If interrupt does not return in a reasonable time, time out, trigger + * interrupt, and continue with process + * + * @param data ignored + */ +void sahara_timeout_handler(unsigned long data) +{ + /* Sahara has not issuing an interrupt, so timed out */ +#ifdef DIAG_DRV_IF + LOG_KDIAG("Sahara HW did not respond. Resetting.\n"); +#endif + /* assume hardware needs resetting */ + sah_Handle_Interrupt(SAH_EXEC_FAULT); + /* wake up sleeping thread to try again */ + wake_up_interruptible(&Wait_queue); +} + +#endif /* ifndef SAHARA_POLL_MODE */ + +/* End of sah_driver_interface.c */ diff --git a/drivers/mxc/security/sahara2/sah_hardware_interface.c b/drivers/mxc/security/sahara2/sah_hardware_interface.c new file mode 100644 index 000000000000..f4341b5748e0 --- /dev/null +++ b/drivers/mxc/security/sahara2/sah_hardware_interface.c @@ -0,0 +1,862 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file sah_hardware_interface.c + * + * @brief Provides an interface to the SAHARA hardware registers. + * + */ + +/* SAHARA Includes */ +#include <sah_driver_common.h> +#include <sah_hardware_interface.h> +#include <sah_memory_mapper.h> +#include <sah_kernel.h> + +#if defined DIAG_DRV_IF || defined(DO_DBG) +#include <diagnostic.h> +#ifndef LOG_KDIAG +#define LOG_KDIAG(x) os_printk("%s\n", x) +#endif + +static void sah_Dump_Link(const char *prefix, const sah_Link * link, + dma_addr_t addr); +void sah_Dump_Words(const char *prefix, const unsigned *data, dma_addr_t addr, + unsigned length); + +/* This is for sprintf() to use when constructing output. */ +#define DIAG_MSG_SIZE 1024 +/* was 200 */ +#define MAX_DUMP 200 +static char Diag_msg[DIAG_MSG_SIZE]; + +#endif /* DIAG_DRV_IF */ + +/*! + * Number of descriptors sent to Sahara. This value should only be updated + * with the main queue lock held. + */ +uint32_t dar_count; + +/*! The "link-list optimize" bit in the Header of a Descriptor */ +#define SAH_HDR_LLO 0x01000000 + +/* IO_ADDRESS() is Linux macro -- need portable equivalent */ +#define SAHARA_BASE_ADDRESS IO_ADDRESS(SAHA_BASE_ADDR) +#define SAHARA_VERSION_REGISTER_OFFSET 0x000 +#define SAHARA_DAR_REGISTER_OFFSET 0x004 +#define SAHARA_CONTROL_REGISTER_OFFSET 0x008 +#define SAHARA_COMMAND_REGISTER_OFFSET 0x00C +#define SAHARA_STATUS_REGISTER_OFFSET 0x010 +#define SAHARA_ESTATUS_REGISTER_OFFSET 0x014 +#define SAHARA_FLT_ADD_REGISTER_OFFSET 0x018 +#define SAHARA_CDAR_REGISTER_OFFSET 0x01C +#define SAHARA_IDAR_REGISTER_OFFSET 0x020 +#define SAHARA_OSTATUS_REGISTER_OFFSET 0x028 +#define SAHARA_CONFIG_REGISTER_OFFSET 0x02C +#define SAHARA_MM_STAT_REGISTER_OFFSET 0x030 + +/*! Register within Sahara which contains hardware version. (1 or 2). */ +#define SAHARA_VERSION_REGISTER (SAHARA_BASE_ADDRESS + \ + SAHARA_VERSION_REGISTER_OFFSET) + +/*! Register within Sahara which is used to provide new work to the block. */ +#define SAHARA_DAR_REGISTER (SAHARA_BASE_ADDRESS + \ + SAHARA_DAR_REGISTER_OFFSET) + +/*! Register with Sahara which is used for configuration. */ +#define SAHARA_CONTROL_REGISTER (SAHARA_BASE_ADDRESS + \ + SAHARA_CONTROL_REGISTER_OFFSET) + +/*! Register with Sahara which is used for changing status. */ +#define SAHARA_COMMAND_REGISTER (SAHARA_BASE_ADDRESS + \ + SAHARA_COMMAND_REGISTER_OFFSET) + +/*! Register with Sahara which is contains status and state. */ +#define SAHARA_STATUS_REGISTER (SAHARA_BASE_ADDRESS + \ + SAHARA_STATUS_REGISTER_OFFSET) + +/*! Register with Sahara which is contains error status information. */ +#define SAHARA_ESTATUS_REGISTER (SAHARA_BASE_ADDRESS + \ + SAHARA_ESTATUS_REGISTER_OFFSET) + +/*! Register with Sahara which is contains faulting address information. */ +#define SAHARA_FLT_ADD_REGISTER (SAHARA_BASE_ADDRESS + \ + SAHARA_FLT_ADD_REGISTER_OFFSET) + +/*! Register with Sahara which is contains current descriptor address. */ +#define SAHARA_CDAR_REGISTER (SAHARA_BASE_ADDRESS + \ + SAHARA_CDAR_REGISTER_OFFSET) + +/*! Register with Sahara which is contains initial descriptor address (of a + chain). */ +#define SAHARA_IDAR_REGISTER (SAHARA_BASE_ADDRESS + \ + SAHARA_IDAR_REGISTER_OFFSET) + +/*! Register with Sahara which is contains op status information. */ +#define SAHARA_OSTATUS_REGISTER (SAHARA_BASE_ADDRESS + \ + SAHARA_OSTATUS_REGISTER_OFFSET) + +/*! Register with Sahara which is contains configuration information. */ +#define SAHARA_CONFIG_REGISTER (SAHARA_BASE_ADDRESS + \ + SAHARA_CONFIG_REGISTER_OFFSET) + +/*! Register with Sahara which is contains configuration information. */ +#define SAHARA_MM_STAT_REGISTER (SAHARA_BASE_ADDRESS + \ + SAHARA_MM_STAT_REGISTER_OFFSET) + +/* Local Functions */ +#if defined DIAG_DRV_IF || defined DO_DBG +void sah_Dump_Region(const char *prefix, const unsigned char *data, + dma_addr_t addr, unsigned length); + +#endif /* DIAG_DRV_IF */ + +/* time out value when polling SAHARA status register for completion */ +static uint32_t sah_poll_timeout = 0xFFFFFFFF; + +/*! + * Polls Sahara to determine when its current operation is complete + * + * @return last value found in Sahara's status register + */ +sah_Execute_Status sah_Wait_On_Sahara() +{ + uint32_t count = 0; /* ensure we don't get stuck in the loop forever */ + sah_Execute_Status status; /* Sahara's status register */ + uint32_t stat_reg; + + pr_debug("Entered sah_Wait_On_Sahara\n"); + + do { + /* get current status register from Sahara */ + stat_reg = sah_HW_Read_Status(); + status = stat_reg & SAH_EXEC_STATE_MASK; + + /* timeout if SAHARA takes too long to complete */ + if (++count == sah_poll_timeout) { + status = SAH_EXEC_FAULT; + printk("sah_Wait_On_Sahara timed out\n"); + } + + /* stay in loop as long as Sahara is still busy */ + } while ((status == SAH_EXEC_BUSY) || (status == SAH_EXEC_DONE1_BUSY2)); + + if (status == SAH_EXEC_ERROR1) { + if (stat_reg & OP_STATUS) { + status = SAH_EXEC_OPSTAT1; + } + } + + return status; +} /* sah_Wait_on_Sahara() */ + +/*! + * This function resets the SAHARA hardware. The following operations are + * performed: + * 1. Resets SAHARA. + * 2. Requests BATCH mode. + * 3. Enables interrupts. + * 4. Requests Little Endian mode. + * + * @brief SAHARA hardware reset function. + * + * @return void + */ +int sah_HW_Reset(void) +{ + sah_Execute_Status sah_state; + int status; /* this is the value to return to the calling routine */ + uint32_t saha_control = 0; + +#ifndef USE_3WORD_BURST +#ifdef FSL_HAVE_SAHARA2 + saha_control |= (8 << 16); /* Allow 8-word burst */ +#endif +#else +/***************** HARDWARE BUG WORK AROUND ******************/ +/* A burst size of > 4 can cause Sahara DMA to issue invalid AHB transactions + * when crossing 1KB boundaries. By limiting the 'burst size' to 3, these + * invalid transactions will not be generated, but Sahara will still transfer + * data more efficiently than if the burst size were set to 1. + */ + saha_control |= (3 << 16); /* Limit DMA burst size. For versions 2/3 */ +#endif /* USE_3WORD_BURST */ + +#ifdef DIAG_DRV_IF + snprintf(Diag_msg, DIAG_MSG_SIZE, + "Address of SAHARA_BASE_ADDRESS = 0x%08x\n", + SAHARA_BASE_ADDRESS); + LOG_KDIAG(Diag_msg); + snprintf(Diag_msg, DIAG_MSG_SIZE, + "Sahara Status register before reset: %08x", + sah_HW_Read_Status()); + LOG_KDIAG(Diag_msg); +#endif + + /* Write the Reset & BATCH mode command to the SAHARA Command register. */ + sah_HW_Write_Command(CMD_BATCH | CMD_RESET); +#ifdef SAHARA4_NO_USE_SQUIB + { + uint32_t cfg = sah_HW_Read_Config(); + cfg &= ~0x10000; + sah_HW_Write_Config(cfg); + } +#endif + + sah_poll_timeout = 0x0FFFFFFF; + sah_state = sah_Wait_On_Sahara(); +#ifdef DIAG_DRV_IF + snprintf(Diag_msg, DIAG_MSG_SIZE, + "Sahara Status register after reset: %08x", + sah_HW_Read_Status()); + LOG_KDIAG(Diag_msg); +#endif + /* on reset completion, check that Sahara is in the idle state */ + status = (sah_state == SAH_EXEC_IDLE) ? 0 : OS_ERROR_FAIL_S; + + /* Set initial value out of reset */ + sah_HW_Write_Control(saha_control); + +#ifndef NO_RESEED_WORKAROUND +/***************** HARDWARE BUG WORK AROUND ******************/ +/* In order to set the 'auto reseed' bit, must first acquire a random value. */ + /* + * to solve a hardware bug, a random number must be generated before + * the 'RNG Auto Reseed' bit can be set. So this generates a random + * number that is thrown away. + * + * Note that the interrupt bit has not been set at this point so + * the result can be polled. + */ +#ifdef DIAG_DRV_IF + LOG_KDIAG("Create and submit Random Number Descriptor"); +#endif + + if (status == OS_ERROR_OK_S) { + /* place to put random number */ + volatile uint32_t *random_data_ptr; + sah_Head_Desc *random_desc; + dma_addr_t desc_dma; + dma_addr_t rand_dma; + const int rnd_cnt = 3; /* how many random 32-bit values to get */ + + /* Get space for data -- assume at least 32-bit aligned! */ + random_data_ptr = os_alloc_memory(rnd_cnt * sizeof(uint32_t), + GFP_ATOMIC); + + random_desc = sah_Alloc_Head_Descriptor(); + + if ((random_data_ptr == NULL) || (random_desc == NULL)) { + status = OS_ERROR_FAIL_S; + } else { + int i; + + /* Clear out values */ + for (i = 0; i < rnd_cnt; i++) { + random_data_ptr[i] = 0; + } + + rand_dma = os_pa(random_data_ptr); + + random_desc->desc.header = 0xB18C0000; /* LLO get random number */ + random_desc->desc.len1 = + rnd_cnt * sizeof(*random_data_ptr); +#ifdef USE_OLD_PTRS + random_desc->desc.ptr1 = (void *)rand_dma; + random_desc->desc.original_ptr1 = + (void *)random_data_ptr; +#else + random_desc->desc.hw_ptr1 = rand_dma; + random_desc->desc.ptr1 = (void *)random_data_ptr; +#endif + random_desc->desc.len2 = 0; /* not used */ + random_desc->desc.ptr2 = 0; /* not used */ +#ifdef USE_OLD_PTRS + random_desc->desc.next = 0; /* chain terminates here */ + random_desc->desc.original_next = 0; /* chain terminates here */ +#else + random_desc->desc.hw_next = 0; /* chain terminates here */ + random_desc->desc.next = 0; /* chain terminates here */ +#endif + desc_dma = random_desc->desc.dma_addr; + + /* Force in-cache data out to RAM */ + os_cache_clean_range(random_data_ptr, + rnd_cnt * + sizeof(*random_data_ptr)); + + /* pass descriptor to Sahara */ + sah_HW_Write_DAR(desc_dma); + + /* + * Wait for RNG to complete (interrupts are disabled at this point + * due to sahara being reset previously) then check for error + */ + sah_state = sah_Wait_On_Sahara(); + /* Force CPU to ignore in-cache and reload from RAM */ + os_cache_inv_range(random_data_ptr, + rnd_cnt * sizeof(*random_data_ptr)); + + /* if it didn't move to done state, an error occured */ + if ( +#ifndef SUBMIT_MULTIPLE_DARS + (sah_state != SAH_EXEC_IDLE) && +#endif + (sah_state != SAH_EXEC_DONE1) + ) { + status = OS_ERROR_FAIL_S; + os_printk + ("(sahara) Failure: state is %08x; random_data is" + " %08x\n", sah_state, *random_data_ptr); + os_printk + ("(sahara) CDAR: %08x, IDAR: %08x, FADR: %08x," + " ESTAT: %08x\n", sah_HW_Read_CDAR(), + sah_HW_Read_IDAR(), + sah_HW_Read_Fault_Address(), + sah_HW_Read_Error_Status()); + } else { + int i; + int seen_rand = 0; + + for (i = 0; i < rnd_cnt; i++) { + if (*random_data_ptr != 0) { + seen_rand = 1; + break; + } + } + if (!seen_rand) { + status = OS_ERROR_FAIL_S; + os_printk + ("(sahara) Error: Random number is zero!\n"); + } + } + } + + if (random_data_ptr) { + os_free_memory((void *)random_data_ptr); + } + if (random_desc) { + sah_Free_Head_Descriptor(random_desc); + } + } +/***************** END HARDWARE BUG WORK AROUND ******************/ +#endif + + if (status == 0) { +#ifdef FSL_HAVE_SAHARA2 + saha_control |= CTRL_RNG_RESEED; +#endif + +#ifndef SAHARA_POLL_MODE + saha_control |= CTRL_INT_EN; /* enable interrupts */ +#else + sah_poll_timeout = SAHARA_POLL_MODE_TIMEOUT; +#endif + +#ifdef DIAG_DRV_IF + snprintf(Diag_msg, DIAG_MSG_SIZE, + "Setting up Sahara's Control Register: %08x\n", + saha_control); + LOG_KDIAG(Diag_msg); +#endif + + /* Rewrite the setup to the SAHARA Control register */ + sah_HW_Write_Control(saha_control); +#ifdef DIAG_DRV_IF + snprintf(Diag_msg, DIAG_MSG_SIZE, + "Sahara Status register after control write: %08x", + sah_HW_Read_Status()); + LOG_KDIAG(Diag_msg); +#endif + +#ifdef FSL_HAVE_SAHARA4 + { + uint32_t cfg = sah_HW_Read_Config(); + sah_HW_Write_Config(cfg | 0x100); /* Add RNG auto-reseed */ + } +#endif + } else { +#ifdef DIAG_DRV_IF + LOG_KDIAG("Reset failed\n"); +#endif + } + + return status; +} /* sah_HW_Reset() */ + +/*! + * This function enables High Assurance mode. + * + * @brief SAHARA hardware enable High Assurance mode. + * + * @return FSL_RETURN_OK_S - if HA was set successfully + * @return FSL_RETURN_INTERNAL_ERROR_S - if HA was not set due to SAHARA + * being busy. + */ +fsl_shw_return_t sah_HW_Set_HA(void) +{ + /* This is the value to write to the register */ + uint32_t value; + + /* Read from the control register. */ + value = sah_HW_Read_Control(); + + /* Set the HA bit */ + value |= CTRL_HA; + + /* Write to the control register. */ + sah_HW_Write_Control(value); + + /* Read from the control register. */ + value = sah_HW_Read_Control(); + + return (value & CTRL_HA) ? FSL_RETURN_OK_S : + FSL_RETURN_INTERNAL_ERROR_S; +} + +/*! + * This function reads the SAHARA hardware Version Register. + * + * @brief Read SAHARA hardware Version Register. + * + * @return uint32_t Register value. + */ +uint32_t sah_HW_Read_Version(void) +{ + return os_read32(SAHARA_VERSION_REGISTER); +} + +/*! + * This function reads the SAHARA hardware Control Register. + * + * @brief Read SAHARA hardware Control Register. + * + * @return uint32_t Register value. + */ +uint32_t sah_HW_Read_Control(void) +{ + return os_read32(SAHARA_CONTROL_REGISTER); +} + +/*! + * This function reads the SAHARA hardware Status Register. + * + * @brief Read SAHARA hardware Status Register. + * + * @return uint32_t Register value. + */ +uint32_t sah_HW_Read_Status(void) +{ + return os_read32(SAHARA_STATUS_REGISTER); +} + +/*! + * This function reads the SAHARA hardware Error Status Register. + * + * @brief Read SAHARA hardware Error Status Register. + * + * @return uint32_t Error Status value. + */ +uint32_t sah_HW_Read_Error_Status(void) +{ + return os_read32(SAHARA_ESTATUS_REGISTER); +} + +/*! + * This function reads the SAHARA hardware Op Status Register. + * + * @brief Read SAHARA hardware Op Status Register. + * + * @return uint32_t Op Status value. + */ +uint32_t sah_HW_Read_Op_Status(void) +{ + return os_read32(SAHARA_OSTATUS_REGISTER); +} + +/*! + * This function reads the SAHARA hardware Descriptor Address Register. + * + * @brief Read SAHARA hardware DAR Register. + * + * @return uint32_t DAR value. + */ +uint32_t sah_HW_Read_DAR(void) +{ + return os_read32(SAHARA_DAR_REGISTER); +} + +/*! + * This function reads the SAHARA hardware Current Descriptor Address Register. + * + * @brief Read SAHARA hardware CDAR Register. + * + * @return uint32_t CDAR value. + */ +uint32_t sah_HW_Read_CDAR(void) +{ + return os_read32(SAHARA_CDAR_REGISTER); +} + +/*! + * This function reads the SAHARA hardware Initial Descriptor Address Register. + * + * @brief Read SAHARA hardware IDAR Register. + * + * @return uint32_t IDAR value. + */ +uint32_t sah_HW_Read_IDAR(void) +{ + return os_read32(SAHARA_IDAR_REGISTER); +} + +/*! + * This function reads the SAHARA hardware Fault Address Register. + * + * @brief Read SAHARA Fault Address Register. + * + * @return uint32_t Fault Address value. + */ +uint32_t sah_HW_Read_Fault_Address(void) +{ + return os_read32(SAHARA_FLT_ADD_REGISTER); +} + +/*! + * This function reads the SAHARA hardware Multiple Master Status Register. + * + * @brief Read SAHARA hardware MM Stat Register. + * + * @return uint32_t MM Stat value. + */ +uint32_t sah_HW_Read_MM_Status(void) +{ + return os_read32(SAHARA_MM_STAT_REGISTER); +} + +/*! + * This function reads the SAHARA hardware Configuration Register. + * + * @brief Read SAHARA Configuration Register. + * + * @return uint32_t Configuration value. + */ +uint32_t sah_HW_Read_Config(void) +{ + return os_read32(SAHARA_CONFIG_REGISTER); +} + +/*! + * This function writes a command to the SAHARA hardware Command Register. + * + * @brief Write to SAHARA hardware Command Register. + * + * @param command An unsigned 32bit command value. + * + * @return void + */ +void sah_HW_Write_Command(uint32_t command) +{ + os_write32(SAHARA_COMMAND_REGISTER, command); +} + +/*! + * This function writes a control value to the SAHARA hardware Control + * Register. + * + * @brief Write to SAHARA hardware Control Register. + * + * @param control An unsigned 32bit control value. + * + * @return void + */ +void sah_HW_Write_Control(uint32_t control) +{ + os_write32(SAHARA_CONTROL_REGISTER, control); +} + +/*! + * This function writes a configuration value to the SAHARA hardware Configuration + * Register. + * + * @brief Write to SAHARA hardware Configuration Register. + * + * @param configuration An unsigned 32bit configuration value. + * + * @return void + */ +void sah_HW_Write_Config(uint32_t configuration) +{ + os_write32(SAHARA_CONFIG_REGISTER, configuration); +} + +/*! + * This function writes a descriptor address to the SAHARA Descriptor Address + * Register. + * + * @brief Write to SAHARA Descriptor Address Register. + * + * @param pointer An unsigned 32bit descriptor address value. + * + * @return void + */ +void sah_HW_Write_DAR(uint32_t pointer) +{ + os_write32(SAHARA_DAR_REGISTER, pointer); + dar_count++; +} + +#if defined DIAG_DRV_IF || defined DO_DBG + +static char *interpret_header(uint32_t header) +{ + unsigned desc_type = ((header >> 24) & 0x70) | ((header >> 16) & 0xF); + + switch (desc_type) { + case 0x12: + return "5/SKHA_ST_CTX"; + case 0x13: + return "35/SKHA_LD_MODE_KEY"; + case 0x14: + return "38/SKHA_LD_MODE_IN_CPHR_ST_CTX"; + case 0x15: + return "4/SKHA_IN_CPHR_OUT"; + case 0x16: + return "34/SKHA_ST_SBOX"; + case 0x18: + return "1/SKHA_LD_MODE_IV_KEY"; + case 0x19: + return "33/SKHA_ST_SBOX"; + case 0x1D: + return "2/SKHA_LD_MODE_IN_CPHR_OUT"; + case 0x22: + return "11/MDHA_ST_MD"; + case 0x25: + return "10/MDHA_HASH_ST_MD"; + case 0x28: + return "6/MDHA_LD_MODE_MD_KEY"; + case 0x2A: + return "39/MDHA_ICV"; + case 0x2D: + return "8/MDHA_LD_MODE_HASH_ST_MD"; + case 0x3C: + return "18/RNG_GEN"; + case 0x40: + return "19/PKHA_LD_N_E"; + case 0x41: + return "36/PKHA_LD_A3_B0"; + case 0x42: + return "27/PKHA_ST_A_B"; + case 0x43: + return "22/PKHA_LD_A_B"; + case 0x44: + return "23/PKHA_LD_A0_A1"; + case 0x45: + return "24/PKHA_LD_A2_A3"; + case 0x46: + return "25/PKHA_LD_B0_B1"; + case 0x47: + return "26/PKHA_LD_B2_B3"; + case 0x48: + return "28/PKHA_ST_A0_A1"; + case 0x49: + return "29/PKHA_ST_A2_A3"; + case 0x4A: + return "30/PKHA_ST_B0_B1"; + case 0x4B: + return "31/PKHA_ST_B2_B3"; + case 0x4C: + return "32/PKHA_EX_ST_B1"; + case 0x4D: + return "20/PKHA_LD_A_EX_ST_B"; + case 0x4E: + return "21/PKHA_LD_N_EX_ST_B"; + case 0x4F: + return "37/PKHA_ST_B1_B2"; + default: + return "??/UNKNOWN"; + } +} /* cvt_desc_name() */ + +/*! + * Dump chain of descriptors to the log. + * + * @brief Dump descriptor chain + * + * @param chain Kernel virtual address of start of chain of descriptors + * + * @return void + */ +void sah_Dump_Chain(const sah_Desc * chain, dma_addr_t addr) +{ + int desc_no = 1; + + pr_debug("Chain for Sahara\n"); + + while (chain != NULL) { + char desc_name[50]; + + sprintf(desc_name, "Desc %02d (%s)\n" KERN_DEBUG "Desc ", + desc_no++, interpret_header(chain->header)); + + sah_Dump_Words(desc_name, (unsigned *)chain, addr, + 6 /* #words in h/w link */ ); + if (chain->original_ptr1) { + if (chain->header & SAH_HDR_LLO) { + sah_Dump_Region(" Data1", + (unsigned char *)chain-> + original_ptr1, + (dma_addr_t) chain->ptr1, + chain->len1); + } else { + sah_Dump_Link(" Link1", chain->original_ptr1, + (dma_addr_t) chain->ptr1); + } + } + if (chain->ptr2) { + if (chain->header & SAH_HDR_LLO) { + sah_Dump_Region(" Data2", + (unsigned char *)chain-> + original_ptr2, + (dma_addr_t) chain->ptr2, + chain->len2); + } else { + sah_Dump_Link(" Link2", chain->original_ptr2, + (dma_addr_t) chain->ptr2); + } + } + + addr = (dma_addr_t) chain->next; + chain = (chain->next) ? (chain->original_next) : NULL; + } +} + +/*! + * Dump chain of links to the log. + * + * @brief Dump chain of links + * + * @param prefix Text to put in front of dumped data + * @param link Kernel virtual address of start of chain of links + * + * @return void + */ +static void sah_Dump_Link(const char *prefix, const sah_Link * link, + dma_addr_t addr) +{ +#ifdef DUMP_SCC_DATA + extern uint8_t *sahara_partition_base; + extern dma_addr_t sahara_partition_phys; +#endif + + while (link != NULL) { + sah_Dump_Words(prefix, (unsigned *)link, addr, + 3 /* # words in h/w link */ ); + if (link->flags & SAH_STORED_KEY_INFO) { +#ifdef SAH_DUMP_DATA +#ifdef DUMP_SCC_DATA + sah_Dump_Region(" Data", + (uint8_t *) link->data - + (uint8_t *) sahara_partition_phys + + sahara_partition_base, + (dma_addr_t) link->data, link->len); +#else + pr_debug(" Key Slot %d\n", link->slot); +#endif +#endif + } else { +#ifdef SAH_DUMP_DATA + sah_Dump_Region(" Data", link->original_data, + (dma_addr_t) link->data, link->len); +#endif + } + addr = (dma_addr_t) link->next; + link = link->original_next; + } +} + +/*! + * Dump given region of data to the log. + * + * @brief Dump data + * + * @param prefix Text to put in front of dumped data + * @param data Kernel virtual address of start of region to dump + * @param length Amount of data to dump + * + * @return void + */ +void sah_Dump_Region(const char *prefix, const unsigned char *data, + dma_addr_t addr, unsigned length) +{ + unsigned count; + char *output; + unsigned data_len; + + sprintf(Diag_msg, "%s (%08X,%u):", prefix, addr, length); + + /* Restrict amount of data to dump */ + if (length > MAX_DUMP) { + data_len = MAX_DUMP; + } else { + data_len = length; + } + + /* We've already printed some text in output buffer, skip over it */ + output = Diag_msg + strlen(Diag_msg); + + for (count = 0; count < data_len; count++) { + if ((count % 4) == 0) { + *output++ = ' '; + } + sprintf(output, "%02X", *data++); + output += 2; + } + + pr_debug("%s\n", Diag_msg); +} + +/*! + * Dump given word of data to the log. + * + * @brief Dump data + * + * @param prefix Text to put in front of dumped data + * @param data Kernel virtual address of start of region to dump + * @param word_count Amount of data to dump + * + * @return void + */ +void sah_Dump_Words(const char *prefix, const unsigned *data, dma_addr_t addr, + unsigned word_count) +{ + char *output; + + sprintf(Diag_msg, "%s (%08X,%uw): ", prefix, addr, word_count); + + /* We've already printed some text in output buffer, skip over it */ + output = Diag_msg + strlen(Diag_msg); + + while (word_count--) { + sprintf(output, "%08X ", *data++); + output += 9; + } + + pr_debug("%s\n", Diag_msg); +} + +#endif /* DIAG_DRV_IF */ + +/* End of sah_hardware_interface.c */ diff --git a/drivers/mxc/security/sahara2/sah_interrupt_handler.c b/drivers/mxc/security/sahara2/sah_interrupt_handler.c new file mode 100644 index 000000000000..4d87bdb61e8c --- /dev/null +++ b/drivers/mxc/security/sahara2/sah_interrupt_handler.c @@ -0,0 +1,211 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! +* @file sah_interrupt_handler.c +* +* @brief Provides a hardware interrupt handling mechanism for device driver. +* +* This file needs to be ported for a non-Linux OS. +* +* It gets a call at #sah_Intr_Init() during initialization. +* +* #sah_Intr_Top_Half() is intended to be the Interrupt Service Routine. It +* calls a portable function in another file to process the Sahara status. +* +* #sah_Intr_Bottom_Half() is a 'background' task scheduled by the top half to +* take care of the expensive tasks of the interrupt processing. +* +* The driver shutdown code calls #sah_Intr_Release(). +* +*/ + +#include <portable_os.h> + +/* SAHARA Includes */ +#include <sah_kernel.h> +#include <sah_interrupt_handler.h> +#include <sah_status_manager.h> +#include <sah_hardware_interface.h> +#include <sah_queue_manager.h> + +#ifdef DIAG_DRV_INTERRUPT +#include <diagnostic.h> +#endif + +/*! + * Number of interrupts received. This value should only be updated during + * interrupt processing. + */ +uint32_t interrupt_count; + +#ifndef SAHARA_POLL_MODE + +#if !defined(LINUX_VERSION_CODE) || LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) +#define irqreturn_t void +#define IRQ_HANDLED +#define IRQ_RETVAL(x) +#endif + +/* Internal Prototypes */ +static irqreturn_t sah_Intr_Top_Half(int irq, void *dev_id); + +#ifdef KERNEL_TEST +extern void (*SAHARA_INT_PTR) (int, void *); +#endif + +unsigned long reset_flag; +static void sah_Intr_Bottom_Half(unsigned long reset_flag); + +/* This is the Bottom Half Task, (reset flag set to false) */ +DECLARE_TASKLET(BH_task, sah_Intr_Bottom_Half, (unsigned long)&reset_flag); + +/*! This is set by the Initialisation function */ +wait_queue_head_t *int_queue = NULL; + +/*! +******************************************************************************* +* This function registers the Top Half of the interrupt handler with the Kernel +* and the SAHARA IRQ number. +* +* @brief SAHARA Interrupt Handler Initialisation +* +* @param wait_queue Pointer to the wait queue used by driver interface +* +* @return int A return of Zero indicates successful initialisation. +*/ +/****************************************************************************** +* +* CAUTION: NONE +* +* MODIFICATION HISTORY: +* +* Date Person Change +* 30/07/2003 MW Initial Creation +******************************************************************************/ +int sah_Intr_Init(wait_queue_head_t * wait_queue) +{ + +#ifdef DIAG_DRV_INTERRUPT + char err_string[200]; +#endif + + int result; + +#ifdef KERNEL_TEST + SAHARA_INT_PTR = sah_Intr_Top_Half; +#endif + + /* Set queue used by the interrupt handler to match the driver interface */ + int_queue = wait_queue; + + /* Request use of the Interrupt line. */ + result = request_irq(SAHARA_IRQ, + sah_Intr_Top_Half, 0, SAHARA_NAME, NULL); + +#ifdef DIAG_DRV_INTERRUPT + if (result != 0) { + sprintf(err_string, "Cannot use SAHARA interrupt line %d. " + "request_irq() return code is %i.", SAHARA_IRQ, result); + LOG_KDIAG(err_string); + } else { + sprintf(err_string, + "SAHARA driver registered for interrupt %d. ", + SAHARA_IRQ); + LOG_KDIAG(err_string); + } +#endif + + return result; +} + +/*! +******************************************************************************* +* This function releases the Top Half of the interrupt handler. The driver will +* not receive any more interrupts after calling this functions. +* +* @brief SAHARA Interrupt Handler Release +* +* @return void +*/ +/****************************************************************************** +* +* CAUTION: NONE +* +* MODIFICATION HISTORY: +* +* Date Person Change +* 30/07/2003 MW Initial Creation +******************************************************************************/ +void sah_Intr_Release(void) +{ + /* Release the Interrupt. */ + free_irq(SAHARA_IRQ, NULL); +} + +/*! +******************************************************************************* +* This function is the Top Half of the interrupt handler. It updates the +* status of any finished descriptor chains and then tries to add any pending +* requests into the hardware. It then queues the bottom half to complete +* operations on the finished chains. +* +* @brief SAHARA Interrupt Handler Top Half +* +* @param irq Part of the kernel prototype. +* @param dev_id Part of the kernel prototype. +* +* @return An IRQ_RETVAL() -- non-zero to that function means 'handled' +*/ +static irqreturn_t sah_Intr_Top_Half(int irq, void *dev_id) +{ +#if defined(DIAG_DRV_INTERRUPT) && defined(DIAG_DURING_INTERRUPT) + LOG_KDIAG("Top half of Sahara's interrupt handler called."); +#endif + + interrupt_count++; + reset_flag = sah_Handle_Interrupt(sah_HW_Read_Status()); + + /* Schedule the Bottom Half of the Interrupt. */ + tasklet_schedule(&BH_task); + + /* To get rid of the unused parameter warnings. */ + irq = 0; + dev_id = NULL; + return IRQ_RETVAL(1); +} + +/*! +******************************************************************************* +* This function is the Bottom Half of the interrupt handler. It calls +* #sah_postprocess_queue() to complete the processing of the Descriptor Chains +* which were finished by the hardware. +* +* @brief SAHARA Interrupt Handler Bottom Half +* +* @param data Part of the kernel prototype. +* +* @return void +*/ +static void sah_Intr_Bottom_Half(unsigned long reset_flag) +{ +#if defined(DIAG_DRV_INTERRUPT) && defined(DIAG_DURING_INTERRUPT) + LOG_KDIAG("Bottom half of Sahara's interrupt handler called."); +#endif + sah_postprocess_queue(*(unsigned long *)reset_flag); + + return; +} + +/* end of sah_interrupt_handler.c */ +#endif /* ifndef SAHARA_POLL_MODE */ diff --git a/drivers/mxc/security/sahara2/sah_memory_mapper.c b/drivers/mxc/security/sahara2/sah_memory_mapper.c new file mode 100644 index 000000000000..d338e3a74b1a --- /dev/null +++ b/drivers/mxc/security/sahara2/sah_memory_mapper.c @@ -0,0 +1,2041 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! +* @file sah_memory_mapper.c +* +* @brief Re-creates SAHARA data structures in Kernel memory such that they are +* suitable for DMA. Provides support for kernel API. +* +* This file needs to be ported. +* +* The memory mapper gets a call at #sah_Init_Mem_Map() during driver +* initialization. +* +* The routine #sah_Copy_Descriptors() is used to bring descriptor chains from +* user memory down to kernel memory, relink using physical addresses, and make +* sure that all user data will be accessible by the Sahara DMA. +* #sah_Destroy_Descriptors() does the inverse. +* +* The #sah_Alloc_Block(), #sah_Free_Block(), and #sah_Block_Add_Page() routines +* implement a cache of free blocks used when allocating descriptors and links +* within the kernel. +* +* The memory mapper gets a call at #sah_Stop_Mem_Map() during driver shutdown. +* +*/ + +#include <sah_driver_common.h> +#include <sah_kernel.h> +#include <sah_queue_manager.h> +#include <sah_memory_mapper.h> +#ifdef FSL_HAVE_SCC +#include <asm/arch/mxc_scc_driver.h> +#else +#include <asm/arch/mxc_scc2_driver.h> +#endif + +#if defined(DIAG_DRV_IF) || defined(DIAG_MEM) || defined(DO_DBG) +#include <diagnostic.h> +#endif + +#include <linux/mm.h> /* get_user_pages() */ +#include <linux/pagemap.h> +#include <linux/dmapool.h> + +#include <linux/slab.h> +#include <linux/highmem.h> + +#if defined(DIAG_MEM) || defined(DIAG_DRV_IF) +#define DIAG_MSG_SIZE 1024 +static char Diag_msg[DIAG_MSG_SIZE]; +#endif + +#ifdef LINUX_VERSION_CODE +#define FLUSH_SPECIFIC_DATA_ONLY +#else +#define SELF_MANAGED_POOL +#endif + +#if defined(LINUX_VERSION_CODE) +EXPORT_SYMBOL(sah_Alloc_Link); +EXPORT_SYMBOL(sah_Free_Link); +EXPORT_SYMBOL(sah_Alloc_Descriptor); +EXPORT_SYMBOL(sah_Free_Descriptor); +EXPORT_SYMBOL(sah_Alloc_Head_Descriptor); +EXPORT_SYMBOL(sah_Free_Head_Descriptor); +EXPORT_SYMBOL(sah_Physicalise_Descriptors); +EXPORT_SYMBOL(sah_DePhysicalise_Descriptors); +#endif + +/* Number of bytes the hardware uses out of sah_Link and sah_*Desc structs */ +#define SAH_HW_LINK_LEN 12 +#define SAH_HW_DESC_LEN 24 + +/* Macros for Descriptors */ +#define SAH_LLO_BIT 0x01000000 +#define sah_Desc_Get_LLO(desc) (desc->header & SAH_LLO_BIT) +#define sah_Desc_Set_Header(desc, h) (desc->header = (h)) + +#define sah_Desc_Get_Next(desc) (desc->next) +#define sah_Desc_Set_Next(desc, n) (desc->next = (n)) + +#define sah_Desc_Get_Ptr1(desc) (desc->ptr1) +#define sah_Desc_Get_Ptr2(desc) (desc->ptr2) +#define sah_Desc_Set_Ptr1(desc,p1) (desc->ptr1 = (p1)) +#define sah_Desc_Set_Ptr2(desc,p2) (desc->ptr2 = (p2)) + +#define sah_Desc_Get_Len1(desc) (desc->len1) +#define sah_Desc_Get_Len2(desc) (desc->len2) +#define sah_Desc_Set_Len1(desc,l1) (desc->len1 = (l1)) +#define sah_Desc_Set_Len2(desc,l2) (desc->len2 = (l2)) + +/* Macros for Links */ +#define sah_Link_Get_Next(link) (link->next) +#define sah_Link_Set_Next(link, n) (link->next = (n)) + +#define sah_Link_Get_Data(link) (link->data) +#define sah_Link_Set_Data(link,d) (link->data = (d)) + +#define sah_Link_Get_Len(link) (link->len) +#define sah_Link_Set_Len(link, l) (link->len = (l)) + +#define sah_Link_Get_Flags(link) (link->flags) + +/* Memory block defines */ +/* Warning! This assumes that kernel version of sah_Link + * is larger than kernel version of sah_Desc. + */ +#define MEM_BLOCK_SIZE sizeof(sah_Link) + +/*! Structure for link/descriptor memory blocks in internal pool */ +typedef struct mem_block { + uint8_t data[MEM_BLOCK_SIZE]; /*!< the actual buffer area */ + struct mem_block *next; /*!< next block when in free chain */ + dma_addr_t dma_addr; /*!< physical address of @a data */ +} Mem_Block; + +#define MEM_BLOCK_ENTRIES (PAGE_SIZE / sizeof(Mem_Block)) + +#define MEM_BIG_BLOCK_SIZE sizeof(sah_Head_Desc) + +/*! Structure for head descriptor memory blocks in internal pool */ +typedef struct mem_big_block { + uint8_t data[MEM_BIG_BLOCK_SIZE]; /*!< the actual buffer area */ + struct mem_big_block *next; /*!< next block when in free chain */ + uint32_t dma_addr; /*!< physical address of @a data */ +} Mem_Big_Block; + +#define MEM_BIG_BLOCK_ENTRIES (PAGE_SIZE / sizeof(Mem_Big_Block)) + +/* Shared variables */ + +/*! + * Lock to protect the memory chain composed of #block_free_head and + * #block_free_tail. + */ +static os_lock_t mem_lock; + +#ifndef SELF_MANAGED_POOL +static struct dma_pool *big_dma_pool = NULL; +static struct dma_pool *small_dma_pool = NULL; +#endif + +#ifdef SELF_MANAGED_POOL +/*! + * Memory block free pool - pointer to first block. Chain is protected by + * #mem_lock. + */ +static Mem_Block *block_free_head = NULL; +/*! + * Memory block free pool - pointer to last block. Chain is protected by + * #mem_lock. + */ +static Mem_Block *block_free_tail = NULL; +/*! + * Memory block free pool - pointer to first block. Chain is protected by + * #mem_lock. + */ +static Mem_Big_Block *big_block_free_head = NULL; +/*! + * Memory block free pool - pointer to last block. Chain is protected by + * #mem_lock. +a */ +static Mem_Big_Block *big_block_free_tail = NULL; +#endif /* SELF_MANAGED_POOL */ + +static Mem_Block *sah_Alloc_Block(void); +static void sah_Free_Block(Mem_Block * block); +static Mem_Big_Block *sah_Alloc_Big_Block(void); +static void sah_Free_Big_Block(Mem_Big_Block * block); +#ifdef SELF_MANAGED_POOL +static void sah_Append_Block(Mem_Block * block); +static void sah_Append_Big_Block(Mem_Big_Block * block); +#endif /* SELF_MANAGED_POOL */ + +/*! +******************************************************************************* +* Free descriptor back to free pool +* +* @brief Free descriptor +* +* @param desc A descriptor allocated with sah_Alloc_Descriptor(). +* +* @return none +* +*/ +void sah_Free_Descriptor(sah_Desc * desc) +{ + memset(desc, 0x45, sizeof(*desc)); + sah_Free_Block((Mem_Block *) desc); +} + +/*! +******************************************************************************* +* Free Head descriptor back to free pool +* +* @brief Free Head descriptor +* +* @param desc A Head descriptor allocated with sah_Alloc_Head_Descriptor(). +* +* @return none +* +*/ +void sah_Free_Head_Descriptor(sah_Head_Desc * desc) +{ + memset(desc, 0x43, sizeof(*desc)); + sah_Free_Big_Block((Mem_Big_Block *) desc); +} + +/*! +******************************************************************************* +* Free link back to free pool +* +* @brief Free link +* +* @param link A link allocated with sah_Alloc_Link(). +* +* @return none +* +*/ +void sah_Free_Link(sah_Link * link) +{ + memset(link, 0x41, sizeof(*link)); + sah_Free_Block((Mem_Block *) link); +} + +/*! +******************************************************************************* +* This function runs through a descriptor chain pointed to by a user-space +* address. It duplicates each descriptor in Kernel space memory and calls +* sah_Copy_Links() to handle any links attached to the descriptors. This +* function cleans-up everything that it created in the case of a failure. +* +* @brief Kernel Descriptor Chain Copier +* +* @param user_head_desc A Head Descriptor pointer from user-space. +* +* @return sah_Head_Desc * - A virtual address of the first descriptor in the +* chain. +* @return NULL - If there was some error. +* +*/ +sah_Head_Desc *sah_Copy_Descriptors(sah_Head_Desc * user_head_desc) +{ + sah_Desc *curr_desc = NULL; + sah_Desc *prev_desc = NULL; + sah_Desc *next_desc = NULL; + sah_Head_Desc *head_desc = NULL; + sah_Desc *user_desc = NULL; + unsigned long result; + + /* Internal status variable to be used in this function */ + fsl_shw_return_t status = FSL_RETURN_OK_S; + sah_Head_Desc *ret_val = NULL; + + /* This will be set to True when we have finished processing our + * descriptor chain. + */ + int drv_if_done = FALSE; + int is_this_the_head = TRUE; + + do { + /* Allocate memory for this descriptor */ + if (is_this_the_head) { + head_desc = + (sah_Head_Desc *) sah_Alloc_Head_Descriptor(); + +#ifdef DIAG_MEM + sprintf(Diag_msg, + "Alloc_Head_Descriptor returned %p\n", + head_desc); + LOG_KDIAG(Diag_msg); +#endif + if (head_desc == NULL) { +#ifdef DIAG_DRV_IF + LOG_KDIAG + ("sah_Alloc_Head_Descriptor() failed."); +#endif + drv_if_done = TRUE; + status = FSL_RETURN_NO_RESOURCE_S; + } else { + void *virt_addr = head_desc->desc.virt_addr; + dma_addr_t dma_addr = head_desc->desc.dma_addr; + + /* Copy the head descriptor from user-space */ + /* Instead of copying the whole structure, + * unneeded bits at the end are left off. + * The user space version is missing virt/dma addrs, which + * means that the copy will be off for flags... */ + result = copy_from_user(head_desc, + user_head_desc, + (sizeof(*head_desc) - + sizeof(head_desc->desc. + dma_addr) - + sizeof(head_desc->desc. + virt_addr) - + sizeof(head_desc->desc. + original_ptr1) - +/* sizeof(head_desc->desc.original_ptr2) - + sizeof(head_desc->status) - + sizeof(head_desc->error_status) - + sizeof(head_desc->fault_address) - + sizeof(head_desc->current_dar) - + sizeof(head_desc->result) - + sizeof(head_desc->next) - + sizeof(head_desc->prev) - + sizeof(head_desc->user_desc) - +*/ sizeof(head_desc->out1_ptr) - + sizeof(head_desc-> + out2_ptr) - + sizeof(head_desc-> + out_len))); + /* there really isn't a 'next' descriptor at this point, so + * set that pointer to NULL, but remember it for if/when there + * is a next */ + next_desc = head_desc->desc.next; + head_desc->desc.next = NULL; + + if (result != 0) { +#ifdef DIAG_DRV_IF + LOG_KDIAG("copy_from_user() failed."); +#endif + drv_if_done = TRUE; + status = FSL_RETURN_INTERNAL_ERROR_S; + /* when destroying the descriptor, skip these links. + * They've not been copied down, so don't exist */ + head_desc->desc.ptr1 = NULL; + head_desc->desc.ptr2 = NULL; + + } else { + /* The kernel DESC has five more words than user DESC, so + * the missing values are in the middle of the HEAD DESC, + * causing values after the missing ones to be at different + * offsets in kernel and user space. + * + * Patch up the problem by moving field two spots. + * This assumes sizeof(pointer) == sizeof(uint32_t). + * Note that 'user_info' is not needed, so not copied. + */ + head_desc->user_ref = + (uint32_t) head_desc->desc.dma_addr; + head_desc->uco_flags = + (uint32_t) head_desc->desc. + original_ptr1; +#ifdef DIAG_DRV_IF + sprintf(Diag_msg, + "User flags: %x; User Reference: %x", + head_desc->uco_flags, + head_desc->user_ref); + LOG_KDIAG(Diag_msg); +#endif + /* These values were destroyed by the copy. */ + head_desc->desc.virt_addr = virt_addr; + head_desc->desc.dma_addr = dma_addr; + + /* ensure that the save descriptor chain bit is not set. + * the copy of the user space descriptor chain should + * always be deleted */ + head_desc->uco_flags &= + ~FSL_UCO_SAVE_DESC_CHAIN; + + curr_desc = (sah_Desc *) head_desc; + is_this_the_head = FALSE; + } + } + } else { /* not head */ + curr_desc = sah_Alloc_Descriptor(); +#ifdef DIAG_MEM + sprintf(Diag_msg, "Alloc_Descriptor returned %p\n", + curr_desc); + LOG_KDIAG(Diag_msg); +#endif + if (curr_desc == NULL) { +#ifdef DIAG_DRV_IF + LOG_KDIAG("sah_Alloc_Descriptor() failed."); +#endif + drv_if_done = TRUE; + status = FSL_RETURN_NO_RESOURCE_S; + } else { + /* need to update the previous descriptors' next field to + * pointer to the current descriptor. */ + prev_desc->original_next = curr_desc; + prev_desc->next = + (sah_Desc *) curr_desc->dma_addr; + + /* Copy the current descriptor from user-space */ + /* The virtual address and DMA address part of the sah_Desc + * struct are not copied to user space */ + result = copy_from_user(curr_desc, user_desc, (sizeof(sah_Desc) - sizeof(dma_addr_t) - /* dma_addr */ + sizeof(uint32_t) - /* virt_addr */ + sizeof(void *) - /* original_ptr1 */ + sizeof(void *) - /* original_ptr2 */ + sizeof(sah_Desc **))); /* original_next */ + /* there really isn't a 'next' descriptor at this point, so + * set that pointer to NULL, but remember it for if/when there + * is a next */ + next_desc = curr_desc->next; + curr_desc->next = NULL; + curr_desc->original_next = NULL; + + if (result != 0) { +#ifdef DIAG_DRV_IF + LOG_KDIAG("copy_from_user() failed."); +#endif + drv_if_done = TRUE; + status = FSL_RETURN_INTERNAL_ERROR_S; + /* when destroying the descriptor chain, skip these links. + * They've not been copied down, so don't exist */ + curr_desc->ptr1 = NULL; + curr_desc->ptr2 = NULL; + } + } + } /* end if (is_this_the_head) */ + + if (status == FSL_RETURN_OK_S) { + if (!(curr_desc->header & SAH_LLO_BIT)) { + /* One or both pointer fields being NULL is a valid + * configuration. */ + if (curr_desc->ptr1 == NULL) { + curr_desc->original_ptr1 = NULL; + } else { + /* pointer fields point to sah_Link structures */ + curr_desc->original_ptr1 = + sah_Copy_Links(curr_desc->ptr1); + if (curr_desc->original_ptr1 == NULL) { + /* This descriptor and any links created successfully + * are cleaned-up at the bottom of this function. */ + drv_if_done = TRUE; + status = + FSL_RETURN_INTERNAL_ERROR_S; + /* mark that link 2 doesn't exist */ + curr_desc->ptr2 = NULL; +#ifdef DIAG_DRV_IF + LOG_KDIAG + ("sah_Copy_Links() failed."); +#endif + } else { + curr_desc->ptr1 = (void *) + ((sah_Link *) curr_desc-> + original_ptr1)->dma_addr; + } + } + + if (status == FSL_RETURN_OK_S) { + if (curr_desc->ptr2 == NULL) { + curr_desc->original_ptr2 = NULL; + } else { + /* pointer fields point to sah_Link structures */ + curr_desc->original_ptr2 = + sah_Copy_Links(curr_desc-> + ptr2); + if (curr_desc->original_ptr2 == + NULL) { + /* This descriptor and any links created + * successfully are cleaned-up at the bottom of + * this function. */ + drv_if_done = TRUE; + status = + FSL_RETURN_INTERNAL_ERROR_S; +#ifdef DIAG_DRV_IF + LOG_KDIAG + ("sah_Copy_Links() failed."); +#endif + } else { + curr_desc->ptr2 = + (void + *)(((sah_Link *) + curr_desc-> + original_ptr2) + ->dma_addr); + } + } + } + } else { + /* Pointer fields point directly to user buffers. We don't + * support this mode. + */ +#ifdef DIAG_DRV_IF + LOG_KDIAG + ("The LLO bit in the Descriptor Header field was " + "set. This an invalid configuration."); +#endif + drv_if_done = TRUE; + status = FSL_RETURN_INTERNAL_ERROR_S; + } + } + + if (status == FSL_RETURN_OK_S) { + user_desc = next_desc; + prev_desc = curr_desc; + if (user_desc == NULL) { + /* We have reached the end our our descriptor chain */ + drv_if_done = TRUE; + } + } + + } while (drv_if_done == FALSE); + + if (status != FSL_RETURN_OK_S) { + /* Clean-up if failed */ + if (head_desc != NULL) { +#ifdef DIAG_DRV_IF + LOG_KDIAG("Error! Calling destroy descriptors!\n"); +#endif + sah_Destroy_Descriptors(head_desc); + } + ret_val = NULL; + } else { + /* Flush the caches */ +#ifndef FLUSH_SPECIFIC_DATA_ONLY + os_flush_cache_all(); +#endif + + /* Success. Return the DMA'able head descriptor. */ + ret_val = head_desc; + + } + + return ret_val; +} /* sah_Copy_Descriptors() */ + +/*! +******************************************************************************* +* This function runs through a sah_Link chain pointed to by a kernel-space +* address. It computes the physical address for each pointer, and converts +* the chain to use these physical addresses. +* +****** +* This function needs to return some indication that the chain could not be +* converted. It also needs to back out any conversion already taken place on +* this chain of links. +* +* Then, of course, sah_Physicalise_Descriptors() will need to recognize that +* an error occured, and then be able to back out any physicalization of the +* chain which had taken place up to that point! +****** +* +* @brief Convert kernel Link chain +* +* @param first_link A sah_Link pointer from kernel space; must not be +* NULL, so error case can be distinguished. +* +* @return sah_Link * A dma'able address of the first descriptor in the +* chain. +* @return NULL If Link chain could not be physicalised, i.e. ERROR +* +*/ +sah_Link *sah_Physicalise_Links(sah_Link * first_link) +{ + sah_Link *link = first_link; + + while (link != NULL) { + +#ifdef DO_DBG + sah_Dump_Words("Link", (unsigned *)link, 3); +#endif + link->vm_info = NULL; + + /* need to retrieve sorted key? */ + if (link->flags & SAH_STORED_KEY_INFO) { + uint32_t max_len = 0; /* max slot length */ + scc_return_t scc_status; + + /* get length and physical address of stored key */ + scc_status = scc_get_slot_info(link->ownerid, link->slot, (uint32_t *) & link->data, /* RED key address */ + NULL, /* key length */ + &max_len); + if ((scc_status != SCC_RET_OK) || (link->len > max_len)) { + /* trying to illegally/incorrectly access a key. Cause the + * error status register to show a Link Length Error by + * putting a zero in the links length. */ + link->len = 0; /* Cause error. Somebody is up to no good. */ + } + } else { + if (!(link->flags & SAH_PREPHYS_DATA)) { + link->original_data = link->data; + + /* All pointers are virtual right now */ + link->data = (void *)os_pa(link->data); +#ifdef DO_DBG + os_printk("%sput: %p (%d)\n", + (link-> + flags & SAH_OUTPUT_LINK) ? "out" : + "in", link->data, link->len); +#endif + + if (link->flags & SAH_OUTPUT_LINK) { + /* clean and invalidate */ + os_cache_flush_range(link-> + original_data, + link->len); + } else { + os_cache_clean_range(link-> + original_data, + link->len); + } + } /* not prephys */ + } /* else not key reference */ + +#if defined(NO_OUTPUT_1K_CROSSING) || defined(NO_1K_CROSSING) + if ( +#ifdef NO_OUTPUT_1K_CROSSING + /* Insert extra link if 1k boundary on output pointer + * crossed not at an 8-word boundary */ + (link->flags & SAH_OUTPUT_LINK) && + (((uint32_t) link->data % 32) != 0) && +#endif + ((((uint32_t) link->data & 1023) + link->len) > + 1024)) { + uint32_t full_length = link->len; + sah_Link *new_link = sah_Alloc_Link(); + link->len = 1024 - ((uint32_t) link->data % 1024); + new_link->len = full_length - link->len; + new_link->data = link->data + link->len; + new_link->original_data = + link->original_data + link->len; + new_link->flags = link->flags & ~(SAH_OWNS_LINK_DATA); + new_link->flags |= SAH_LINK_INSERTED_LINK; + new_link->next = link->next; + + link->next = (sah_Link *) new_link->dma_addr; + link->original_next = new_link; + link = new_link; + } +#endif /* ALLOW_OUTPUT_1K_CROSSING */ + + link->original_next = link->next; + if (link->next != NULL) { + link->next = (sah_Link *) link->next->dma_addr; + } +#ifdef DO_DBG + sah_Dump_Words("Linc", link, 3); +#endif + + link = link->original_next; + } + + return (sah_Link *) first_link->dma_addr; +} /* sah_Physicalise_Links */ + +/*! + * Run through descriptors and links created by KM-API and set the + * dma addresses and 'do not free' flags. + * + * @param first_desc KERNEL VIRTUAL address of first descriptor in chain. + * + * Warning! This ONLY works without LLO flags in headers!!! + * + * @return Virtual address of @a first_desc. + * @return NULL if Descriptor Chain could not be physicalised + */ +sah_Head_Desc *sah_Physicalise_Descriptors(sah_Head_Desc * first_desc) +{ + sah_Desc *desc = &first_desc->desc; + + if (!(first_desc->uco_flags & FSL_UCO_CHAIN_PREPHYSICALIZED)) { + while (desc != NULL) { + sah_Desc *next_desc; + +#ifdef DO_DBG + sah_Dump_Words("Desc", (unsigned *)desc, 6); +#endif + + desc->original_ptr1 = desc->ptr1; + if (desc->ptr1 != NULL) { + if ((desc->ptr1 = + sah_Physicalise_Links(desc->ptr1)) == + NULL) { + /* Clean up ... */ + sah_DePhysicalise_Descriptors + (first_desc); + first_desc = NULL; + break; + } + } + desc->original_ptr2 = desc->ptr2; + if (desc->ptr2 != NULL) { + if ((desc->ptr2 = + sah_Physicalise_Links(desc->ptr2)) == + NULL) { + /* Clean up ... */ + sah_DePhysicalise_Descriptors + (first_desc); + first_desc = NULL; + break; + } + } + + desc->original_next = desc->next; + next_desc = desc->next; /* save for bottom of while loop */ + if (desc->next != NULL) { + desc->next = (sah_Desc *) desc->next->dma_addr; + } + + desc = next_desc; + } + } + /* not prephysicalized */ +#ifdef DO_DBG + os_printk("Physicalise finished\n"); +#endif + + return first_desc; +} /* sah_Physicalise_Descriptors() */ + +/*! +******************************************************************************* +* This function runs through a sah_Link chain pointed to by a physical address. +* It computes the virtual address for each pointer +* +* @brief Convert physical Link chain +* +* @param first_link A kernel address of a sah_Link +* +* @return sah_Link * A kernal address for the link chain of @c first_link +* @return NULL If there was some error. +* +* @post All links will be chained together by original virtual addresses, +* data pointers will point to virtual addresses. Appropriate cache +* lines will be flushed, memory unwired, etc. +*/ +sah_Link *sah_DePhysicalise_Links(sah_Link * first_link) +{ + sah_Link *link = first_link; + sah_Link *prev_link = NULL; + + /* Loop on virtual link pointer */ + while (link != NULL) { + +#ifdef DO_DBG + sah_Dump_Words("Link", (unsigned *)link, 3); +#endif + + /* if this references stored keys, don't want to dephysicalize them */ + if (!(link->flags & SAH_STORED_KEY_INFO) + && !(link->flags & SAH_PREPHYS_DATA)) { + + /* */ + if (link->flags & SAH_OUTPUT_LINK) { + os_cache_inv_range(link->original_data, + link->len); + } + + /* determine if there is a page in user space associated with this + * link */ + if (link->vm_info != NULL) { + /* check that this isn't reserved and contains output */ + if (!PageReserved(link->vm_info) && + (link->flags & SAH_OUTPUT_LINK)) { + + /* Mark to force page eventually to backing store */ + SetPageDirty(link->vm_info); + } + + /* Untie this page from physical memory */ + page_cache_release(link->vm_info); + } else { + /* kernel-mode data */ +#ifdef DO_DBG + os_printk("%sput: %p (%d)\n", + (link-> + flags & SAH_OUTPUT_LINK) ? "out" : + "in", link->original_data, link->len); +#endif + } + link->data = link->original_data; + } +#ifndef ALLOW_OUTPUT_1K_CROSSING + if (link->flags & SAH_LINK_INSERTED_LINK) { + /* Reconsolidate data by merging this link with previous */ + prev_link->len += link->len; + prev_link->next = link->next; + prev_link->original_next = link->original_next; + sah_Free_Link(link); + link = prev_link; + + } +#endif + + if (link->next != NULL) { + link->next = link->original_next; + } + prev_link = link; + link = link->next; + } + + return first_link; +} /* sah_DePhysicalise_Links() */ + +/*! + * Run through descriptors and links that have been Physicalised + * (sah_Physicalise_Descriptors function) and set the dma addresses back + * to KM virtual addresses + * + * @param first_desc Kernel virtual address of first descriptor in chain. + * + * Warning! This ONLY works without LLO flags in headers!!! + */ +sah_Head_Desc *sah_DePhysicalise_Descriptors(sah_Head_Desc * first_desc) +{ + sah_Desc *desc = &first_desc->desc; + + if (!(first_desc->uco_flags & FSL_UCO_CHAIN_PREPHYSICALIZED)) { + while (desc != NULL) { +#ifdef DO_DBG + sah_Dump_Words("Desc", (unsigned *)desc, 6); +#endif + + if (desc->ptr1 != NULL) { + desc->ptr1 = + sah_DePhysicalise_Links(desc-> + original_ptr1); + } + if (desc->ptr2 != NULL) { + desc->ptr2 = + sah_DePhysicalise_Links(desc-> + original_ptr2); + } + if (desc->next != NULL) { + desc->next = desc->original_next; + } + desc = desc->next; + } + } + /* not prephysicalized */ + return first_desc; +} /* sah_DePhysicalise_Descriptors() */ + +/*! +******************************************************************************* +* This walks through a SAHARA descriptor chain and free()'s everything +* that is not NULL. Finally it also unmaps all of the physical memory and +* frees the kiobuf_list Queue. +* +* @brief Kernel Descriptor Chain Destructor +* +* @param head_desc A Descriptor pointer from kernel-space. +* +* @return void +* +*/ +void sah_Free_Chained_Descriptors(sah_Head_Desc * head_desc) +{ + sah_Desc *desc = NULL; + sah_Desc *next_desc = NULL; + int this_is_head = 1; + + desc = &head_desc->desc; + + while (desc != NULL) { + + sah_Free_Chained_Links(desc->ptr1); + sah_Free_Chained_Links(desc->ptr2); + + /* Get a bus pointer to the next Descriptor */ + next_desc = desc->next; + + /* Zero the header and Length fields for security reasons. */ + desc->header = 0; + desc->len1 = 0; + desc->len2 = 0; + + if (this_is_head) { + sah_Free_Head_Descriptor(head_desc); + this_is_head = 0; +#ifdef DIAG_MEM + sprintf(Diag_msg, "Free_Head_Descriptor: %p\n", + head_desc); + LOG_KDIAG(Diag_msg); +#endif + } else { + /* free this descriptor */ + sah_Free_Descriptor(desc); +#ifdef DIAG_MEM + sprintf(Diag_msg, "Free_Descriptor: %p\n", desc); + LOG_KDIAG(Diag_msg); +#endif + } + + /* Look at the next Descriptor */ + desc = next_desc; + } +} /* sah_Free_Chained_Descriptors() */ + +/*! +******************************************************************************* +* This walks through a SAHARA link chain and frees everything that is +* not NULL, excluding user-space buffers. +* +* @brief Kernel Link Chain Destructor +* +* @param link A Link pointer from kernel-space. This is in bus address +* space. +* +* @return void +* +*/ +void sah_Free_Chained_Links(sah_Link * link) +{ + sah_Link *next_link = NULL; + + while (link != NULL) { + /* Get a bus pointer to the next Link */ + next_link = link->next; + + /* Zero some fields for security reasons. */ + link->data = NULL; + link->len = 0; + link->ownerid = 0; + + /* Free this Link */ +#ifdef DIAG_MEM + sprintf(Diag_msg, "Free_Link: %p(->%p)\n", link, link->next); + LOG_KDIAG(Diag_msg); +#endif + sah_Free_Link(link); + + /* Move on to the next Link */ + link = next_link; + } +} + +/*! +******************************************************************************* +* This function runs through a link chain pointed to by a user-space +* address. It makes a temporary kernel-space copy of each link in the +* chain and calls sah_Make_Links() to create a set of kernel-side links +* to replace it. +* +* @brief Kernel Link Chain Copier +* +* @param ptr A link pointer from user-space. +* +* @return sah_Link * - The virtual address of the first link in the +* chain. +* @return NULL - If there was some error. +*/ +sah_Link *sah_Copy_Links(sah_Link * ptr) +{ + sah_Link *head_link = NULL; + sah_Link *new_head_link = NULL; + sah_Link *new_tail_link = NULL; + sah_Link *prev_tail_link = NULL; + sah_Link *user_link = ptr; + sah_Link link_copy; + int link_data_length = 0; + + /* Internal status variable to be used in this function */ + fsl_shw_return_t status = FSL_RETURN_OK_S; + sah_Link *ret_val = NULL; + + /* This will be set to True when we have finished processing our + * link chain. */ + int drv_if_done = FALSE; + int is_this_the_head = TRUE; + int result; + + /* transfer all links, on this link chain, from user space */ + while (drv_if_done == FALSE) { + /* Copy the current link from user-space. The virtual address, DMA + * address, and vm_info fields of the sah_Link struct are not part + * of the user-space structure. They must be the last elements and + * should not be copied. */ + result = copy_from_user(&link_copy, + user_link, (sizeof(sah_Link) - +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)) + sizeof(struct page *) - /* vm_info */ +#endif + sizeof(dma_addr_t) - /* dma_addr */ + sizeof(uint32_t) - /* virt_addr */ + sizeof(uint8_t *) - /* original_data */ + sizeof(sah_Link *))); /* original_next */ + + if (result != 0) { +#ifdef DIAG_DRV_IF + LOG_KDIAG("copy_from_user() failed."); +#endif + drv_if_done = TRUE; + status = FSL_RETURN_INTERNAL_ERROR_S; + } + + if (status == FSL_RETURN_OK_S) { + /* This will create new links which can be used to replace tmp_link + * in the chain. This will return a new head and tail link. */ + link_data_length = link_data_length + link_copy.len; + new_head_link = + sah_Make_Links(&link_copy, &new_tail_link); + + if (new_head_link == NULL) { + /* If we ran out of memory or a user pointer was invalid */ + drv_if_done = TRUE; + status = FSL_RETURN_INTERNAL_ERROR_S; +#ifdef DIAG_DRV_IF + LOG_KDIAG("sah_Make_Links() failed."); +#endif + } else { + if (is_this_the_head == TRUE) { + /* Keep a reference to the head link */ + head_link = new_head_link; + is_this_the_head = FALSE; + } else { + /* Need to update the previous links' next field to point + * to the current link. */ + prev_tail_link->next = + (void *)new_head_link->dma_addr; + prev_tail_link->original_next = + new_head_link; + } + } + } + + if (status == FSL_RETURN_OK_S) { + /* Get to the next link in the chain. */ + user_link = link_copy.next; + prev_tail_link = new_tail_link; + + /* Check if the end of the link chain was reached (TRUE) or if + * there is another linked to this one (FALSE) */ + drv_if_done = (user_link == NULL) ? TRUE : FALSE; + } + } /* end while */ + + if (status != FSL_RETURN_OK_S) { + ret_val = NULL; + /* There could be clean-up to do here because we may have made some + * successful iterations through the while loop and as a result, the + * links created by sah_Make_Links() need to be destroyed. + */ + if (head_link != NULL) { + /* Failed somewhere in the while loop and need to clean-up. */ + sah_Destroy_Links(head_link); + } + } else { + /* Success. Return the head link. */ + ret_val = head_link; + } + + return ret_val; +} /* sah_Copy_Links() */ + +/*! +******************************************************************************* +* This function takes an input link pointed to by a user-space address +* and returns a chain of links that span the physical pages pointed +* to by the input link. +* +* @brief Kernel Link Chain Constructor +* +* @param ptr A link pointer from user-space. +* @param tail The address of a link pointer. This is used to return +* the tail link created by this function. +* +* @return sah_Link * - A virtual address of the first link in the +* chain. +* @return NULL - If there was some error. +* +*/ +sah_Link *sah_Make_Links(sah_Link * ptr, sah_Link ** tail) +{ + int result = -1; + int page_index = 0; + fsl_shw_return_t status = FSL_RETURN_OK_S; + int is_this_the_head = TRUE; + void *buffer_start = NULL; + sah_Link *link = NULL; + sah_Link *prev_link = NULL; + sah_Link *head_link = NULL; + sah_Link *ret_val = NULL; + int buffer_length = 0; + struct page **local_pages = NULL; + int nr_pages = 0; + int write = (sah_Link_Get_Flags(ptr) & SAH_OUTPUT_LINK) ? WRITE : READ; + + /* need to retrieve stored key? */ + if (ptr->flags & SAH_STORED_KEY_INFO) { + scc_return_t scc_status; + + /* allocate space for this link */ + link = sah_Alloc_Link(); +#ifdef DIAG_MEM + sprintf(Diag_msg, "Alloc_Link returned %p/%p\n", link, + (void *)link->dma_addr); + LOG_KDIAG(Diag_msg); +#endif + + if (link == NULL) { + status = FSL_RETURN_NO_RESOURCE_S; +#ifdef DIAG_DRV_IF + LOG_KDIAG("sah_Alloc_Link() failed!"); +#endif + return link; + } else { + uint32_t max_len = 0; /* max slot length */ + + /* get length and physical address of stored key */ + scc_status = scc_get_slot_info(ptr->ownerid, ptr->slot, (uint32_t *) & link->data, /* RED key address */ + NULL, &max_len); + + if ((scc_status == SCC_RET_OK) && (ptr->len <= max_len)) { + /* finish populating the link */ + link->len = ptr->len; + link->flags = ptr->flags & ~SAH_PREPHYS_DATA; + *tail = link; + } else { +#ifdef DIAG_DRV_IF + if (scc_status == SCC_RET_OK) { + LOG_KDIAG + ("SCC sah_Link key slot reference is too long"); + } else { + LOG_KDIAG + ("SCC sah_Link slot slot reference is invalid"); + } +#endif + sah_Free_Link(link); + status = FSL_RETURN_INTERNAL_ERROR_S; + return NULL; + } + return link; + } + } + /* stored-key support */ + if (ptr->data == NULL) { + /* The user buffer must not be NULL because map_user_kiobuf() cannot + * handle NULL pointer input. + */ + status = FSL_RETURN_BAD_DATA_LENGTH_S; +#ifdef DIAG_DRV_IF + LOG_KDIAG("sah_Link data pointer is NULL."); +#endif + } + + if (status == FSL_RETURN_OK_S) { + unsigned long start_page = (unsigned long)ptr->data & PAGE_MASK; + + /* determine number of pages being used for this link */ + nr_pages = (((unsigned long)(ptr->data) & ~PAGE_MASK) + + ptr->len + ~PAGE_MASK) >> PAGE_SHIFT; + + /* ptr contains all the 'user space' information, add the pages + * to it also just so everything is in one place */ + local_pages = + kmalloc(nr_pages * sizeof(struct page *), GFP_KERNEL); + + if (local_pages == NULL) { + status = FSL_RETURN_NO_RESOURCE_S; /* no memory! */ +#ifdef DIAG_DRV_IF + LOG_KDIAG("kmalloc() failed."); +#endif + } else { + /* get the actual pages being used in 'user space' */ + + down_read(¤t->mm->mmap_sem); + result = get_user_pages(current, current->mm, + start_page, nr_pages, + write, 0 /* noforce */ , + local_pages, NULL); + up_read(¤t->mm->mmap_sem); + + if (result < nr_pages) { +#ifdef DIAG_DRV_IF + LOG_KDIAG("get_user_pages() failed."); +#endif + if (result > 0) { + for (page_index = 0; + page_index < result; + page_index++) { + page_cache_release(local_pages + [page_index]); + } + } + status = FSL_RETURN_INTERNAL_ERROR_S; + } + } + } + + /* Now we can walk through the list of pages in the buffer */ + if (status == FSL_RETURN_OK_S) { + +#if defined(FLUSH_SPECIFIC_DATA_ONLY) && !defined(CONFIG_OUTER_CACHE) + /* + * Now that pages are wired, clear user data from cache lines. When + * there is just an L1 cache, clean based on user virtual for ARM. + */ + if (write == WRITE) { + os_cache_flush_range(ptr->data, ptr->len); + } else { + os_cache_clean_range(ptr->data, ptr->len); + } +#endif + + for (page_index = 0; page_index < nr_pages; page_index++) { + /* Allocate a new link structure */ + link = sah_Alloc_Link(); +#ifdef DIAG_MEM + sprintf(Diag_msg, "Alloc_Link returned %p/%p\n", link, + (void *)link->dma_addr); + LOG_KDIAG(Diag_msg); +#endif + if (link == NULL) { +#ifdef DIAG_DRV_IF + LOG_KDIAG("sah_Alloc_Link() failed."); +#endif + status = FSL_RETURN_NO_RESOURCE_S; + + /* need to free the rest of the pages. Destroy_Links will take + * care of the ones already assigned to a link */ + for (; page_index < nr_pages; page_index++) { + page_cache_release(local_pages + [page_index]); + } + break; /* exit 'for page_index' loop */ + } + + if (status == FSL_RETURN_OK_S) { + if (is_this_the_head == TRUE) { + /* keep a reference to the head link */ + head_link = link; + /* remember that we have seen the head link */ + is_this_the_head = FALSE; + } else { + /* If this is not the head link then set the previous + * link's next pointer to point to this link */ + prev_link->original_next = link; + prev_link->next = + (sah_Link *) link->dma_addr; + } + + buffer_start = + page_address(local_pages[page_index]); + + if (page_index == 0) { + /* If this is the first page, there might be an + * offset. We need to increment the address by this offset + * so we don't just get the start of the page. + */ + buffer_start += + (unsigned long) + sah_Link_Get_Data(ptr) + & ~PAGE_MASK; + buffer_length = PAGE_SIZE + - + ((unsigned long) + sah_Link_Get_Data(ptr) + & ~PAGE_MASK); + } else { + buffer_length = PAGE_SIZE; + } + + if (page_index == nr_pages - 1) { + /* if this is the last page, we need to adjust + * the buffer_length to account for the last page being + * partially used. + */ + buffer_length -= + nr_pages * PAGE_SIZE - + sah_Link_Get_Len(ptr) - + ((unsigned long) + sah_Link_Get_Data(ptr) & + ~PAGE_MASK); + } +#if defined(FLUSH_SPECIFIC_DATA_ONLY) && defined(CONFIG_OUTER_CACHE) + /* + * When there is an L2 cache, clean based on kernel + * virtual.. + */ + if (write == WRITE) { + os_cache_flush_range(buffer_start, + buffer_length); + } else { + os_cache_clean_range(buffer_start, + buffer_length); + } +#endif + + /* Fill in link information */ + link->len = buffer_length; +#if !defined(CONFIG_OUTER_CACHE) + /* use original virtual */ + link->original_data = ptr->data; +#else + /* use kernel virtual */ + link->original_data = buffer_start; +#endif + link->data = (void *)os_pa(buffer_start); + link->flags = ptr->flags & ~SAH_PREPHYS_DATA; + link->vm_info = local_pages[page_index]; + prev_link = link; + +#if defined(NO_OUTPUT_1K_CROSSING) || defined(NO_1K_CROSSING) + if ( +#ifdef NO_OUTPUT_1K_CROSSING + /* Insert extra link if 1k boundary on output pointer + * crossed not at an 8-word boundary */ + (link->flags & SAH_OUTPUT_LINK) && + (((uint32_t) buffer_start % 32) != 0) + && +#endif + ((((uint32_t) buffer_start & 1023) + + buffer_length) > 1024)) { + + /* Shorten current link to 1k boundary */ + link->len = + 1024 - + ((uint32_t) buffer_start % 1024); + + /* Get new link to follow it */ + link = sah_Alloc_Link(); + prev_link->len = + 1024 - + ((uint32_t) buffer_start % 1024); + prev_link->original_next = link; + prev_link->next = + (sah_Link *) link->dma_addr; + buffer_length -= prev_link->len; + buffer_start += prev_link->len; + +#if !defined(CONFIG_OUTER_CACHE) + /* use original virtual */ + link->original_data = ptr->data; +#else + /* use kernel virtual */ + link->original_data = buffer_start; +#endif + link->data = + (void *)os_pa(buffer_start); + link->vm_info = prev_link->vm_info; + prev_link->vm_info = NULL; /* delay release */ + link->flags = ptr->flags; + link->len = buffer_length; + prev_link = link; + } /* while link would cross 1K boundary */ +#endif /* 1K_CROSSING */ + } + } /* for each page */ + } + + if (local_pages != NULL) { + kfree(local_pages); + } + + if (status != FSL_RETURN_OK_S) { + /* De-allocated any links created, this routine first looks if + * head_link is NULL */ + sah_Destroy_Links(head_link); + + /* Clean-up of the KIOBUF will occur in the * sah_Copy_Descriptors() + * function. + * Clean-up of the Queue entry must occur in the function called + * sah_Copy_Descriptors(). + */ + } else { + + /* Success. Return the head link. */ + ret_val = head_link; + link->original_next = NULL; + /* return the tail link as well */ + *tail = link; + } + + return ret_val; +} /* sah_Make_Links() */ + +/*! +******************************************************************************* +* This walks through a SAHARA descriptor chain and frees everything +* that is not NULL. Finally it also unmaps all of the physical memory and +* frees the kiobuf_list Queue. +* +* @brief Kernel Descriptor Chain Destructor +* +* @param desc A Descriptor pointer from kernel-space. This should be +* in bus address space. +* +* @return void +* +*/ +void sah_Destroy_Descriptors(sah_Head_Desc * head_desc) +{ + sah_Desc *this_desc = (sah_Desc *) head_desc; + sah_Desc *next_desc = NULL; + int this_is_head = 1; + + /* + * Flush the D-cache. This flush is here because the hardware has finished + * processing this descriptor and probably has changed the contents of + * some linked user buffers as a result. This flush will enable + * user-space applications to see the correct data rather than the + * out-of-date cached version. + */ +#ifndef FLUSH_SPECIFIC_DATA_ONLY + os_flush_cache_all(); +#endif + + head_desc = (sah_Head_Desc *) this_desc->virt_addr; + + while (this_desc != NULL) { + if (this_desc->ptr1 != NULL) { + sah_Destroy_Links(this_desc->original_ptr1 + ? this_desc-> + original_ptr1 : this_desc->ptr1); + } + if (this_desc->ptr2 != NULL) { + sah_Destroy_Links(this_desc->original_ptr2 + ? this_desc-> + original_ptr2 : this_desc->ptr2); + } + + /* Get a bus pointer to the next Descriptor */ + next_desc = (this_desc->original_next + ? this_desc->original_next : this_desc->next); + + /* Zero the header and Length fields for security reasons. */ + this_desc->header = 0; + this_desc->len1 = 0; + this_desc->len2 = 0; + + if (this_is_head) { + sah_Free_Head_Descriptor(head_desc); +#ifdef DIAG_MEM + sprintf(Diag_msg, "Free_Head_Descriptor: %p\n", + head_desc); + LOG_KDIAG(Diag_msg); +#endif + this_is_head = 0; + } else { + /* free this descriptor */ + sah_Free_Descriptor(this_desc); +#ifdef DIAG_MEM + sprintf(Diag_msg, "Free_Descriptor: %p\n", this_desc); + LOG_KDIAG(Diag_msg); +#endif + } + + /* Set up for next round. */ + this_desc = (sah_Desc *) next_desc; + } +} + +/*! +******************************************************************************* +* This walks through a SAHARA link chain and frees everything that is +* not NULL excluding user-space buffers. +* +* @brief Kernel Link Chain Destructor +* +* @param link A Link pointer from kernel-space. +* +* @return void +* +*/ +void sah_Destroy_Links(sah_Link * link) +{ + sah_Link *this_link = link; + sah_Link *next_link = NULL; + + while (this_link != NULL) { + + /* if this link indicates an associated page, process it */ + if (this_link->vm_info != NULL) { + /* since this function is only called from the routine that + * creates a kernel copy of the user space descriptor chain, + * there are no pages to dirty. All that is needed is to release + * the page from cache */ + page_cache_release(this_link->vm_info); + } + + /* Get a bus pointer to the next Link */ + next_link = (this_link->original_next + ? this_link->original_next : this_link->next); + + /* Zero the Pointer and Length fields for security reasons. */ + this_link->data = NULL; + this_link->len = 0; + + /* Free this Link */ + sah_Free_Link(this_link); +#ifdef DIAG_MEM + sprintf(Diag_msg, "Free_Link: %p\n", this_link); + LOG_KDIAG(Diag_msg); +#endif + + /* Look at the next Link */ + this_link = next_link; + } +} + +/*! +******************************************************************************* +* @brief Initialize memory manager/mapper. +* +* In 2.4, this function also allocates a kiovec to be used when mapping user +* data to kernel space +* +* @return 0 for success, OS error code on failure +* +*/ +int sah_Init_Mem_Map(void) +{ + int ret = OS_ERROR_FAIL_S; + + mem_lock = os_lock_alloc_init(); + + /* + * If one of these fails, change the calculation in the #define earlier in + * the file to be the other one. + */ + if (sizeof(sah_Link) > MEM_BLOCK_SIZE) { + os_printk("Sahara Driver: sah_Link structure is too large\n"); + } else if (sizeof(sah_Desc) > MEM_BLOCK_SIZE) { + os_printk("Sahara Driver: sah_Desc structure is too large\n"); + } else { + ret = OS_ERROR_OK_S; + } + +#ifndef SELF_MANAGED_POOL + + big_dma_pool = dma_pool_create("sah_big_blocks", NULL, + sizeof(Mem_Big_Block), sizeof(uint32_t), + PAGE_SIZE); + small_dma_pool = dma_pool_create("sah_small_blocks", NULL, + sizeof(Mem_Block), sizeof(uint32_t), + PAGE_SIZE); +#else + +#endif + return ret; +} + +/*! +******************************************************************************* +* @brief Clean up memory manager/mapper. +* +* In 2.4, this function also frees the kiovec used when mapping user data to +* kernel space. +* +* @return none +* +*/ +void sah_Stop_Mem_Map(void) +{ + os_lock_deallocate(mem_lock); + +#ifndef SELF_MANAGED_POOL + if (big_dma_pool != NULL) { + dma_pool_destroy(big_dma_pool); + } + if (small_dma_pool != NULL) { + dma_pool_destroy(small_dma_pool); + } +#endif +} + +/*! +******************************************************************************* +* Allocate Head descriptor from free pool. +* +* @brief Allocate Head descriptor +* +* @return sah_Head_Desc Free descriptor, NULL if no free descriptors available. +* +*/ +sah_Head_Desc *sah_Alloc_Head_Descriptor(void) +{ + Mem_Big_Block *block; + sah_Head_Desc *desc; + + block = sah_Alloc_Big_Block(); + if (block != NULL) { + /* initialize everything */ + desc = (sah_Head_Desc *) block->data; + + desc->desc.virt_addr = (sah_Desc *) desc; + desc->desc.dma_addr = block->dma_addr; + desc->desc.original_ptr1 = NULL; + desc->desc.original_ptr2 = NULL; + desc->desc.original_next = NULL; + + desc->desc.ptr1 = NULL; + desc->desc.ptr2 = NULL; + desc->desc.next = NULL; + } else { + desc = NULL; + } + + return desc; +} + +/*! +******************************************************************************* +* Allocate descriptor from free pool. +* +* @brief Allocate descriptor +* +* @return sah_Desc Free descriptor, NULL if no free descriptors available. +* +*/ +sah_Desc *sah_Alloc_Descriptor(void) +{ + Mem_Block *block; + sah_Desc *desc; + + block = sah_Alloc_Block(); + if (block != NULL) { + /* initialize everything */ + desc = (sah_Desc *) block->data; + + desc->virt_addr = desc; + desc->dma_addr = block->dma_addr; + desc->original_ptr1 = NULL; + desc->original_ptr2 = NULL; + desc->original_next = NULL; + + desc->ptr1 = NULL; + desc->ptr2 = NULL; + desc->next = NULL; + } else { + desc = NULL; + } + + return (desc); +} + +/*! +******************************************************************************* +* Allocate link from free pool. +* +* @brief Allocate link +* +* @return sah_Link Free link, NULL if no free links available. +* +*/ +sah_Link *sah_Alloc_Link(void) +{ + Mem_Block *block; + sah_Link *link; + + block = sah_Alloc_Block(); + if (block != NULL) { + /* initialize everything */ + link = (sah_Link *) block->data; + + link->virt_addr = link; + link->original_next = NULL; + link->original_data = NULL; + /* information found in allocated block */ + link->dma_addr = block->dma_addr; + + /* Sahara link fields */ + link->len = 0; + link->data = NULL; + link->next = NULL; + + /* driver required fields */ + link->flags = 0; + link->vm_info = NULL; + } else { + link = NULL; + } + + return link; +} + +#ifdef SELF_MANAGED_POOL +/*! +******************************************************************************* +* Add a new page to end of block free pool. This will allocate one page and +* fill the pool with entries, appending to the end. +* +* @brief Add page of blocks to block free pool. +* +* @pre This function must be called with the #mem_lock held. +* +* @param big 0 - make blocks big enough for sah_Desc +* non-zero - make blocks big enough for sah_Head_Desc +* +* @return int TRUE if blocks added succeesfully, FALSE otherwise +* +*/ +int sah_Block_Add_Page(int big) +{ + void *page; + int success; + dma_addr_t dma_addr; + unsigned block_index; + uint32_t dma_offset; + unsigned block_entries = + big ? MEM_BIG_BLOCK_ENTRIES : MEM_BLOCK_ENTRIES; + unsigned block_size = big ? sizeof(Mem_Big_Block) : sizeof(Mem_Block); + void *block; + + /* Allocate page of memory */ +#ifndef USE_COHERENT_MEMORY + page = os_alloc_memory(PAGE_SIZE, GFP_ATOMIC | __GFP_DMA); + dma_addr = os_pa(page); +#else + page = os_alloc_coherent(PAGE_SIZE, &dma_addr, GFP_ATOMIC); +#endif + if (page != NULL) { + /* + * Find the difference between the virtual address and the DMA + * address of the page. This is used later to determine the DMA + * address of each individual block. + */ + dma_offset = page - (void *)dma_addr; + + /* Split page into blocks and add to free pool */ + block = page; + for (block_index = 0; block_index < block_entries; + block_index++) { + if (big) { + register Mem_Big_Block *blockp = block; + blockp->dma_addr = + (uint32_t) (block - dma_offset); + sah_Append_Big_Block(blockp); + } else { + register Mem_Block *blockp = block; + blockp->dma_addr = + (uint32_t) (block - dma_offset); + /* sah_Append_Block must be protected with spin locks. This is + * done in sah_Alloc_Block(), which calls + * sah_Block_Add_Page() */ + sah_Append_Block(blockp); + } + block += block_size; + } + success = TRUE; +#ifdef DIAG_MEM + LOG_KDIAG("Succeeded in allocating new page"); +#endif + } else { + success = FALSE; +#ifdef DIAG_MEM + LOG_KDIAG("Failed in allocating new page"); +#endif + } + + return success; +} +#endif /* SELF_MANAGED_POOL */ + +#ifdef SELF_MANAGED_POOL +/*! +******************************************************************************* +* Allocate block from free pool. A block is large enough to fit either a link +* or descriptor. +* +* @brief Allocate memory block +* +* @return Mem_Block Free block, NULL if no free blocks available. +* +*/ +static Mem_Big_Block *sah_Alloc_Big_Block(void) +{ + Mem_Big_Block *block; + os_lock_context_t lock_flags; + + os_lock_save_context(mem_lock, lock_flags); + + /* If the pool is empty, try to allocate more entries */ + if (big_block_free_head == NULL) { + (void)sah_Block_Add_Page(1); + } + + /* Check that the pool now has some free entries */ + if (big_block_free_head != NULL) { + /* Return the head of the free pool */ + block = big_block_free_head; + + big_block_free_head = big_block_free_head->next; + if (big_block_free_head == NULL) { + /* Allocated last entry in pool */ + big_block_free_tail = NULL; + } + } else { + block = NULL; + } + os_unlock_restore_context(mem_lock, lock_flags); + + return block; +} +#else +/*! +******************************************************************************* +* Allocate block from free pool. A block is large enough to fit either a link +* or descriptor. +* +* @brief Allocate memory block +* +* @return Mem_Block Free block, NULL if no free blocks available. +* +*/ +static Mem_Big_Block *sah_Alloc_Big_Block(void) +{ + dma_addr_t dma_addr; + Mem_Big_Block *block = + dma_pool_alloc(big_dma_pool, GFP_ATOMIC, &dma_addr); + + if (block == NULL) { + } else { + block->dma_addr = dma_addr; + } + + return block; +} +#endif + +#ifdef SELF_MANAGED_POOL +/*! +******************************************************************************* +* Allocate block from free pool. A block is large enough to fit either a link +* or descriptor. +* +* @brief Allocate memory block +* +* @return Mem_Block Free block, NULL if no free blocks available. +* +*/ +/****************************************************************************** +* +* MODIFICATION HISTORY: +* +* Date Person Change +* 31/10/2003 RWK PR52734 - Implement functions to allocate +* descriptors and links. Replace +* consistent_alloc() calls. Initial creation. +* +******************************************************************************/ +static Mem_Block *sah_Alloc_Block(void) +{ + Mem_Block *block; + os_lock_context_t lock_flags; + + os_lock_save_context(mem_lock, lock_flags); + + /* If the pool is empty, try to allocate more entries */ + if (block_free_head == NULL) { + (void)sah_Block_Add_Page(0); + } + + /* Check that the pool now has some free entries */ + if (block_free_head != NULL) { + /* Return the head of the free pool */ + block = block_free_head; + + block_free_head = block_free_head->next; + if (block_free_head == NULL) { + /* Allocated last entry in pool */ + block_free_tail = NULL; + } + } else { + block = NULL; + } + os_unlock_restore_context(mem_lock, lock_flags); + + return block; +} +#else +/*! +******************************************************************************* +* Allocate block from free pool. A block is large enough to fit either a link +* or descriptor. +* +* @brief Allocate memory block +* +* @return Mem_Block Free block, NULL if no free blocks available. +* +*/ +/****************************************************************************** +* +* MODIFICATION HISTORY: +* +* Date Person Change +* 31/10/2003 RWK PR52734 - Implement functions to allocate +* descriptors and links. Replace +* consistent_alloc() calls. Initial creation. +* +******************************************************************************/ +static Mem_Block *sah_Alloc_Block(void) +{ + + dma_addr_t dma_addr; + Mem_Block *block = + dma_pool_alloc(small_dma_pool, GFP_ATOMIC, &dma_addr); + + if (block == NULL) { + } else { + block->dma_addr = dma_addr; + } + + return block; +} +#endif + +#ifdef SELF_MANAGED_POOL +/*! +******************************************************************************* +* Free memory block back to free pool +* +* @brief Free memory block +* +* @param block A block allocated with sah_Alloc_Block(). +* +* @return none +* +*/ +static void sah_Free_Block(Mem_Block * block) +{ + os_lock_context_t lock_flags; + + os_lock_save_context(mem_lock, lock_flags); + sah_Append_Block(block); + os_unlock_restore_context(mem_lock, lock_flags); +} +#else +/*! +******************************************************************************* +* Free memory block back to free pool +* +* @brief Free memory block +* +* @param block A block allocated with sah_Alloc_Block(). +* +* @return none +* +*/ +static void sah_Free_Block(Mem_Block * block) +{ + dma_pool_free(small_dma_pool, block, block->dma_addr); +} +#endif + +#ifdef SELF_MANAGED_POOL +/*! +******************************************************************************* +* Free memory block back to free pool +* +* @brief Free memory block +* +* @param block A block allocated with sah_Alloc_Block(). +* +* @return none +* +*/ +static void sah_Free_Big_Block(Mem_Big_Block * block) +{ + os_lock_context_t lock_flags; + + os_lock_save_context(mem_lock, lock_flags); + sah_Append_Big_Block(block); + os_unlock_restore_context(mem_lock, lock_flags); +} +#else +/*! +******************************************************************************* +* Free memory block back to free pool +* +* @brief Free memory block +* +* @param block A block allocated with sah_Alloc_Block(). +* +* @return none +* +*/ +static void sah_Free_Big_Block(Mem_Big_Block * block) +{ + dma_pool_free(big_dma_pool, block, block->dma_addr); +} +#endif + +#ifdef SELF_MANAGED_POOL +/*! +******************************************************************************* +* Append memory block to end of free pool. +* +* @param block A block entry +* +* @return none +* +* @pre This function must be called with the #mem_lock held. +* +* @brief Append memory block to free pool +*/ +static void sah_Append_Big_Block(Mem_Big_Block * block) +{ + + /* Initialise block */ + block->next = NULL; + + /* Remember that block may be the first in the pool */ + if (big_block_free_tail != NULL) { + big_block_free_tail->next = block; + } else { + /* Pool is empty */ + big_block_free_head = block; + } + + big_block_free_tail = block; +} + +/*! +******************************************************************************* +* Append memory block to end of free pool. +* +* @brief Append memory block to free pool +* +* @param block A block entry +* +* @return none +* +* @pre #mem_lock must be held +* +*/ +static void sah_Append_Block(Mem_Block * block) +{ + + /* Initialise block */ + block->next = NULL; + + /* Remember that block may be the first in the pool */ + if (block_free_tail != NULL) { + block_free_tail->next = block; + } else { + /* Pool is empty */ + block_free_head = block; + } + + block_free_tail = block; +} +#endif /* SELF_MANAGED_POOL */ + +/* End of sah_memory_mapper.c */ diff --git a/drivers/mxc/security/sahara2/sah_queue.c b/drivers/mxc/security/sahara2/sah_queue.c new file mode 100644 index 000000000000..0f3e56e4c254 --- /dev/null +++ b/drivers/mxc/security/sahara2/sah_queue.c @@ -0,0 +1,249 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! +* @file sah_queue.c +* +* @brief This file provides a FIFO Queue implementation. +* +*/ +/****************************************************************************** +* +* CAUTION: +******************************************************************* +*/ + +/* SAHARA Includes */ +#include <sah_queue_manager.h> +#ifdef DIAG_DRV_QUEUE +#include <diagnostic.h> +#endif + +/****************************************************************************** +* Queue Functions +******************************************************************************/ + +/*! +******************************************************************************* +* This function constructs a new sah_Queue. +* +* @brief sah_Queue Constructor +* +* @return A pointer to a newly allocated sah_Queue. +* @return NULL if allocation of memory failed. +*/ +/****************************************************************************** +* +* CAUTION: This function may sleep in low-memory situations, as it uses +* kmalloc ( ..., GFP_KERNEL). +******************************************************************************/ +sah_Queue *sah_Queue_Construct(void) +{ + sah_Queue *q = (sah_Queue *) os_alloc_memory(sizeof(sah_Queue), + GFP_KERNEL); + + if (q != NULL) { + /* Initialise the queue to an empty state. */ + q->head = NULL; + q->tail = NULL; + q->count = 0; + } +#ifdef DIAG_DRV_QUEUE + else { + LOG_KDIAG("kmalloc() failed."); + } +#endif + + return q; +} + +/*! +******************************************************************************* +* This function destroys a sah_Queue. +* +* @brief sah_Queue Destructor +* +* @param q A pointer to a sah_Queue. +* +* @return void +*/ +/****************************************************************************** +* +* CAUTION: This function does not free any queue entries. +* +******************************************************************************/ +void sah_Queue_Destroy(sah_Queue * q) +{ +#ifdef DIAG_DRV_QUEUE + if (q == NULL) { + LOG_KDIAG("Trying to kfree() a NULL pointer."); + } else { + if (q->count != 0) { + LOG_KDIAG + ("Trying to destroy a queue that is not empty."); + } + } +#endif + + if (q != NULL) { + os_free_memory(q); + q = NULL; + } +} + +/*! +******************************************************************************* +* This function appends a sah_Head_Desc to the tail of a sah_Queue. +* +* @brief Appends a sah_Head_Desc to a sah_Queue. +* +* @param q A pointer to a sah_Queue to append to. +* @param entry A pointer to a sah_Head_Desc to append. +* +* @pre The #desc_queue_lock must be held before calling this function. +* +* @return void +*/ +/****************************************************************************** +* +* CAUTION: NONE +******************************************************************************/ +void sah_Queue_Append_Entry(sah_Queue * q, sah_Head_Desc * entry) +{ + sah_Head_Desc *tail_entry = NULL; + + if ((q == NULL) || (entry == NULL)) { +#ifdef DIAG_DRV_QUEUE + LOG_KDIAG("Null pointer input."); +#endif + return; + } + + if (q->count == 0) { + /* The queue is empty */ + q->head = entry; + q->tail = entry; + entry->next = NULL; + entry->prev = NULL; + } else { + /* The queue is not empty */ + tail_entry = q->tail; + tail_entry->next = entry; + entry->next = NULL; + entry->prev = tail_entry; + q->tail = entry; + } + q->count++; +} + +/*! +******************************************************************************* +* This function a removes a sah_Head_Desc from the head of a sah_Queue. +* +* @brief Removes a sah_Head_Desc from a the head of a sah_Queue. +* +* @param q A pointer to a sah_Queue to remove from. +* +* @pre The #desc_queue_lock must be held before calling this function. +* +* @return void +*/ +/****************************************************************************** +* +* CAUTION: This does not kfree() the entry. +******************************************************************************/ +void sah_Queue_Remove_Entry(sah_Queue * q) +{ + sah_Queue_Remove_Any_Entry(q, q->head); +} + +/*! +******************************************************************************* +* This function a removes a sah_Head_Desc from anywhere in a sah_Queue. +* +* @brief Removes a sah_Head_Desc from anywhere in a sah_Queue. +* +* @param qq A pointer to a sah_Queue to remove from. +* @param entry A pointer to a sah_Head_Desc to remove. +* +* @pre The #desc_queue_lock must be held before calling this function. +* +* @return void +*/ +/****************************************************************************** +* +* CAUTION: This does not kfree() the entry. Does not check to see if the entry +* actually belongs to the queue. +******************************************************************************/ +void sah_Queue_Remove_Any_Entry(sah_Queue * q, sah_Head_Desc * entry) +{ + sah_Head_Desc *prev_entry = NULL; + sah_Head_Desc *next_entry = NULL; + + if ((q == NULL) || (entry == NULL)) { +#if defined DIAG_DRV_QUEUE && defined DIAG_DURING_INTERRUPT + LOG_KDIAG("Null pointer input."); +#endif + return; + } + + if (q->count == 1) { + /* If q is the only entry in the queue. */ + q->tail = NULL; + q->head = NULL; + q->count = 0; + } else if (q->count > 1) { + /* There are 2 or more entries in the queue. */ + +#if defined DIAG_DRV_QUEUE && defined DIAG_DURING_INTERRUPT + if ((entry->next == NULL) && (entry->prev == NULL)) { + LOG_KDIAG + ("Queue is not empty yet both next and prev pointers" + " are NULL"); + } +#endif + + if (entry->next == NULL) { + /* If this is the end of the queue */ + prev_entry = entry->prev; + prev_entry->next = NULL; + q->tail = prev_entry; + } else if (entry->prev == NULL) { + /* If this is the head of the queue */ + next_entry = entry->next; + next_entry->prev = NULL; + q->head = next_entry; + } else { + /* If this is somewhere in the middle of the queue */ + prev_entry = entry->prev; + next_entry = entry->next; + prev_entry->next = next_entry; + next_entry->prev = prev_entry; + } + q->count--; + } + /* + * Otherwise we are removing an entry from an empty queue. + * Don't do anything in the product code + */ +#if defined DIAG_DRV_QUEUE && defined DIAG_DURING_INTERRUPT + else { + LOG_KDIAG("Trying to remove an entry from an empty queue."); + } +#endif + + entry->next = NULL; + entry->prev = NULL; +} + +/* End of sah_queue.c */ diff --git a/drivers/mxc/security/sahara2/sah_queue_manager.c b/drivers/mxc/security/sahara2/sah_queue_manager.c new file mode 100644 index 000000000000..8afaf17c28ca --- /dev/null +++ b/drivers/mxc/security/sahara2/sah_queue_manager.c @@ -0,0 +1,1033 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file sah_queue_manager.c + * + * @brief This file provides a Queue Manager implementation. + * + * The Queue Manager manages additions and removal from the queue and updates + * the status of queue entries. It also calls sah_HW_* functions to interract + * with the hardware. +*/ + +#include "portable_os.h" + +/* SAHARA Includes */ +#include <sah_driver_common.h> +#include <sah_queue_manager.h> +#include <sah_status_manager.h> +#include <sah_hardware_interface.h> +#if defined(DIAG_DRV_QUEUE) || defined(DIAG_DRV_STATUS) +#include <diagnostic.h> +#endif +#include <sah_memory_mapper.h> + +#ifdef DIAG_DRV_STATUS + +#define FSL_INVALID_RETURN 13 +#define MAX_RETURN_STRING_LEN 22 +#endif + +/* Defines for parsing value from Error Status register */ +#define SAH_STATUS_MASK 0x07 +#define SAH_ERROR_MASK 0x0F +#define SAH_CHA_ERR_SOURCE_MASK 0x07 +#define SAH_CHA_ERR_STATUS_MASK 0x0FFF +#define SAH_DMA_ERR_STATUS_MASK 0x0F +#define SAH_DMA_ERR_SIZE_MASK 0x03 +#define SAH_DMA_ERR_DIR_MASK 0x01 + +#define SHA_ERROR_STATUS_CONTINUE 0xFFFFFFFF +#define SHA_CHA_ERROR_STATUS_DONE 0xFFFFFFFF + +/* this maps the error status register's error source 4 bit field to the API + * return values. A 0xFFFFFFFF indicates additional fields must be checked to + * determine an appropriate return value */ +static sah_Execute_Error sah_Execute_Error_Array[] = { + FSL_RETURN_ERROR_S, /* SAH_ERR_NONE */ + FSL_RETURN_BAD_FLAG_S, /* SAH_ERR_HEADER */ + FSL_RETURN_BAD_DATA_LENGTH_S, /* SAH_ERR_DESC_LENGTH */ + FSL_RETURN_BAD_DATA_LENGTH_S, /* SAH_ERR_DESC_POINTER */ + FSL_RETURN_BAD_DATA_LENGTH_S, /* SAH_ERR_LINK_LENGTH */ + FSL_RETURN_BAD_DATA_LENGTH_S, /* SAH_ERR_LINK_POINTER */ + FSL_RETURN_INTERNAL_ERROR_S, /* SAH_ERR_INPUT_BUFFER */ + FSL_RETURN_INTERNAL_ERROR_S, /* SAH_ERR_OUTPUT_BUFFER */ + FSL_RETURN_BAD_DATA_LENGTH_S, /* SAH_ERR_OUTPUT_BUFFER_STARVATION */ + FSL_RETURN_INTERNAL_ERROR_S, /* SAH_ERR_INTERNAL_STATE */ + FSL_RETURN_ERROR_S, /* SAH_ERR_GENERAL_DESCRIPTOR */ + FSL_RETURN_INTERNAL_ERROR_S, /* SAH_ERR_RESERVED_FIELDS */ + FSL_RETURN_MEMORY_ERROR_S, /* SAH_ERR_DESCRIPTOR_ADDRESS */ + FSL_RETURN_MEMORY_ERROR_S, /* SAH_ERR_LINK_ADDRESS */ + SHA_ERROR_STATUS_CONTINUE, /* SAH_ERR_CHA */ + SHA_ERROR_STATUS_CONTINUE /* SAH_ERR_DMA */ +}; + +static sah_DMA_Error_Status sah_DMA_Error_Status_Array[] = { + FSL_RETURN_INTERNAL_ERROR_S, /* SAH_DMA_NO_ERR */ + FSL_RETURN_INTERNAL_ERROR_S, /* SAH_DMA_AHB_ERR */ + FSL_RETURN_INTERNAL_ERROR_S, /* SAH_DMA_IP_ERR */ + FSL_RETURN_INTERNAL_ERROR_S, /* SAH_DMA_PARITY_ERR */ + FSL_RETURN_BAD_DATA_LENGTH_S, /* SAH_DMA_BOUNDRY_ERR */ + FSL_RETURN_INTERNAL_ERROR_S, /* SAH_DMA_BUSY_ERR */ + FSL_RETURN_INTERNAL_ERROR_S, /* SAH_DMA_RESERVED_ERR */ + FSL_RETURN_INTERNAL_ERROR_S /* SAH_DMA_INT_ERR */ +}; + +static sah_CHA_Error_Status sah_CHA_Error_Status_Array[] = { + FSL_RETURN_INTERNAL_ERROR_S, /* SAH_CHA_NO_ERR */ + FSL_RETURN_BAD_DATA_LENGTH_S, /* SAH_CHA_IP_BUF */ + FSL_RETURN_INTERNAL_ERROR_S, /* SAH_CHA_ADD_ERR */ + FSL_RETURN_BAD_MODE_S, /* SAH_CHA_MODE_ERR */ + FSL_RETURN_BAD_DATA_LENGTH_S, /* SAH_CHA_DATA_SIZE_ERR */ + FSL_RETURN_BAD_KEY_LENGTH_S, /* SAH_CHA_KEY_SIZE_ERR */ + FSL_RETURN_BAD_MODE_S, /* SAH_CHA_PROC_ERR */ + FSL_RETURN_ERROR_S, /* SAH_CHA_CTX_READ_ERR */ + FSL_RETURN_INTERNAL_ERROR_S, /* SAH_CHA_INTERNAL_HW_ERR */ + FSL_RETURN_MEMORY_ERROR_S, /* SAH_CHA_IP_BUFF_ERR */ + FSL_RETURN_MEMORY_ERROR_S, /* SAH_CHA_OP_BUFF_ERR */ + FSL_RETURN_BAD_KEY_PARITY_S, /* SAH_CHA_DES_KEY_ERR */ + FSL_RETURN_INTERNAL_ERROR_S, /* SAH_CHA_RES */ +}; + +#ifdef DIAG_DRV_STATUS + +char sah_return_text[FSL_INVALID_RETURN][MAX_RETURN_STRING_LEN] = { + "No error", /* FSL_RETURN_OK_S */ + "Error", /* FSL_RETURN_ERROR_S */ + "No resource", /* FSL_RETURN_NO_RESOURCE_S */ + "Bad algorithm", /* FSL_RETURN_BAD_ALGORITHM_S */ + "Bad mode", /* FSL_RETURN_BAD_MODE_S */ + "Bad flag", /* FSL_RETURN_BAD_FLAG_S */ + "Bad key length", /* FSL_RETURN_BAD_KEY_LENGTH_S */ + "Bad key parity", /* FSL_RETURN_BAD_KEY_PARITY_S */ + "Bad data length", /* FSL_RETURN_BAD_DATA_LENGTH_S */ + "Authentication failed", /* FSL_RETURN_AUTH_FAILED_S */ + "Memory error", /* FSL_RETURN_MEMORY_ERROR_S */ + "Internal error", /* FSL_RETURN_INTERNAL_ERROR_S */ + "unknown value", /* default */ +}; + +#endif /* DIAG_DRV_STATUS */ + +/*! + * This lock must be held while performing any queuing or unqueuing functions, + * including reading the first pointer on the queue. It also protects reading + * and writing the Sahara DAR register. It must be held during a read-write + * operation on the DAR so that the 'test-and-set' is atomic. + */ +os_lock_t desc_queue_lock; + +/*! This is the main queue for the driver. This is shared between all threads + * and is not protected by mutexes since the kernel is non-preemptable. */ +sah_Queue *main_queue = NULL; + +/* Internal Prototypes */ +sah_Head_Desc *sah_Find_With_State(sah_Queue_Status state); + +#ifdef DIAG_DRV_STATUS +void sah_Log_Error(uint32_t descriptor, uint32_t error, uint32_t fault_address); +#endif + +extern wait_queue_head_t *int_queue; + +/*! + * This function initialises the Queue Manager + * + * @brief Initialise the Queue Manager + * + * @return FSL_RETURN_OK_S on success; FSL_RETURN_MEMORY_ERROR_S if not + */ +fsl_shw_return_t sah_Queue_Manager_Init(void) +{ + fsl_shw_return_t ret_val = FSL_RETURN_OK_S; + + desc_queue_lock = os_lock_alloc_init(); + + if (main_queue == NULL) { + /* Construct the main queue. */ + main_queue = sah_Queue_Construct(); + + if (main_queue == NULL) { + ret_val = FSL_RETURN_MEMORY_ERROR_S; + } + } else { +#ifdef DIAG_DRV_QUEUE + LOG_KDIAG + ("Trying to initialise the queue manager more than once."); +#endif + } + + return ret_val; +} + +/*! + * This function closes the Queue Manager + * + * @brief Close the Queue Manager + * + * @return void + */ +void sah_Queue_Manager_Close(void) +{ +#ifdef DIAG_DRV_QUEUE + if (main_queue && main_queue->count != 0) { + LOG_KDIAG + ("Trying to close the main queue when it is not empty."); + } +#endif + + if (main_queue) { + /* There is no error checking here because there is no way to handle + it. */ + sah_Queue_Destroy(main_queue); + main_queue = NULL; + } +} + +/*! + * Count the number of entries on the Queue Manager's queue + * + * @param ignore_state If non-zero, the @a state parameter is ignored. + * If zero, only entries matching @a state are counted. + * @param state State of entry to match for counting. + * + * @return Number of entries which matched criteria + */ +int sah_Queue_Manager_Count_Entries(int ignore_state, sah_Queue_Status state) +{ + int count = 0; + sah_Head_Desc *current_entry; + + /* Start at the head */ + current_entry = main_queue->head; + while (current_entry != NULL) { + if (ignore_state || (current_entry->status == state)) { + count++; + } + /* Jump to the next entry. */ + current_entry = current_entry->next; + } + + return count; +} + +/*! + * This function removes an entry from the Queue Manager's queue. The entry to + * be removed can be anywhere in the queue. + * + * @brief Remove an entry from the Queue Manager's queue. + * + * @param entry A pointer to a sah_Head_Desc to remove from the Queue + * Manager's queue. + * + * @pre The #desc_queue_lock must be held before calling this function. + * + * @return void + */ +void sah_Queue_Manager_Remove_Entry(sah_Head_Desc * entry) +{ + if (entry == NULL) { +#ifdef DIAG_DRV_QUEUE + LOG_KDIAG("NULL pointer input."); +#endif + } else { + sah_Queue_Remove_Any_Entry(main_queue, entry); + } +} + +/*! + * This function appends an entry to the Queue Managers queue. It primes SAHARA + * if this entry is the first PENDING entry in the Queue Manager's Queue. + * + * @brief Appends an entry to the Queue Manager's queue. + * + * @param entry A pointer to a sah_Head_Desc to append to the Queue + * Manager's queue. + * + * @pre The #desc_queue_lock may not may be held when calling this function. + * + * @return void + */ +void sah_Queue_Manager_Append_Entry(sah_Head_Desc * entry) +{ + sah_Head_Desc *current_entry; + os_lock_context_t int_flags; + +#ifdef DIAG_DRV_QUEUE + if (entry == NULL) { + LOG_KDIAG("NULL pointer input."); + } +#endif + entry->status = SAH_STATE_PENDING; + os_lock_save_context(desc_queue_lock, int_flags); + sah_Queue_Append_Entry(main_queue, entry); + + /* Prime SAHARA if the operation that was just appended is the only PENDING + * operation in the queue. + */ + current_entry = sah_Find_With_State(SAH_STATE_PENDING); + if (current_entry != NULL) { + if (current_entry == entry) { + sah_Queue_Manager_Prime(entry); + } + } + + os_unlock_restore_context(desc_queue_lock, int_flags); +} + +/*! + * This function marks all entries in the Queue Manager's queue with state + * SAH_STATE_RESET. + * + * @brief Mark all entries with state SAH_STATE_RESET + * + * @return void + * + * @note This feature needs re-visiting + */ +void sah_Queue_Manager_Reset_Entries(void) +{ + sah_Head_Desc *current_entry = NULL; + + /* Start at the head */ + current_entry = main_queue->head; + + while (current_entry != NULL) { + /* Set the state. */ + current_entry->status = SAH_STATE_RESET; + /* Jump to the next entry. */ + current_entry = current_entry->next; + } +} + +/*! + * This function primes SAHARA for the first time or after the queue becomes + * empty. Queue lock must have been set by the caller of this routine. + * + * @brief Prime SAHARA. + * + * @param entry A pointer to a sah_Head_Desc to Prime SAHARA with. + * + * @return void + */ +void sah_Queue_Manager_Prime(sah_Head_Desc * entry) +{ +#ifdef DIAG_DRV_QUEUE + LOG_KDIAG("Priming SAHARA"); + if (entry == NULL) { + LOG_KDIAG("Trying to prime SAHARA with a NULL entry pointer."); + } +#endif + +#ifndef SUBMIT_MULTIPLE_DARS + /* BUG FIX: state machine can transition from Done1 Busy2 directly + * to Idle. To fix that problem, only one DAR is being allowed on + * SAHARA at a time */ + if (sah_Find_With_State(SAH_STATE_ON_SAHARA) != NULL) { + return; + } +#endif + +#ifdef SAHARA_POWER_MANAGEMENT + /* check that dynamic power management is not asserted */ + if (!sah_dpm_flag) { +#endif + /* Make sure nothing is in the DAR */ + if (sah_HW_Read_DAR() == 0) { +#if defined(DIAG_DRV_IF) + sah_Dump_Chain(&entry->desc, entry->desc.dma_addr); +#endif /* DIAG_DRV_IF */ + + sah_HW_Write_DAR((entry->desc.dma_addr)); + entry->status = SAH_STATE_ON_SAHARA; + } +#ifdef DIAG_DRV_QUEUE + else { + LOG_KDIAG("DAR should be empty when Priming SAHARA"); + } +#endif +#ifdef SAHARA_POWER_MANAGEMENT + } +#endif +} + +#ifndef SAHARA_POLL_MODE + +/*! + * Reset SAHARA, then load the next descriptor on it, if one exists + */ +void sah_reset_sahara_request(void) +{ + sah_Head_Desc *desc; + os_lock_context_t lock_flags; + +#ifdef DIAG_DRV_STATUS + LOG_KDIAG("Sahara required reset from tasklet, replace chip"); +#endif + sah_HW_Reset(); + + /* Now stick in a waiting request */ + os_lock_save_context(desc_queue_lock, lock_flags); + if ((desc = sah_Find_With_State(SAH_STATE_PENDING))) { + sah_Queue_Manager_Prime(desc); + } + os_unlock_restore_context(desc_queue_lock, lock_flags); +} + +/*! + * Post-process a descriptor chain after the hardware has finished with it. + * + * The status of the descriptor could also be checked. (for FATAL or IGNORED). + * + * @param desc_head The finished chain + * @param error A boolean to mark whether hardware reported error + * + * @pre The #desc_queue_lock may not be held when calling this function. + */ +void sah_process_finished_request(sah_Head_Desc * desc_head, unsigned error) +{ + os_lock_context_t lock_flags; + + if (!error) { + desc_head->result = FSL_RETURN_OK_S; + } else if (desc_head->error_status == -1) { + /* Disaster! Sahara has faulted */ + desc_head->result = FSL_RETURN_ERROR_S; + } else { + /* translate from SAHARA error status to fsl_shw return values */ + desc_head->result = + sah_convert_error_status(desc_head->error_status); +#ifdef DIAG_DRV_STATUS + sah_Log_Error(desc_head->current_dar, desc_head->error_status, + desc_head->fault_address); +#endif + } + + /* Show that the request has been processd */ + desc_head->status = error ? SAH_STATE_FAILED : SAH_STATE_COMPLETE; + + if (desc_head->uco_flags & FSL_UCO_BLOCKING_MODE) { + + /* Wake up all processes on Sahara queue */ + wake_up_interruptible(int_queue); + + } else { + os_lock_save_context(desc_queue_lock, lock_flags); + sah_Queue_Append_Entry(&desc_head->user_info->result_pool, + desc_head); + os_unlock_restore_context(desc_queue_lock, lock_flags); + + /* perform callback */ + if (desc_head->uco_flags & FSL_UCO_CALLBACK_MODE) { + desc_head->user_info->callback(desc_head->user_info); + } + } +} /* sah_process_finished_request */ + +/*! Called from bottom half. + * + * @pre The #desc_queue_lock may not be held when calling this function. + */ +void sah_postprocess_queue(unsigned long reset_flag) +{ + + /* if SAHARA needs to be reset, do it here. This starts a descriptor chain + * if one is ready also */ + if (reset_flag) { + sah_reset_sahara_request(); + } + + /* now handle the descriptor chain(s) that has/have completed */ + do { + sah_Head_Desc *first_entry; + os_lock_context_t lock_flags; + + os_lock_save_context(desc_queue_lock, lock_flags); + + first_entry = main_queue->head; + if ((first_entry != NULL) && + (first_entry->status == SAH_STATE_OFF_SAHARA)) { + sah_Queue_Remove_Entry(main_queue); + os_unlock_restore_context(desc_queue_lock, lock_flags); + + sah_process_finished_request(first_entry, + (first_entry-> + error_status != 0)); + } else { + os_unlock_restore_context(desc_queue_lock, lock_flags); + break; + } + } while (1); + + return; +} + +#endif /* ifndef SAHARA_POLL_MODE */ + +/*! + * This is a helper function for Queue Manager. This function finds the first + * entry in the Queue Manager's queue whose state matches the given input + * state. This function starts at the head of the queue and works towards the + * tail. If a matching entry was found, the address of the entry is returned. + * + * @brief Handle the IDLE state. + * + * @param state A sah_Queue_Status value. + * + * @pre The #desc_queue_lock must be held before calling this function. + * + * @return A pointer to a sah_Head_Desc that matches the given state. + * @return NULL otherwise. + */ +sah_Head_Desc *sah_Find_With_State(sah_Queue_Status state) +{ + sah_Head_Desc *current_entry = NULL; + sah_Head_Desc *ret_val = NULL; + int done_looping = FALSE; + + /* Start at the head */ + current_entry = main_queue->head; + + while ((current_entry != NULL) && (done_looping == FALSE)) { + if (current_entry->status == state) { + done_looping = TRUE; + ret_val = current_entry; + } + /* Jump to the next entry. */ + current_entry = current_entry->next; + } + + return ret_val; +} /* sah_postprocess_queue */ + +/*! + * Process the value from the Sahara error status register and convert it into + * an FSL SHW API error code. + * + * Warning, this routine must only be called if an error exists. + * + * @param error_status The value from the error status register. + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t sah_convert_error_status(uint32_t error_status) +{ + fsl_shw_return_t ret = FSL_RETURN_ERROR_S; /* catchall */ + uint8_t error_source; + uint8_t DMA_error_status; + uint8_t DMA_error_size; + + /* get the error source from the error status register */ + error_source = error_status & SAH_ERROR_MASK; + + /* array size is maximum allowed by mask, so no boundary checking is + * needed here */ + ret = sah_Execute_Error_Array[error_source]; + + /* is this one that needs additional fields checked to determine the + * error condition? */ + if (ret == SHA_ERROR_STATUS_CONTINUE) { + /* check the DMA fields */ + if (error_source == SAH_ERR_DMA) { + /* get the DMA transfer error size. If this indicates that no + * error was detected, something is seriously wrong */ + DMA_error_size = + (error_status >> 9) & SAH_DMA_ERR_SIZE_MASK; + if (DMA_error_size == SAH_DMA_NO_ERR) { + ret = FSL_RETURN_INTERNAL_ERROR_S; + } else { + /* get DMA error status */ + DMA_error_status = (error_status >> 12) & + SAH_DMA_ERR_STATUS_MASK; + + /* the DMA error bits cover all the even numbers. By dividing + * by 2 it can be used as an index into the error array */ + ret = + sah_DMA_Error_Status_Array[DMA_error_status + >> 1]; + } + } else { /* not SAH_ERR_DMA, so must be SAH_ERR_CHA */ + uint16_t CHA_error_status; + uint8_t CHA_error_source; + + /* get CHA Error Source. If this indicates that no error was + * detected, something is seriously wrong */ + CHA_error_source = + (error_status >> 28) & SAH_CHA_ERR_SOURCE_MASK; + if (CHA_error_source == SAH_CHA_NO_ERROR) { + ret = FSL_RETURN_INTERNAL_ERROR_S; + } else { + uint32_t mask = 1; + uint32_t count = 0; + + /* get CHA Error Status */ + CHA_error_status = (error_status >> 16) & + SAH_CHA_ERR_STATUS_MASK; + + /* If more than one bit is set (which shouldn't happen), only + * the first will be captured */ + if (CHA_error_status != 0) { + count = 1; + while (CHA_error_status != mask) { + ++count; + mask <<= 1; + } + } + + ret = sah_CHA_Error_Status_Array[count]; + } + } + } + + return ret; +} + +fsl_shw_return_t sah_convert_op_status(uint32_t op_status) +{ + unsigned op_source = (op_status >> 28) & 0x7; + unsigned op_detail = op_status & 0x3f; + fsl_shw_return_t ret = FSL_RETURN_ERROR_S; + + switch (op_source) { + case 1: /* SKHA */ + /* Can't this have "ICV" error from CCM ?? */ + break; + case 2: /* MDHA */ + if (op_detail == 1) { + ret = FSL_RETURN_AUTH_FAILED_S; + } + break; + case 3: /* RNGA */ + /* Self-test and Compare errors... what to do? */ + break; + case 4: /* PKHA */ + switch (op_detail) { + case 0x01: + ret = FSL_RETURN_PRIME_S; + break; + case 0x02: + ret = FSL_RETURN_NOT_PRIME_S; + break; + case 0x04: + ret = FSL_RETURN_POINT_AT_INFINITY_S; + break; + case 0x08: + ret = FSL_RETURN_POINT_NOT_AT_INFINITY_S; + break; + case 0x10: + ret = FSL_RETURN_GCD_IS_ONE_S; + break; + case 0x20: + ret = FSL_RETURN_GCD_IS_NOT_ONE_S; + break; + default: + break; + } + break; + default: + break; + } + return ret; +} + +#ifdef DIAG_DRV_STATUS + +/*! + * This function logs the diagnostic information for the given error and + * descriptor address. Only used for diagnostic purposes. + * + * @brief (debug only) Log a description of hardware-detected error. + * + * @param descriptor The descriptor address that caused the error + * @param error The SAHARA error code + * @param fault_address Value from the Fault address register + * + * @return void + */ +void sah_Log_Error(uint32_t descriptor, uint32_t error, uint32_t fault_address) +{ + char *source_text; /* verbose error source from register */ + char *address; /* string buffer for descriptor address */ + char *error_log; /* the complete logging message */ + char *cha_log = NULL; /* string buffer for descriptor address */ + char *dma_log = NULL; /* string buffer for descriptor address */ + + uint16_t cha_error = 0; + uint16_t dma_error = 0; + + uint8_t error_source; + sah_Execute_Error return_code; + + /* log error code and descriptor address */ + error_source = error & SAH_ERROR_MASK; + return_code = sah_Execute_Error_Array[error_source]; + + source_text = os_alloc_memory(64, GFP_KERNEL); + + switch (error_source) { + case SAH_ERR_HEADER: + sprintf(source_text, "%s", "Header is not valid"); + break; + + case SAH_ERR_DESC_LENGTH: + sprintf(source_text, "%s", + "Descriptor length not equal to sum of link lengths"); + break; + + case SAH_ERR_DESC_POINTER: + sprintf(source_text, "%s", "Length or pointer " + "field is zero while the other is non-zero"); + break; + + case SAH_ERR_LINK_LENGTH: + /* note that the Sahara Block Guide 2.7 has an invalid explaination + * of this. It only happens when a link length is zero */ + sprintf(source_text, "%s", "A data length is a link is zero"); + break; + + case SAH_ERR_LINK_POINTER: + sprintf(source_text, "%s", + "The data pointer in a link is zero"); + break; + + case SAH_ERR_INPUT_BUFFER: + sprintf(source_text, "%s", "Input Buffer reported an overflow"); + break; + + case SAH_ERR_OUTPUT_BUFFER: + sprintf(source_text, "%s", + "Output Buffer reported an underflow"); + break; + + case SAH_ERR_OUTPUT_BUFFER_STARVATION: + sprintf(source_text, "%s", "Incorrect data in output " + "buffer after CHA has signalled 'done'"); + break; + + case SAH_ERR_INTERNAL_STATE: + sprintf(source_text, "%s", "Internal Hardware Failure"); + break; + + case SAH_ERR_GENERAL_DESCRIPTOR: + sprintf(source_text, "%s", + "Current Descriptor was not legal, but cause is unknown"); + break; + + case SAH_ERR_RESERVED_FIELDS: + sprintf(source_text, "%s", + "Reserved pointer field is non-zero"); + break; + + case SAH_ERR_DESCRIPTOR_ADDRESS: + sprintf(source_text, "%s", + "Descriptor address not word aligned"); + break; + + case SAH_ERR_LINK_ADDRESS: + sprintf(source_text, "%s", "Link address not word aligned"); + break; + + case SAH_ERR_CHA: + sprintf(source_text, "%s", "CHA Error"); + { + char *cha_module = os_alloc_memory(5, GFP_KERNEL); + char *cha_text = os_alloc_memory(45, GFP_KERNEL); + + cha_error = (error >> 28) & SAH_CHA_ERR_SOURCE_MASK; + + switch (cha_error) { + case SAH_CHA_SKHA_ERROR: + sprintf(cha_module, "%s", "SKHA"); + break; + + case SAH_CHA_MDHA_ERROR: + sprintf(cha_module, "%s", "MDHA"); + break; + + case SAH_CHA_RNG_ERROR: + sprintf(cha_module, "%s", "RNG "); + break; + + case SAH_CHA_PKHA_ERROR: + sprintf(cha_module, "%s", "PKHA"); + break; + + case SAH_CHA_NO_ERROR: + /* can't happen */ + /* no break */ + default: + sprintf(cha_module, "%s", "????"); + break; + } + + cha_error = (error >> 16) & SAH_CHA_ERR_STATUS_MASK; + + /* Log CHA Error Status */ + switch (cha_error) { + case SAH_CHA_IP_BUF: + sprintf(cha_text, "%s", + "Non-empty input buffer when done"); + break; + + case SAH_CHA_ADD_ERR: + sprintf(cha_text, "%s", "Illegal address"); + break; + + case SAH_CHA_MODE_ERR: + sprintf(cha_text, "%s", "Illegal mode"); + break; + + case SAH_CHA_DATA_SIZE_ERR: + sprintf(cha_text, "%s", "Illegal data size"); + break; + + case SAH_CHA_KEY_SIZE_ERR: + sprintf(cha_text, "%s", "Illegal key size"); + break; + + case SAH_CHA_PROC_ERR: + sprintf(cha_text, "%s", + "Mode/Context/Key written during processing"); + break; + + case SAH_CHA_CTX_READ_ERR: + sprintf(cha_text, "%s", + "Context read during processing"); + break; + + case SAH_CHA_INTERNAL_HW_ERR: + sprintf(cha_text, "%s", "Internal hardware"); + break; + + case SAH_CHA_IP_BUFF_ERR: + sprintf(cha_text, "%s", + "Input buffer not enabled or underflow"); + break; + + case SAH_CHA_OP_BUFF_ERR: + sprintf(cha_text, "%s", + "Output buffer not enabled or overflow"); + break; + + case SAH_CHA_DES_KEY_ERR: + sprintf(cha_text, "%s", "DES key parity error"); + break; + + case SAH_CHA_RES: + sprintf(cha_text, "%s", "Reserved"); + break; + + case SAH_CHA_NO_ERR: + /* can't happen */ + /* no break */ + default: + sprintf(cha_text, "%s", "Unknown error"); + break; + } + + cha_log = os_alloc_memory(90, GFP_KERNEL); + sprintf(cha_log, + " Module %s encountered the error: %s.", + cha_module, cha_text); + + os_free_memory(cha_module); + os_free_memory(cha_text); + + { + uint32_t mask = 1; + uint32_t count = 0; + + if (cha_error != 0) { + count = 1; + while (cha_error != mask) { + ++count; + mask <<= 1; + } + } + + return_code = sah_CHA_Error_Status_Array[count]; + } + cha_error = 1; + } + break; + + case SAH_ERR_DMA: + sprintf(source_text, "%s", "DMA Error"); + { + char *dma_direction = os_alloc_memory(6, GFP_KERNEL); + char *dma_size = os_alloc_memory(14, GFP_KERNEL); + char *dma_text = os_alloc_memory(250, GFP_KERNEL); + + if ((dma_direction == NULL) || (dma_size == NULL) || + (dma_text == NULL)) { + LOG_KDIAG + ("No memory allocated for DMA debug messages\n"); + } + + /* log DMA error direction */ + sprintf(dma_direction, "%s", + (((error >> 8) & SAH_DMA_ERR_DIR_MASK) == 1) ? + "read" : "write"); + + /* log the size of the DMA transfer error */ + dma_error = (error >> 9) & SAH_DMA_ERR_SIZE_MASK; + switch (dma_error) { + case SAH_DMA_SIZE_BYTE: + sprintf(dma_size, "%s", "byte"); + break; + + case SAH_DMA_SIZE_HALF_WORD: + sprintf(dma_size, "%s", "half-word"); + break; + + case SAH_DMA_SIZE_WORD: + sprintf(dma_size, "%s", "word"); + break; + + case SAH_DMA_SIZE_RES: + sprintf(dma_size, "%s", "reserved size"); + break; + + default: + sprintf(dma_size, "%s", "unknown size"); + break; + } + + /* log DMA error status */ + dma_error = (error >> 12) & SAH_DMA_ERR_STATUS_MASK; + switch (dma_error) { + case SAH_DMA_NO_ERR: + sprintf(dma_text, "%s", "No DMA Error Code"); + break; + + case SAH_DMA_AHB_ERR: + sprintf(dma_text, "%s", + "AHB terminated a bus cycle with an error"); + break; + + case SAH_DMA_IP_ERR: + sprintf(dma_text, "%s", + "Internal IP bus cycle was terminated with an " + "error termination. This would likely be " + "caused by a descriptor length being too " + "large, and thus accessing an illegal " + "internal address. Verify the length field " + "of the current descriptor"); + break; + + case SAH_DMA_PARITY_ERR: + sprintf(dma_text, "%s", + "Parity error detected on DMA command from " + "Descriptor Decoder. Cause is likely to be " + "internal hardware fault"); + break; + + case SAH_DMA_BOUNDRY_ERR: + sprintf(dma_text, "%s", + "DMA was requested to cross a 256 byte " + "internal address boundary. Cause is likely a " + "descriptor length being too large, thus " + "accessing two different internal hardware " + "blocks"); + break; + + case SAH_DMA_BUSY_ERR: + sprintf(dma_text, "%s", + "Descriptor Decoder has made a DMA request " + "while the DMA controller is busy. Cause is " + "likely due to hardware fault"); + break; + + case SAH_DMA_RESERVED_ERR: + sprintf(dma_text, "%s", "Reserved"); + break; + + case SAH_DMA_INT_ERR: + sprintf(dma_text, "%s", + "Internal DMA hardware error detected. The " + "DMA controller has detected an internal " + "condition which should never occur"); + break; + + default: + sprintf(dma_text, "%s", + "Unknown DMA Error Status Code"); + break; + } + + return_code = + sah_DMA_Error_Status_Array[dma_error >> 1]; + dma_error = 1; + + dma_log = os_alloc_memory(320, GFP_KERNEL); + sprintf(dma_log, + " Occurred during a %s operation of a %s transfer: %s.", + dma_direction, dma_size, dma_text); + + os_free_memory(dma_direction); + os_free_memory(dma_size); + os_free_memory(dma_text); + } + break; + + case SAH_ERR_NONE: + default: + sprintf(source_text, "%s", "Unknown Error Code"); + break; + } + + address = os_alloc_memory(35, GFP_KERNEL); + + /* convert error & descriptor address to strings */ + if (dma_error) { + sprintf(address, "Fault address is 0x%08x", fault_address); + } else { + sprintf(address, "Descriptor bus address is 0x%08x", + descriptor); + } + + if (return_code > FSL_INVALID_RETURN) { + return_code = FSL_INVALID_RETURN; + } + + error_log = os_alloc_memory(250, GFP_KERNEL); + + /* construct final log message */ + sprintf(error_log, "Error source = 0x%08x. Return = %s. %s. %s.", + error, sah_return_text[return_code], address, source_text); + + os_free_memory(source_text); + os_free_memory(address); + + /* log standard messages */ + LOG_KDIAG(error_log); + os_free_memory(error_log); + + /* add additional information if available */ + if (cha_error) { + LOG_KDIAG(cha_log); + os_free_memory(cha_log); + } + + if (dma_error) { + LOG_KDIAG(dma_log); + os_free_memory(dma_log); + } + + return; +} /* sah_Log_Error */ + +#endif /* DIAG_DRV_STATUS */ + +/* End of sah_queue_manager.c */ diff --git a/drivers/mxc/security/sahara2/sah_status_manager.c b/drivers/mxc/security/sahara2/sah_status_manager.c new file mode 100644 index 000000000000..5b5e67bda125 --- /dev/null +++ b/drivers/mxc/security/sahara2/sah_status_manager.c @@ -0,0 +1,684 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! +* @file sah_status_manager.c +* +* @brief Status Manager Function +* +* This file contains the function which processes the Sahara status register +* during an interrupt. +* +* This file does not need porting. +*/ + +#include "portable_os.h" + +#include <sah_status_manager.h> +#include <sah_hardware_interface.h> +#include <sah_queue_manager.h> +#include <sah_memory_mapper.h> +#include <sah_kernel.h> + +#if defined(DIAG_DRV_INTERRUPT) && defined(DIAG_DURING_INTERRUPT) +#include <diagnostic.h> +#endif + +/*! Compile-time flag to count various interrupt types. */ +#define DIAG_INT_COUNT + +/*! + * Number of interrupts processed with Done1Done2 status. Updates to this + * value should only be done in interrupt processing. + */ +uint32_t done1_count; + +/*! + * Number of interrupts processed with Done1Busy2 status. Updates to this + * value should only be done in interrupt processing. + */ +uint32_t done1busy2_count; + +/*! + * Number of interrupts processed with Done1Done2 status. Updates to this + * value should only be done in interrupt processing. + */ +uint32_t done1done2_count; + +/*! + * the dynameic power management flag is false when power management is not + * asserted and true when dpm is. + */ +#ifdef SAHARA_POWER_MANAGEMENT +bool sah_dpm_flag = FALSE; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,11) +static int sah_dpm_suspend(struct device *dev, uint32_t state, uint32_t level); +static int sah_dpm_resume(struct device *dev, uint32_t level); +#else +static int sah_dpm_suspend(struct platform_device *dev, pm_message_t state); +static int sah_dpm_resume(struct platform_device *dev); +#endif +#endif + +#ifndef SAHARA_POLL_MODE +/*! +******************************************************************************* +* This functionx processes the status register of the Sahara, updates the state +* of the finished queue entry, and then tries to find more work for Sahara to +* do. +* +* @brief The bulk of the interrupt handling code. +* +* @param hw_status The status register of Sahara at time of interrupt. +* The Clear interrupt bit is already handled by this +* register read prior to entry into this function. +* @return void +*/ +unsigned long sah_Handle_Interrupt(sah_Execute_Status hw_status) +{ + unsigned long reset_flag = 0; /* assume no SAHARA reset needed */ + os_lock_context_t lock_flags; + + /* HW status at time of interrupt */ + hw_status &= SAH_EXEC_STATE_MASK; + + do { + sah_Head_Desc *current_entry; + uint32_t dar; + +#ifdef DIAG_INT_COUNT + if (hw_status == SAH_EXEC_DONE1) { + done1_count++; + } else if (hw_status == SAH_EXEC_DONE1_BUSY2) { + done1busy2_count++; + } else if (hw_status == SAH_EXEC_DONE1_DONE2) { + done1done2_count++; + } +#endif + + /* if the first entry on sahara has completed... */ + if ((hw_status & SAH_EXEC_DONE1_BIT) || + (hw_status == SAH_EXEC_ERROR1)) { + /* lock queue while searching */ + os_lock_save_context(desc_queue_lock, lock_flags); + current_entry = + sah_Find_With_State(SAH_STATE_ON_SAHARA); + os_unlock_restore_context(desc_queue_lock, lock_flags); + + /* an active descriptor was not found */ + if (current_entry == NULL) { + /* change hw_status to avoid an infinite loop (possible if + * hw_status is SAH_EXEC_DONE1_BUSY2 first time into loop) */ + hw_status = SAH_EXEC_IDLE; +#if defined(DIAG_DRV_INTERRUPT) && defined(DIAG_DURING_INTERRUPT) + LOG_KDIAG + ("Interrupt received with nothing on queue."); +#endif + } else { + /* SAHARA has completed its work on this descriptor chain */ + current_entry->status = SAH_STATE_OFF_SAHARA; + + /* SAHARA is reporting an error with descriptor chain 1 */ + if (hw_status == SAH_EXEC_ERROR1) { + /* Gather extra diagnostic information */ + current_entry->fault_address = + sah_HW_Read_Fault_Address(); + current_entry->current_dar = + sah_HW_Read_CDAR(); + /* Read this last - it clears the error */ + current_entry->error_status = + sah_HW_Read_Error_Status(); + } else { + /* indicate that no errors were found with descriptor + * chain 1 */ + current_entry->error_status = 0; + + /* is there a second, successfully, completed descriptor + * chain? (done1/error2 processing is handled later) */ + if (hw_status == SAH_EXEC_DONE1_DONE2) { + os_lock_save_context + (desc_queue_lock, + lock_flags); + current_entry = + sah_Find_With_State + (SAH_STATE_ON_SAHARA); + os_unlock_restore_context + (desc_queue_lock, + lock_flags); + + if (current_entry == NULL) { +#if defined(DIAG_DRV_INTERRUPT) && defined(DIAG_DURING_INTERRUPT) + LOG_KDIAG + ("Done1_Done2 Interrupt received with " + "one entry on queue."); +#endif + } else { + /* indicate no errors in descriptor chain 2 */ + current_entry-> + error_status = 0; + current_entry->status = + SAH_STATE_OFF_SAHARA; + } + } + } + } + +#ifdef SAHARA_POWER_MANAGEMENT + /* check dynamic power management is not asserted */ + if (!sah_dpm_flag) { +#endif + do { + /* protect DAR and main_queue */ + os_lock_save_context(desc_queue_lock, + lock_flags); + dar = sah_HW_Read_DAR(); + /* check if SAHARA has space for another descriptor. SAHARA + * only accepts up to the DAR queue size number of DAR + * entries, after that 'dar' will not be zero until the + * pending interrupt is serviced */ + if (dar == 0) { + current_entry = + sah_Find_With_State + (SAH_STATE_PENDING); + if (current_entry != NULL) { +#ifndef SUBMIT_MULTIPLE_DARS + /* BUG FIX: state machine can transition from Done1 + * Busy2 directly to Idle. To fix that problem, + * only one DAR is being allowed on SAHARA at a + * time. If a high level interrupt has happened, + * there could * be an active descriptor chain */ + if (sah_Find_With_State + (SAH_STATE_ON_SAHARA) + == NULL) { +#endif +#if defined(DIAG_DRV_IF) && defined(DIAG_DURING_INTERRUPT) + sah_Dump_Chain + (¤t_entry-> + desc, + current_entry-> + desc. + dma_addr); +#endif /* DIAG_DRV_IF */ + sah_HW_Write_DAR + (current_entry-> + desc. + dma_addr); + current_entry-> + status = + SAH_STATE_ON_SAHARA; +#ifndef SUBMIT_MULTIPLE_DARS + } + current_entry = NULL; /* exit loop */ +#endif + } + } + os_unlock_restore_context + (desc_queue_lock, lock_flags); + } while ((dar == 0) && (current_entry != NULL)); +#ifdef SAHARA_POWER_MANAGEMENT + } /* sah_device_power_manager */ +#endif + } else { + if (hw_status == SAH_EXEC_FAULT) { + sah_Head_Desc *previous_entry; /* point to chain 1 */ + /* Address of request when fault occured */ + uint32_t bad_dar = sah_HW_Read_IDAR(); + + reset_flag = 1; /* SAHARA needs to be reset */ + + /* get first of possible two descriptor chain that was + * on SAHARA */ + os_lock_save_context(desc_queue_lock, + lock_flags); + previous_entry = + sah_Find_With_State(SAH_STATE_ON_SAHARA); + os_unlock_restore_context(desc_queue_lock, + lock_flags); + + /* if it exists, continue processing the fault */ + if (previous_entry) { + /* assume this chain didn't complete correctly */ + previous_entry->error_status = -1; + previous_entry->status = + SAH_STATE_OFF_SAHARA; + + /* get the second descriptor chain */ + os_lock_save_context(desc_queue_lock, + lock_flags); + current_entry = + sah_Find_With_State + (SAH_STATE_ON_SAHARA); + os_unlock_restore_context + (desc_queue_lock, lock_flags); + + /* if it exists, continue processing both chains */ + if (current_entry) { + /* assume this chain didn't complete correctly */ + current_entry->error_status = + -1; + current_entry->status = + SAH_STATE_OFF_SAHARA; + + /* now see if either can be identified as the one + * in progress when the fault occured */ + if (current_entry->desc. + dma_addr == bad_dar) { + /* the second descriptor chain was active when the + * fault occured, so the first descriptor chain + * was successfull */ + previous_entry-> + error_status = 0; + } else { + if (previous_entry-> + desc.dma_addr == + bad_dar) { + /* if the first chain was in progress when the + * fault occured, the second has not yet been + * touched, so reset it to PENDING */ + current_entry-> + status = + SAH_STATE_PENDING; + } + } + } + } +#if defined(DIAG_DRV_INTERRUPT) && defined(DIAG_DURING_INTERRUPT) + } else { + /* shouldn't ever get here */ + if (hw_status == SAH_EXEC_BUSY) { + LOG_KDIAG + ("Got Sahara interrupt in Busy state"); + } else { + if (hw_status == SAH_EXEC_IDLE) { + LOG_KDIAG + ("Got Sahara interrupt in Idle state"); + } else { + LOG_KDIAG + ("Got Sahara interrupt in unknown state"); + } + } +#endif + } + } + + /* haven't handled the done1/error2 (the error 2 part), so setup to + * do that now. Otherwise, exit loop */ + hw_status = (hw_status == SAH_EXEC_DONE1_ERROR2) ? + SAH_EXEC_ERROR1 : SAH_EXEC_IDLE; + + /* Keep going while further status is available. */ + } while (hw_status == SAH_EXEC_ERROR1); + + return reset_flag; +} + +#endif /* ifndef SAHARA_POLL_MODE */ + +#ifdef SAHARA_POLL_MODE +/*! +******************************************************************************* +* Submits descriptor chain to SAHARA, polls on SAHARA for completion, process +* results, and dephysicalizes chain +* +* @brief Handle poll mode. +* +* @param entry Virtual address of a physicalized chain +* +* @return 0 this function is always successful +*/ + +unsigned long sah_Handle_Poll(sah_Head_Desc * entry) +{ + sah_Execute_Status hw_status; /* Sahara's status register */ + os_lock_context_t lock_flags; + + /* lock SARAHA */ + os_lock_save_context(desc_queue_lock, lock_flags); + +#ifdef SAHARA_POWER_MANAGEMENT + /* check if the dynamic power management is asserted */ + if (sah_dpm_flag) { + /* return that request failed to be processed */ + entry->result = FSL_RETURN_ERROR_S; + entry->fault_address = 0xBAD; + entry->current_dar = 0xBAD; + entry->error_status = 0xBAD; + } else { +#endif /* SAHARA_POWER_MANAGEMENT */ + +#if defined(DIAG_DRV_IF) + sah_Dump_Chain(&entry->desc, entry->desc.dma_addr); +#endif /* DIAG_DRV_IF */ + /* Nothing can be in the dar if we got the lock */ + sah_HW_Write_DAR((uint32_t) (entry->desc.dma_addr)); + + /* Wait for SAHARA to finish with this entry */ + hw_status = sah_Wait_On_Sahara(); + + /* if entry completed successfully, mark it as such */ + /**** HARDWARE ERROR WORK AROUND (hw_status == SAH_EXEC_IDLE) *****/ + if ( +#ifndef SUBMIT_MULTIPLE_DARS + (hw_status == SAH_EXEC_IDLE) || (hw_status == SAH_EXEC_DONE1_BUSY2) || /* should not happen */ +#endif + (hw_status == SAH_EXEC_DONE1) + ) { + entry->error_status = 0; + entry->result = FSL_RETURN_OK_S; + } else { + /* SAHARA is reporting an error with entry */ + if (hw_status == SAH_EXEC_ERROR1) { + /* Gather extra diagnostic information */ + entry->fault_address = + sah_HW_Read_Fault_Address(); + entry->current_dar = sah_HW_Read_CDAR(); + /* Read this register last - it clears the error */ + entry->error_status = + sah_HW_Read_Error_Status(); + /* translate from SAHARA error status to fsl_shw return values */ + entry->result = + sah_convert_error_status(entry-> + error_status); +#ifdef DIAG_DRV_STATUS + sah_Log_Error(entry->current_dar, + entry->error_status, + entry->fault_address); +#endif + } else if (hw_status == SAH_EXEC_OPSTAT1) { + uint32_t op_status = sah_HW_Read_Op_Status(); + entry->result = + sah_convert_op_status(op_status); + } else { + /* SAHARA entered FAULT state (or something bazaar has + * happened) */ + entry->error_status = -1; + entry->result = FSL_RETURN_ERROR_S; + sah_HW_Reset(); + } + } +#ifdef SAHARA_POWER_MANAGEMENT + } +#endif + + if (!(entry->uco_flags & FSL_UCO_BLOCKING_MODE)) { + /* put it in results pool to allow get_results to work */ + sah_Queue_Append_Entry(&entry->user_info->result_pool, entry); + if (entry->uco_flags & FSL_UCO_CALLBACK_MODE) { + /* invoke callback */ + entry->user_info->callback(entry->user_info); + } + } else { + /* convert the descriptor link back to virtual memory, mark dirty pages + * if they are from user mode, and release the page cache for user + * pages + */ + entry = sah_DePhysicalise_Descriptors(entry); + } + + os_unlock_restore_context(desc_queue_lock, lock_flags); + + return 0; +} + +#endif /* SAHARA_POLL_MODE */ + +/****************************************************************************** +* The following is the implementation of the Dynamic Power Management +* functionality. +******************************************************************************/ +#ifdef SAHARA_POWER_MANAGEMENT + +static bool sah_dpm_init_flag; + +/* dynamic power management information for the sahara driver */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,11) +static struct device_driver sah_dpm_driver = { + .name = "sahara_", + .bus = &platform_bus_type, +#else +static struct platform_driver sah_dpm_driver = { + .driver.name = "sahara_", + .driver.bus = &platform_bus_type, +#endif + .suspend = sah_dpm_suspend, + .resume = sah_dpm_resume +}; + +/* dynamic power management information for the sahara HW device */ +static struct platform_device sah_dpm_device = { + .name = "sahara_", + .id = 1, +}; + +/*! +******************************************************************************* +* Initilaizes the dynamic power managment functionality +* +* @brief Initialization of the Dynamic Power Management functionality +* +* @return 0 = success; failed otherwise +*/ +int sah_dpm_init() +{ + int status; + + /* dpm is not asserted */ + sah_dpm_flag = FALSE; + + /* register the driver to the kernel */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,11) + status = os_register_to_driver(&sah_dpm_driver); +#else + status = os_register_to_driver(&sah_dpm_driver.driver); +#endif + + if (status == 0) { + /* register a single sahara chip */ + /*status = platform_device_register(&sah_dpm_device); */ + status = os_register_a_device(&sah_dpm_device); + + /* if something went awry, unregister the driver */ + if (status != 0) { + /*driver_unregister(&sah_dpm_driver); */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,11) + os_unregister_from_driver(&sah_dpm_driver); +#else + os_unregister_from_driver(&sah_dpm_driver.driver); +#endif + sah_dpm_init_flag = FALSE; + } else { + /* if everything went okay, flag that life is good */ + sah_dpm_init_flag = TRUE; + } + } + + /* let the kernel know how it went */ + return status; + +} + +/*! +******************************************************************************* +* Unregister the dynamic power managment functionality +* +* @brief Unregister the Dynamic Power Management functionality +* +*/ +void sah_dpm_close() +{ + /* if dynamic power management was initilaized, kill it */ + if (sah_dpm_init_flag == TRUE) { + /*driver_unregister(&sah_dpm_driver); */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,11) + os_unregister_from_driver(&sah_dpm_driver); +#else + os_unregister_from_driver(&sah_dpm_driver.driver); +#endif + /*platform_device_register(&sah_dpm_device); */ + os_unregister_a_device(&sah_dpm_device); + } +} + +/*! +******************************************************************************* +* Callback routine defined by the Linux Device Model / Dynamic Power management +* extension. It sets a global flag to disallow the driver to enter queued items +* into Sahara's DAR. +* +* It allows the current active descriptor chains to complete before it returns +* +* @brief Suspends the driver +* +* @param dev contains device information +* @param state contains state information +* @param level level of shutdown +* +* @return 0 = success; failed otherwise +*/ + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,11) +static int sah_dpm_suspend(struct device *dev, uint32_t state, uint32_t level) +#else +static int sah_dpm_suspend(struct platform_device *dev, pm_message_t state) +#endif +{ + sah_Head_Desc *entry = NULL; + os_lock_context_t lock_flags; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,11) + switch (level) { + case SUSPEND_DISABLE: + /* Assert dynamic power management. This stops the driver from + * entering queued requests to Sahara */ + sah_dpm_flag = TRUE; + break; + + case SUSPEND_SAVE_STATE: + break; + + case SUSPEND_POWER_DOWN: + /* hopefully between the DISABLE call and this one, the outstanding + * work Sahara was doing complete. this checks (and waits) for + * those entries that were already active on Sahara to complete */ + /* lock queue while searching */ + os_lock_save_context(desc_queue_lock, lock_flags); + do { + entry = sah_Find_With_State(SAH_STATE_ON_SAHARA); + } while (entry != NULL); + os_unlock_restore_context(desc_queue_lock, lock_flags); + + /* now we kill the clock so the control circuitry isn't sucking + * any power */ + mxc_clks_disable(SAHARA2_CLK); + break; + } +#else + /* Assert dynamic power management. This stops the driver from + * entering queued requests to Sahara */ + sah_dpm_flag = TRUE; + + /* Now wait for any outstanding work Sahara was doing to complete. + * this checks (and waits) for + * those entries that were already active on Sahara to complete */ + do { + /* lock queue while searching */ + os_lock_save_context(desc_queue_lock, lock_flags); + entry = sah_Find_With_State(SAH_STATE_ON_SAHARA); + os_unlock_restore_context(desc_queue_lock, lock_flags); + } while (entry != NULL); + + /* now we kill the clock so the control circuitry isn't sucking + * any power */ + { + struct clk *clk = clk_get(NULL, "sahara_clk"); + if (clk != ERR_PTR(ENOENT)) { + clk_disable(clk); + } + } +#endif + + return 0; +} + +/*! +******************************************************************************* +* Callback routine defined by the Linux Device Model / Dynamic Power management +* extension. It cleears a global flag to allow the driver to enter queued items +* into Sahara's DAR. +* +* It primes the mechanism to start depleting the queue +* +* @brief Resumes the driver +* +* @param dev contains device information +* @param level level of resumption +* +* @return 0 = success; failed otherwise +*/ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,11) +static int sah_dpm_resume(struct device *dev, uint32_t level) +#else +static int sah_dpm_resume(struct platform_device *dev) +#endif +{ + sah_Head_Desc *entry = NULL; + os_lock_context_t lock_flags; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,11) + switch (level) { + case RESUME_POWER_ON: + /* enable Sahara's clock */ + mxc_clks_enable(SAHARA2_CLK); + break; + + case RESUME_RESTORE_STATE: + break; + + case RESUME_ENABLE: + /* Disable dynamic power managment. This allows the driver to put + * entries into Sahara's DAR */ + sah_dpm_flag = FALSE; + + /* find a pending entry to prime the pump */ + os_lock_save_context(desc_queue_lock, lock_flags); + entry = sah_Find_With_State(SAH_STATE_PENDING); + if (entry != NULL) { + sah_Queue_Manager_Prime(entry); + } + os_unlock_restore_context(desc_queue_lock, lock_flags); + break; + } +#else + { + /* enable Sahara's clock */ + struct clk *clk = clk_get(NULL, "sahara_clk"); + + if (clk != ERR_PTR(ENOENT)) { + clk_enable(clk); + } + } + sah_dpm_flag = FALSE; + + /* find a pending entry to prime the pump */ + os_lock_save_context(desc_queue_lock, lock_flags); + entry = sah_Find_With_State(SAH_STATE_PENDING); + if (entry != NULL) { + sah_Queue_Manager_Prime(entry); + } + os_unlock_restore_context(desc_queue_lock, lock_flags); +#endif + return 0; +} + +#endif /* SAHARA_POWER_MANAGEMENT */ diff --git a/drivers/mxc/security/sahara2/sf_util.c b/drivers/mxc/security/sahara2/sf_util.c new file mode 100644 index 000000000000..4bd2b3ac6f8a --- /dev/null +++ b/drivers/mxc/security/sahara2/sf_util.c @@ -0,0 +1,1289 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/** +* @file sf_util.c +* +* @brief Security Functions component API - Utility functions +* +* These are the 'Sahara api' functions which are used by the higher-level +* FSL SHW API to build and then execute descriptor chains. +*/ + + +#include "sf_util.h" +#include <adaptor.h> + +#ifdef DIAG_SECURITY_FUNC +#include <diagnostic.h> +#endif /*DIAG_SECURITY_FUNC*/ + + +#ifdef __KERNEL__ +EXPORT_SYMBOL(sah_Append_Desc); +EXPORT_SYMBOL(sah_Append_Link); +EXPORT_SYMBOL(sah_Create_Link); +EXPORT_SYMBOL(sah_Create_Key_Link); +EXPORT_SYMBOL(sah_Destroy_Link); +EXPORT_SYMBOL(sah_Descriptor_Chain_Execute); +EXPORT_SYMBOL(sah_insert_mdha_algorithm); +EXPORT_SYMBOL(sah_insert_skha_algorithm); +EXPORT_SYMBOL(sah_insert_skha_mode); +EXPORT_SYMBOL(sah_insert_skha_modulus); +EXPORT_SYMBOL(sah_Descriptor_Chain_Destroy); +EXPORT_SYMBOL(sah_add_two_in_desc); +EXPORT_SYMBOL(sah_add_in_key_desc); +EXPORT_SYMBOL(sah_add_two_out_desc); +EXPORT_SYMBOL(sah_add_in_out_desc); +EXPORT_SYMBOL(sah_add_key_out_desc); +#endif + +#ifdef DEBUG_REWORK +#ifndef __KERNEL__ +#include <stdio.h> +#define os_printk printf +#endif +#endif + +/** + * Convert fsl_shw_hash_alg_t to mdha mode bits. + * + * Index must be maintained in order of fsl_shw_hash_alg_t enumeration!!! + */ +const uint32_t sah_insert_mdha_algorithm[] = +{ + [FSL_HASH_ALG_MD5] = sah_insert_mdha_algorithm_md5, + [FSL_HASH_ALG_SHA1] = sah_insert_mdha_algorithm_sha1, + [FSL_HASH_ALG_SHA224] = sah_insert_mdha_algorithm_sha224, + [FSL_HASH_ALG_SHA256] = sah_insert_mdha_algorithm_sha256, +}; + +/** + * Header bits for Algorithm field of SKHA header + * + * Index value must be kept in sync with fsl_shw_key_alg_t + */ +const uint32_t sah_insert_skha_algorithm[] = +{ + [FSL_KEY_ALG_HMAC] = 0x00000040, + [FSL_KEY_ALG_AES] = sah_insert_skha_algorithm_aes, + [FSL_KEY_ALG_DES] = sah_insert_skha_algorithm_des, + [FSL_KEY_ALG_TDES] = sah_insert_skha_algorithm_tdes, + [FSL_KEY_ALG_ARC4] = sah_insert_skha_algorithm_arc4, +}; + + +/** + * Header bits for MODE field of SKHA header + * + * Index value must be kept in sync with fsl_shw_sym_mod_t + */ +const uint32_t sah_insert_skha_mode[] = +{ + [FSL_SYM_MODE_STREAM] = sah_insert_skha_mode_ecb, + [FSL_SYM_MODE_ECB] = sah_insert_skha_mode_ecb, + [FSL_SYM_MODE_CBC] = sah_insert_skha_mode_cbc, + [FSL_SYM_MODE_CTR] = sah_insert_skha_mode_ctr, +}; + + +/** + * Header bits to set CTR modulus size. These have parity + * included to allow XOR insertion of values. + * + * @note Must be kept in sync with fsl_shw_ctr_mod_t + */ +const uint32_t sah_insert_skha_modulus[] = +{ + [FSL_CTR_MOD_8] = 0x00000000, /**< 2**8 */ + [FSL_CTR_MOD_16] = 0x80000200, /**< 2**16 */ + [FSL_CTR_MOD_24] = 0x80000400, /**< 2**24 */ + [FSL_CTR_MOD_32] = 0x00000600, /**< 2**32 */ + [FSL_CTR_MOD_40] = 0x80000800, /**< 2**40 */ + [FSL_CTR_MOD_48] = 0x00000a00, /**< 2**48 */ + [FSL_CTR_MOD_56] = 0x00000c00, /**< 2**56 */ + [FSL_CTR_MOD_64] = 0x80000e00, /**< 2**64 */ + [FSL_CTR_MOD_72] = 0x80001000, /**< 2**72 */ + [FSL_CTR_MOD_80] = 0x00001200, /**< 2**80 */ + [FSL_CTR_MOD_88] = 0x00001400, /**< 2**88 */ + [FSL_CTR_MOD_96] = 0x80001600, /**< 2**96 */ + [FSL_CTR_MOD_104] = 0x00001800, /**< 2**104 */ + [FSL_CTR_MOD_112] = 0x80001a00, /**< 2**112 */ + [FSL_CTR_MOD_120] = 0x80001c00, /**< 2**120 */ + [FSL_CTR_MOD_128] = 0x00001e00 /**< 2**128 */ +}; + + +/****************************************************************************** +* Internal function declarations +******************************************************************************/ +static fsl_shw_return_t sah_Create_Desc( + const sah_Mem_Util *mu, + sah_Desc ** desc, + int head, + uint32_t header, + sah_Link * link1, + sah_Link * link2); + + +/** + * Create a descriptor chain using the the header and links passed in as + * parameters. The newly created descriptor will be added to the end of + * the descriptor chain passed. + * + * If @a desc_head points to a NULL value, then a sah_Head_Desc will be created + * as the first descriptor. Otherwise a sah_Desc will be created and appended. + * + * @pre + * + * - None + * + * @post + * + * - A descriptor has been created from the header, link1 and link2. + * + * - The newly created descriptor has been appended to the end of + * desc_head, or its location stored into the location pointed to by + * @a desc_head. + * + * - On allocation failure, @a link1 and @a link2 will be destroyed., and + * @a desc_head will be untouched. + * + * @brief Create and append descriptor chain, inserting header and links + * pointing to link1 and link2 + * + * @param mu Memory functions + * @param header Value of descriptor header to be added + * @param desc_head Pointer to head of descriptor chain to append new desc + * @param link1 Pointer to sah_Link 1 (or NULL) + * @param link2 Pointer to sah_Link 2 (or NULL) + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t sah_Append_Desc( + const sah_Mem_Util *mu, + sah_Head_Desc **desc_head, + const uint32_t header, + sah_Link *link1, + sah_Link *link2) +{ + fsl_shw_return_t status; + sah_Desc *desc; + sah_Desc *desc_ptr; + + + status = sah_Create_Desc(mu, (sah_Desc**)&desc, (*desc_head == NULL), + header, link1, link2); + /* append newly created descriptor to end of current chain */ + if (status == FSL_RETURN_OK_S) { + if (*desc_head == NULL) { + (*desc_head) = (sah_Head_Desc*)desc; + (*desc_head)->out1_ptr = NULL; + (*desc_head)->out2_ptr = NULL; + + } else { + desc_ptr = (sah_Desc*)*desc_head; + while (desc_ptr->next != NULL) { + desc_ptr = desc_ptr->next; + } + desc_ptr->next = desc; + } + } + + return status; +} + + +/** + * Releases the memory allocated by the Security Function library for + * descriptors, links and any internally allocated memory referenced in the + * given chain. Note that memory allocated by user applications is not + * released. + * + * @post The @a desc_head pointer will be set to NULL to prevent further use. + * + * @brief Destroy a descriptor chain and free memory of associated links + * + * @param mu Memory functions + * @param desc_head Pointer to head of descriptor chain to be freed + * + * @return none + */ +void sah_Descriptor_Chain_Destroy ( + const sah_Mem_Util *mu, + sah_Head_Desc **desc_head) +{ + sah_Desc *desc_ptr = &(*desc_head)->desc; + sah_Head_Desc *desc_head_ptr = (sah_Head_Desc *)desc_ptr; + + while (desc_ptr != NULL) { + register sah_Desc *next_desc_ptr; + + if (desc_ptr->ptr1 != NULL) { + sah_Destroy_Link(mu, desc_ptr->ptr1); + } + if (desc_ptr->ptr2 != NULL) { + sah_Destroy_Link(mu, desc_ptr->ptr2); + } + + next_desc_ptr = desc_ptr->next; + + /* Be sure to free head descriptor as such */ + if (desc_ptr == (sah_Desc*)desc_head_ptr) { + mu->mu_free_head_desc(mu->mu_ref, desc_head_ptr); + } else { + mu->mu_free_desc(mu->mu_ref, desc_ptr); + } + + desc_ptr = next_desc_ptr; + } + + *desc_head = NULL; +} + + +#ifndef NO_INPUT_WORKAROUND +/** + * Reworks the link chain + * + * @brief Reworks the link chain + * + * @param mu Memory functions + * @param link Pointer to head of link chain to be reworked + * + * @return none + */ +static fsl_shw_return_t sah_rework_link_chain( + const sah_Mem_Util *mu, + sah_Link* link) +{ + fsl_shw_return_t status = FSL_RETURN_OK_S; + int found_potential_problem = 0; + uint32_t total_data = 0; +#ifdef DEBUG_REWORK + sah_Link* first_link = link; +#endif + + if ((link->flags & SAH_OUTPUT_LINK)) { + return status; + } + + while (link != NULL) { + total_data += link->len; + + /* Only non-key Input Links are affected by the DMA flush-to-FIFO + * problem */ + + /* If have seen problem and at end of chain... */ + if (found_potential_problem && (link->next == NULL) && + (total_data > 16)) { + /* insert new 1-byte link */ + sah_Link* new_tail_link = mu->mu_alloc_link(mu->mu_ref); + if (new_tail_link == NULL) { + status = FSL_RETURN_NO_RESOURCE_S; + } else { +#ifdef DEBUG_REWORK + sah_Link* dump_link = first_link; + while (dump_link != NULL) { + uint32_t i; + unsigned bytes_to_dump = (dump_link->len > 32) ? + 32 : dump_link->len; + os_printk("(rework)Link %p: %p/%u/%p\n", dump_link, + dump_link->data, dump_link->len, + dump_link->next); + if (!(dump_link->flags & SAH_STORED_KEY_INFO)) { + os_printk("(rework)Data %p: ", dump_link->data); + for (i = 0; i < bytes_to_dump; i++) { + os_printk("%02X ", dump_link->data[i]); + } + os_printk("\n"); + } + else { + os_printk("rework)Data %p: Red key data\n", dump_link); + } + dump_link = dump_link->next; + } +#endif + link->len--; + link->next = new_tail_link; + new_tail_link->len = 1; + new_tail_link->data = link->data+link->len; + new_tail_link->flags = link->flags & ~(SAH_OWNS_LINK_DATA); + new_tail_link->next = NULL; + link = new_tail_link; +#ifdef DEBUG_REWORK + os_printk("(rework)New link chain:\n"); + dump_link = first_link; + while (dump_link != NULL) { + uint32_t i; + unsigned bytes_to_dump = (dump_link->len > 32) ? + 32 : dump_link->len; + + os_printk("Link %p: %p/%u/%p\n", dump_link, + dump_link->data, dump_link->len, + dump_link->next); + if (!(dump_link->flags & SAH_STORED_KEY_INFO)) { + os_printk("Data %p: ", dump_link->data); + for (i = 0; i < bytes_to_dump; i++) { + os_printk("%02X ", dump_link->data[i]); + } + os_printk("\n"); + } + else { + os_printk("Data %p: Red key data\n", dump_link); + } + dump_link = dump_link->next; + } +#endif + } + } else if ((link->len % 4) || ((uint32_t)link->data % 4)) { + found_potential_problem = 1; + } + + link = link->next; + } + + return status; +} + + +/** + * Rework links to avoid H/W bug + * + * @param head Beginning of descriptor chain + * + * @return A return code of type #fsl_shw_return_t. + */ +static fsl_shw_return_t sah_rework_links( + const sah_Mem_Util *mu, + sah_Head_Desc *head) +{ + fsl_shw_return_t status = FSL_RETURN_OK_S; + sah_Desc* desc = &head->desc; + + while ((status == FSL_RETURN_OK_S) && (desc != NULL)) { + if (desc->header & SAH_HDR_LLO) { + status = FSL_RETURN_ERROR_S; + break; + } + if (desc->ptr1 != NULL) { + status = sah_rework_link_chain(mu, desc->ptr1); + } + if ((status == FSL_RETURN_OK_S) && (desc->ptr2 != NULL)) { + status = sah_rework_link_chain(mu, desc->ptr2); + } + desc = desc->next; + } + + return status; +} +#endif /* NO_INPUT_WORKAROUND */ + + +/** + * Send a descriptor chain to the SAHARA driver for processing. + * + * Note that SAHARA will read the input data from and write the output data + * directly to the locations indicated during construction of the chain. + * + * @pre + * + * - None + * + * @post + * + * - @a head will have been executed on SAHARA + * - @a head Will be freed unless a SAVE flag is set + * + * @brief Execute a descriptor chain + * + * @param head Pointer to head of descriptor chain to be executed + * @param user_ctx The user context on which to execute the descriptor chain. + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t sah_Descriptor_Chain_Execute( + sah_Head_Desc *head, + fsl_shw_uco_t *user_ctx) +{ + fsl_shw_return_t status; + + + /* Check for null pointer or non-multiple-of-four value */ + if ((head == NULL) || ((uint32_t)head & 0x3)) { + status = FSL_RETURN_ERROR_S; + goto out; + } + +#ifndef NO_INPUT_WORKAROUND + status = sah_rework_links(user_ctx->mem_util, head); + if (status != FSL_RETURN_OK_S) { + goto out; + } +#endif + + /* complete the information in the descriptor chain head node */ + head->user_ref = user_ctx->user_ref; + head->uco_flags = user_ctx->flags; + head->next = NULL; /* driver will use this to link chain heads */ + + status = adaptor_Exec_Descriptor_Chain(head, user_ctx); + + out: + return status; +} + + +/** + * Create Link + * + * @brief Allocate Memory for Link structure and populate using input + * parameters + * + * @post On allocation failure, @a p will be freed if #SAH_OWNS_LINK_DATA is + * p set in @a flags. + + * @param mu Memory functions + * @param link Pointer to link to be created + * @param p Pointer to data to use in link + * @param length Length of buffer 'p' in bytes + * @param flags Indicates whether memory has been allocated by the calling + * function or the security function + * + * @return FSL_RETURN_OK_S or FSL_RETURN_NO_RESOURCE_S + */ +fsl_shw_return_t sah_Create_Link( + const sah_Mem_Util *mu, + sah_Link **link, + uint8_t *p, + const size_t length, + const sah_Link_Flags flags) +{ +#ifdef DIAG_SECURITY_FUNC_UGLY + char diag[50]; +#endif /*DIAG_SECURITY_FUNC_UGLY*/ + fsl_shw_return_t status = FSL_RETURN_NO_RESOURCE_S; + + + *link = mu->mu_alloc_link(mu->mu_ref); + + /* populate link if memory allocation successful */ + if (*link != NULL) { + (*link)->len = length; + (*link)->data = p; + (*link)->next = NULL; + (*link)->flags = flags; + status = FSL_RETURN_OK_S; + +#ifdef DIAG_SECURITY_FUNC_UGLY + LOG_DIAG("Created Link"); + LOG_DIAG("------------"); + sprintf(diag," address = 0x%x", (int) *link); + LOG_DIAG(diag); + sprintf(diag," link->len = %d",(*link)->len); + LOG_DIAG(diag); + sprintf(diag," link->data = 0x%x",(int) (*link)->data); + LOG_DIAG(diag); + sprintf(diag," link->flags = 0x%x",(*link)->flags); + LOG_DIAG(diag); + LOG_DIAG(" link->next = NULL"); +#endif /*DIAG_SECURITY_FUNC_UGLY*/ + + } else { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("Allocation of memory for sah_Link failed!\n"); +#endif /*DIAG_SECURITY_FUNC*/ + + /* Free memory previously allocated by the security function layer for + link data. Note that the memory being pointed to will be zeroed before + being freed, for security reasons. */ + if (flags & SAH_OWNS_LINK_DATA) { + mu->mu_memset(mu->mu_ref, p, 0x00, length); + mu->mu_free(mu->mu_ref, p); + } + } + + return status; +} + + +/** + * Create Key Link + * + * @brief Allocate Memory for Link structure and populate using key info + * object + * + * @param mu Memory functions + * @param link Pointer to store address of link to be created + * @param key_info Pointer to Key Info object to be referenced + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t sah_Create_Key_Link( + const sah_Mem_Util *mu, + sah_Link **link, + fsl_shw_sko_t *key_info) +{ +#ifdef DIAG_SECURITY_FUNC_UGLY + char diag[50]; +#endif /*DIAG_SECURITY_FUNC_UGLY*/ + fsl_shw_return_t status = FSL_RETURN_NO_RESOURCE_S; + sah_Link_Flags flags = 0; + + + *link = mu->mu_alloc_link(mu->mu_ref); + + /* populate link if memory allocation successful */ + if (*link != NULL) { + (*link)->len = key_info->key_length; + + if (key_info->flags & FSL_SKO_KEY_PRESENT) { + (*link)->data = key_info->key; + status = FSL_RETURN_OK_S; + } else { + if (key_info->flags & FSL_SKO_KEY_ESTABLISHED) { + (*link)->slot = key_info->handle; + (*link)->ownerid = key_info->userid; + (*link)->data = 0; + flags |= SAH_STORED_KEY_INFO; + status = FSL_RETURN_OK_S; + } else { + /* the flag is bad. Should never get here */ + status = FSL_RETURN_BAD_FLAG_S; + } + } + + (*link)->next = NULL; + (*link)->flags = flags; + +#ifdef DIAG_SECURITY_FUNC_UGLY + if (status == FSL_RETURN_OK_S) { + LOG_DIAG("Created Link"); + LOG_DIAG("------------"); + sprintf(diag," address = 0x%x", (int) *link); + LOG_DIAG(diag); + sprintf(diag," link->len = %d", (*link)->len); + LOG_DIAG(diag); + if (key_info->flags & FSL_SKO_KEY_ESTABLISHED) { + sprintf(diag," link->slot = 0x%x", (*link)->slot); + LOG_DIAG(diag); + } else { + sprintf(diag," link->data = 0x%x", (int)(*link)->data); + LOG_DIAG(diag); + } + sprintf(diag," link->flags = 0x%x", (*link)->flags); + LOG_DIAG(diag); + LOG_DIAG(" link->next = NULL"); + } +#endif /*DIAG_SECURITY_FUNC_UGLY*/ + + if (status == FSL_RETURN_BAD_FLAG_S) { + mu->mu_free_link(mu->mu_ref, *link); + *link = NULL; +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("Creation of sah_Key_Link failed due to bad key flag!\n"); +#endif /*DIAG_SECURITY_FUNC*/ + } + + } else { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("Allocation of memory for sah_Key_Link failed!\n"); +#endif /*DIAG_SECURITY_FUNC*/ + } + + return status; +} + + +/** + * Append Link + * + * @brief Allocate Memory for Link structure and append it to the end of + * the link chain. + * + * @post On allocation failure, @a p will be freed if #SAH_OWNS_LINK_DATA is + * p set in @a flags. + * + * @param mu Memory functions + * @param link_head Pointer to (head of) existing link chain + * @param p Pointer to data to use in link + * @param length Length of buffer 'p' in bytes + * @param flags Indicates whether memory has been allocated by the calling + * function or the security function + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t sah_Append_Link( + const sah_Mem_Util *mu, + sah_Link *link_head, + uint8_t *p, + const size_t length, + const sah_Link_Flags flags) +{ + sah_Link* new_link; + fsl_shw_return_t status; + + + status = sah_Create_Link(mu, &new_link, p, length, flags); + + if (status == FSL_RETURN_OK_S) { + /* chase for the tail */ + while (link_head->next != NULL) { + link_head = link_head->next; + } + + /* and add new tail */ + link_head->next = new_link; + } + + return status; +} + + +/** + * Create and populate a single descriptor + * + * The pointer and length fields will be be set based on the chains passed in + * as @a link1 and @a link2. + * + * @param mu Memory utility suite + * @param desc Location to store pointer of new descriptor + * @param head_desc Non-zero if this will be first in chain; zero otherwise + * @param header The Sahara header value to store in the descriptor + * @param link1 A value (or NULL) for the first ptr + * @param link2 A value (or NULL) for the second ptr + * + * @post If allocation succeeded, the @a link1 and @link2 will be linked into + * the descriptor. If allocation failed, those link structues will be + * freed, and the @a desc will be unchanged. + * + * @return A return code of type #fsl_shw_return_t. + */ +static fsl_shw_return_t sah_Create_Desc( + const sah_Mem_Util *mu, + sah_Desc ** desc, + int head_desc, + uint32_t header, + sah_Link * link1, + sah_Link * link2) +{ + fsl_shw_return_t status = FSL_RETURN_NO_RESOURCE_S; +#ifdef DIAG_SECURITY_FUNC_UGLY + char diag[50]; +#endif /*DIAG_SECURITY_FUNC_UGLY*/ + + + if (head_desc != 0) { + *desc = (sah_Desc *)mu->mu_alloc_head_desc(mu->mu_ref); + } else { + *desc = mu->mu_alloc_desc(mu->mu_ref); + } + + /* populate descriptor if memory allocation successful */ + if ((*desc) != NULL) { + sah_Link* temp_link; + + status = FSL_RETURN_OK_S; + (*desc)->header = header; + + temp_link = (*desc)->ptr1 = link1; + (*desc)->len1 = 0; + while (temp_link != NULL) { + (*desc)->len1 += temp_link->len; + temp_link = temp_link->next; + } + + temp_link = (*desc)->ptr2 = link2; + (*desc)->len2 = 0; + while (temp_link != NULL) { + (*desc)->len2 += temp_link->len; + temp_link = temp_link->next; + } + + (*desc)->next = NULL; + +#ifdef DIAG_SECURITY_FUNC_UGLY + LOG_DIAG("Created Desc"); + LOG_DIAG("------------"); + sprintf(diag," address = 0x%x",(int) *desc); + LOG_DIAG(diag); + sprintf(diag," desc->header = 0x%x",(*desc)->header); + LOG_DIAG(diag); + sprintf(diag," desc->len1 = %d",(*desc)->len1); + LOG_DIAG(diag); + sprintf(diag," desc->ptr1 = 0x%x",(int) ((*desc)->ptr1)); + LOG_DIAG(diag); + sprintf(diag," desc->len2 = %d",(*desc)->len2); + LOG_DIAG(diag); + sprintf(diag," desc->ptr2 = 0x%x",(int) ((*desc)->ptr2)); + LOG_DIAG(diag); + sprintf(diag," desc->next = 0x%x",(int) ((*desc)->next)); + LOG_DIAG(diag); +#endif /*DIAG_SECURITY_FUNC_UGLY*/ + } else { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("Allocation of memory for sah_Desc failed!\n"); +#endif /*DIAG_SECURITY_FUNC*/ + + /* Destroy the links, otherwise the memory allocated by the Security + Function layer for the links (and possibly the data within the links) + will be lost */ + if (link1 != NULL) { + sah_Destroy_Link(mu, link1); + } + if (link2 != NULL) { + sah_Destroy_Link(mu, link2); + } + } + + return status; +} + + +/** + * Destroy a link (chain) and free memory + * + * @param mu memory utility suite + * @param link The Link to destroy + * + * @return none + */ +void sah_Destroy_Link( + const sah_Mem_Util *mu, + sah_Link * link) +{ + + while (link != NULL) { + sah_Link * next_link = link->next; + + if (link->flags & SAH_OWNS_LINK_DATA) { + /* zero data for security purposes */ + mu->mu_memset(mu->mu_ref, link->data, 0x00, link->len); + mu->mu_free(mu->mu_ref, link->data); + } + + link->data = NULL; + link->next = NULL; + mu->mu_free_link(mu->mu_ref, link); + + link = next_link; + } +} + + +/** + * Add descriptor where both links are inputs. + * + * @param header The Sahara header value for the descriptor. + * @param in1 The first input buffer (or NULL) + * @param in1_length Size of @a in1 + * @param[out] in2 The second input buffer (or NULL) + * @param in2_length Size of @a in2 + * @param mu Memory functions + * @param[in,out] desc_chain Chain to start or append to + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t sah_add_two_in_desc(uint32_t header, + const uint8_t* in1, + uint32_t in1_length, + const uint8_t* in2, + uint32_t in2_length, + const sah_Mem_Util* mu, + sah_Head_Desc** desc_chain) +{ + fsl_shw_return_t status = FSL_RETURN_OK_S; + sah_Link* link1 = NULL; + sah_Link* link2 = NULL; + + + if (in1 != NULL) { + status = sah_Create_Link(mu, &link1, + (sah_Oct_Str) in1, in1_length, + SAH_USES_LINK_DATA); + } + + if ( (in2 != NULL) && (status == FSL_RETURN_OK_S) ) { + status = sah_Create_Link(mu, &link2, + (sah_Oct_Str) in2, in2_length, + SAH_USES_LINK_DATA); + } + + if (status != FSL_RETURN_OK_S) { + if (link1 != NULL) { + sah_Destroy_Link(mu, link1); + } + if (link2 != NULL) { + sah_Destroy_Link(mu, link2); + } + } else { + status = sah_Append_Desc(mu, desc_chain, header, link1, link2); + } + + return status; +} + + +/** + * Add descriptor where both links are inputs, the second one being a key. + * + * @param header The Sahara header value for the descriptor. + * @param in1 The first input buffer (or NULL) + * @param in1_length Size of @a in1 + * @param[out] in2 The second input buffer (or NULL) + * @param in2_length Size of @a in2 + * @param mu Memory functions + * @param[in,out] desc_chain Chain to start or append to + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t sah_add_in_key_desc(uint32_t header, + const uint8_t* in1, + uint32_t in1_length, + fsl_shw_sko_t* key_info, + const sah_Mem_Util* mu, + sah_Head_Desc** desc_chain) +{ + fsl_shw_return_t status = FSL_RETURN_OK_S; + sah_Link *link1 = NULL; + sah_Link *link2 = NULL; + + + if (in1 != NULL) { + status = sah_Create_Link(mu, &link1, + (sah_Oct_Str) in1, in1_length, + SAH_USES_LINK_DATA); + } + + if (status == FSL_RETURN_OK_S) { + status = sah_Create_Key_Link(mu, &link2, key_info); + } + + if (status != FSL_RETURN_OK_S) { + if (link1 != NULL) { + sah_Destroy_Link(mu, link1); + } + if (link2 != NULL) { + sah_Destroy_Link(mu, link2); + } + } else { + status = sah_Append_Desc(mu, desc_chain, header, link1, link2); + } + + return status; +} + + +/** + * Create two links using keys allocated in the scc + * + * @param header The Sahara header value for the descriptor. + * @param in1 The first input buffer (or NULL) + * @param in1_length Size of @a in1 + * @param[out] in2 The second input buffer (or NULL) + * @param in2_length Size of @a in2 + * @param mu Memory functions + * @param[in,out] desc_chain Chain to start or append to + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t sah_add_key_key_desc(uint32_t header, + fsl_shw_sko_t *key_info1, + fsl_shw_sko_t *key_info2, + const sah_Mem_Util *mu, + sah_Head_Desc **desc_chain) +{ + fsl_shw_return_t status; + sah_Link *link1 = NULL; + sah_Link *link2 = NULL; + + + status = sah_Create_Key_Link(mu, &link1, key_info1); + + if (status == FSL_RETURN_OK_S) { + status = sah_Create_Key_Link(mu, &link2, key_info2); + } + + if (status != FSL_RETURN_OK_S) { + if (link1 != NULL) { + sah_Destroy_Link(mu, link1); + } + if (link2 != NULL) { + sah_Destroy_Link(mu, link2); + } + } else { + status = sah_Append_Desc(mu, desc_chain, header, link1, link2); + } + + return status; +} + + +/** + * Add descriptor where first link is input, the second is a changing key + * + * @param header The Sahara header value for the descriptor. + * @param in1 The first input buffer (or NULL) + * @param in1_length Size of @a in1 + * @param[out] in2 The key for output + * @param mu Memory functions + * @param[in,out] desc_chain Chain to start or append to + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t sah_add_in_keyout_desc(uint32_t header, + const uint8_t* in1, + uint32_t in1_length, + fsl_shw_sko_t* key_info, + const sah_Mem_Util* mu, + sah_Head_Desc** desc_chain) +{ + fsl_shw_return_t status = FSL_RETURN_OK_S; + sah_Link *link1 = NULL; + sah_Link *link2 = NULL; + + + if (in1 != NULL) { + status = sah_Create_Link(mu, &link1, + (sah_Oct_Str) in1, in1_length, + SAH_USES_LINK_DATA); + } + + if (status == FSL_RETURN_OK_S) { + status = sah_Create_Key_Link(mu, &link2, key_info); + } + + if (status != FSL_RETURN_OK_S) { + if (link1 != NULL) { + sah_Destroy_Link(mu, link1); + } + if (link2 != NULL) { + sah_Destroy_Link(mu, link2); + } + } else { + link2->flags |= SAH_OUTPUT_LINK; + + status = sah_Append_Desc(mu, desc_chain, header, link1, link2); + } + + return status; +} + +/** + * Add descriptor where both links are outputs. + * + * @param header The Sahara header value for the descriptor. + * @param out1 The first output buffer (or NULL) + * @param out1_length Size of @a out1 + * @param[out] out2 The second output buffer (or NULL) + * @param out2_length Size of @a out2 + * @param mu Memory functions + * @param[in,out] desc_chain Chain to start or append to + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t sah_add_two_out_desc(uint32_t header, + uint8_t* out1, + uint32_t out1_length, + uint8_t* out2, + uint32_t out2_length, + const sah_Mem_Util* mu, + sah_Head_Desc** desc_chain) +{ + fsl_shw_return_t status = FSL_RETURN_OK_S; + sah_Link *link1 = NULL; + sah_Link *link2 = NULL; + + + if (out1 != NULL) { + status = sah_Create_Link(mu, &link1, + (sah_Oct_Str) out1, out1_length, + SAH_OUTPUT_LINK | + SAH_USES_LINK_DATA); + } + + if ( (out2 != NULL) && (status == FSL_RETURN_OK_S) ) { + status = sah_Create_Link(mu, &link2, + (sah_Oct_Str) out2, out2_length, + SAH_OUTPUT_LINK | + SAH_USES_LINK_DATA); + } + + if (status != FSL_RETURN_OK_S) { + if (link1 != NULL) { + sah_Destroy_Link(mu, link1); + } + if (link2 != NULL) { + sah_Destroy_Link(mu, link2); + } + } else { + status = sah_Append_Desc(mu, desc_chain, header, link1, link2); + } + + return status; +} + + +/** + * Add descriptor where first link is output, second is output + * + * @param header The Sahara header value for the descriptor. + * @param out1 The first output buffer (or NULL) + * @param out1_length Size of @a out1 + * @param[out] in2 The input buffer (or NULL) + * @param in2_length Size of @a in2 + * @param mu Memory functions + * @param[in,out] desc_chain Chain to start or append to + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t sah_add_out_in_desc(uint32_t header, + uint8_t* out1, + uint32_t out1_length, + const uint8_t* in2, + uint32_t in2_length, + const sah_Mem_Util* mu, + sah_Head_Desc** desc_chain) +{ + fsl_shw_return_t status = FSL_RETURN_OK_S; + sah_Link *link1 = NULL; + sah_Link *link2 = NULL; + + + if (out1 != NULL) { + status = sah_Create_Link(mu, &link1, + (sah_Oct_Str) out1, out1_length, + SAH_OUTPUT_LINK | + SAH_USES_LINK_DATA); + } + + if ( (in2 != NULL) && (status == FSL_RETURN_OK_S) ) { + status = sah_Create_Link(mu, &link2, + (sah_Oct_Str) in2, in2_length, + SAH_USES_LINK_DATA); + } + + if (status != FSL_RETURN_OK_S) { + if (link1 != NULL) { + sah_Destroy_Link(mu, link1); + } + if (link2 != NULL) { + sah_Destroy_Link(mu, link2); + } + } else { + status = sah_Append_Desc(mu, desc_chain, header, link1, link2); + } + + return status; +} + + +/** + * Add descriptor where link1 is input buffer, link2 is output buffer. + * + * @param header The Sahara header value for the descriptor. + * @param in The input buffer + * @param in_length Size of the input buffer + * @param[out] out The output buffer + * @param out_length Size of the output buffer + * @param mu Memory functions + * @param[in,out] desc_chain Chain to start or append to + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t sah_add_in_out_desc(uint32_t header, + const uint8_t* in, uint32_t in_length, + uint8_t* out, uint32_t out_length, + const sah_Mem_Util* mu, + sah_Head_Desc** desc_chain) +{ + fsl_shw_return_t status = FSL_RETURN_OK_S; + sah_Link *link1 = NULL; + sah_Link *link2 = NULL; + + + if (in != NULL) { + status = sah_Create_Link(mu, &link1, + (sah_Oct_Str) in, in_length, + SAH_USES_LINK_DATA); + } + + if ((status == FSL_RETURN_OK_S) && (out != NULL)) { + status = sah_Create_Link(mu, &link2, + (sah_Oct_Str) out, out_length, + SAH_OUTPUT_LINK | + SAH_USES_LINK_DATA); + } + + if (status != FSL_RETURN_OK_S) { + if (link1 != NULL) { + sah_Destroy_Link(mu, link1); + } + if (link2 != NULL) { + sah_Destroy_Link(mu, link2); + } + } else { + status = sah_Append_Desc(mu, desc_chain, header, link1, link2); + } + + return status; +} + + +/** + * Add descriptor where link1 is a key, link2 is output buffer. + * + * @param header The Sahara header value for the descriptor. + * @param key_info Key information + * @param[out] out The output buffer + * @param out_length Size of the output buffer + * @param mu Memory functions + * @param[in,out] desc_chain Chain to start or append to + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t sah_add_key_out_desc(uint32_t header, fsl_shw_sko_t *key_info, + uint8_t* out, uint32_t out_length, + const sah_Mem_Util *mu, + sah_Head_Desc **desc_chain) +{ + fsl_shw_return_t status; + sah_Link *link1 = NULL; + sah_Link *link2 = NULL; + + + status = sah_Create_Key_Link(mu, &link1, key_info); + + if ((status == FSL_RETURN_OK_S) && (out != NULL)) { + status = sah_Create_Link(mu, &link2, + (sah_Oct_Str) out, out_length, + SAH_OUTPUT_LINK | + SAH_USES_LINK_DATA); + } + + if (status != FSL_RETURN_OK_S) { + if (link1 != NULL) { + sah_Destroy_Link(mu, link1); + } + if (link2 != NULL) { + sah_Destroy_Link(mu, link2); + } + } else { + status = sah_Append_Desc(mu, desc_chain, header, link1, link2); + } + + return status; +} + + +/** + * Sanity checks the user context object fields to ensure that they make some + * sense before passing the uco as a parameter + * + * @brief Verify the user context object + * + * @param uco user context object + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t sah_validate_uco(fsl_shw_uco_t *uco) +{ + fsl_shw_return_t status = FSL_RETURN_OK_S; + + + /* check if file is opened */ + if (uco->sahara_openfd < 0) { + status = FSL_RETURN_NO_RESOURCE_S; + } else { + /* check flag combination: the only invalid setting of the + * blocking and callback flags is blocking with callback. So check + * for that + */ + if ((uco->flags & (FSL_UCO_BLOCKING_MODE | FSL_UCO_CALLBACK_MODE)) == + (FSL_UCO_BLOCKING_MODE | FSL_UCO_CALLBACK_MODE)) { + status = FSL_RETURN_BAD_FLAG_S; + } else { + /* check that memory utilities have been attached */ + if (uco->mem_util == NULL) { + status = FSL_RETURN_MEMORY_ERROR_S; + } else { + /* must have pool of at least 1, even for blocking mode */ + if (uco->pool_size == 0) { + status = FSL_RETURN_ERROR_S; + } else { + /* if callback flag is set, it better have a callback + * routine */ + if (uco->flags & FSL_UCO_CALLBACK_MODE) { + if (uco->callback == NULL) { + status = FSL_RETURN_INTERNAL_ERROR_S; + } + } + } + } + } + } + + return status; +} + + +/** + * Perform any post-processing on non-blocking results. + * + * For instance, free descriptor chains, compare authentication codes, ... + * + * @param user_ctx User context object + * @param result_info A set of results + */ +void sah_Postprocess_Results(fsl_shw_uco_t* user_ctx, sah_results* result_info) +{ + unsigned i; + + /* for each result returned */ + for (i = 0; i < *result_info->actual; i++) { + sah_Head_Desc* desc = result_info->results[i].user_desc; + uint8_t* out1 = desc->out1_ptr; + uint8_t* out2 = desc->out2_ptr; + uint32_t len = desc->out_len; + + /* + * For now, tne only post-processing besides freeing the + * chain is the need to check the auth code for fsl_shw_auth_decrypt(). + * + * If other uses are required in the future, this code will probably + * need a flag in the sah_Head_Desc (or a function pointer!) to + * determine what needs to be done. + */ + if ((out1 != NULL) && (out2 != NULL)) { + unsigned j; + for (j = 0; j < len; j++) { + if (out1[j] != out2[j]) { + /* Problem detected. Change result. */ + result_info->results[i].code = FSL_RETURN_AUTH_FAILED_S; + break; + } + } + /* free allocated 'calced_auth' */ + user_ctx->mem_util-> + mu_free(user_ctx->mem_util->mu_ref, out1); + } + + /* Free the API-created chain, unless tagged as not-from-API */ + if (! (desc->uco_flags & FSL_UCO_SAVE_DESC_CHAIN)) { + sah_Descriptor_Chain_Destroy(user_ctx->mem_util, &desc); + } + } +} + + +/* End of sah_util.c */ diff --git a/drivers/mxc/security/scc2_driver.c b/drivers/mxc/security/scc2_driver.c new file mode 100644 index 000000000000..85c2a738aac5 --- /dev/null +++ b/drivers/mxc/security/scc2_driver.c @@ -0,0 +1,2854 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! @file scc2_driver.c + * + * This is the driver code for the Security Controller (SCC). It has no device + * driver interface, so no user programs may access it. Its interaction with + * the Linux kernel is from calls to #scc_init() when the driver is loaded, and + * #scc_cleanup() should the driver be unloaded. The driver uses locking and + * (task-sleep/task-wakeup) functions of the kernel. It also registers itself + * to handle the interrupt line(s) from the SCC. + * + * Other drivers in the kernel may use the remaining API functions to get at + * the services of the SCC. The main service provided is the Secure Memory, + * which allows encoding and decoding of secrets with a per-chip secret key. + * + * The SCC is single-threaded, and so is this module. When the scc_crypt() + * routine is called, it will lock out other accesses to the function. If + * another task is already in the module, the subsequent caller will spin on a + * lock waiting for the other access to finish. + * + * Note that long crypto operations could cause a task to spin for a while, + * preventing other kernel work (other than interrupt processing) to get done. + * + * The external (kernel module) interface is through the following functions: + * @li scc_get_configuration() + * @li scc_crypt() + * @li scc_zeroize_memories() + * @li scc_monitor_security_failure() + * @li scc_stop_monitoring_security_failure() + * @li scc_set_sw_alarm() + * @li scc_read_register() + * @li scc_write_register() + * + * All other functions are internal to the driver. + */ + +#include "scc2_internals.h" +#include "sahara2/include/portable_os.h" +#include <linux/delay.h> + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,18)) + +#include <linux/device.h> +#include <asm/arch/clock.h> +#include <linux/device.h> + +#else + +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <linux/err.h> + +#endif + +#include <linux/dmapool.h> + +#define SAHARA_PART_NO 4 +#define VPU_PART_NO 0 + +/*! + * This is the set of errors which signal that access to the SCM RAM has + * failed or will fail. + */ +#define SCM_ACCESS_ERRORS \ + (SCM_ERRSTAT_ILM | SCM_ERRSTAT_SUP | SCM_ERRSTAT_ERC_MASK) + +/****************************************************************************** + * + * Global / Static Variables + * + *****************************************************************************/ + +#ifdef SCC_REGISTER_DEBUG + +#define REG_PRINT_BUFFER_SIZE 200 + +static char reg_print_buffer[REG_PRINT_BUFFER_SIZE]; + +typedef char *(*reg_print_routine_t) (uint32_t value, char *print_buffer, + int buf_size); + +#endif + +/*! + * This is type void* so that a) it cannot directly be dereferenced, + * and b) pointer arithmetic on it will function in a 'normal way' for + * the offsets in scc_defines.h + * + * scc_base is the location in the iomap where the SCC's registers + * (and memory) start. + * + * The referenced data is declared volatile so that the compiler will + * not make any assumptions about the value of registers in the SCC, + * and thus will always reload the register into CPU memory before + * using it (i.e. wherever it is referenced in the driver). + * + * This value should only be referenced by the #SCC_READ_REGISTER and + * #SCC_WRITE_REGISTER macros and their ilk. All dereferences must be + * 32 bits wide. + */ +static volatile void *scc_base; + +/*! Array to hold function pointers registered by + #scc_monitor_security_failure() and processed by + #scc_perform_callbacks() */ +static void (*scc_callbacks[SCC_CALLBACK_SIZE]) (void); + +uint32_t scm_ram_phys_base = SCM_RAM_BASE_ADDR; + +void *scm_ram_base = NULL; + +/*! + * Starting address of Sahara key partition + */ +uint8_t *sahara_partition_base; +dma_addr_t sahara_partition_phys; + +uint8_t *scm_black_part_virt; +uint32_t scm_black_part_phys; + +uint8_t *scm_red_part_virt; +uint32_t scm_red_part_cmd; +/*! + * Starting address of VPU Partition + */ +uint8_t *vpu_partition_base; +dma_addr_t vpu_partition_phys; +/*! Calculated once for quick reference to size of the unreserved space in + * RAM in SCM. + */ +uint32_t scm_memory_size_bytes; + +/*! Structure returned by #scc_get_configuration() */ +static scc_config_t scc_configuration = { + .driver_major_version = SCC_DRIVER_MAJOR_VERSION_1, + .driver_minor_version = SCC_DRIVER_MINOR_VERSION_97, + .scm_version = -1, + .smn_version = -1, + .block_size_bytes = -1, + .partition_size_bytes = -1, + .partition_count = -1, +}; + +/*! Key Control Information. Integrity is controlled by use of + #scc_crypto_lock. */ +static struct scc_key_slot scc_key_info[SCC_KEY_SLOTS]; + +/*! Internal flag to know whether SCC is in Failed state (and thus many + * registers are unavailable). Once it goes failed, it never leaves it. */ +static volatile enum scc_status scc_availability = SCC_STATUS_INITIAL; + +/*! Flag to say whether interrupt handler has been registered for + * SMN interrupt */ +static int smn_irq_set = 0; + +/*! Flag to say whether interrupt handler has been registered for + * SCM interrupt */ +static int scm_irq_set = 0; + +/*! This lock protects the #scc_callbacks list as well as the @c + * callbacks_performed flag in #scc_perform_callbacks. Since the data this + * protects may be read or written from either interrupt or base level, all + * operations should use the irqsave/irqrestore or similar to make sure that + * interrupts are inhibited when locking from base level. + */ +static spinlock_t scc_callbacks_lock = SPIN_LOCK_UNLOCKED; + +/*! + * Ownership of this lock prevents conflicts on the crypto operation in the SCC + * and the integrity of the #scc_key_info. + */ +static spinlock_t scc_crypto_lock = SPIN_LOCK_UNLOCKED; + +/*! Calculated once for quick reference to size of SCM address space */ +//static uint32_t scm_highest_memory_address; + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,18)) +/*! Pointer to SCC's clock information. Initialized during scc_init(). */ +static struct clk *scc_clk = NULL; +#endif + +/*! The lookup table for an 8-bit value. Calculated once + * by #scc_init_ccitt_crc(). + */ +static uint16_t scc_crc_lookup_table[256]; + +/*! Fixed padding for appending to plaintext to fill out a block */ +static uint8_t scc_block_padding[16] = + { SCC_DRIVER_PAD_CHAR, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + +static scc_return_t make_sahara_partition(void); +uint8_t make_vpu_partition(void); +/****************************************************************************** + * + * Function Implementations - Externally Accessible + * + *****************************************************************************/ + +/*****************************************************************************/ +/* fn scc_init() */ +/*****************************************************************************/ +/*! + * Initialize the driver at boot time or module load time. + * + * Register with the kernel as the interrupt handler for the SCC interrupt + * line(s). + * + * Map the SCC's register space into the driver's memory space. + * + * Query the SCC for its configuration and status. Save the configuration in + * #scc_configuration and save the status in #scc_availability. Called by the + * kernel. + * + * Do any locking/wait queue initialization which may be necessary. + * + * The availability fuse may be checked, depending on platform. + */ +static int scc_init(void) +{ + uint32_t smn_status; + int i; + int return_value = -EIO; /* assume error */ + + if (scc_availability == SCC_STATUS_INITIAL) { + + /* Set this until we get an initial reading */ + scc_availability = SCC_STATUS_CHECKING; + + /* Initialize the constant for the CRC function */ + scc_init_ccitt_crc(); + + /* initialize the callback table */ + for (i = 0; i < SCC_CALLBACK_SIZE; i++) { + scc_callbacks[i] = 0; + } + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,18)) + mxc_clks_enable(SCC_CLK); +#else + scc_clk = clk_get(NULL, "scc_clk"); + if (scc_clk != ERR_PTR(ENOENT)) { + clk_enable(scc_clk); + } +#endif + + /* See whether there is an SCC available */ + if (0 && !SCC_ENABLED()) { + printk(KERN_ERR + "SCC2: Fuse for SCC is set to disabled. Exiting.\n"); + goto out; + } + /* Map the SCC (SCM and SMN) memory on the internal bus into + kernel address space */ + scc_base = (void *)IO_ADDRESS(SCC_BASE); + if (scc_base == NULL) { + printk(KERN_ERR + "SCC2: Register mapping failed. Exiting.\n"); + goto out; + } + + /* If that worked, we can try to use the SCC */ + /* Get SCM into 'clean' condition w/interrupts cleared & + disabled */ + SCC_WRITE_REGISTER(SCM_INT_CTL_REG, 0); + + /* Clear error status register */ + (void)SCC_READ_REGISTER(SCM_ERR_STATUS_REG); + + /* + * There is an SCC. Determine its current state. Side effect + * is to populate scc_config and scc_availability + */ + smn_status = scc_grab_config_values(); + + /* Try to set up interrupt handler(s) */ + if (scc_availability != SCC_STATUS_OK) { + goto out; + } + + scm_ram_base = + (void *)ioremap_nocache(scm_ram_phys_base, + scc_configuration.partition_count * + scc_configuration. + partition_size_bytes); + if (scm_ram_base == NULL) { + printk(KERN_ERR + "SCC2: RAM failed to remap: %p for %d bytes\n", + (void *)scm_ram_phys_base, + scc_configuration.partition_count * + scc_configuration.partition_size_bytes); + goto out; + } + pr_debug("SCC2: RAM at Physical %p / Virtual %p\n", + (void *)scm_ram_phys_base, scm_ram_base); + + return_value = make_sahara_partition(); + if (return_value != SCC_RET_OK) { + scc_availability = SCC_STATUS_UNIMPLEMENTED; + goto out; + } + + /* Initialize key slots */ + for (i = 0; i < SCC_KEY_SLOTS; i++) { + scc_key_info[i].offset = i * SCC_KEY_SLOT_SIZE; + scc_key_info[i].part_ctl = + (((i * SCC_KEY_SLOT_SIZE) / + SCC_BLOCK_SIZE_BYTES() << SCM_CCMD_OFFSET_SHIFT) + | (SAHARA_PART_NO << SCM_CCMD_PART_SHIFT)); + scc_key_info[i].status = 0; /* unassigned */ + } + + if (setup_interrupt_handling() != 0) { + unsigned err_cond; + /*! + * The error could be only that the SCM interrupt was + * not set up. This interrupt is always masked, so + * that is not an issue. + * + * The SMN's interrupt may be shared on that line, it + * may be separate, or it may not be wired. Do what + * is necessary to check its status. + * + * Although the driver is coded for possibility of not + * having SMN interrupt, the fact that there is one + * means it should be available and used. + */ +#ifdef USE_SMN_INTERRUPT + err_cond = !smn_irq_set; /* Separate. Check SMN binding */ +#elif !defined(NO_SMN_INTERRUPT) + err_cond = !scm_irq_set; /* Shared. Check SCM binding */ +#else + err_cond = FALSE; /* SMN not wired at all. Ignore. */ +#endif + if (err_cond) { + /* setup was not able to set up SMN interrupt */ + scc_availability = SCC_STATUS_UNIMPLEMENTED; + goto out; + } + } + + /* interrupt handling returned non-zero */ + /* Get SMN into 'clean' condition w/interrupts cleared & + enabled */ + SCC_WRITE_REGISTER(SMN_COMMAND_REG, + SMN_COMMAND_CLEAR_INTERRUPT + | SMN_COMMAND_ENABLE_INTERRUPT); + + out: + /* + * If status is SCC_STATUS_UNIMPLEMENTED or is still + * SCC_STATUS_CHECKING, could be leaving here with the driver partially + * initialized. In either case, cleanup (which will mark the SCC as + * UNIMPLEMENTED). + */ + if (scc_availability == SCC_STATUS_CHECKING || + scc_availability == SCC_STATUS_UNIMPLEMENTED) { + scc_cleanup(); + } else { + return_value = 0; /* All is well */ + } + } + /* ! STATUS_INITIAL */ + printk("SCC2: Driver Status is %s\n", + (scc_availability == SCC_STATUS_INITIAL) ? "INITIAL" : + (scc_availability == SCC_STATUS_CHECKING) ? "CHECKING" : + (scc_availability == + SCC_STATUS_UNIMPLEMENTED) ? "UNIMPLEMENTED" : (scc_availability + == + SCC_STATUS_OK) ? + "OK" : (scc_availability == + SCC_STATUS_FAILED) ? "FAILED" : "UNKNOWN"); + + return return_value; +} /* scc_init */ + +/*****************************************************************************/ +/* fn scc_cleanup() */ +/*****************************************************************************/ +/*! + * Perform cleanup before driver/module is unloaded by setting the machine + * state close to what it was when the driver was loaded. This function is + * called when the kernel is shutting down or when this driver is being + * unloaded. + * + * A driver like this should probably never be unloaded, especially if there + * are other module relying upon the callback feature for monitoring the SCC + * status. + * + * In any case, cleanup the callback table (by clearing out all of the + * pointers). Deregister the interrupt handler(s). Unmap SCC registers. + * + */ +static void scc_cleanup(void) +{ + int i; + + /* Mark the driver / SCC as unusable. */ + scc_availability = SCC_STATUS_UNIMPLEMENTED; + + /* Clear out callback table */ + for (i = 0; i < SCC_CALLBACK_SIZE; i++) { + scc_callbacks[i] = 0; + } + + /* If SCC has been mapped in, clean it up and unmap it */ + if (scc_base) { + /* For the SCM, disable interrupts. */ + SCC_WRITE_REGISTER(SCM_INT_CTL_REG, 0); + + /* For the SMN, clear and disable interrupts */ + SCC_WRITE_REGISTER(SMN_COMMAND_REG, + SMN_COMMAND_CLEAR_INTERRUPT); + } + + if (sahara_partition_base != NULL) { + + } + /* Now that interrupts cannot occur, disassociate driver from the interrupt + * lines. + */ + + /* Deregister SCM interrupt handler */ + if (scm_irq_set) { + free_irq(INT_SCC_SCM, NULL); + } + + /* Deregister SMN interrupt handler */ + if (smn_irq_set) { +#ifdef USE_SMN_INTERRUPT + free_irq(INT_SCC_SMN, NULL); +#endif + } + + pr_debug("SCC2 driver cleaned up.\n"); + +} /* scc_cleanup */ + +/*****************************************************************************/ +/* fn scc_get_configuration() */ +/*****************************************************************************/ +scc_config_t *scc_get_configuration(void) +{ + /* + * If some other driver calls scc before the kernel does, make sure that + * this driver's initialization is performed. + */ + if (scc_availability == SCC_STATUS_INITIAL) { + scc_init(); + } + + /*! + * If there is no SCC, yet the driver exists, the value -1 will be in + * the #scc_config_t fields for other than the driver versions. + */ + return &scc_configuration; +} /* scc_get_configuration */ + +/*****************************************************************************/ +/* fn scc_zeroize_memories() */ +/*****************************************************************************/ +scc_return_t scc_zeroize_memories(void) +{ + scc_return_t return_status = SCC_RET_FAIL; + + return return_status; +} /* scc_zeroize_memories */ + +/*****************************************************************************/ +/* fn scc_crypt() */ +/*****************************************************************************/ +scc_return_t +scc_crypt(unsigned long count_in_bytes, uint8_t * data_in, + uint8_t * init_vector, scc_enc_dec_t direction, + scc_crypto_mode_t crypto_mode, scc_verify_t check_mode, + uint8_t * data_out, unsigned long *count_out_bytes) +{ + uint32_t scm_command = scm_red_part_cmd; + unsigned long irq_flags; /* for IRQ save/restore */ + unsigned locked = FALSE; + scc_return_t return_code = SCC_RET_FAIL; + + if (scc_availability == SCC_STATUS_INITIAL) { + scc_init(); + } + (void)scc_update_state(); /* in case no interrupt line from SMN */ + /* make initial error checks */ + if (scc_availability != SCC_STATUS_OK + || count_in_bytes == 0 + || data_in == 0 + || data_out == 0 + || (crypto_mode != SCC_CBC_MODE && crypto_mode != SCC_ECB_MODE) + || (crypto_mode == SCC_CBC_MODE && init_vector == NULL) + || (direction != SCC_ENCRYPT && direction != SCC_DECRYPT) + || (check_mode == SCC_VERIFY_MODE_NONE && + count_in_bytes % SCC_BLOCK_SIZE_BYTES() != 0) + || (direction == SCC_DECRYPT && + count_in_bytes % SCC_BLOCK_SIZE_BYTES() != 0) + || (check_mode != SCC_VERIFY_MODE_NONE && + check_mode != SCC_VERIFY_MODE_CCITT_CRC)) { + pr_debug("SCC2: scc_crypt() detected bad argument\n"); + goto out; + } + /* Lock access to crypto memory of the SCC */ + spin_lock_irqsave(&scc_crypto_lock, irq_flags); + locked = TRUE; + /* Special needs for CBC Mode */ + if (crypto_mode == SCC_CBC_MODE) { + scm_command |= SCM_CCMD_CBC; /* change default of ECB */ + /* Put in Initial Context. Vector registers are contiguous */ + copy_to_scc(init_vector, + (uint32_t) (scc_base + SCM_AES_CBC_IV0_REG), + SCC_BLOCK_SIZE_BYTES(), NULL); + } + + /* Fill the BLACK_START register */ + SCC_WRITE_REGISTER(SCM_C_BLACK_ST_REG, scm_black_part_phys); + + if (direction == SCC_ENCRYPT) { + /* Check for sufficient space in data_out */ + if (check_mode == SCC_VERIFY_MODE_NONE) { + if (*count_out_bytes < count_in_bytes) { + return_code = SCC_RET_INSUFFICIENT_SPACE; + goto out; + } + } else { /* SCC_VERIFY_MODE_CCITT_CRC */ + /* Calculate extra bytes needed for crc (2) and block + padding */ + int padding_needed = + CRC_SIZE_BYTES + SCC_BLOCK_SIZE_BYTES() - + ((count_in_bytes + CRC_SIZE_BYTES) + % SCC_BLOCK_SIZE_BYTES()); + + /* Verify space is available */ + if (*count_out_bytes < count_in_bytes + padding_needed) { + return_code = SCC_RET_INSUFFICIENT_SPACE; + goto out; + } + } + return_code = + scc_encrypt(count_in_bytes, data_in, scm_command, data_out, + check_mode == SCC_VERIFY_MODE_CCITT_CRC, + count_out_bytes); + } + /* direction == SCC_ENCRYPT */ + else { /* SCC_DECRYPT */ + /* Check for sufficient space in data_out */ + if (check_mode == SCC_VERIFY_MODE_NONE) { + if (*count_out_bytes < count_in_bytes) { + return_code = SCC_RET_INSUFFICIENT_SPACE; + } + } else { /* SCC_VERIFY_MODE_CCITT_CRC */ + /* Do initial check. Assume last block (of padding) and CRC + * will get stripped. After decipher is done and padding is + * removed, will know exact value. + */ + int possible_size = (int)count_in_bytes - CRC_SIZE_BYTES + - SCC_BLOCK_SIZE_BYTES(); + if ((int)*count_out_bytes < possible_size) { + pr_debug + ("SCC2: insufficient decrypt space %ld/%d.\n", + *count_out_bytes, possible_size); + return_code = SCC_RET_INSUFFICIENT_SPACE; + goto out; + } + } + + return_code = + scc_decrypt(count_in_bytes, data_in, scm_command, data_out, + check_mode == SCC_VERIFY_MODE_CCITT_CRC, + count_out_bytes); + } /* SCC_DECRYPT */ + + out: + /* unlock the SCC */ + if (locked) { + spin_unlock_irqrestore(&scc_crypto_lock, irq_flags); + } + + return return_code; +} /* scc_crypt */ + +/*****************************************************************************/ +/* fn scc_set_sw_alarm() */ +/*****************************************************************************/ +void scc_set_sw_alarm(void) +{ + + if (scc_availability == SCC_STATUS_INITIAL) { + scc_init(); + } + + /* Update scc_availability based on current SMN status. This might + * perform callbacks. + */ + (void)scc_update_state(); + + /* if everything is OK, make it fail */ + if (scc_availability == SCC_STATUS_OK) { + + /* sound the alarm (and disable SMN interrupts */ + SCC_WRITE_REGISTER(SMN_COMMAND_REG, + SMN_COMMAND_SET_SOFTWARE_ALARM); + + scc_availability = SCC_STATUS_FAILED; /* Remember what we've done */ + + /* In case SMN interrupt is not available, tell the world */ + scc_perform_callbacks(); + } + + return; +} /* scc_set_sw_alarm */ + +/*****************************************************************************/ +/* fn scc_monitor_security_failure() */ +/*****************************************************************************/ +scc_return_t scc_monitor_security_failure(void callback_func(void)) +{ + int i; + unsigned long irq_flags; /* for IRQ save/restore */ + scc_return_t return_status = SCC_RET_TOO_MANY_FUNCTIONS; + int function_stored = FALSE; + + if (scc_availability == SCC_STATUS_INITIAL) { + scc_init(); + } + + /* Acquire lock of callbacks table. Could be spin_lock_irq() if this + * routine were just called from base (not interrupt) level + */ + spin_lock_irqsave(&scc_callbacks_lock, irq_flags); + + /* Search through table looking for empty slot */ + for (i = 0; i < SCC_CALLBACK_SIZE; i++) { + if (scc_callbacks[i] == callback_func) { + if (function_stored) { + /* Saved duplicate earlier. Clear this later one. */ + scc_callbacks[i] = NULL; + } + /* Exactly one copy is now stored */ + return_status = SCC_RET_OK; + break; + } else if (scc_callbacks[i] == NULL && !function_stored) { + /* Found open slot. Save it and remember */ + scc_callbacks[i] = callback_func; + return_status = SCC_RET_OK; + function_stored = TRUE; + } + } + + /* Free the lock */ + spin_unlock_irqrestore(&scc_callbacks_lock, irq_flags); + + return return_status; +} /* scc_monitor_security_failure */ + +/*****************************************************************************/ +/* fn scc_stop_monitoring_security_failure() */ +/*****************************************************************************/ +void scc_stop_monitoring_security_failure(void callback_func(void)) +{ + unsigned long irq_flags; /* for IRQ save/restore */ + int i; + + if (scc_availability == SCC_STATUS_INITIAL) { + scc_init(); + } + + /* Acquire lock of callbacks table. Could be spin_lock_irq() if this + * routine were just called from base (not interrupt) level + */ + spin_lock_irqsave(&scc_callbacks_lock, irq_flags); + + /* Search every entry of the table for this function */ + for (i = 0; i < SCC_CALLBACK_SIZE; i++) { + if (scc_callbacks[i] == callback_func) { + scc_callbacks[i] = NULL; /* found instance - clear it out */ + break; + } + } + + /* Free the lock */ + spin_unlock_irqrestore(&scc_callbacks_lock, irq_flags); + + return; +} /* scc_stop_monitoring_security_failure */ + +/*****************************************************************************/ +/* fn scc_read_register() */ +/*****************************************************************************/ +scc_return_t scc_read_register(int register_offset, uint32_t * value) +{ + scc_return_t return_status = SCC_RET_FAIL; + uint32_t smn_status; + uint32_t scm_status; + + if (scc_availability == SCC_STATUS_INITIAL) { + scc_init(); + } + + /* First layer of protection -- completely unaccessible SCC */ + if (scc_availability != SCC_STATUS_UNIMPLEMENTED) { + + /* Second layer -- that offset is valid */ + if (register_offset != SMN_BB_DEC_REG && /* write only! */ + check_register_offset(register_offset) == SCC_RET_OK) { + + /* Get current status / update local state */ + smn_status = scc_update_state(); + scm_status = SCC_READ_REGISTER(SCM_STATUS_REG); + + /* + * Third layer - verify that the register being requested is + * available in the current state of the SCC. + */ + if ((return_status = + check_register_accessible(register_offset, + smn_status, + scm_status)) == + SCC_RET_OK) { + *value = SCC_READ_REGISTER(register_offset); + } + } + } + + return return_status; +} /* scc_read_register */ + +/*****************************************************************************/ +/* fn scc_write_register() */ +/*****************************************************************************/ +scc_return_t scc_write_register(int register_offset, uint32_t value) +{ + scc_return_t return_status = SCC_RET_FAIL; + uint32_t smn_status; + uint32_t scm_status; + + if (scc_availability == SCC_STATUS_INITIAL) { + scc_init(); + } + + /* First layer of protection -- completely unaccessible SCC */ + if (scc_availability != SCC_STATUS_UNIMPLEMENTED) { + + /* Second layer -- that offset is valid */ + if (!((register_offset == SCM_STATUS_REG) || /* These registers are */ + (register_offset == SCM_VERSION_REG) || /* Read Only */ + (register_offset == SMN_BB_CNT_REG) || + (register_offset == SMN_TIMER_REG)) && + check_register_offset(register_offset) == SCC_RET_OK) { + + /* Get current status / update local state */ + smn_status = scc_update_state(); + scm_status = SCC_READ_REGISTER(SCM_STATUS_REG); + + /* + * Third layer - verify that the register being requested is + * available in the current state of the SCC. + */ + if (check_register_accessible + (register_offset, smn_status, scm_status) == 0) { + SCC_WRITE_REGISTER(register_offset, value); + return_status = SCC_RET_OK; + } + } + } + + return return_status; +} /* scc_write_register() */ + +/****************************************************************************** + * + * Function Implementations - Internal + * + *****************************************************************************/ + +/*****************************************************************************/ +/* fn scc_irq() */ +/*****************************************************************************/ +/*! + * This is the interrupt handler for the SCC. + * + * This function checks the SMN Status register to see whether it + * generated the interrupt, then it checks the SCM Status register to + * see whether it needs attention. + * + * If an SMN Interrupt is active, then the SCC state set to failure, and + * #scc_perform_callbacks() is invoked to notify any interested parties. + * + * The SCM Interrupt should be masked, as this driver uses polling to determine + * when the SCM has completed a crypto or zeroing operation. Therefore, if the + * interrupt is active, the driver will just clear the interrupt and (re)mask. + * + * @param irq Channel number for the IRQ. (@c SCC_INT_SMN or @c SCC_INT_SCM). + * @param dev_id Pointer to the identification of the device. Ignored. + */ +static irqreturn_t scc_irq(int irq, void *dev_id) +{ + uint32_t smn_status; + uint32_t scm_status; + int handled = 0; /* assume interrupt isn't from SMN */ +#if defined(USE_SMN_INTERRUPT) + int smn_irq = INT_SCC_SMN; /* SMN interrupt is on a line by itself */ +#elif defined (NO_SMN_INTERRUPT) + int smn_irq = -1; /* not wired to CPU at all */ +#else + int smn_irq = INT_SCC_SCM; /* SMN interrupt shares a line with SCM */ +#endif + + /* Update current state... This will perform callbacks... */ + smn_status = scc_update_state(); + + /* SMN is on its own interrupt line. Verify the IRQ was triggered + * before clearing the interrupt and marking it handled. */ + if ((irq == smn_irq) && (smn_status & SMN_STATUS_SMN_STATUS_IRQ)) { + SCC_WRITE_REGISTER(SMN_COMMAND_REG, + SMN_COMMAND_CLEAR_INTERRUPT); + handled++; /* tell kernel that interrupt was handled */ + } + + /* Check on the health of the SCM */ + scm_status = SCC_READ_REGISTER(SCM_STATUS_REG); + + /* The driver masks interrupts, so this should never happen. */ + if (irq == INT_SCC_SCM) { + /* but if it does, try to prevent it in the future */ + SCC_WRITE_REGISTER(SCM_INT_CTL_REG, 0); + handled++; + } + + /* Any non-zero value of handled lets kernel know we got something */ + return IRQ_RETVAL(handled); +} + +/*****************************************************************************/ +/* fn scc_perform_callbacks() */ +/*****************************************************************************/ +/*! Perform callbacks registered by #scc_monitor_security_failure(). + * + * Make sure callbacks only happen once... Since there may be some reason why + * the interrupt isn't generated, this routine could be called from base(task) + * level. + * + * One at a time, go through #scc_callbacks[] and call any non-null pointers. + */ +static void scc_perform_callbacks(void) +{ + static int callbacks_performed = 0; + unsigned long irq_flags; /* for IRQ save/restore */ + int i; + + /* Acquire lock of callbacks table and callbacks_performed flag */ + spin_lock_irqsave(&scc_callbacks_lock, irq_flags); + + if (!callbacks_performed) { + callbacks_performed = 1; + + /* Loop over all of the entries in the table */ + for (i = 0; i < SCC_CALLBACK_SIZE; i++) { + /* If not null, ... */ + if (scc_callbacks[i]) { + scc_callbacks[i] (); /* invoke the callback routine */ + } + } + } + + spin_unlock_irqrestore(&scc_callbacks_lock, irq_flags); + + return; +} + +/*****************************************************************************/ +/* fn copy_to_scc() */ +/*****************************************************************************/ +/*! + * Move data from possibly unaligned source and realign for SCC, possibly + * while calculating CRC. + * + * Multiple calls can be made to this routine (without intervening calls to + * #copy_from_scc(), as long as the sum total of bytes copied is a multiple of + * four (SCC native word size). + * + * @param[in] from Location in memory + * @param[out] to Location in SCC + * @param[in] count_bytes Number of bytes to copy + * @param[in,out] crc Pointer to CRC. Initial value must be + * #CRC_CCITT_START if this is the start of + * message. Output is the resulting (maybe + * partial) CRC. If NULL, no crc is calculated. + * + * @return Zero - success. Non-zero - SCM status bits defining failure. + */ +static uint32_t +copy_to_scc(const uint8_t * from, uint32_t to, unsigned long count_bytes, + uint16_t * crc) +{ + int i; + uint32_t scm_word; + uint16_t current_crc = 0; /* local copy for fast access */ + + pr_debug("SCC2: copying %ld bytes to 0x%0x.\n", count_bytes, to); + + if (crc) { + current_crc = *crc; + } + + /* Initialize value being built for SCM. If we are starting 'clean', + * set it to zero. Otherwise pick up partial value which had been saved + * earlier. */ + if (SCC_BYTE_OFFSET(to) == 0) { + scm_word = 0; + } else { + scm_word = *(uint32_t *) SCC_WORD_PTR(to); /* recover */ + } + + /* Now build up SCM words and write them out when each is full */ + for (i = 0; i < count_bytes; i++) { + uint8_t byte = *from++; /* value from plaintext */ + +#ifdef __BIG_ENDIAN + scm_word = (scm_word << 8) | byte; /* add byte to SCM word */ +#else + scm_word = (byte << 24) | (scm_word >> 8); +#endif + /* now calculate CCITT CRC */ + if (crc) { + CALC_CRC(byte, current_crc); + } + + to++; /* bump location in SCM */ + + /* check for full word */ + if (SCC_BYTE_OFFSET(to) == 0) { + *(uint32_t *) (to - 4) = scm_word; /* write it out */ + } + } + + /* If at partial word after previous loop, save it in SCM memory for + next time. */ + if (SCC_BYTE_OFFSET(to) != 0) { + *(uint32_t *) SCC_WORD_PTR(to) = scm_word; /* save */ + } + + /* Copy CRC back */ + if (crc) { + *crc = current_crc; + } + + return SCC_RET_OK; +} + +/*****************************************************************************/ +/* fn copy_from_scc() */ +/*****************************************************************************/ +/*! + * Move data from aligned 32-bit source and place in (possibly unaligned) + * target, and maybe calculate CRC at the same time. + * + * Multiple calls can be made to this routine (without intervening calls to + * #copy_to_scc(), as long as the sum total of bytes copied is be a multiple + * of four. + * + * @param[in] from Location in SCC + * @param[out] to Location in memory + * @param[in] count_bytes Number of bytes to copy + * @param[in,out] crc Pointer to CRC. Initial value must be + * #CRC_CCITT_START if this is the start of + * message. Output is the resulting (maybe + * partial) CRC. If NULL, crc is not calculated. + * + * @return Zero - success. Non-zero - SCM status bits defining failure. + */ +static uint32_t +copy_from_scc(const uint32_t from, uint8_t * to, unsigned long count_bytes, + uint16_t * crc) +{ + uint32_t running_from = from; + uint32_t scm_word; + uint16_t current_crc = 0; /* local copy for fast access */ + + pr_debug("SCC2: copying %ld bytes from 0x%x.\n", count_bytes, from); + + if (crc) { + current_crc = *crc; + } + + /* Read word which is sitting in SCM memory. Ignore byte offset */ + scm_word = *(uint32_t *) SCC_WORD_PTR(running_from); + pr_debug("%08x ", scm_word); + /* If necessary, move the 'first' byte into place */ + if (SCC_BYTE_OFFSET(running_from) != 0) { +#ifdef __BIG_ENDIAN + scm_word <<= 8 * SCC_BYTE_OFFSET(running_from); +#else + scm_word >>= 8 * SCC_BYTE_OFFSET(running_from); +#endif + } + + /* Now build up SCM words and write them out when each is full */ + while (count_bytes--) { + uint8_t byte; /* value from plaintext */ + +#ifdef __BIG_ENDIAN + byte = (scm_word & 0xff000000) >> 24; /* pull byte out of SCM word */ + scm_word <<= 8; /* shift over to remove the just-pulled byte */ +#else + byte = (scm_word & 0xff); + scm_word >>= 8; /* shift over to remove the just-pulled byte */ +#endif + *to++ = byte; /* send byte to memory */ + + /* now calculate CRC */ + if (crc) { + CALC_CRC(byte, current_crc); + } + + running_from++; + /* check for empty word */ + if (count_bytes && SCC_BYTE_OFFSET(running_from) == 0) { + /* read one in */ + scm_word = *(uint32_t *) running_from; + pr_debug("%08x ", scm_word); + } + } + + pr_debug("\n"); + if (crc) { + *crc = current_crc; + } + + return SCC_RET_OK; +} + +/*****************************************************************************/ +/* fn scc_strip_padding() */ +/*****************************************************************************/ +/*! + * Remove padding from plaintext. Search backwards for #SCC_DRIVER_PAD_CHAR, + * verifying that each byte passed over is zero (0). Maximum number of bytes + * to examine is 8. + * + * @param[in] from Pointer to byte after end of message + * @param[out] count_bytes_stripped Number of padding bytes removed by this + * function. + * + * @return #SCC_RET_OK if all goes, well, #SCC_RET_FAIL if padding was + * not present. +*/ +static scc_return_t +scc_strip_padding(uint8_t * from, unsigned *count_bytes_stripped) +{ + int i = SCC_BLOCK_SIZE_BYTES(); + scc_return_t return_code = SCC_RET_VERIFICATION_FAILED; + + /* + * Search backwards looking for the magic marker. If it isn't found, + * make sure that a 0 byte is there in its place. Stop after the maximum + * amount of padding (8 bytes) has been searched); + */ + while (i-- > 0) { + if (*--from == SCC_DRIVER_PAD_CHAR) { + *count_bytes_stripped = SCC_BLOCK_SIZE_BYTES() - i; + return_code = SCC_RET_OK; + break; + } else if (*from != 0) { /* if not marker, check for 0 */ + pr_debug("SCC2: Found non-zero interim pad: 0x%x\n", + *from); + break; + } + } + + return return_code; +} + +/*****************************************************************************/ +/* fn scc_update_state() */ +/*****************************************************************************/ +/*! + * Make certain SCC is still running. + * + * Side effect is to update #scc_availability and, if the state goes to failed, + * run #scc_perform_callbacks(). + * + * (If #SCC_BRINGUP is defined, bring SCC to secure state if it is found to be + * in health check state) + * + * @return Current value of #SMN_STATUS register. + */ +static uint32_t scc_update_state(void) +{ + uint32_t smn_status_register = SMN_STATE_FAIL; + int smn_state; + + /* if FAIL or UNIMPLEMENTED, don't bother */ + if (scc_availability == SCC_STATUS_CHECKING || + scc_availability == SCC_STATUS_OK) { + + smn_status_register = SCC_READ_REGISTER(SMN_STATUS_REG); + smn_state = smn_status_register & SMN_STATUS_STATE_MASK; + +#ifdef SCC_BRINGUP + /* If in Health Check while booting, try to 'bringup' to Secure mode */ + if (scc_availability == SCC_STATUS_CHECKING && + smn_state == SMN_STATE_HEALTH_CHECK) { + /* Code up a simple algorithm for the ASC */ + SCC_WRITE_REGISTER(SMN_SEQUENCE_START, 0xaaaa); + SCC_WRITE_REGISTER(SMN_SEQUENCE_END, 0x5555); + SCC_WRITE_REGISTER(SMN_SEQUENCE_CHECK, 0x5555); + /* State should be SECURE now */ + smn_status_register = SCC_READ_REGISTER(SMN_STATUS); + smn_state = smn_status_register & SMN_STATUS_STATE_MASK; + } +#endif + + /* + * State should be SECURE or NON_SECURE for operation of the part. If + * FAIL, mark failed (i.e. limited access to registers). Any other + * state, mark unimplemented, as the SCC is unuseable. + */ + if (smn_state == SMN_STATE_SECURE + || smn_state == SMN_STATE_NON_SECURE) { + /* Healthy */ + scc_availability = SCC_STATUS_OK; + } else if (smn_state == SMN_STATE_FAIL) { + scc_availability = SCC_STATUS_FAILED; /* uh oh - unhealthy */ + scc_perform_callbacks(); + printk(KERN_ERR "SCC2: SCC went into FAILED mode\n"); + } else { + /* START, ZEROIZE RAM, HEALTH CHECK, or unknown */ + scc_availability = SCC_STATUS_UNIMPLEMENTED; /* unuseable */ + printk(KERN_ERR "SCC2: SCC declared UNIMPLEMENTED\n"); + } + } + /* if availability is initial or ok */ + return smn_status_register; +} + +/*****************************************************************************/ +/* fn scc_init_ccitt_crc() */ +/*****************************************************************************/ +/*! + * Populate the partial CRC lookup table. + * + * @return none + * + */ +static void scc_init_ccitt_crc(void) +{ + int dividend; /* index for lookup table */ + uint16_t remainder; /* partial value for a given dividend */ + int bit; /* index into bits of a byte */ + + /* + * Compute the remainder of each possible dividend. + */ + for (dividend = 0; dividend < 256; ++dividend) { + /* + * Start with the dividend followed by zeros. + */ + remainder = dividend << (8); + + /* + * Perform modulo-2 division, a bit at a time. + */ + for (bit = 8; bit > 0; --bit) { + /* + * Try to divide the current data bit. + */ + if (remainder & 0x8000) { + remainder = (remainder << 1) ^ CRC_POLYNOMIAL; + } else { + remainder = (remainder << 1); + } + } + + /* + * Store the result into the table. + */ + scc_crc_lookup_table[dividend] = remainder; + } + +} /* scc_init_ccitt_crc() */ + +/*****************************************************************************/ +/* fn grab_config_values() */ +/*****************************************************************************/ +/*! + * grab_config_values() will read the SCM Configuration and SMN Status + * registers and store away version and size information for later use. + * + * @return The current value of the SMN Status register. + */ +static uint32_t scc_grab_config_values(void) +{ + uint32_t scm_version_register; + uint32_t smn_status_register = SMN_STATE_FAIL; + + if (scc_availability != SCC_STATUS_CHECKING) { + goto out; + } + scm_version_register = SCC_READ_REGISTER(SCM_VERSION_REG); + pr_debug("SCC2 Driver: SCM version is 0x%08x\n", scm_version_register); + + /* Get SMN status and update scc_availability */ + smn_status_register = scc_update_state(); + pr_debug("SCC2 Driver: SMN status is 0x%08x\n", smn_status_register); + + /* save sizes and versions information for later use */ + scc_configuration.block_size_bytes = 16; /* BPCP ? */ + scc_configuration.partition_count = + 1 + ((scm_version_register & SCM_VER_NP_MASK) >> SCM_VER_NP_SHIFT); + scc_configuration.partition_size_bytes = + 1 << ((scm_version_register & SCM_VER_BPP_MASK) >> + SCM_VER_BPP_SHIFT); + scc_configuration.scm_version = + (scm_version_register & SCM_VER_MAJ_MASK) >> SCM_VER_MAJ_SHIFT; + scc_configuration.smn_version = + (smn_status_register & SMN_STATUS_VERSION_ID_MASK) + >> SMN_STATUS_VERSION_ID_SHIFT; + if (scc_configuration.scm_version != SCM_MAJOR_VERSION_2) { + scc_availability = SCC_STATUS_UNIMPLEMENTED; /* Unknown version */ + } + + out: + return smn_status_register; +} /* grab_config_values */ + +/*****************************************************************************/ +/* fn setup_interrupt_handling() */ +/*****************************************************************************/ +/*! + * Register the SCM and SMN interrupt handlers. + * + * Called from #scc_init() + * + * @return 0 on success + */ +static int setup_interrupt_handling(void) +{ + int smn_error_code = -1; + int scm_error_code = -1; + + /* Disnable SCM interrupts */ + SCC_WRITE_REGISTER(SCM_INT_CTL_REG, 0); + +#ifdef USE_SMN_INTERRUPT + /* Install interrupt service routine for SMN. */ + smn_error_code = request_irq(INT_SCC_SMN, scc_irq, 0, + SCC_DRIVER_NAME, NULL); + if (smn_error_code != 0) { + printk + ("SCC2 Driver: Error installing SMN Interrupt Handler: %d\n", + smn_error_code); + } else { + smn_irq_set = 1; /* remember this for cleanup */ + /* Enable SMN interrupts */ + SCC_WRITE_REGISTER(SMN_COMMAND_REG, + SMN_COMMAND_CLEAR_INTERRUPT | + SMN_COMMAND_ENABLE_INTERRUPT); + } +#else + smn_error_code = 0; /* no problems... will handle later */ +#endif + + /* + * Install interrupt service routine for SCM (or both together). + */ + scm_error_code = request_irq(INT_SCC_SCM, scc_irq, 0, + SCC_DRIVER_NAME, NULL); + if (scm_error_code != 0) { +#ifndef MXC + printk + ("SCC2 Driver: Error installing SCM Interrupt Handler: %d\n", + scm_error_code); +#else + printk + ("SCC2 Driver: Error installing SCC Interrupt Handler: %d\n", + scm_error_code); +#endif + } else { + scm_irq_set = 1; /* remember this for cleanup */ +#if defined(USE_SMN_INTERRUPT) && !defined(NO_SMN_INTERRUPT) + /* Enable SMN interrupts */ + SCC_WRITE_REGISTER(SMN_COMMAND_REG, + SMN_COMMAND_CLEAR_INTERRUPT | + SMN_COMMAND_ENABLE_INTERRUPT); +#endif + } + + /* Return an error if one was encountered */ + return scm_error_code ? scm_error_code : smn_error_code; +} /* setup_interrupt_handling */ + +/*****************************************************************************/ +/* fn scc_do_crypto() */ +/*****************************************************************************/ +/*! Have the SCM perform the crypto function. + * + * Set up length register, and the store @c scm_control into control register + * to kick off the operation. Wait for completion, gather status, clear + * interrupt / status. + * + * @param byte_count number of bytes to perform in this operation + * @param scm_command Bit values to be set in @c SCM_CCMD_REG register + * + * @return 0 on success, value of #SCM_ERROR_STATUS on failure + */ +static uint32_t scc_do_crypto(int byte_count, uint32_t scm_command) +{ + int block_count = byte_count / SCC_BLOCK_SIZE_BYTES(); + uint32_t crypto_status; + scc_return_t ret; + + /* In length register, 0 means 1, etc. */ + scm_command |= (block_count - 1) << SCM_CCMD_LENGTH_SHIFT; + + /* set modes and kick off the operation */ + SCC_WRITE_REGISTER(SCM_CCMD_REG, scm_command); + + ret = scc_wait_completion(&crypto_status); + + /* Only done bit should be on */ + if (crypto_status & SCM_STATUS_ERR) { + /* Replace with error status instead */ + crypto_status = SCC_READ_REGISTER(SCM_ERR_STATUS_REG); + pr_debug("SCM Failure: 0x%x\n", crypto_status); + if (crypto_status == 0) { + /* That came up 0. Turn on arbitrary bit to signal error. */ + crypto_status = SCM_ERRSTAT_ILM; + } + } else { + crypto_status = 0; + } + pr_debug("SCC2: Done waiting.\n"); + + return crypto_status; +} + +/*****************************************************************************/ +/* fn scc_encrypt() */ +/*****************************************************************************/ +/*! + * Perform an encryption on the input. If @c verify_crc is true, a CRC must be + * calculated on the plaintext, and appended, with padding, before computing + * the ciphertext. + * + * @param[in] count_in_bytes Count of bytes of plaintext + * @param[in] data_in Pointer to the plaintext + * @param[in] scm_control Bit values for the SCM_CONTROL register + * @param[in,out] data_out Pointer for storing ciphertext + * @param[in] add_crc Flag for computing CRC - 0 no, else yes + * @param[in,out] count_out_bytes Number of bytes available at @c data_out + */ +static scc_return_t +scc_encrypt(uint32_t count_in_bytes, uint8_t * data_in, uint32_t scm_control, + uint8_t * data_out, int add_crc, unsigned long *count_out_bytes) +{ + scc_return_t return_code = SCC_RET_FAIL; /* initialised for failure */ + uint32_t input_bytes_left = count_in_bytes; /* local copy */ + uint32_t output_bytes_copied = 0; /* running total */ + uint32_t bytes_to_process; /* multi-purpose byte counter */ + uint16_t crc = CRC_CCITT_START; /* running CRC value */ + crc_t *crc_ptr = NULL; /* Reset if CRC required */ + /* byte address into SCM RAM */ + uint32_t scm_location = (uint32_t) scm_red_part_virt; + uint32_t scm_bytes_remaining = scm_memory_size_bytes; /* free RED RAM */ + uint8_t padding_buffer[PADDING_BUFFER_MAX_BYTES]; /* CRC+padding holder */ + unsigned padding_byte_count = 0; /* Reset if padding required */ + uint32_t scm_error_status = 0; /* No known SCM error initially */ + + scm_control |= SCM_CCMD_ENC; + /* Set location of CRC and prepare padding bytes if required */ + if (add_crc != 0) { + crc_ptr = &crc; + padding_byte_count = SCC_BLOCK_SIZE_BYTES() + - (count_in_bytes + + CRC_SIZE_BYTES) % SCC_BLOCK_SIZE_BYTES(); + memcpy(padding_buffer + CRC_SIZE_BYTES, scc_block_padding, + padding_byte_count); + } + + /* Process remaining input or padding data */ + while (input_bytes_left > 0) { + /* Determine how much work to do this pass */ + bytes_to_process = (input_bytes_left > scm_bytes_remaining) ? + scm_bytes_remaining : input_bytes_left; + /* Copy plaintext into SCM RAM, calculating CRC if required */ + copy_to_scc(data_in, scm_location, bytes_to_process, crc_ptr); + /* Adjust pointers & counters */ + input_bytes_left -= bytes_to_process; + data_in += bytes_to_process; + scm_location += bytes_to_process; + scm_bytes_remaining -= bytes_to_process; + + /* Add CRC and padding after the last byte is copied if required */ + if ((input_bytes_left == 0) && (crc_ptr != NULL)) { + + /* Copy CRC into padding buffer MSB first */ + padding_buffer[0] = (crc >> 8) & 0xFF; + padding_buffer[1] = crc & 0xFF; + + /* Reset pointers and counter */ + data_in = padding_buffer; + input_bytes_left = CRC_SIZE_BYTES + padding_byte_count; + crc_ptr = NULL; /* CRC no longer required */ + + /* Go round loop again to copy CRC and padding to SCM */ + continue; + } + + /* if no input and crc_ptr */ + /* Now have block-sized plaintext in SCM to encrypt */ + /* Encrypt plaintext; exit loop on error */ + bytes_to_process = scm_location - (uint32_t) scm_red_part_virt; + + if (output_bytes_copied + bytes_to_process > *count_out_bytes) { + return_code = SCC_RET_INSUFFICIENT_SPACE; + scm_error_status = -1; /* error signal */ + pr_debug + ("SCC2: too many ciphertext bytes for space available\n"); + break; + } + pr_debug("SCC2: Starting encryption. %x for %d bytes (%p)\n", + scm_control, bytes_to_process, + (void *)SCC_READ_REGISTER(SCM_C_BLACK_ST_REG)); + scm_error_status = scc_do_crypto(bytes_to_process, scm_control); + if (scm_error_status != 0) { + break; + } + + /* Copy out ciphertext */ + copy_from_scc((uint32_t) scm_black_part_virt, data_out, + bytes_to_process, NULL); + + /* Adjust pointers and counters for next loop */ + output_bytes_copied += bytes_to_process; + data_out += bytes_to_process; + scm_location = (uint32_t) scm_red_part_virt; + scm_bytes_remaining = scm_memory_size_bytes; + } /* input_bytes_left > 0 */ + + /* If no SCM error, set OK status and save ouput byte count */ + if (scm_error_status == 0) { + return_code = SCC_RET_OK; + *count_out_bytes = output_bytes_copied; + } + + return return_code; +} /* scc_encrypt */ + +/*****************************************************************************/ +/* fn scc_decrypt() */ +/*****************************************************************************/ +/*! + * Perform a decryption on the input. If @c verify_crc is true, the last block + * (maybe the two last blocks) is special - it should contain a CRC and + * padding. These must be stripped and verified. + * + * @param[in] count_in_bytes Count of bytes of ciphertext + * @param[in] data_in Pointer to the ciphertext + * @param[in] scm_control Bit values for the SCM_CONTROL register + * @param[in,out] data_out Pointer for storing plaintext + * @param[in] verify_crc Flag for running CRC - 0 no, else yes + * @param[in,out] count_out_bytes Number of bytes available at @c data_out + + */ +static scc_return_t +scc_decrypt(uint32_t count_in_bytes, uint8_t * data_in, uint32_t scm_control, + uint8_t * data_out, int verify_crc, unsigned long *count_out_bytes) +{ + scc_return_t return_code = SCC_RET_FAIL; + uint32_t bytes_left = count_in_bytes; /* local copy */ + uint32_t bytes_copied = 0; /* running total of bytes going to user */ + uint32_t bytes_to_copy = 0; /* Number in this encryption 'chunk' */ + uint16_t crc = CRC_CCITT_START; /* running CRC value */ + /* next target for ctext */ + uint32_t scm_location = (uint32_t) scm_black_part_virt; + unsigned padding_byte_count; /* number of bytes of padding stripped */ + uint8_t last_two_blocks[2 * SCC_BLOCK_SIZE_BYTES()]; /* temp */ + uint32_t scm_error_status = 0; /* register value */ + + scm_control |= SCM_CCMD_DEC; + if (verify_crc) { + /* Save last two blocks (if there are at least two) of ciphertext for + special treatment. */ + bytes_left -= SCC_BLOCK_SIZE_BYTES(); + if (bytes_left >= SCC_BLOCK_SIZE_BYTES()) { + bytes_left -= SCC_BLOCK_SIZE_BYTES(); + } + } + + /* Copy ciphertext into SCM BLACK memory */ + while (bytes_left && scm_error_status == 0) { + + /* Determine how much work to do this pass */ + if (bytes_left > (scm_memory_size_bytes)) { + bytes_to_copy = scm_memory_size_bytes; + } else { + bytes_to_copy = bytes_left; + } + + if (bytes_copied + bytes_to_copy > *count_out_bytes) { + scm_error_status = -1; + break; + } + copy_to_scc(data_in, scm_location, bytes_to_copy, NULL); + data_in += bytes_to_copy; /* move pointer */ + + pr_debug("SCC2: Starting decryption of %d bytes.\n", + bytes_to_copy); + + /* Do the work, wait for completion */ + scm_error_status = scc_do_crypto(bytes_to_copy, scm_control); + + copy_from_scc((uint32_t) scm_red_part_virt, data_out, + bytes_to_copy, &crc); + bytes_copied += bytes_to_copy; + data_out += bytes_to_copy; + scm_location = (uint32_t) scm_black_part_virt; + + /* Do housekeeping */ + bytes_left -= bytes_to_copy; + + } /* while bytes_left */ + + /* At this point, either the process is finished, or this is verify mode */ + + if (scm_error_status == 0) { + if (!verify_crc) { + *count_out_bytes = bytes_copied; + return_code = SCC_RET_OK; + } else { + /* Verify mode. There are one or two blocks of unprocessed + * ciphertext sitting at data_in. They need to be moved to the + * SCM, decrypted, searched to remove padding, then the plaintext + * copied back to the user (while calculating CRC, of course). + */ + + /* Calculate ciphertext still left */ + bytes_to_copy = count_in_bytes - bytes_copied; + + copy_to_scc(data_in, scm_location, bytes_to_copy, NULL); + data_in += bytes_to_copy; /* move pointer */ + + pr_debug("SCC2: Finishing decryption (%d bytes).\n", + bytes_to_copy); + + /* Do the work, wait for completion */ + scm_error_status = + scc_do_crypto(bytes_to_copy, scm_control); + + if (scm_error_status == 0) { + /* Copy decrypted data back from SCM RED memory */ + copy_from_scc((uint32_t) scm_red_part_virt, + last_two_blocks, bytes_to_copy, + NULL); + + /* (Plaintext) + crc + padding should be in temp buffer */ + if (scc_strip_padding + (last_two_blocks + bytes_to_copy, + &padding_byte_count) == SCC_RET_OK) { + bytes_to_copy -= + padding_byte_count + CRC_SIZE_BYTES; + + /* verify enough space in user buffer */ + if (bytes_copied + bytes_to_copy <= + *count_out_bytes) { + int i = 0; + + /* Move out last plaintext and calc CRC */ + while (i < bytes_to_copy) { + CALC_CRC(last_two_blocks + [i], crc); + *data_out++ = + last_two_blocks + [i++]; + bytes_copied++; + } + + /* Verify the CRC by running over itself */ + CALC_CRC(last_two_blocks + [bytes_to_copy], crc); + CALC_CRC(last_two_blocks + [bytes_to_copy + 1], + crc); + if (crc == 0) { + /* Just fine ! */ + *count_out_bytes = + bytes_copied; + return_code = + SCC_RET_OK; + } else { + return_code = + SCC_RET_VERIFICATION_FAILED; + pr_debug + ("SCC2: CRC values are %04x, %02x%02x\n", + crc, + last_two_blocks + [bytes_to_copy], + last_two_blocks + [bytes_to_copy + + 1]); + } + } /* if space available */ + } /* if scc_strip_padding... */ + else { + /* bad padding means bad verification */ + return_code = + SCC_RET_VERIFICATION_FAILED; + } + } + /* scm_error_status == 0 */ + } /* verify_crc */ + } + + /* scm_error_status == 0 */ + return return_code; +} /* scc_decrypt */ + +/*****************************************************************************/ +/* fn scc_alloc_slot() */ +/*****************************************************************************/ +/*! + * Allocate a key slot to fit the requested size. + * + * @param value_size_bytes Size of the key or other secure data + * @param owner_id Value to tie owner to slot + * @param[out] slot Handle to access or deallocate slot + * + * @return SCC_RET_OK on success, SCC_RET_INSUFFICIENT_SPACE if not slots of + * requested size are available. + */ +scc_return_t +scc_alloc_slot(uint32_t value_size_bytes, uint64_t owner_id, uint32_t * slot) +{ + scc_return_t status = SCC_RET_FAIL; + unsigned long irq_flags; + + if (scc_availability != SCC_STATUS_OK) { + goto out; + } + /* ACQUIRE LOCK to prevent others from using SCC crypto */ + spin_lock_irqsave(&scc_crypto_lock, irq_flags); + + pr_debug("SCC2: Allocating %d-byte slot\n", value_size_bytes); + + if ((value_size_bytes != 0) && (value_size_bytes <= SCC_MAX_KEY_SIZE)) { + int i; + + for (i = 0; i < SCC_KEY_SLOTS; i++) { + if (scc_key_info[i].status == 0) { + scc_key_info[i].owner_id = owner_id; + scc_key_info[i].length = value_size_bytes; + scc_key_info[i].status = 1; /* assigned! */ + *slot = i; + status = SCC_RET_OK; + break; /* exit 'for' loop */ + } + } + if (status != SCC_RET_OK) { + status = SCC_RET_INSUFFICIENT_SPACE; + } else { + pr_debug("SCC2: Allocated slot %d\n", i); + } + } + + spin_unlock_irqrestore(&scc_crypto_lock, irq_flags); + + out: + return status; +} + +/*****************************************************************************/ +/* fn verify_slot_access() */ +/*****************************************************************************/ +inline static scc_return_t +verify_slot_access(uint64_t owner_id, uint32_t slot, uint32_t access_len) +{ + scc_return_t status = SCC_RET_FAIL; + + if (scc_availability != SCC_STATUS_OK) { + goto out; + } + + if ((slot < SCC_KEY_SLOTS) && scc_key_info[slot].status + && (scc_key_info[slot].owner_id == owner_id) + && (access_len <= SCC_KEY_SLOT_SIZE)) { + status = SCC_RET_OK; + pr_debug("SCC2: Verify on slot %d succeeded\n", slot); + } else { + if (slot >= SCC_KEY_SLOTS) { + pr_debug("SCC2: Verify on bad slot (%d) failed\n", + slot); + } else if (scc_key_info[slot].status) { + pr_debug("SCC2: Verify on slot %d failed (%Lx) \n", + slot, owner_id); + } else { + pr_debug + ("SC2C: Verify on slot %d failed: not allocated\n", + slot); + } + } + + out: + return status; +} + +/*****************************************************************************/ +/* fn scc_dealloc_slot() */ +/*****************************************************************************/ +scc_return_t scc_dealloc_slot(uint64_t owner_id, uint32_t slot) +{ + scc_return_t status; + unsigned long irq_flags; + uint8_t *slot_loc = NULL; + int i; + + /* ACQUIRE LOCK to prevent others from using SCC crypto */ + spin_lock_irqsave(&scc_crypto_lock, irq_flags); + + status = verify_slot_access(owner_id, slot, 0); + if (status != SCC_RET_OK) { + goto out; + } + + scc_key_info[slot].owner_id = 0; + scc_key_info[slot].status = 0; /* unassign */ + slot_loc = sahara_partition_base + scc_key_info[slot].offset; + + for (i = 0; i < SCC_KEY_SLOT_SIZE; i++) { + slot_loc[i] = 0; + } + pr_debug("SCC2: Deallocated slot %d\n", slot); + + out: + spin_unlock_irqrestore(&scc_crypto_lock, irq_flags); + + return status; +} + +/*****************************************************************************/ +/* fn scc_load_slot() */ +/*****************************************************************************/ +/*! + * Load a value into a slot. + * + * @param owner_id Value of owner of slot + * @param slot Handle of slot + * @param key_data Data to load into the slot + * @param key_length Length, in bytes, of @c key_data to copy to SCC. + * + * @return SCC_RET_OK on success. SCC_RET_FAIL will be returned if slot + * specified cannot be accessed for any reason, or SCC_RET_INSUFFICIENT_SPACE + * if @c key_length exceeds the size of the slot. + */ +scc_return_t +scc_load_slot(uint64_t owner_id, uint32_t slot, uint8_t * key_data, + uint32_t key_length) +{ + scc_return_t status; + unsigned long irq_flags; + + /* ACQUIRE LOCK to prevent others from using SCC crypto */ + spin_lock_irqsave(&scc_crypto_lock, irq_flags); + + status = verify_slot_access(owner_id, slot, key_length); + if ((status == SCC_RET_OK) && (key_data != NULL)) { + status = SCC_RET_FAIL; /* reset expectations */ + + if (key_length > SCC_KEY_SLOT_SIZE) { + pr_debug + ("SCC2: scc_load_slot() rejecting key of %d bytes.\n", + key_length); + status = SCC_RET_INSUFFICIENT_SPACE; + } else { + if (copy_to_scc(key_data, + (uint32_t) sahara_partition_base + + scc_key_info[slot].offset, key_length, + NULL)) { + pr_debug("SCC2: RED copy_to_scc() failed for" + " scc_load_slot()\n"); + } else { + status = SCC_RET_OK; + } + } + } + + spin_unlock_irqrestore(&scc_crypto_lock, irq_flags); + + return status; +} /* scc_load_slot */ + +/*****************************************************************************/ +/* fn scc_encrypt_slot() */ +/*****************************************************************************/ +/*! + * Allocate a key slot to fit the requested size. + * + * @param owner_id Value of owner of slot + * @param slot Handle of slot + * @param length Length, in bytes, of @c black_data + * @param black_data Location to store result of encrypting RED data in slot + * + * @return SCC_RET_OK on success, SCC_RET_FAIL if slot specified cannot be + * accessed for any reason. + */ +scc_return_t scc_encrypt_slot(uint64_t owner_id, uint32_t slot, + uint32_t length, uint8_t * black_data) +{ + unsigned long irq_flags; + scc_return_t status; + uint32_t crypto_status; + uint32_t scm_command = scc_key_info[slot].part_ctl; + + /* ACQUIRE LOCK to prevent others from using crypto or releasing slot */ + spin_lock_irqsave(&scc_crypto_lock, irq_flags); + + status = verify_slot_access(owner_id, slot, length); + if (status == SCC_RET_OK) { + SCC_WRITE_REGISTER(SCM_C_BLACK_ST_REG, scm_black_part_phys); + + /* Use OwnerID as CBC IV to tie Owner to data */ + SCC_WRITE_REGISTER(SCM_AES_CBC_IV0_REG, + *(uint32_t *) & owner_id); + SCC_WRITE_REGISTER(SCM_AES_CBC_IV1_REG, + *(((uint32_t *) & owner_id) + 1)); + SCC_WRITE_REGISTER(SCM_AES_CBC_IV2_REG, 0); + SCC_WRITE_REGISTER(SCM_AES_CBC_IV3_REG, 0); + + /* Set modes and kick off the encryption */ + crypto_status = + scc_do_crypto(length, scm_command | SCM_CCMD_AES_ENC_CBC); + + if (crypto_status != 0) { + pr_debug("SCM encrypt red crypto failure: 0x%x\n", + crypto_status); + } else { + + /* Give blob back to caller */ + if (!copy_from_scc + ((uint32_t) scm_black_part_virt, black_data, length, + NULL)) { + status = SCC_RET_OK; + pr_debug + ("SCC2: Encrypted slot %d for %d bytes\n", + slot, length); + } + } + } + + spin_unlock_irqrestore(&scc_crypto_lock, irq_flags); + + return status; +} + +/*****************************************************************************/ +/* fn scc_decrypt_slot() */ +/*****************************************************************************/ +/*! + * Decrypt some black data and leave result in the slot. + * + * @param owner_id Value of owner of slot + * @param slot Handle of slot + * @param length Length, in bytes, of @c black_data + * @param black_data Location of data to dencrypt and store in slot + * + * @return SCC_RET_OK on success, SCC_RET_FAIL if slot specified cannot be + * accessed for any reason. + */ +scc_return_t scc_decrypt_slot(uint64_t owner_id, uint32_t slot, + uint32_t length, const uint8_t * black_data) +{ + unsigned long irq_flags; + scc_return_t status; + uint32_t crypto_status; + uint32_t scm_command = scc_key_info[slot].part_ctl; + + /* ACQUIRE LOCK to prevent others from using crypto or releasing slot */ + spin_lock_irqsave(&scc_crypto_lock, irq_flags); + + status = verify_slot_access(owner_id, slot, length); + if (status == SCC_RET_OK) { + status = SCC_RET_FAIL; /* reset expectations */ + + /* Place black key in to BLACK RAM and set up the SCC */ + copy_to_scc(black_data, + (uint32_t) scm_black_part_virt, length, NULL); + + SCC_WRITE_REGISTER(SCM_C_BLACK_ST_REG, scm_black_part_phys); + + /* Use OwnerID as CBC IV to tie Owner to data */ + SCC_WRITE_REGISTER(SCM_AES_CBC_IV0_REG, + *(uint32_t *) & owner_id); + SCC_WRITE_REGISTER(SCM_AES_CBC_IV1_REG, + *(((uint32_t *) & owner_id) + 1)); + SCC_WRITE_REGISTER(SCM_AES_CBC_IV2_REG, 0); + SCC_WRITE_REGISTER(SCM_AES_CBC_IV3_REG, 0); + + /* Set modes and kick off the decryption */ + crypto_status = scc_do_crypto(length, + scm_command | + SCM_CCMD_AES_DEC_CBC); + + if (crypto_status != 0) { + pr_debug("SCM decrypt black crypto failure: 0x%x\n", + crypto_status); + } else { + status = SCC_RET_OK; + pr_debug("SCC2: Decrypted slot %d for %d bytes\n", slot, + length); + } + } + + spin_unlock_irqrestore(&scc_crypto_lock, irq_flags); + + return status; +} + +/*****************************************************************************/ +/* fn scc_get_slot_info() */ +/*****************************************************************************/ +/*! + * Determine address and value length for a give slot. + * + * @param owner_id Value of owner of slot + * @param slot Handle of slot + * @param address Location to store kernel address of slot data + * @param value_size_bytes Location to store allocated length of data in slot. + * May be NULL if value is not needed by caller. + * @param slot_size_bytes Location to store max length data in slot + * May be NULL if value is not needed by caller. + * + * @return SCC_RET_OK or error indication + */ +scc_return_t +scc_get_slot_info(uint64_t owner_id, uint32_t slot, uint32_t * address, + uint32_t * value_size_bytes, uint32_t * slot_size_bytes) +{ + scc_return_t status = verify_slot_access(owner_id, slot, 0); + + if (status == SCC_RET_OK) { + *address = sahara_partition_phys + scc_key_info[slot].offset; + if (value_size_bytes != NULL) { + *value_size_bytes = scc_key_info[slot].length; + } + if (slot_size_bytes != NULL) { + *slot_size_bytes = SCC_KEY_SLOT_SIZE; + } + } + + return status; +} + +/*! + * For now, this function will create a shared Sahara and SCC2 partition. It + * will be used as a key store for Sahara and, to mimic SCCv1 behavior, as the + * temporary black and red memories for ephemeral cipher operations. + * + * This means that it will not be secure against kernel access, and in fact + * must be available for host read/write. + * + * *========================================* + * * Key Slot 0 * + * * Key Slot 1 * + * * Key Slot ... * + * * Key Slot n * + * * -------------------------------------- * + * * * + * * 'BLACK RAM' * + * * * + * * -------------------------------------- * + * * * + * * 'RED RAM' * + * * * + * *========================================* + * + * or -- the BLACK RAM gets put in a separate partition... + */ +static +scc_return_t make_sahara_partition() +{ + unsigned part_no = SAHARA_PART_NO; /* better be free!! */ + int retval = -EIO; + uint32_t *part_base = + scm_ram_base + (part_no * scc_configuration.partition_size_bytes); + uint32_t reg_value; + + reg_value = SCC_READ_REGISTER(SCM_PART_OWNERS_REG); + + /* Store SMID to grab a partition */ + SCC_WRITE_REGISTER(SCM_SMID0_REG + 8 * part_no, 0x00000000); + mdelay(2); + + /* Now make sure it is ours... ? */ + reg_value = SCC_READ_REGISTER(SCM_PART_OWNERS_REG); + + if (((reg_value >> (2 * part_no)) & 0x3) != 3) { + printk(KERN_ERR "Could not acquire partition %u\n", part_no); + goto out; + } + sahara_partition_base = (uint8_t *) part_base; + + pr_debug("SCC2 Writing UMID at %p\n", part_base); + + /* Write in the UMID */ + part_base[4] = 0x42; + part_base[5] = 0x43; + part_base[6] = 0x19; + part_base[7] = 0x59; + + mdelay(2); + + /* Give both host and Sahara access for read/write */ + part_base[0] = + SCM_PERM_HD_WRITE | SCM_PERM_HD_READ | SCM_PERM_TH_READ | + SCM_PERM_TH_WRITE; + mdelay(2); + + reg_value = SCC_READ_REGISTER(SCM_PART_ENGAGED_REG); + + if (((reg_value >> part_no) & 1) != 1) { + printk(KERN_ERR "SCC2 Could not engage partition %u\n", + part_no); + retval = SCC_RET_FAIL; + goto out; + } +// (void)SCC_READ_REGISTER(SCM_ACC4_REG); + + sahara_partition_phys = + (uint32_t) scm_ram_phys_base + + (part_no * scc_configuration.partition_size_bytes); + + scm_black_part_virt = + sahara_partition_base + (SCC_KEY_SLOTS * SCC_KEY_SLOT_SIZE); + scm_black_part_phys = + sahara_partition_phys + (scm_black_part_virt - + sahara_partition_base); + + scm_memory_size_bytes = 256; + scm_red_part_virt = scm_black_part_virt + scm_memory_size_bytes; + if ((uint32_t) (scm_red_part_virt - sahara_partition_phys) < 256) { + printk(KERN_ERR + "SCC2: not enough space in Sahara partition: too many / too large keys\n"); + retval = SCC_RET_INSUFFICIENT_SPACE; + goto out; + } + + scm_red_part_cmd = (((part_no << SCM_CCMD_PART_SHIFT) + | ((scm_red_part_virt - sahara_partition_base) / + SCC_BLOCK_SIZE_BYTES()) + << SCM_CCMD_OFFSET_SHIFT) + | SCM_CCMD_AES); + + pr_debug + ("SCC2: Sahara partition %08x/%p; Black RAM: %08x/%p; Red RAM: %p\n", + sahara_partition_phys, sahara_partition_base, scm_black_part_phys, + scm_black_part_virt, scm_red_part_virt); + + retval = SCC_RET_OK; + + out: + return retval; +} /* make_sahara_partition() */ + +uint8_t make_vpu_partition() +{ + unsigned part_no = VPU_PART_NO; /* better be free!! */ + uint32_t *part_base = + scm_ram_base + (part_no * scc_configuration.partition_size_bytes); + uint32_t reg_value; + + reg_value = SCC_READ_REGISTER(SCM_PART_OWNERS_REG); + + /* Store SMID to grab a partition */ + for (; part_no < 4; part_no++) + SCC_WRITE_REGISTER(SCM_SMID0_REG + 8 * part_no, 0x00000000); + + mdelay(2); + + /* Now make sure it is ours... ? */ + reg_value = SCC_READ_REGISTER(SCM_PART_OWNERS_REG); + for (; part_no < 4; part_no++) { + if (((reg_value >> (2 * part_no)) & 0x3) != 3) { + printk(KERN_ERR "Could not acquire partition %u\n", + part_no); + goto out; + } + } + vpu_partition_base = (uint8_t *) part_base; + + mdelay(2); + for (; part_no < 4; part_no++) { + part_base = + scm_ram_base + + (part_no * scc_configuration.partition_size_bytes); + part_base[0] = + SCM_PERM_HD_WRITE | SCM_PERM_HD_READ | SCM_PERM_TH_READ | + SCM_PERM_TH_WRITE; + } + mdelay(2); + + reg_value = SCC_READ_REGISTER(SCM_PART_ENGAGED_REG); + + if (((reg_value >> part_no) & 1) != 1) { + printk(KERN_ERR "SCC2 Could not engage partition %u\n", + part_no); + goto out; + } + part_no = VPU_PART_NO; + vpu_partition_phys = + (uint32_t) scm_ram_phys_base + + (part_no * scc_configuration.partition_size_bytes); + + return vpu_partition_phys; + + out: + return 0; +} /* make_vpu_partition */ + +/*****************************************************************************/ +/* fn scc_wait_completion() */ +/*****************************************************************************/ +/*! + * Poll looking for end-of-cipher indication. Only used + * if @c SCC_SCM_SLEEP is not defined. + * + * @internal + * + * On a Tahiti, crypto under 230 or so bytes is done after the first loop, all + * the way up to five sets of spins for 1024 bytes. (8- and 16-byte functions + * are done when we first look. Zeroizing takes one pass around. + */ +static scc_return_t scc_wait_completion(uint32_t * scm_status) +{ + scc_return_t ret; + int done; + int i = 0; + + /* check for completion by polling */ + do { + done = is_cipher_done(scm_status); + if (done) + break; + udelay(1000); + } while (i++ < SCC_CIPHER_MAX_POLL_COUNT); + + pr_debug("SCC2: Polled DONE %d times\n", i); + if (!done) { + ret = SCC_RET_FAIL; + } + + return ret; +} /* scc_wait_completion() */ + +/*****************************************************************************/ +/* fn is_cipher_done() */ +/*****************************************************************************/ +/*! + * This function returns non-zero if SCM Status register indicates + * that a cipher has terminated or some other interrupt-generating + * condition has occurred. + */ +static int is_cipher_done(uint32_t * scm_status) +{ + register unsigned status; + register int cipher_done; + + *scm_status = SCC_READ_REGISTER(SCM_STATUS_REG); + status = (*scm_status & SCM_STATUS_SRS_MASK) >> SCM_STATUS_SRS_SHIFT; + + /* + * Done when SCM is not in 'currently performing a function' states. + */ + cipher_done = ((status != SCM_STATUS_SRS_ZBUSY) + && (status != SCM_STATUS_SRS_CBUSY) + && (status != SCM_STATUS_SRS_ABUSY)); + + return cipher_done; +} /* is_cipher_done() */ + +/*****************************************************************************/ +/* fn offset_within_smn() */ +/*****************************************************************************/ +/*! + * Check that the offset is with the bounds of the SMN register set. + * + * @param[in] register_offset register offset of SMN. + * + * @return 1 if true, 0 if false (not within SMN) + */ +static inline int offset_within_smn(uint32_t register_offset) +{ + return ((register_offset >= SMN_STATUS_REG) + && (register_offset <= SMN_HAC_REG)); +} + +/*****************************************************************************/ +/* fn offset_within_scm() */ +/*****************************************************************************/ +/*! + * Check that the offset is with the bounds of the SCM register set. + * + * @param[in] register_offset Register offset of SCM + * + * @return 1 if true, 0 if false (not within SCM) + */ +static inline int offset_within_scm(uint32_t register_offset) +{ + return 1; /* (register_offset >= SCM_RED_START) + && (register_offset < scm_highest_memory_address); */ + /* Although this would cause trouble for zeroize testing, this change would + * close a security whole which currently allows any kernel program to access + * any location in RED RAM. Perhaps enforce in non-SCC_DEBUG compiles? + && (register_offset <= SCM_INIT_VECTOR_1); */ +} + +/*****************************************************************************/ +/* fn check_register_accessible() */ +/*****************************************************************************/ +/*! + * Given the current SCM and SMN status, verify that access to the requested + * register should be OK. + * + * @param[in] register_offset register offset within SCC + * @param[in] smn_status recent value from #SMN_STATUS + * @param[in] scm_status recent value from #SCM_STATUS + * + * @return #SCC_RET_OK if ok, #SCC_RET_FAIL if not + */ +static scc_return_t +check_register_accessible(uint32_t register_offset, uint32_t smn_status, + uint32_t scm_status) +{ + int error_code = SCC_RET_FAIL; + + /* Verify that the register offset passed in is not among the verboten set + * if the SMN is in Fail mode. + */ + if (offset_within_smn(register_offset)) { + if ((smn_status & SMN_STATUS_STATE_MASK) == SMN_STATE_FAIL) { + if (!((register_offset == SMN_STATUS_REG) || + (register_offset == SMN_COMMAND_REG) || + (register_offset == SMN_SEC_VIO_REG))) { + pr_debug + ("SCC2 Driver: Note: Security State is in FAIL state.\n"); + } /* register not a safe one */ + else { + /* SMN is in FAIL, but register is a safe one */ + error_code = SCC_RET_OK; + } + } /* State is FAIL */ + else { + /* State is not fail. All registers accessible. */ + error_code = SCC_RET_OK; + } + } + /* offset within SMN */ + /* Not SCM register. Check for SCM busy. */ + else if (offset_within_scm(register_offset)) { + /* This is the 'cannot access' condition in the SCM */ + if (0 /* (scm_status & SCM_STATUS_BUSY) */ + /* these are always available - rest fail on busy */ + && !((register_offset == SCM_STATUS_REG) || + (register_offset == SCM_ERR_STATUS_REG) || + (register_offset == SCM_INT_CTL_REG) || + (register_offset == SCM_VERSION_REG))) { + pr_debug + ("SCC2 Driver: Note: Secure Memory is in BUSY state.\n"); + } /* status is busy & register inaccessible */ + else { + error_code = SCC_RET_OK; + } + } + /* offset within SCM */ + return error_code; + +} /* check_register_accessible() */ + +/*****************************************************************************/ +/* fn check_register_offset() */ +/*****************************************************************************/ +/*! + * Check that the offset is with the bounds of the SCC register set. + * + * @param[in] register_offset register offset of SMN. + * + * #SCC_RET_OK if ok, #SCC_RET_FAIL if not + */ +static scc_return_t check_register_offset(uint32_t register_offset) +{ + int return_value = SCC_RET_FAIL; + + /* Is it valid word offset ? */ + if (SCC_BYTE_OFFSET(register_offset) == 0) { + /* Yes. Is register within SCM? */ + if (offset_within_scm(register_offset)) { + return_value = SCC_RET_OK; /* yes, all ok */ + } + /* Not in SCM. Now look within the SMN */ + else if (offset_within_smn(register_offset)) { + return_value = SCC_RET_OK; /* yes, all ok */ + } + } + + return return_value; +} + +#ifdef SCC_REGISTER_DEBUG + +/*! + * Names of the SCC Registers, indexed by register number + */ +static char *scc_regnames[] = { + "SCM_VERSION_REG", + "0x04", + "SCM_INT_CTL_REG", + "SCM_STATUS_REG", + "SCM_ERR_STATUS_REG", + "SCM_FAULT_ADR_REG", + "SCM_PART_OWNERS_REG", + "SCM_PART_ENGAGED_REG", + "SCM_UNIQUE_ID0_REG", + "SCM_UNIQUE_ID1_REG", + "SCM_UNIQUE_ID2_REG", + "SCM_UNIQUE_ID3_REG", + "0x30", + "0x34", + "0x38", + "0x3C", + "0x40", + "0x44", + "0x48", + "0x4C", + "SCM_ZCMD_REG", + "SCM_CCMD_REG", + "SCM_C_BLACK_ST_REG", + "SCM_DBG_STATUS_REG", + "SCM_AES_CBC_IV0_REG", + "SCM_AES_CBC_IV1_REG", + "SCM_AES_CBC_IV2_REG", + "SCM_AES_CBC_IV3_REG", + "0x70", + "0x74", + "0x78", + "0x7C", + "SCM_SMID0_REG", + "SCM_ACC0_REG", + "SCM_SMID1_REG", + "SCM_ACC1_REG", + "SCM_SMID2_REG", + "SCM_ACC2_REG", + "SCM_SMID3_REG", + "SCM_ACC3_REG", + "SCM_SMID4_REG", + "SCM_ACC4_REG", + "SCM_SMID5_REG", + "SCM_ACC5_REG", + "SCM_SMID6_REG", + "SCM_ACC6_REG", + "SCM_SMID7_REG", + "SCM_ACC7_REG", + "SCM_SMID8_REG", + "SCM_ACC8_REG", + "SCM_SMID9_REG", + "SCM_ACC9_REG", + "SCM_SMID10_REG", + "SCM_ACC10_REG", + "SCM_SMID11_REG", + "SCM_ACC11_REG", + "SCM_SMID12_REG", + "SCM_ACC12_REG", + "SCM_SMID13_REG", + "SCM_ACC13_REG", + "SCM_SMID14_REG", + "SCM_ACC14_REG", + "SCM_SMID15_REG", + "SCM_ACC15_REG", + "SMN_STATUS_REG", + "SMN_COMMAND_REG", + "SMN_SEQ_START_REG", + "SMN_SEQ_END_REG", + "SMN_SEQ_CHECK_REG", + "SMN_BB_CNT_REG", + "SMN_BB_INC_REG", + "SMN_BB_DEC_REG", + "SMN_COMPARE_REG", + "SMN_PT_CHK_REG", + "SMN_CT_CHK_REG", + "SMN_TIMER_IV_REG", + "SMN_TIMER_CTL_REG", + "SMN_SEC_VIO_REG", + "SMN_TIMER_REG", + "SMN_HAC_REG" +}; + +/*! + * Names of the Secure RAM States + */ +static char *srs_names[] = { + "SRS_Reset", + "SRS_All_Ready", + "SRS_ZeroizeBusy", + "SRS_CipherBusy", + "SRS_AllBusy", + "SRS_ZeroizeDoneCipherReady", + "SRS_CipherDoneZeroizeReady", + "SRS_ZeroizeDoneCipherBusy", + "SRS_CipherDoneZeroizeBusy", + "SRS_UNKNOWN_STATE_9", + "SRS_TransitionalA", + "SRS_TransitionalB", + "SRS_TransitionalC", + "SRS_TransitionalD", + "SRS_AllDone", + "SRS_UNKNOWN_STATE_E", + "SRS_FAIL" +}; + +/*! + * Create a text interpretation of the SCM Version Register + * + * @param value The value of the register + * @param[out] print_buffer Place to store the interpretation + * @param buf_size Number of bytes available at print_buffer + * + * @return The print_buffer + */ +static +char *scm_print_version_reg(uint32_t value, char *print_buffer, int buf_size) +{ + snprintf(print_buffer, buf_size, + "Bpp: %u, Bpcb: %u, np: %u, maj: %u, min: %u", + (value & SCM_VER_BPP_MASK) >> SCM_VER_BPP_SHIFT, + ((value & SCM_VER_BPCB_MASK) >> SCM_VER_BPCB_SHIFT) + 1, + ((value & SCM_VER_NP_MASK) >> SCM_VER_NP_SHIFT) + 1, + (value & SCM_VER_MAJ_MASK) >> SCM_VER_MAJ_SHIFT, + (value & SCM_VER_MIN_MASK) >> SCM_VER_MIN_SHIFT); + + return print_buffer; +} + +/*! + * Create a text interpretation of the SCM Status Register + * + * @param value The value of the register + * @param[out] print_buffer Place to store the interpretation + * @param buf_size Number of bytes available at print_buffer + * + * @return The print_buffer + */ +static +char *scm_print_status_reg(uint32_t value, char *print_buffer, int buf_size) +{ + + snprintf(print_buffer, buf_size, "%s%s%s%s%s%s%s%s%s%s%s%s%s", + (value & SCM_STATUS_KST_DEFAULT_KEY) ? "KST_DefaultKey " : "", + /* reserved */ + (value & SCM_STATUS_KST_WRONG_KEY) ? "KST_WrongKey " : "", + (value & SCM_STATUS_KST_BAD_KEY) ? "KST_BadKey " : "", + (value & SCM_STATUS_ERR) ? "Error " : "", + (value & SCM_STATUS_MSS_FAIL) ? "MSS_FailState " : "", + (value & SCM_STATUS_MSS_SEC) ? "MSS_SecureState " : "", + (value & SCM_STATUS_RSS_FAIL) ? "RSS_FailState " : "", + (value & SCM_STATUS_RSS_SEC) ? "RSS_SecureState " : "", + (value & SCM_STATUS_RSS_INIT) ? "RSS_Initializing " : "", + (value & SCM_STATUS_UNV) ? "UID_Invalid " : "", + (value & SCM_STATUS_BIG) ? "BigEndian " : "", + (value & SCM_STATUS_USK) ? "SecretKey " : "", + srs_names[(value & SCM_STATUS_SRS_MASK) >> + SCM_STATUS_SRS_SHIFT]); + + return print_buffer; +} + +/*! + * Names of the SCM Error Codes + */ +static +char *scm_err_code[] = { + "Unknown_0", + "UnknownAddress", + "UnknownCommand", + "ReadPermErr", + "WritePermErr", + "DMAErr", + "EncBlockLenOvfl", + "KeyNotEngaged", + "ZeroizeCmdQOvfl", + "CipherCmdQOvfl", + "ProcessIntr", + "WrongKey", + "DeviceBusy", + "DMAUnalignedAddr", + "Unknown_E", + "Unknown_F", +}; + +/*! + * Names of the SMN States + */ +static char *smn_state_name[] = { + "Start", + "Invalid_01", + "Invalid_02", + "Invalid_03", + "Zeroizing_04", + "Zeroizing", + "HealthCheck", + "HealthCheck_07", + "Invalid_08", + "Fail", + "Secure", + "Invalid_0B", + "NonSecure", + "Invalid_0D", + "Invalid_0E", + "Invalid_0F", + "Invalid_10", + "Invalid_11", + "Invalid_12", + "Invalid_13", + "Invalid_14", + "Invalid_15", + "Invalid_16", + "Invalid_17", + "Invalid_18", + "FailHard", + "Invalid_1A", + "Invalid_1B", + "Invalid_1C", + "Invalid_1D", + "Invalid_1E", + "Invalid_1F" +}; + +/*! + * Create a text interpretation of the SCM Error Status Register + * + * @param value The value of the register + * @param[out] print_buffer Place to store the interpretation + * @param buf_size Number of bytes available at print_buffer + * + * @return The print_buffer + */ +static +char *scm_print_err_status_reg(uint32_t value, char *print_buffer, int buf_size) +{ + snprintf(print_buffer, buf_size, + "MID: 0x%x, %s%s ErrorCode: %s, SMSState: %s, SCMState: %s", + (value & SCM_ERRSTAT_MID_MASK) >> SCM_ERRSTAT_MID_SHIFT, + (value & SCM_ERRSTAT_ILM) ? "ILM, " : "", + (value & SCM_ERRSTAT_SUP) ? "SUP, " : "", + scm_err_code[(value & SCM_ERRSTAT_ERC_MASK) >> + SCM_ERRSTAT_ERC_SHIFT], + smn_state_name[(value & SCM_ERRSTAT_SMS_MASK) >> + SCM_ERRSTAT_SMS_SHIFT], + srs_names[(value & SCM_ERRSTAT_SRS_MASK) >> + SCM_ERRSTAT_SRS_SHIFT]); + return print_buffer; +} + +/*! + * Create a text interpretation of the SCM Zeroize Command Register + * + * @param value The value of the register + * @param[out] print_buffer Place to store the interpretation + * @param buf_size Number of bytes available at print_buffer + * + * @return The print_buffer + */ +static +char *scm_print_zcmd_reg(uint32_t value, char *print_buffer, int buf_size) +{ + unsigned cmd = (value & SCM_ZCMD_CCMD_MASK) >> SCM_CCMD_CCMD_SHIFT; + + snprintf(print_buffer, buf_size, "%s %u", + (cmd == + ZCMD_DEALLOC_PART) ? "DeallocPartition" : + "(unknown function)", + (value & SCM_ZCMD_PART_MASK) >> SCM_ZCMD_PART_SHIFT); + + return print_buffer; +} + +/*! + * Create a text interpretation of the SCM Cipher Command Register + * + * @param value The value of the register + * @param[out] print_buffer Place to store the interpretation + * @param buf_size Number of bytes available at print_buffer + * + * @return The print_buffer + */ +static +char *scm_print_ccmd_reg(uint32_t value, char *print_buffer, int buf_size) +{ + unsigned cmd = (value & SCM_CCMD_CCMD_MASK) >> SCM_CCMD_CCMD_SHIFT; + + snprintf(print_buffer, buf_size, + "%s %u bytes, %s offset 0x%x, in partition %u", + (cmd == SCM_CCMD_AES_DEC_ECB) ? "ECB Decrypt" : (cmd == + SCM_CCMD_AES_ENC_ECB) + ? "ECB Encrypt" : (cmd == + SCM_CCMD_AES_DEC_CBC) ? "CBC Decrypt" : (cmd + == + SCM_CCMD_AES_ENC_CBC) + ? "CBC Encrypt" : "(unknown function)", + 16 + + 16 * ((value & SCM_CCMD_LENGTH_MASK) >> SCM_CCMD_LENGTH_SHIFT), + ((cmd == SCM_CCMD_AES_ENC_CBC) + || (cmd == SCM_CCMD_AES_ENC_ECB)) ? "at" : "to", + 16 * ((value & SCM_CCMD_OFFSET_MASK) >> SCM_CCMD_OFFSET_SHIFT), + (value & SCM_CCMD_PART_MASK) >> SCM_CCMD_PART_SHIFT); + + return print_buffer; +} + +/*! + * Create a text interpretation of an SCM Access Permissions Register + * + * @param value The value of the register + * @param[out] print_buffer Place to store the interpretation + * @param buf_size Number of bytes available at print_buffer + * + * @return The print_buffer + */ +static +char *scm_print_acc_reg(uint32_t value, char *print_buffer, int buf_size) +{ + snprintf(print_buffer, buf_size, "%s%s%s%s%s%s%s%s%s%s", + (value & SCM_PERM_NO_ZEROIZE) ? "NO_ZERO " : "", + (value & SCM_PERM_HD_SUP_DISABLE) ? "SUP_DIS " : "", + (value & SCM_PERM_HD_READ) ? "HD_RD " : "", + (value & SCM_PERM_HD_WRITE) ? "HD_WR " : "", + (value & SCM_PERM_HD_EXECUTE) ? "HD_EX " : "", + (value & SCM_PERM_TH_READ) ? "TH_RD " : "", + (value & SCM_PERM_TH_WRITE) ? "TH_WR " : "", + (value & SCM_PERM_OT_READ) ? "OT_RD " : "", + (value & SCM_PERM_OT_WRITE) ? "OT_WR " : "", + (value & SCM_PERM_OT_EXECUTE) ? "OT_EX" : ""); + + return print_buffer; +} + +/*! + * Create a text interpretation of the SCM Partitions Engaged Register + * + * @param value The value of the register + * @param[out] print_buffer Place to store the interpretation + * @param buf_size Number of bytes available at print_buffer + * + * @return The print_buffer + */ +static +char *scm_print_part_eng_reg(uint32_t value, char *print_buffer, int buf_size) +{ + snprintf(print_buffer, buf_size, "%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s", + (value & 0x8000) ? "15 " : "", + (value & 0x4000) ? "14 " : "", + (value & 0x2000) ? "13 " : "", + (value & 0x1000) ? "12 " : "", + (value & 0x0800) ? "11 " : "", + (value & 0x0400) ? "10 " : "", + (value & 0x0200) ? "9 " : "", + (value & 0x0100) ? "8 " : "", + (value & 0x0080) ? "7 " : "", + (value & 0x0040) ? "6 " : "", + (value & 0x0020) ? "5 " : "", + (value & 0x0010) ? "4 " : "", + (value & 0x0008) ? "3 " : "", + (value & 0x0004) ? "2 " : "", + (value & 0x0002) ? "1 " : "", (value & 0x0001) ? "0" : ""); + + return print_buffer; +} + +/*! + * Create a text interpretation of the SMN Status Register + * + * @param value The value of the register + * @param[out] print_buffer Place to store the interpretation + * @param buf_size Number of bytes available at print_buffer + * + * @return The print_buffer + */ +static +char *smn_print_status_reg(uint32_t value, char *print_buffer, int buf_size) +{ + snprintf(print_buffer, buf_size, + "Version %d %s%s%s%s%s%s%s%s%s%s%s%s%s", + (value & SMN_STATUS_VERSION_ID_MASK) >> + SMN_STATUS_VERSION_ID_SHIFT, + (value & SMN_STATUS_ILLEGAL_MASTER) ? "IllMaster " : "", + (value & SMN_STATUS_SCAN_EXIT) ? "ScanExit " : "", + (value & SMN_STATUS_PERIP_INIT) ? "PeripInit " : "", + (value & SMN_STATUS_SMN_ERROR) ? "SMNError " : "", + (value & SMN_STATUS_SOFTWARE_ALARM) ? "SWAlarm " : "", + (value & SMN_STATUS_TIMER_ERROR) ? "TimerErr " : "", + (value & SMN_STATUS_PC_ERROR) ? "PTCTErr " : "", + (value & SMN_STATUS_BITBANK_ERROR) ? "BitbankErr " : "", + (value & SMN_STATUS_ASC_ERROR) ? "ASCErr " : "", + (value & SMN_STATUS_SECURITY_POLICY_ERROR) ? "SecPlcyErr " : + "", + (value & SMN_STATUS_SEC_VIO_ACTIVE_ERROR) ? "SecVioAct " : "", + (value & SMN_STATUS_INTERNAL_BOOT) ? "IntBoot " : "", + smn_state_name[(value & SMN_STATUS_STATE_MASK) >> + SMN_STATUS_STATE_SHIFT]); + + return print_buffer; +} + +/*! + * The array, indexed by register number (byte-offset / 4), of print routines + * for the SCC (SCM and SMN) registers. + */ +static reg_print_routine_t reg_printers[] = { + scm_print_version_reg, + NULL, //"0x04", + NULL, //"SCM_INT_CTL_REG", + scm_print_status_reg, + scm_print_err_status_reg, + NULL, //"SCM_FAULT_ADR_REG", + NULL, //"SCM_PART_OWNERS_REG", + scm_print_part_eng_reg, + NULL, //"SCM_UNIQUE_ID0_REG", + NULL, //"SCM_UNIQUE_ID1_REG", + NULL, //"SCM_UNIQUE_ID2_REG", + NULL, //"SCM_UNIQUE_ID3_REG", + NULL, //"0x30", + NULL, //"0x34", + NULL, //"0x38", + NULL, //"0x3C", + NULL, //"0x40", + NULL, //"0x44", + NULL, //"0x48", + NULL, //"0x4C", + scm_print_zcmd_reg, + scm_print_ccmd_reg, + NULL, //"SCM_C_BLACK_ST_REG", + NULL, //"SCM_DBG_STATUS_REG", + NULL, //"SCM_AES_CBC_IV0_REG", + NULL, //"SCM_AES_CBC_IV1_REG", + NULL, //"SCM_AES_CBC_IV2_REG", + NULL, //"SCM_AES_CBC_IV3_REG", + NULL, //"0x70", + NULL, //"0x74", + NULL, //"0x78", + NULL, //"0x7C", + NULL, //"SCM_SMID0_REG", + scm_print_acc_reg, /* ACC0 */ + NULL, //"SCM_SMID1_REG", + scm_print_acc_reg, /* ACC1 */ + NULL, //"SCM_SMID2_REG", + scm_print_acc_reg, /* ACC2 */ + NULL, //"SCM_SMID3_REG", + scm_print_acc_reg, /* ACC3 */ + NULL, //"SCM_SMID4_REG", + scm_print_acc_reg, /* ACC4 */ + NULL, //"SCM_SMID5_REG", + scm_print_acc_reg, /* ACC5 */ + NULL, //"SCM_SMID6_REG", + scm_print_acc_reg, /* ACC6 */ + NULL, //"SCM_SMID7_REG", + scm_print_acc_reg, /* ACC7 */ + NULL, //"SCM_SMID8_REG", + scm_print_acc_reg, /* ACC8 */ + NULL, //"SCM_SMID9_REG", + scm_print_acc_reg, /* ACC9 */ + NULL, //"SCM_SMID10_REG", + scm_print_acc_reg, /* ACC10 */ + NULL, //"SCM_SMID11_REG", + scm_print_acc_reg, /* ACC11 */ + NULL, //"SCM_SMID12_REG", + scm_print_acc_reg, /* ACC12 */ + NULL, //"SCM_SMID13_REG", + scm_print_acc_reg, /* ACC13 */ + NULL, //"SCM_SMID14_REG", + scm_print_acc_reg, /* ACC14 */ + NULL, //"SCM_SMID15_REG", + scm_print_acc_reg, /* ACC15 */ + smn_print_status_reg, + NULL, //"SMN_COMMAND_REG", + NULL, //"SMN_SEQ_START_REG", + NULL, //"SMN_SEQ_END_REG", + NULL, //"SMN_SEQ_CHECK_REG", + NULL, //"SMN_BB_CNT_REG", + NULL, //"SMN_BB_INC_REG", + NULL, //"SMN_BB_DEC_REG", + NULL, //"SMN_COMPARE_REG", + NULL, //"SMN_PT_CHK_REG", + NULL, //"SMN_CT_CHK_REG", + NULL, //"SMN_TIMER_IV_REG", + NULL, //"SMN_TIMER_CTL_REG", + NULL, //"SMN_SEC_VIO_REG", + NULL, //"SMN_TIMER_REG", + NULL, //"SMN_HAC_REG" +}; + +/*****************************************************************************/ +/* fn dbg_scc_read_register() */ +/*****************************************************************************/ +/*! + * Noisily read a 32-bit value to an SCC register. + * @param offset The address of the register to read. + * + * @return The register value + * */ +uint32_t dbg_scc_read_register(uint32_t offset) +{ + uint32_t value; + char *regname = scc_regnames[offset / 4]; + + value = __raw_readl(scc_base + offset); + pr_debug("SCC2 RD: 0x%03x : 0x%08x (%s) %s\n", offset, value, regname, + reg_printers[offset / 4] + ? reg_printers[offset / 4] (value, reg_print_buffer, + REG_PRINT_BUFFER_SIZE) + : ""); + + return value; +} + +/*****************************************************************************/ +/* fn dbg_scc_write_register() */ +/*****************************************************************************/ +/* + * Noisily read a 32-bit value to an SCC register. + * @param offset The address of the register to written. + * + * @param value The new register value + */ +void dbg_scc_write_register(uint32_t offset, uint32_t value) +{ + char *regname = scc_regnames[offset / 4]; + + pr_debug("SCC2 WR: 0x%03x : 0x%08x (%s) %s\n", offset, value, regname, + reg_printers[offset / 4] + ? reg_printers[offset / 4] (value, reg_print_buffer, + REG_PRINT_BUFFER_SIZE) + : ""); + (void)__raw_writel(value, scc_base + offset); +} + +#endif /* SCC_REGISTER_DEBUG */ diff --git a/drivers/mxc/security/scc2_internals.h b/drivers/mxc/security/scc2_internals.h new file mode 100644 index 000000000000..90a32eb57397 --- /dev/null +++ b/drivers/mxc/security/scc2_internals.h @@ -0,0 +1,512 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef SCC_INTERNALS_H +#define SCC_INTERNALS_H + +/** @file scc_internals.h + * + * @brief This is intended to be the file which contains most or all of the + * code or changes need to port the driver. It also includes other definitions + * needed by the driver. + * + * This header file should only ever be included by scc_driver.c + * + * Compile-time flags minimally needed: + * + * @li Some sort of platform flag. Currently TAHITI and MXC are understood. + * @li Some start-of-SCC consideration, such as SCC_BASE_ADDR + * + * Some changes which could be made when porting this driver: + * #SCC_SPIN_COUNT + * + */ + +#include <linux/version.h> /* Current version Linux kernel */ +#include <linux/module.h> /* Basic support for loadable modules, + printk */ +#include <linux/init.h> /* module_init, module_exit */ +#include <linux/kernel.h> /* General kernel system calls */ +#include <linux/sched.h> /* for interrupt.h */ +#include <linux/spinlock.h> + +#include <asm/io.h> /* ioremap() */ +#include <linux/interrupt.h> /* IRQ / interrupt definitions */ + + +#include <asm/arch/mxc_scc2_driver.h> + +#if defined(MXC) + +#include <asm/arch/iim.h> +#include <asm/arch/mxc_scc.h> + + +/** + * This macro is used to determine whether the SCC is enabled/available + * on the platform. This macro may need to be ported. + */ +#define SCC_FUSE IO_ADDRESS(IIM_BASE_ADDR + MXC_IIMHWV1) +#define SCC_ENABLED() ((SCC_FUSE & MXC_IIMHWV1_SCC_DISABLE) == 0) + +#else /* neither TAHITI nor MXC */ + +#error Do not understand target architecture + +#endif /* TAHITI */ +/** + * Define the number of Stored Keys which the SCC driver will make available. + * Value shall be from 0 to 20. Default is zero (0). + */ +#define SCC_KEY_SLOTS 20 + + +/* Temporarily define compile-time flags to make Doxygen happy. */ +#ifdef DOXYGEN_HACK +/** @addtogroup scccompileflags */ +/** @{ */ + + +/** @def NO_SMN_INTERRUPT + * The SMN interrupt is not wired to the CPU at all. + */ +#define NO_SMN_INTERRUPT + + +/** + * Register an interrupt handler for the SMN as well as + * the SCM. In some implementations, the SMN is not connected at all (see + * #NO_SMN_INTERRUPT), and in others, it is on the same interrupt line as the + * SCM. When defining this flag, the SMN interrupt should be on a separate + * line from the SCM interrupt. + */ + +#define USE_SMN_INTERRUPT + + +/** + * Turn on generation of run-time operational, debug, and error messages + */ +#define SCC_DEBUG + + +/** + * Turn on generation of run-time logging of access to the SCM and SMN + * registers. + */ +#define SCC_REGISTER_DEBUG + + +/** + * Turn on generation of run-time logging of access to the SCM Red and + * Black memories. Will only work if #SCC_REGISTER_DEBUG is also defined. + */ +#define SCC_RAM_DEBUG + + +/** + * If the driver finds the SCC in HEALTH_CHECK state, go ahead and + * run a quick ASC to bring it to SECURE state. + */ +#define SCC_BRINGUP + + +/** + * Expected to come from platform header files or compile command line. + * This symbol must be the address of the SCC + */ +#define SCC_BASE + +/** + * This must be the interrupt line number of the SCM interrupt. + */ +#define INT_SCM + +/** + * if #USE_SMN_INTERRUPT is defined, this must be the interrupt line number of + * the SMN interrupt. + */ +#define INT_SMN + +/** + * Define the number of Stored Keys which the SCC driver will make available. + * Value shall be from 0 to 20. Default is zero (0). + */ +#define SCC_KEY_SLOTS + +/** + * Make sure that this flag is defined if compiling for a Little-Endian + * platform. Linux Kernel builds provide this flag. + */ +#define __LITTLE_ENDIAN + +/** + * Make sure that this flag is defined if compiling for a Big-Endian platform. + * Linux Kernel builds provide this flag. + */ +#define __BIG_ENDIAN + +/** + * Read a 32-bit register value from a 'peripheral'. Standard Linux/Unix + * macro. + * + * @param offset Bus address of register to be read + * + * @return The value of the register + */ +#define readl(offset) + + +/** + * Write a 32-bit value to a register in a 'peripheral'. Standard Linux/Unix + * macro. + * + * @param value The 32-bit value to store + * @param offset Bus address of register to be written + * + * return (none) + */ +#define writel(value,offset) + + +/** @} */ /* end group scccompileflags */ + +#endif /* DOXYGEN_HACK */ + + +#ifndef SCC_KEY_SLOTS +#define SCC_KEY_SLOTS 0 + +#else + +#if (SCC_KEY_SLOTS < 0) || (SCC_KEY_SLOTS > 20) +#error Bad value for SCC_KEY_SLOTS +#endif + +#endif + + +/** + * Maximum length of key/secret value which can be stored in SCC. + */ +#define SCC_MAX_KEY_SIZE 256 + + +/** + * This is the size, in bytes, of each key slot, and therefore the maximum size + * of the wrapped key. + */ +#define SCC_KEY_SLOT_SIZE 32 + + +/* These come for free with Linux, but may need to be set in a port. */ +#ifndef __BIG_ENDIAN +#ifndef __LITTLE_ENDIAN +#error One of __LITTLE_ENDIAN or __BIG_ENDIAN must be #defined +#endif +#else +#ifdef __LITTLE_ENDIAN +#error Exactly one of __LITTLE_ENDIAN or __BIG_ENDIAN must be #defined +#endif +#endif + + +#ifndef SCC_CALLBACK_SIZE +/** The number of function pointers which can be stored in #scc_callbacks. + * Defaults to 4, can be overridden with compile-line argument. + */ +#define SCC_CALLBACK_SIZE 4 +#endif + + +/** Initial CRC value for CCITT-CRC calculation. */ +#define CRC_CCITT_START 0xFFFF + + +#ifdef TAHITI + +/** + * The SCC_BASE has to be SMN_BASE_ADDR on TAHITI, as the banks of + * registers are swapped in place. + */ +#define SCC_BASE SMN_BASE_ADDR + + +/** The interrupt number for the SCC (SCM only!) on Tahiti */ +#define INT_SCC_SCM 62 + + +/** Tahiti does not have the SMN interrupt wired to the CPU. */ +#define NO_SMN_INTERRUPT + + +#endif /* TAHITI */ + + +/** Number of times to spin between polling of SCC while waiting for cipher + * or zeroizing function to complete. See also #SCC_CIPHER_MAX_POLL_COUNT. */ +#define SCC_SPIN_COUNT 1000 + + +/** Number of times to polling SCC while waiting for cipher + * or zeroizing function to complete. See also #SCC_SPIN_COUNT. */ +#define SCC_CIPHER_MAX_POLL_COUNT 100 + + +/** + * @def SCC_READ_REGISTER + * Read a 32-bit value from an SCC register. Macro which depends upon + * #scc_base. Linux readl()/writel() macros operate on 32-bit quantities, as + * do SCC register reads/writes. + * + * @param offset Register offset within SCC. + * + * @return The value from the SCC's register. + */ +#ifndef SCC_REGISTER_DEBUG +#define SCC_READ_REGISTER(offset) __raw_readl(scc_base+(offset)) +#else +#define SCC_READ_REGISTER(offset) dbg_scc_read_register(offset) +#endif + + +/** + * Write a 32-bit value to an SCC register. Macro depends upon #scc_base. + * Linux readl()/writel() macros operate on 32-bit quantities, as do SCC + * register reads/writes. + * + * @param offset Register offset within SCC. + * @param value 32-bit value to store into the register + * + * @return (void) + */ +#ifndef SCC_REGISTER_DEBUG +#define SCC_WRITE_REGISTER(offset,value) (void)__raw_writel(value, scc_base+(offset)) +#else +#define SCC_WRITE_REGISTER(offset,value) dbg_scc_write_register(offset, value) +#endif + + +/** + * Calculates the byte offset into a word + * @param bp The byte (char*) pointer + * @return The offset (0, 1, 2, or 3) + */ +#define SCC_BYTE_OFFSET(bp) ((uint32_t)(bp) % sizeof(uint32_t)) + + +/** + * Converts (by rounding down) a byte pointer into a word pointer + * @param bp The byte (char*) pointer + * @return The word (uint32_t) as though it were an aligned (uint32_t*) + */ +#define SCC_WORD_PTR(bp) (((uint32_t)(bp)) & ~(sizeof(uint32_t)-1)) + + +/** + * Determine number of bytes in an SCC block + * + * @return Bytes / block + */ +#define SCC_BLOCK_SIZE_BYTES() scc_configuration.block_size_bytes + + +/** + * Maximum number of additional bytes which may be added in CRC+padding mode. + */ +#define PADDING_BUFFER_MAX_BYTES (CRC_SIZE_BYTES + sizeof(scc_block_padding)) + +/** + * Shorthand (clearer, anyway) for number of bytes in a CRC. + */ +#define CRC_SIZE_BYTES (sizeof(crc_t)) + +/** + * The polynomial used in CCITT-CRC calculation + */ +#define CRC_POLYNOMIAL 0x1021 + +/** + * Calculate CRC on one byte of data + * + * @param[in,out] running_crc A value of type crc_t where CRC is kept. This + * must be an rvalue and an lvalue. + * @param[in] byte_value The byte (uint8_t, char) to be put in the CRC + * + * @return none + */ +#define CALC_CRC(byte_value,running_crc) { \ + uint8_t data; \ + data = (0xff&(byte_value)) ^ (running_crc >> 8); \ + running_crc = scc_crc_lookup_table[data] ^ (running_crc << 8); \ +} + +/** Value of 'beginning of padding' marker in driver-provided padding */ +#define SCC_DRIVER_PAD_CHAR 0x80 + + +/** Name of the driver. Used (on Linux, anyway) when registering interrupts */ +#define SCC_DRIVER_NAME "scc" + + +/* Port -- these symbols are defined in Linux 2.6 and later. They are defined + * here for backwards compatibility because this started life as a 2.4 + * driver, and as a guide to portation to other platforms. + */ + +#if !defined(LINUX_VERSION_CODE) || LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) + +#define irqreturn_t void /* Return type of an interrupt handler */ + +#define IRQ_HANDLED /* Would be '1' for handled -- as in return IRQ_HANDLED; */ + +#define IRQ_NONE /* would be '0' for not handled -- as in return IRQ_NONE; */ + +#define IRQ_RETVAL(x) /* Return x==0 (not handled) or non-zero (handled) */ + +#endif /* LINUX earlier than 2.5 */ + + +/* These are nice to have around */ +#ifndef FALSE +#define FALSE 0 +#endif +#ifndef TRUE +#define TRUE 1 +#endif + + +/** Provide a typedef for the CRC which can be used in encrypt/decrypt */ +typedef uint16_t crc_t; + + +/** Gives high-level view of state of the SCC */ +enum scc_status { + SCC_STATUS_INITIAL, /**< State of driver before ever checking */ + SCC_STATUS_CHECKING, /**< Transient state while driver loading */ + SCC_STATUS_UNIMPLEMENTED, /**< SCC is non-existent or unuseable */ + SCC_STATUS_OK, /**< SCC is in Secure or Default state */ + SCC_STATUS_FAILED /**< In Failed state */ +}; + +/** + * Information about a key slot. + */ +struct scc_key_slot +{ + uint64_t owner_id; /**< Access control value. */ + uint32_t length; /**< Length of value in slot. */ + uint32_t offset; /**< Offset of value from start of RAM. */ + uint32_t status; /**< 0 = unassigned, 1 = assigned. */ + uint32_t part_ctl; /**< for the CCMD register */ +}; + +/* Forward-declare a number routines which are not part of user api */ +static int scc_init(void); +static void scc_cleanup(void); + +/* Forward defines of internal functions */ +static irqreturn_t scc_irq(int irq, void *dev_id); +/** Perform callbacks registered by #scc_monitor_security_failure(). + * + * Make sure callbacks only happen once... Since there may be some reason why + * the interrupt isn't generated, this routine could be called from base(task) + * level. + * + * One at a time, go through #scc_callbacks[] and call any non-null pointers. + */ +static void scc_perform_callbacks(void); +static uint32_t copy_to_scc(const uint8_t* from, uint32_t to, + unsigned long count_bytes, uint16_t* crc); +static uint32_t copy_from_scc(const uint32_t from, uint8_t* to, + unsigned long count_bytes, uint16_t* crc); +static scc_return_t scc_strip_padding(uint8_t* from, + unsigned* count_bytes_stripped); +static uint32_t scc_update_state(void); +static void scc_init_ccitt_crc(void); +static uint32_t scc_grab_config_values(void); +static int setup_interrupt_handling(void); +/** + * Perform an encryption on the input. If @c verify_crc is true, a CRC must be + * calculated on the plaintext, and appended, with padding, before computing + * the ciphertext. + * + * @param[in] count_in_bytes Count of bytes of plaintext + * @param[in] data_in Pointer to the plaintext + * @param[in] scm_control Bit values for the SCM_CONTROL register + * @param[in,out] data_out Pointer for storing ciphertext + * @param[in] add_crc Flag for computing CRC - 0 no, else yes + * @param[in,out] count_out_bytes Number of bytes available at @c data_out + */ +static scc_return_t scc_encrypt(uint32_t count_in_bytes, uint8_t* data_in, + uint32_t scm_control, uint8_t* data_out, + int add_crc, unsigned long* count_out_bytes); +/** + * Perform a decryption on the input. If @c verify_crc is true, the last block + * (maybe the two last blocks) is special - it should contain a CRC and + * padding. These must be stripped and verified. + * + * @param[in] count_in_bytes Count of bytes of ciphertext + * @param[in] data_in Pointer to the ciphertext + * @param[in] scm_control Bit values for the SCM_CONTROL register + * @param[in,out] data_out Pointer for storing plaintext + * @param[in] verify_crc Flag for running CRC - 0 no, else yes + * @param[in,out] count_out_bytes Number of bytes available at @c data_out + + */ +static scc_return_t scc_decrypt(uint32_t count_in_bytes, uint8_t* data_in, + uint32_t scm_control, uint8_t* data_out, + int verify_crc, + unsigned long* count_out_bytes); +static scc_return_t scc_wait_completion(uint32_t* scm_status); +static int is_cipher_done(uint32_t* scm_status); +static scc_return_t check_register_accessible (uint32_t offset, + uint32_t smn_status, + uint32_t scm_status); +static scc_return_t check_register_offset(uint32_t offset); +uint8_t make_vpu_partition(void); + +#ifdef SCC_REGISTER_DEBUG +static uint32_t dbg_scc_read_register(uint32_t offset); +static void dbg_scc_write_register(uint32_t offset, uint32_t value); +#endif + + +/* For Linux kernel, export the API functions to other kernel modules */ +EXPORT_SYMBOL(scc_get_configuration); +EXPORT_SYMBOL(scc_zeroize_memories); +EXPORT_SYMBOL(scc_crypt); +EXPORT_SYMBOL(scc_set_sw_alarm); +EXPORT_SYMBOL(scc_monitor_security_failure); +EXPORT_SYMBOL(scc_stop_monitoring_security_failure); +EXPORT_SYMBOL(scc_read_register); +EXPORT_SYMBOL(scc_write_register); +EXPORT_SYMBOL(scc_alloc_slot); +EXPORT_SYMBOL(scc_dealloc_slot); +EXPORT_SYMBOL(scc_load_slot); +EXPORT_SYMBOL(scc_encrypt_slot); +EXPORT_SYMBOL(scc_decrypt_slot); +EXPORT_SYMBOL(scc_get_slot_info); +EXPORT_SYMBOL(make_vpu_partition); +/* Tell Linux where to invoke driver at boot/module load time */ +module_init(scc_init); +/* Tell Linux where to invoke driver on module unload */ +module_exit(scc_cleanup); + + +/* Tell Linux this is not GPL code */ +MODULE_LICENSE("Proprietary"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("Device Driver for SCC (SMN/SCM)"); + + +#endif /* SCC_INTERNALS_H */ diff --git a/drivers/mxc/ssi/Kconfig b/drivers/mxc/ssi/Kconfig new file mode 100644 index 000000000000..4cb581c2f945 --- /dev/null +++ b/drivers/mxc/ssi/Kconfig @@ -0,0 +1,12 @@ +# +# SPI device configuration +# + +menu "MXC SSI support" + +config MXC_SSI + tristate "SSI support" + ---help--- + Say Y to get the SSI services API available on MXC platform. + +endmenu diff --git a/drivers/mxc/ssi/Makefile b/drivers/mxc/ssi/Makefile new file mode 100644 index 000000000000..f9bb4419fc19 --- /dev/null +++ b/drivers/mxc/ssi/Makefile @@ -0,0 +1,7 @@ +# +# Makefile for the kernel SSI device drivers. +# + +obj-$(CONFIG_MXC_SSI) += ssimod.o + +ssimod-objs := ssi.o diff --git a/drivers/mxc/ssi/registers.h b/drivers/mxc/ssi/registers.h new file mode 100644 index 000000000000..e91b7bc3fce5 --- /dev/null +++ b/drivers/mxc/ssi/registers.h @@ -0,0 +1,208 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + + /*! + * @file ../ssi/registers.h + * @brief This header file contains SSI driver low level definition to access module registers. + * + * @ingroup SSI + */ + +#ifndef __MXC_SSI_REGISTERS_H__ +#define __MXC_SSI_REGISTERS_H__ + +/*! + * This include to define bool type, false and true definitions. + */ +#include <asm/hardware.h> + +#define SPBA_CPU_SSI 0x07 + +#define MXC_SSISTX0 0x00 +#define MXC_SSISTX1 0x04 +#define MXC_SSISRX0 0x08 +#define MXC_SSISRX1 0x0C +#define MXC_SSISCR 0x10 +#define MXC_SSISISR 0x14 +#define MXC_SSISIER 0x18 +#define MXC_SSISTCR 0x1C +#define MXC_SSISRCR 0x20 +#define MXC_SSISTCCR 0x24 +#define MXC_SSISRCCR 0x28 +#define MXC_SSISFCSR 0x2C +#define MXC_SSISTR 0x30 +#define MXC_SSISOR 0x34 +#define MXC_SSISACNT 0x38 +#define MXC_SSISACADD 0x3C +#define MXC_SSISACDAT 0x40 +#define MXC_SSISATAG 0x44 +#define MXC_SSISTMSK 0x48 +#define MXC_SSISRMSK 0x4C + +/* MXC 91221 only */ +#define MXC_SSISACCST 0x50 +#define MXC_SSISACCEN 0x54 +#define MXC_SSISACCDIS 0x58 + +/*! SSI1 registers offset*/ +#define MXC_SSI1STX0 0x00 +#define MXC_SSI1STX1 0x04 +#define MXC_SSI1SRX0 0x08 +#define MXC_SSI1SRX1 0x0C +#define MXC_SSI1SCR 0x10 +#define MXC_SSI1SISR 0x14 +#define MXC_SSI1SIER 0x18 +#define MXC_SSI1STCR 0x1C +#define MXC_SSI1SRCR 0x20 +#define MXC_SSI1STCCR 0x24 +#define MXC_SSI1SRCCR 0x28 +#define MXC_SSI1SFCSR 0x2C +#define MXC_SSI1STR 0x30 +#define MXC_SSI1SOR 0x34 +#define MXC_SSI1SACNT 0x38 +#define MXC_SSI1SACADD 0x3C +#define MXC_SSI1SACDAT 0x40 +#define MXC_SSI1SATAG 0x44 +#define MXC_SSI1STMSK 0x48 +#define MXC_SSI1SRMSK 0x4C + +/* MXC91221 only */ + +#define MXC_SSISACCST 0x50 +#define MXC_SSISACCEN 0x54 +#define MXC_SSISACCDIS 0x58 + +/* Not on MXC91221 */ +/*! SSI2 registers offset*/ +#define MXC_SSI2STX0 0x00 +#define MXC_SSI2STX1 0x04 +#define MXC_SSI2SRX0 0x08 +#define MXC_SSI2SRX1 0x0C +#define MXC_SSI2SCR 0x10 +#define MXC_SSI2SISR 0x14 +#define MXC_SSI2SIER 0x18 +#define MXC_SSI2STCR 0x1C +#define MXC_SSI2SRCR 0x20 +#define MXC_SSI2STCCR 0x24 +#define MXC_SSI2SRCCR 0x28 +#define MXC_SSI2SFCSR 0x2C +#define MXC_SSI2STR 0x30 +#define MXC_SSI2SOR 0x34 +#define MXC_SSI2SACNT 0x38 +#define MXC_SSI2SACADD 0x3C +#define MXC_SSI2SACDAT 0x40 +#define MXC_SSI2SATAG 0x44 +#define MXC_SSI2STMSK 0x48 +#define MXC_SSI2SRMSK 0x4C + +/*! + * SCR Register bit shift definitions + */ +#define SSI_ENABLE_SHIFT 0 +#define SSI_TRANSMIT_ENABLE_SHIFT 1 +#define SSI_RECEIVE_ENABLE_SHIFT 2 +#define SSI_NETWORK_MODE_SHIFT 3 +#define SSI_SYNCHRONOUS_MODE_SHIFT 4 +#define SSI_I2S_MODE_SHIFT 5 +#define SSI_SYSTEM_CLOCK_SHIFT 7 +#define SSI_TWO_CHANNEL_SHIFT 8 +#define SSI_CLOCK_IDLE_SHIFT 9 + +/* MXC91221 only*/ +#define SSI_TX_FRAME_CLOCK_DISABLE_SHIFT 10 +#define SSI_RX_FRAME_CLOCK_DISABLE_SHIFT 11 + +/*! + * STCR & SRCR Registers bit shift definitions + */ +#define SSI_EARLY_FRAME_SYNC_SHIFT 0 +#define SSI_FRAME_SYNC_LENGTH_SHIFT 1 +#define SSI_FRAME_SYNC_INVERT_SHIFT 2 +#define SSI_CLOCK_POLARITY_SHIFT 3 +#define SSI_SHIFT_DIRECTION_SHIFT 4 +#define SSI_CLOCK_DIRECTION_SHIFT 5 +#define SSI_FRAME_DIRECTION_SHIFT 6 +#define SSI_FIFO_ENABLE_0_SHIFT 7 +#define SSI_FIFO_ENABLE_1_SHIFT 8 +#define SSI_BIT_0_SHIFT 9 + +/* MXC91221 only*/ +#define SSI_TX_FRAME_CLOCK_DISABLE_SHIFT 10 +#define SSI_RX_DATA_EXTENSION_SHIFT 10 /*SRCR only */ +/*! + * STCCR & SRCCR Registers bit shift definitions + */ +#define SSI_PRESCALER_MODULUS_SHIFT 0 +#define SSI_FRAME_RATE_DIVIDER_SHIFT 8 +#define SSI_WORD_LENGTH_SHIFT 13 +#define SSI_PRESCALER_RANGE_SHIFT 17 +#define SSI_DIVIDE_BY_TWO_SHIFT 18 +#define SSI_FRAME_DIVIDER_MASK 31 +#define SSI_MIN_FRAME_DIVIDER_RATIO 1 +#define SSI_MAX_FRAME_DIVIDER_RATIO 32 +#define SSI_PRESCALER_MODULUS_MASK 255 +#define SSI_MIN_PRESCALER_MODULUS_RATIO 1 +#define SSI_MAX_PRESCALER_MODULUS_RATIO 256 +#define SSI_WORD_LENGTH_MASK 15 + +#define SSI_IRQ_STATUS_NUMBER 25 + +/*! + * SFCSR Register bit shift definitions + */ +#define SSI_RX_FIFO_1_COUNT_SHIFT 28 +#define SSI_TX_FIFO_1_COUNT_SHIFT 24 +#define SSI_RX_FIFO_1_WATERMARK_SHIFT 20 +#define SSI_TX_FIFO_1_WATERMARK_SHIFT 16 +#define SSI_RX_FIFO_0_COUNT_SHIFT 12 +#define SSI_TX_FIFO_0_COUNT_SHIFT 8 +#define SSI_RX_FIFO_0_WATERMARK_SHIFT 4 +#define SSI_TX_FIFO_0_WATERMARK_SHIFT 0 +#define SSI_MIN_FIFO_WATERMARK 0 +#define SSI_MAX_FIFO_WATERMARK 8 + +/*! + * SSI Option Register (SOR) bit shift definitions + */ +#define SSI_FRAME_SYN_RESET_SHIFT 0 +#define SSI_WAIT_SHIFT 1 +#define SSI_INIT_SHIFT 3 +#define SSI_TRANSMITTER_CLEAR_SHIFT 4 +#define SSI_RECEIVER_CLEAR_SHIFT 5 +#define SSI_CLOCK_OFF_SHIFT 6 +#define SSI_WAIT_STATE_MASK 0x3 + +/*! + * SSI AC97 Control Register (SACNT) bit shift definitions + */ +#define AC97_MODE_ENABLE_SHIFT 0 +#define AC97_VARIABLE_OPERATION_SHIFT 1 +#define AC97_TAG_IN_FIFO_SHIFT 2 +#define AC97_READ_COMMAND_SHIFT 3 +#define AC97_WRITE_COMMAND_SHIFT 4 +#define AC97_FRAME_RATE_DIVIDER_SHIFT 5 +#define AC97_FRAME_RATE_MASK 0x3F + +/*! + * SSI Test Register (STR) bit shift definitions + */ +#define SSI_TEST_MODE_SHIFT 15 +#define SSI_RCK2TCK_SHIFT 14 +#define SSI_RFS2TFS_SHIFT 13 +#define SSI_RXSTATE_SHIFT 8 +#define SSI_TXD2RXD_SHIFT 7 +#define SSI_TCK2RCK_SHIFT 6 +#define SSI_TFS2RFS_SHIFT 5 +#define SSI_TXSTATE_SHIFT 0 + +#endif /* __MXC_SSI_REGISTERS_H__ */ diff --git a/drivers/mxc/ssi/ssi.c b/drivers/mxc/ssi/ssi.c new file mode 100644 index 000000000000..284090a3ef1f --- /dev/null +++ b/drivers/mxc/ssi/ssi.c @@ -0,0 +1,1238 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file ssi.c + * @brief This file contains the implementation of the SSI driver main services + * + * + * @ingroup SSI + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <asm/uaccess.h> +#include <asm/arch/clock.h> + +#include "registers.h" +#include "ssi.h" + +static spinlock_t ssi_lock; +struct mxc_audio_platform_data *ssi_platform_data; + +EXPORT_SYMBOL(ssi_ac97_frame_rate_divider); +EXPORT_SYMBOL(ssi_ac97_get_command_address_register); +EXPORT_SYMBOL(ssi_ac97_get_command_data_register); +EXPORT_SYMBOL(ssi_ac97_get_tag_register); +EXPORT_SYMBOL(ssi_ac97_mode_enable); +EXPORT_SYMBOL(ssi_ac97_tag_in_fifo); +EXPORT_SYMBOL(ssi_ac97_read_command); +EXPORT_SYMBOL(ssi_ac97_set_command_address_register); +EXPORT_SYMBOL(ssi_ac97_set_command_data_register); +EXPORT_SYMBOL(ssi_ac97_set_tag_register); +EXPORT_SYMBOL(ssi_ac97_variable_mode); +EXPORT_SYMBOL(ssi_ac97_write_command); +EXPORT_SYMBOL(ssi_clock_idle_state); +EXPORT_SYMBOL(ssi_clock_off); +EXPORT_SYMBOL(ssi_enable); +EXPORT_SYMBOL(ssi_get_data); +EXPORT_SYMBOL(ssi_get_status); +EXPORT_SYMBOL(ssi_i2s_mode); +EXPORT_SYMBOL(ssi_interrupt_disable); +EXPORT_SYMBOL(ssi_interrupt_enable); +EXPORT_SYMBOL(ssi_network_mode); +EXPORT_SYMBOL(ssi_receive_enable); +EXPORT_SYMBOL(ssi_rx_bit0); +EXPORT_SYMBOL(ssi_rx_clock_direction); +EXPORT_SYMBOL(ssi_rx_clock_divide_by_two); +EXPORT_SYMBOL(ssi_rx_clock_polarity); +EXPORT_SYMBOL(ssi_rx_clock_prescaler); +EXPORT_SYMBOL(ssi_rx_early_frame_sync); +EXPORT_SYMBOL(ssi_rx_fifo_counter); +EXPORT_SYMBOL(ssi_rx_fifo_enable); +EXPORT_SYMBOL(ssi_rx_fifo_full_watermark); +EXPORT_SYMBOL(ssi_rx_flush_fifo); +EXPORT_SYMBOL(ssi_rx_frame_direction); +EXPORT_SYMBOL(ssi_rx_frame_rate); +EXPORT_SYMBOL(ssi_rx_frame_sync_active); +EXPORT_SYMBOL(ssi_rx_frame_sync_length); +EXPORT_SYMBOL(ssi_rx_mask_time_slot); +EXPORT_SYMBOL(ssi_rx_prescaler_modulus); +EXPORT_SYMBOL(ssi_rx_shift_direction); +EXPORT_SYMBOL(ssi_rx_word_length); +EXPORT_SYMBOL(ssi_set_data); +EXPORT_SYMBOL(ssi_set_wait_states); +EXPORT_SYMBOL(ssi_synchronous_mode); +EXPORT_SYMBOL(ssi_system_clock); +EXPORT_SYMBOL(ssi_transmit_enable); +EXPORT_SYMBOL(ssi_two_channel_mode); +EXPORT_SYMBOL(ssi_tx_bit0); +EXPORT_SYMBOL(ssi_tx_clock_direction); +EXPORT_SYMBOL(ssi_tx_clock_divide_by_two); +EXPORT_SYMBOL(ssi_tx_clock_polarity); +EXPORT_SYMBOL(ssi_tx_clock_prescaler); +EXPORT_SYMBOL(ssi_tx_early_frame_sync); +EXPORT_SYMBOL(ssi_tx_fifo_counter); +EXPORT_SYMBOL(ssi_tx_fifo_empty_watermark); +EXPORT_SYMBOL(ssi_tx_fifo_enable); +EXPORT_SYMBOL(ssi_tx_flush_fifo); +EXPORT_SYMBOL(ssi_tx_frame_direction); +EXPORT_SYMBOL(ssi_tx_frame_rate); +EXPORT_SYMBOL(ssi_tx_frame_sync_active); +EXPORT_SYMBOL(ssi_tx_frame_sync_length); +EXPORT_SYMBOL(ssi_tx_mask_time_slot); +EXPORT_SYMBOL(ssi_tx_prescaler_modulus); +EXPORT_SYMBOL(ssi_tx_shift_direction); +EXPORT_SYMBOL(ssi_tx_word_length); +EXPORT_SYMBOL(get_ssi_fifo_addr); + +struct resource *res; +void *base_addr_1; +void *base_addr_2; + +unsigned int get_ssi_fifo_addr(unsigned int ssi, int direction) +{ + unsigned int fifo_addr; + if (direction == 1) { + if (ssi_platform_data->ssi_num == 2) { + fifo_addr = + (ssi == + SSI1) ? (int)(base_addr_1 + + MXC_SSI1STX0) : (int)(base_addr_2 + + MXC_SSI2STX0); + } else { + fifo_addr = (int)(base_addr_1 + MXC_SSI1STX0); + } + } else { + fifo_addr = (int)(base_addr_1 + MXC_SSI1SRX0); + } + return fifo_addr; +} + +unsigned int get_ssi_base_addr(unsigned int ssi) +{ + int base_addr; + if (ssi_platform_data->ssi_num == 2) { + base_addr = + (ssi == + SSI1) ? IO_ADDRESS((int)base_addr_1) : IO_ADDRESS((int) + base_addr_2); + } else { + base_addr = IO_ADDRESS((int)base_addr_1); + } + return base_addr; +} + +void set_register_bits(unsigned int mask, unsigned int data, + unsigned int offset, unsigned int ssi) +{ + volatile unsigned long reg = 0; + unsigned int base_addr = 0; + unsigned long flags = 0; + + spin_lock_irqsave(&ssi_lock, flags); + base_addr = get_ssi_base_addr(ssi); + reg = __raw_readl(base_addr + offset); + reg = (reg & (~mask)) | data; + __raw_writel(reg, base_addr + offset); + spin_unlock_irqrestore(&ssi_lock, flags); +} + +unsigned long getreg_value(unsigned int offset, unsigned int ssi) +{ + volatile unsigned long reg = 0; + unsigned int base_addr = 0; + unsigned long flags = 0; + + spin_lock_irqsave(&ssi_lock, flags); + base_addr = get_ssi_base_addr(ssi); + reg = __raw_readl(base_addr + offset); + spin_unlock_irqrestore(&ssi_lock, flags); + + return reg; +} + +void set_register(unsigned int data, unsigned int offset, unsigned int ssi) +{ + unsigned int base_addr = 0; + unsigned long flags = 0; + + spin_lock_irqsave(&ssi_lock, flags); + base_addr = get_ssi_base_addr(ssi); + __raw_writel(data, base_addr + offset); + spin_unlock_irqrestore(&ssi_lock, flags); + +} + +/*! + * This function controls the AC97 frame rate divider. + * + * @param module the module number + * @param frame_rate_divider the AC97 frame rate divider + */ +void ssi_ac97_frame_rate_divider(ssi_mod module, + unsigned char frame_rate_divider) +{ + unsigned int reg = 0; + + reg = getreg_value(MXC_SSISACNT, module); + reg |= ((frame_rate_divider & AC97_FRAME_RATE_MASK) + << AC97_FRAME_RATE_DIVIDER_SHIFT); + set_register(reg, MXC_SSISACNT, module); +} + +/*! + * This function gets the AC97 command address register. + * + * @param module the module number + * @return This function returns the command address slot information. + */ +unsigned int ssi_ac97_get_command_address_register(ssi_mod module) +{ + return getreg_value(MXC_SSISACADD, module); +} + +/*! + * This function gets the AC97 command data register. + * + * @param module the module number + * @return This function returns the command data slot information. + */ +unsigned int ssi_ac97_get_command_data_register(ssi_mod module) +{ + return getreg_value(MXC_SSISACDAT, module); +} + +/*! + * This function gets the AC97 tag register. + * + * @param module the module number + * @return This function returns the tag information. + */ +unsigned int ssi_ac97_get_tag_register(ssi_mod module) +{ + return getreg_value(MXC_SSISATAG, module); +} + +/*! + * This function controls the AC97 mode. + * + * @param module the module number + * @param state the AC97 mode state (enabled or disabled) + */ +void ssi_ac97_mode_enable(ssi_mod module, bool state) +{ + unsigned int reg = 0; + + reg = getreg_value(MXC_SSISACNT, module); + if (state == true) { + reg |= (1 << AC97_MODE_ENABLE_SHIFT); + } else { + reg &= ~(1 << AC97_MODE_ENABLE_SHIFT); + } + + set_register(reg, MXC_SSISACNT, module); +} + +/*! + * This function controls the AC97 tag in FIFO behavior. + * + * @param module the module number + * @param state the tag in fifo behavior (Tag info stored in Rx FIFO 0 if true, + * Tag info stored in SATAG register otherwise) + */ +void ssi_ac97_tag_in_fifo(ssi_mod module, bool state) +{ + unsigned int reg = 0; + + reg = getreg_value(MXC_SSISACNT, module); + if (state == true) { + reg |= (1 << AC97_TAG_IN_FIFO_SHIFT); + } else { + reg &= ~(1 << AC97_TAG_IN_FIFO_SHIFT); + } + + set_register(reg, MXC_SSISACNT, module); +} + +/*! + * This function controls the AC97 read command. + * + * @param module the module number + * @param state the next AC97 command is a read command or not + */ +void ssi_ac97_read_command(ssi_mod module, bool state) +{ + unsigned int reg = 0; + + reg = getreg_value(MXC_SSISACNT, module); + if (state == true) { + reg |= (1 << AC97_READ_COMMAND_SHIFT); + } else { + reg &= ~(1 << AC97_READ_COMMAND_SHIFT); + } + + set_register(reg, MXC_SSISACNT, module); +} + +/*! + * This function sets the AC97 command address register. + * + * @param module the module number + * @param address the command address slot information + */ +void ssi_ac97_set_command_address_register(ssi_mod module, unsigned int address) +{ + set_register(address, MXC_SSISACADD, module); +} + +/*! + * This function sets the AC97 command data register. + * + * @param module the module number + * @param data the command data slot information + */ +void ssi_ac97_set_command_data_register(ssi_mod module, unsigned int data) +{ + set_register(data, MXC_SSISACDAT, module); +} + +/*! + * This function sets the AC97 tag register. + * + * @param module the module number + * @param tag the tag information + */ +void ssi_ac97_set_tag_register(ssi_mod module, unsigned int tag) +{ + set_register(tag, MXC_SSISATAG, module); +} + +/*! + * This function controls the AC97 variable mode. + * + * @param module the module number + * @param state the AC97 variable mode state (enabled or disabled) + */ void ssi_ac97_variable_mode(ssi_mod module, bool state) +{ + unsigned int reg = 0; + + reg = getreg_value(MXC_SSISACNT, module); + if (state == true) { + reg |= (1 << AC97_VARIABLE_OPERATION_SHIFT); + } else { + reg &= ~(1 << AC97_VARIABLE_OPERATION_SHIFT); + } + + set_register(reg, MXC_SSISACNT, module); +} + +/*! + * This function controls the AC97 write command. + * + * @param module the module number + * @param state the next AC97 command is a write command or not + */ +void ssi_ac97_write_command(ssi_mod module, bool state) +{ + unsigned int reg = 0; + + reg = getreg_value(MXC_SSISACNT, module); + if (state == true) { + reg |= (1 << AC97_WRITE_COMMAND_SHIFT); + } else { + reg &= ~(1 << AC97_WRITE_COMMAND_SHIFT); + } + + set_register(reg, MXC_SSISACNT, module); +} + +/*! + * This function controls the idle state of the transmit clock port during SSI internal gated mode. + * + * @param module the module number + * @param state the clock idle state + */ +void ssi_clock_idle_state(ssi_mod module, idle_state state) +{ + set_register_bits(1 << SSI_CLOCK_IDLE_SHIFT, + state << SSI_CLOCK_IDLE_SHIFT, MXC_SSISCR, module); +} + +/*! + * This function turns off/on the ccm_ssi_clk to reduce power consumption. + * + * @param module the module number + * @param state the state for ccm_ssi_clk (true: turn off, else:turn on) + */ +void ssi_clock_off(ssi_mod module, bool state) +{ + set_register_bits(1 << SSI_CLOCK_OFF_SHIFT, + state << SSI_CLOCK_OFF_SHIFT, MXC_SSISOR, module); +} + +/*! + * This function enables/disables the SSI module. + * + * @param module the module number + * @param state the state for SSI module + */ +void ssi_enable(ssi_mod module, bool state) +{ + set_register_bits(1 << SSI_ENABLE_SHIFT, state << SSI_ENABLE_SHIFT, + MXC_SSISCR, module); +} + +/*! + * This function gets the data word in the Receive FIFO of the SSI module. + * + * @param module the module number + * @param fifo the Receive FIFO to read + * @return This function returns the read data. + */ +unsigned int ssi_get_data(ssi_mod module, fifo_nb fifo) +{ + unsigned int result = 0; + + if (ssi_fifo_0 == fifo) { + result = getreg_value(MXC_SSISRX0, module); + } else { + result = getreg_value(MXC_SSISRX1, module); + } + + return result; +} + +/*! + * This function returns the status of the SSI module (SISR register) as a combination of status. + * + * @param module the module number + * @return This function returns the status of the SSI module + */ +ssi_status_enable_mask ssi_get_status(ssi_mod module) +{ + unsigned int result; + + result = getreg_value(MXC_SSISISR, module); + result &= ((1 << SSI_IRQ_STATUS_NUMBER) - 1); + + return (ssi_status_enable_mask) result; +} + +/*! + * This function selects the I2S mode of the SSI module. + * + * @param module the module number + * @param mode the I2S mode + */ +void ssi_i2s_mode(ssi_mod module, mode_i2s mode) +{ + set_register_bits(3 << SSI_I2S_MODE_SHIFT, mode << SSI_I2S_MODE_SHIFT, + MXC_SSISCR, module); +} + +/*! + * This function disables the interrupts of the SSI module. + * + * @param module the module number + * @param mask the mask of the interrupts to disable + */ +void ssi_interrupt_disable(ssi_mod module, ssi_status_enable_mask mask) +{ + set_register_bits(mask, 0, MXC_SSISIER, module); +} + +/*! + * This function enables the interrupts of the SSI module. + * + * @param module the module number + * @param mask the mask of the interrupts to enable + */ +void ssi_interrupt_enable(ssi_mod module, ssi_status_enable_mask mask) +{ + set_register_bits(0, mask, MXC_SSISIER, module); +} + +/*! + * This function enables/disables the network mode of the SSI module. + * + * @param module the module number + * @param state the network mode state + */ +void ssi_network_mode(ssi_mod module, bool state) +{ + set_register_bits(1 << SSI_NETWORK_MODE_SHIFT, + state << SSI_NETWORK_MODE_SHIFT, MXC_SSISCR, module); +} + +/*! + * This function enables/disables the receive section of the SSI module. + * + * @param module the module number + * @param state the receive section state + */ +void ssi_receive_enable(ssi_mod module, bool state) +{ + set_register_bits(1 << SSI_RECEIVE_ENABLE_SHIFT, + state << SSI_RECEIVE_ENABLE_SHIFT, MXC_SSISCR, + module); +} + +/*! + * This function configures the SSI module to receive data word at bit position 0 or 23 in the Receive shift register. + * + * @param module the module number + * @param state the state to receive at bit 0 + */ +void ssi_rx_bit0(ssi_mod module, bool state) +{ + set_register_bits(1 << SSI_BIT_0_SHIFT, state << SSI_BIT_0_SHIFT, + MXC_SSISRCR, module); +} + +/*! + * This function controls the source of the clock signal used to clock the Receive shift register. + * + * @param module the module number + * @param direction the clock signal direction + */ +void ssi_rx_clock_direction(ssi_mod module, ssi_tx_rx_direction direction) +{ + set_register_bits(1 << SSI_CLOCK_DIRECTION_SHIFT, + direction << SSI_CLOCK_DIRECTION_SHIFT, MXC_SSISRCR, + module); +} + +/*! + * This function configures the divide-by-two divider of the SSI module for the receive section. + * + * @param module the module number + * @param state the divider state + */ +void ssi_rx_clock_divide_by_two(ssi_mod module, bool state) +{ + set_register_bits(1 << SSI_DIVIDE_BY_TWO_SHIFT, + state << SSI_DIVIDE_BY_TWO_SHIFT, MXC_SSISRCCR, + module); +} + +/*! + * This function controls which bit clock edge is used to clock in data. + * + * @param module the module number + * @param polarity the clock polarity + */ +void ssi_rx_clock_polarity(ssi_mod module, ssi_tx_rx_clock_polarity polarity) +{ + set_register_bits(1 << SSI_CLOCK_POLARITY_SHIFT, + polarity << SSI_CLOCK_POLARITY_SHIFT, MXC_SSISRCR, + module); +} + +/*! + * This function configures a fixed divide-by-eight clock prescaler divider of the SSI module in series with the variable prescaler for the receive section. + * + * @param module the module number + * @param state the prescaler state + */ +void ssi_rx_clock_prescaler(ssi_mod module, bool state) +{ + set_register_bits(1 << SSI_PRESCALER_RANGE_SHIFT, + state << SSI_PRESCALER_RANGE_SHIFT, + MXC_SSISRCCR, module); +} + +/*! + * This function controls the early frame sync configuration. + * + * @param module the module number + * @param early the early frame sync configuration + */ +void ssi_rx_early_frame_sync(ssi_mod module, ssi_tx_rx_early_frame_sync early) +{ + set_register_bits(1 << SSI_EARLY_FRAME_SYNC_SHIFT, + early << SSI_EARLY_FRAME_SYNC_SHIFT, + MXC_SSISRCR, module); +} + +/*! + * This function gets the number of data words in the Receive FIFO. + * + * @param module the module number + * @param fifo the fifo + * @return This function returns the number of words in the Rx FIFO. + */ +unsigned char ssi_rx_fifo_counter(ssi_mod module, fifo_nb fifo) +{ + unsigned char result; + result = 0; + + if (ssi_fifo_0 == fifo) { + result = getreg_value(MXC_SSISFCSR, module); + result &= (0xF << SSI_RX_FIFO_0_COUNT_SHIFT); + result = result >> SSI_RX_FIFO_0_COUNT_SHIFT; + } else { + result = getreg_value(MXC_SSISFCSR, module); + result &= (0xF << SSI_RX_FIFO_1_COUNT_SHIFT); + result = result >> SSI_RX_FIFO_1_COUNT_SHIFT; + } + + return result; +} + +/*! + * This function enables the Receive FIFO. + * + * @param module the module number + * @param fifo the fifo to enable + * @param enable the state of the fifo, enabled or disabled + */ + +void ssi_rx_fifo_enable(ssi_mod module, fifo_nb fifo, bool enable) +{ + volatile unsigned int reg; + + reg = getreg_value(MXC_SSISRCR, module); + if (enable == true) { + reg |= ((1 + fifo) << SSI_FIFO_ENABLE_0_SHIFT); + } else { + reg &= ~((1 + fifo) << SSI_FIFO_ENABLE_0_SHIFT); + } + + set_register(reg, MXC_SSISRCR, module); +} + +/*! + * This function controls the threshold at which the RFFx flag will be set. + * + * @param module the module number + * @param fifo the fifo to enable + * @param watermark the watermark + * @return This function returns the result of the operation (0 if successful, -1 otherwise). + */ +int ssi_rx_fifo_full_watermark(ssi_mod module, + fifo_nb fifo, unsigned char watermark) +{ + int result = -1; + result = -1; + + if ((watermark > SSI_MIN_FIFO_WATERMARK) && + (watermark <= SSI_MAX_FIFO_WATERMARK)) { + if (ssi_fifo_0 == fifo) { + set_register_bits(0xf << SSI_RX_FIFO_0_WATERMARK_SHIFT, + watermark << + SSI_RX_FIFO_0_WATERMARK_SHIFT, + MXC_SSISFCSR, module); + } else { + set_register_bits(0xf << SSI_RX_FIFO_1_WATERMARK_SHIFT, + watermark << + SSI_RX_FIFO_1_WATERMARK_SHIFT, + MXC_SSISFCSR, module); + } + + result = 0; + } + + return result; +} + +/*! + * This function flushes the Receive FIFOs. + * + * @param module the module number + */ +void ssi_rx_flush_fifo(ssi_mod module) +{ + set_register_bits(0, 1 << SSI_RECEIVER_CLEAR_SHIFT, MXC_SSISOR, module); +} + +/*! + * This function controls the direction of the Frame Sync signal for the receive section. + * + * @param module the module number + * @param direction the Frame Sync signal direction + */ +void ssi_rx_frame_direction(ssi_mod module, ssi_tx_rx_direction direction) +{ + set_register_bits(1 << SSI_FRAME_DIRECTION_SHIFT, + direction << SSI_FRAME_DIRECTION_SHIFT, + MXC_SSISRCR, module); +} + +/*! + * This function configures the Receive frame rate divider for the receive section. + * + * @param module the module number + * @param ratio the divide ratio from 1 to 32 + * @return This function returns the result of the operation (0 if successful, -1 otherwise). + */ +int ssi_rx_frame_rate(ssi_mod module, unsigned char ratio) +{ + int result = -1; + + if ((ratio >= SSI_MIN_FRAME_DIVIDER_RATIO) && + (ratio <= SSI_MAX_FRAME_DIVIDER_RATIO)) { + set_register_bits(SSI_FRAME_DIVIDER_MASK << + SSI_FRAME_RATE_DIVIDER_SHIFT, + (ratio - 1) << SSI_FRAME_RATE_DIVIDER_SHIFT, + MXC_SSISRCCR, module); + result = 0; + } + + return result; +} + +/*! + * This function controls the Frame Sync active polarity for the receive section. + * + * @param module the module number + * @param active the Frame Sync active polarity + */ +void ssi_rx_frame_sync_active(ssi_mod module, + ssi_tx_rx_frame_sync_active active) +{ + set_register_bits(1 << SSI_FRAME_SYNC_INVERT_SHIFT, + active << SSI_FRAME_SYNC_INVERT_SHIFT, + MXC_SSISRCR, module); +} + +/*! + * This function controls the Frame Sync length (one word or one bit long) for the receive section. + * + * @param module the module number + * @param length the Frame Sync length + */ +void ssi_rx_frame_sync_length(ssi_mod module, + ssi_tx_rx_frame_sync_length length) +{ + set_register_bits(1 << SSI_FRAME_SYNC_LENGTH_SHIFT, + length << SSI_FRAME_SYNC_LENGTH_SHIFT, + MXC_SSISRCR, module); +} + +/*! + * This function configures the time slot(s) to mask for the receive section. + * + * @param module the module number + * @param mask the mask to indicate the time slot(s) masked + */ +void ssi_rx_mask_time_slot(ssi_mod module, unsigned int mask) +{ + set_register_bits(0xFFFFFFFF, mask, MXC_SSISRMSK, module); +} + +/*! + * This function configures the Prescale divider for the receive section. + * + * @param module the module number + * @param divider the divide ratio from 1 to 256 + * @return This function returns the result of the operation (0 if successful, -1 otherwise). + */ +int ssi_rx_prescaler_modulus(ssi_mod module, unsigned int divider) +{ + int result = -1; + + if ((divider >= SSI_MIN_PRESCALER_MODULUS_RATIO) && + (divider <= SSI_MAX_PRESCALER_MODULUS_RATIO)) { + + set_register_bits(SSI_PRESCALER_MODULUS_MASK << + SSI_PRESCALER_MODULUS_SHIFT, + (divider - 1) << SSI_PRESCALER_MODULUS_SHIFT, + MXC_SSISRCCR, module); + result = 0; + } + + return result; +} + +/*! + * This function controls whether the MSB or LSB will be received first in a sample. + * + * @param module the module number + * @param direction the shift direction + */ +void ssi_rx_shift_direction(ssi_mod module, ssi_tx_rx_shift_direction direction) +{ + set_register_bits(1 << SSI_SHIFT_DIRECTION_SHIFT, + direction << SSI_SHIFT_DIRECTION_SHIFT, + MXC_SSISRCR, module); +} + +/*! + * This function configures the Receive word length. + * + * @param module the module number + * @param length the word length + */ +void ssi_rx_word_length(ssi_mod module, ssi_word_length length) +{ + set_register_bits(SSI_WORD_LENGTH_MASK << SSI_WORD_LENGTH_SHIFT, + length << SSI_WORD_LENGTH_SHIFT, + MXC_SSISRCCR, module); +} + +/*! + * This function sets the data word in the Transmit FIFO of the SSI module. + * + * @param module the module number + * @param fifo the FIFO number + * @param data the data to load in the FIFO + */ + +void ssi_set_data(ssi_mod module, fifo_nb fifo, unsigned int data) +{ + if (ssi_fifo_0 == fifo) { + set_register(data, MXC_SSISTX0, module); + } else { + set_register(data, MXC_SSISTX1, module); + } +} + +/*! + * This function controls the number of wait states between the core and SSI. + * + * @param module the module number + * @param wait the number of wait state(s) + */ +void ssi_set_wait_states(ssi_mod module, ssi_wait_states wait) +{ + set_register_bits(SSI_WAIT_STATE_MASK << SSI_WAIT_SHIFT, + wait << SSI_WAIT_SHIFT, MXC_SSISOR, module); +} + +/*! + * This function enables/disables the synchronous mode of the SSI module. + * + * @param module the module number + * @param state the synchronous mode state + */ +void ssi_synchronous_mode(ssi_mod module, bool state) +{ + set_register_bits(1 << SSI_SYNCHRONOUS_MODE_SHIFT, + state << SSI_SYNCHRONOUS_MODE_SHIFT, + MXC_SSISCR, module); +} + +/*! + * This function allows the SSI module to output the SYS_CLK at the SRCK port. + * + * @param module the module number + * @param state the system clock state + */ +void ssi_system_clock(ssi_mod module, bool state) +{ + set_register_bits(1 << SSI_SYSTEM_CLOCK_SHIFT, + state << SSI_SYSTEM_CLOCK_SHIFT, MXC_SSISCR, module); +} + +/*! + * This function enables/disables the transmit section of the SSI module. + * + * @param module the module number + * @param state the transmit section state + */ +void ssi_transmit_enable(ssi_mod module, bool state) +{ + set_register_bits(1 << SSI_TRANSMIT_ENABLE_SHIFT, + state << SSI_TRANSMIT_ENABLE_SHIFT, + MXC_SSISCR, module); +} + +/*! + * This function allows the SSI module to operate in the two channel mode. + * + * @param module the module number + * @param state the two channel mode state + */ +void ssi_two_channel_mode(ssi_mod module, bool state) +{ + set_register_bits(1 << SSI_TWO_CHANNEL_SHIFT, + state << SSI_TWO_CHANNEL_SHIFT, MXC_SSISCR, module); +} + +/*! + * This function configures the SSI module to transmit data word from bit position 0 or 23 in the Transmit shift register. + * + * @param module the module number + * @param state the transmit from bit 0 state + */ +void ssi_tx_bit0(ssi_mod module, bool state) +{ + set_register_bits(1 << SSI_BIT_0_SHIFT, + state << SSI_BIT_0_SHIFT, MXC_SSISTCR, module); +} + +/*! + * This function controls the direction of the clock signal used to clock the Transmit shift register. + * + * @param module the module number + * @param direction the clock signal direction + */ +void ssi_tx_clock_direction(ssi_mod module, ssi_tx_rx_direction direction) +{ + set_register_bits(1 << SSI_CLOCK_DIRECTION_SHIFT, + direction << SSI_CLOCK_DIRECTION_SHIFT, + MXC_SSISTCR, module); +} + +/*! + * This function configures the divide-by-two divider of the SSI module for the transmit section. + * + * @param module the module number + * @param state the divider state + */ +void ssi_tx_clock_divide_by_two(ssi_mod module, bool state) +{ + set_register_bits(1 << SSI_DIVIDE_BY_TWO_SHIFT, + state << SSI_DIVIDE_BY_TWO_SHIFT, + MXC_SSISTCCR, module); +} + +/*! + * This function controls which bit clock edge is used to clock out data. + * + * @param module the module number + * @param polarity the clock polarity + */ +void ssi_tx_clock_polarity(ssi_mod module, ssi_tx_rx_clock_polarity polarity) +{ + set_register_bits(1 << SSI_CLOCK_POLARITY_SHIFT, + polarity << SSI_CLOCK_POLARITY_SHIFT, + MXC_SSISTCR, module); +} + +/*! + * This function configures a fixed divide-by-eight clock prescaler divider of the SSI module in series with the variable prescaler for the transmit section. + * + * @param module the module number + * @param state the prescaler state + */ +void ssi_tx_clock_prescaler(ssi_mod module, bool state) +{ + set_register_bits(1 << SSI_PRESCALER_RANGE_SHIFT, + state << SSI_PRESCALER_RANGE_SHIFT, + MXC_SSISTCCR, module); +} + +/*! + * This function controls the early frame sync configuration for the transmit section. + * + * @param module the module number + * @param early the early frame sync configuration + */ +void ssi_tx_early_frame_sync(ssi_mod module, ssi_tx_rx_early_frame_sync early) +{ + set_register_bits(1 << SSI_EARLY_FRAME_SYNC_SHIFT, + early << SSI_EARLY_FRAME_SYNC_SHIFT, + MXC_SSISTCR, module); +} + +/*! + * This function gets the number of data words in the Transmit FIFO. + * + * @param module the module number + * @param fifo the fifo + * @return This function returns the number of words in the Tx FIFO. + */ +unsigned char ssi_tx_fifo_counter(ssi_mod module, fifo_nb fifo) +{ + unsigned char result = 0; + + if (ssi_fifo_0 == fifo) { + result = getreg_value(MXC_SSISFCSR, module); + result &= (0xF << SSI_TX_FIFO_0_COUNT_SHIFT); + result >>= SSI_TX_FIFO_0_COUNT_SHIFT; + } else { + result = getreg_value(MXC_SSISFCSR, module); + result &= (0xF << SSI_TX_FIFO_1_COUNT_SHIFT); + result >>= SSI_TX_FIFO_1_COUNT_SHIFT; + } + + return result; +} + +/*! + * This function controls the threshold at which the TFEx flag will be set. + * + * @param module the module number + * @param fifo the fifo to enable + * @param watermark the watermark + * @return This function returns the result of the operation (0 if successful, -1 otherwise). + */ +int ssi_tx_fifo_empty_watermark(ssi_mod module, + fifo_nb fifo, unsigned char watermark) +{ + int result = -1; + + if ((watermark > SSI_MIN_FIFO_WATERMARK) && + (watermark <= SSI_MAX_FIFO_WATERMARK)) { + if (ssi_fifo_0 == fifo) { + set_register_bits(0xf << SSI_TX_FIFO_0_WATERMARK_SHIFT, + watermark << + SSI_TX_FIFO_0_WATERMARK_SHIFT, + MXC_SSISFCSR, module); + } else { + set_register_bits(0xf << SSI_TX_FIFO_1_WATERMARK_SHIFT, + watermark << + SSI_TX_FIFO_1_WATERMARK_SHIFT, + MXC_SSISFCSR, module); + } + + result = 0; + } + + return result; +} + +/*! + * This function enables the Transmit FIFO. + * + * @param module the module number + * @param fifo the fifo to enable + * @param enable the state of the fifo, enabled or disabled + */ + +void ssi_tx_fifo_enable(ssi_mod module, fifo_nb fifo, bool enable) +{ + unsigned int reg; + + reg = getreg_value(MXC_SSISTCR, module); + if (enable == true) { + reg |= ((1 + fifo) << SSI_FIFO_ENABLE_0_SHIFT); + } else { + reg &= ~((1 + fifo) << SSI_FIFO_ENABLE_0_SHIFT); + } + + set_register(reg, MXC_SSISTCR, module); +} + +/*! + * This function flushes the Transmit FIFOs. + * + * @param module the module number + */ +void ssi_tx_flush_fifo(ssi_mod module) +{ + set_register_bits(0, 1 << SSI_TRANSMITTER_CLEAR_SHIFT, + MXC_SSISOR, module); +} + +/*! + * This function controls the direction of the Frame Sync signal for the transmit section. + * + * @param module the module number + * @param direction the Frame Sync signal direction + */ +void ssi_tx_frame_direction(ssi_mod module, ssi_tx_rx_direction direction) +{ + set_register_bits(1 << SSI_FRAME_DIRECTION_SHIFT, + direction << SSI_FRAME_DIRECTION_SHIFT, + MXC_SSISTCR, module); +} + +/*! + * This function configures the Transmit frame rate divider. + * + * @param module the module number + * @param ratio the divide ratio from 1 to 32 + * @return This function returns the result of the operation (0 if successful, -1 otherwise). + */ +int ssi_tx_frame_rate(ssi_mod module, unsigned char ratio) +{ + int result = -1; + + if ((ratio >= SSI_MIN_FRAME_DIVIDER_RATIO) && + (ratio <= SSI_MAX_FRAME_DIVIDER_RATIO)) { + + set_register_bits(SSI_FRAME_DIVIDER_MASK << + SSI_FRAME_RATE_DIVIDER_SHIFT, + (ratio - 1) << SSI_FRAME_RATE_DIVIDER_SHIFT, + MXC_SSISTCCR, module); + result = 0; + } + + return result; +} + +/*! + * This function controls the Frame Sync active polarity for the transmit section. + * + * @param module the module number + * @param active the Frame Sync active polarity + */ +void ssi_tx_frame_sync_active(ssi_mod module, + ssi_tx_rx_frame_sync_active active) +{ + set_register_bits(1 << SSI_FRAME_SYNC_INVERT_SHIFT, + active << SSI_FRAME_SYNC_INVERT_SHIFT, + MXC_SSISTCR, module); +} + +/*! + * This function controls the Frame Sync length (one word or one bit long) for the transmit section. + * + * @param module the module number + * @param length the Frame Sync length + */ +void ssi_tx_frame_sync_length(ssi_mod module, + ssi_tx_rx_frame_sync_length length) +{ + set_register_bits(1 << SSI_FRAME_SYNC_LENGTH_SHIFT, + length << SSI_FRAME_SYNC_LENGTH_SHIFT, + MXC_SSISTCR, module); +} + +/*! + * This function configures the time slot(s) to mask for the transmit section. + * + * @param module the module number + * @param mask the mask to indicate the time slot(s) masked + */ +void ssi_tx_mask_time_slot(ssi_mod module, unsigned int mask) +{ + set_register_bits(0xFFFFFFFF, mask, MXC_SSISTMSK, module); +} + +/*! + * This function configures the Prescale divider for the transmit section. + * + * @param module the module number + * @param divider the divide ratio from 1 to 256 + * @return This function returns the result of the operation (0 if successful, -1 otherwise). + */ +int ssi_tx_prescaler_modulus(ssi_mod module, unsigned int divider) +{ + int result = -1; + + if ((divider >= SSI_MIN_PRESCALER_MODULUS_RATIO) && + (divider <= SSI_MAX_PRESCALER_MODULUS_RATIO)) { + + set_register_bits(SSI_PRESCALER_MODULUS_MASK << + SSI_PRESCALER_MODULUS_SHIFT, + (divider - 1) << SSI_PRESCALER_MODULUS_SHIFT, + MXC_SSISTCCR, module); + result = 0; + } + + return result; +} + +/*! + * This function controls whether the MSB or LSB will be transmitted first in a sample. + * + * @param module the module number + * @param direction the shift direction + */ +void ssi_tx_shift_direction(ssi_mod module, ssi_tx_rx_shift_direction direction) +{ + set_register_bits(1 << SSI_SHIFT_DIRECTION_SHIFT, + direction << SSI_SHIFT_DIRECTION_SHIFT, + MXC_SSISTCR, module); +} + +/*! + * This function configures the Transmit word length. + * + * @param module the module number + * @param length the word length + */ +void ssi_tx_word_length(ssi_mod module, ssi_word_length length) +{ + set_register_bits(SSI_WORD_LENGTH_MASK << SSI_WORD_LENGTH_SHIFT, + length << SSI_WORD_LENGTH_SHIFT, + MXC_SSISTCCR, module); +} + +/*! + * This function initializes the driver in terms of memory of the soundcard + * and some basic HW clock settings. + * + * @return 0 on success, -1 otherwise. + */ +static int __init ssi_probe(struct platform_device *pdev) +{ + int ret = -1; + ssi_platform_data = + (struct mxc_audio_platform_data *)pdev->dev.platform_data; + if (!ssi_platform_data) { + dev_err(&pdev->dev, "can't get the platform data for SSI\n"); + return -EINVAL; + } + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + if (!res) { + dev_err(&pdev->dev, "can't get platform resource - SSI\n"); + ret = -ENOMEM; + goto err; + } + + if (pdev->id == 0) { + base_addr_1 = (void *)res->start; + } else if (pdev->id == 1) { + base_addr_2 = (void *)res->start; + } + + printk(KERN_INFO "SSI %d module loaded successfully \n", pdev->id + 1); + + return 0; + err: + return -1; + +} + +static int ssi_remove(struct platform_device *dev) +{ + return 0; +} + +static struct platform_driver mxc_ssi_driver = { + .probe = ssi_probe, + .remove = ssi_remove, + .driver = { + .name = "mxc_ssi", + }, +}; + +/*! + * This function implements the init function of the SSI device. + * This function is called when the module is loaded. + * + * @return This function returns 0. + */ +static int __init ssi_init(void) +{ + spin_lock_init(&ssi_lock); + return platform_driver_register(&mxc_ssi_driver); + +} + +/*! + * This function implements the exit function of the SPI device. + * This function is called when the module is unloaded. + * + */ +static void __exit ssi_exit(void) +{ + platform_driver_unregister(&mxc_ssi_driver); + printk(KERN_INFO "SSI module unloaded successfully\n"); +} + +module_init(ssi_init); +module_exit(ssi_exit); + +MODULE_DESCRIPTION("SSI char device driver"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/ssi/ssi.h b/drivers/mxc/ssi/ssi.h new file mode 100644 index 000000000000..22032b6616fa --- /dev/null +++ b/drivers/mxc/ssi/ssi.h @@ -0,0 +1,574 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + + /*! + * @defgroup SSI Synchronous Serial Interface (SSI) Driver + */ + + /*! + * @file ssi.h + * @brief This header file contains SSI driver functions prototypes. + * + * @ingroup SSI + */ + +#ifndef __MXC_SSI_H__ +#define __MXC_SSI_H__ + +#include "ssi_types.h" + +/*! + * This function gets the SSI fifo address. + * + * @param ssi ssi number + * @param direction To indicate playback / recording + * @return This function returns the SSI fifo address. + */ +unsigned int get_ssi_fifo_addr(unsigned int ssi, int direction); + +/*! + * This function controls the AC97 frame rate divider. + * + * @param module the module number + * @param frame_rate_divider the AC97 frame rate divider + */ +void ssi_ac97_frame_rate_divider(ssi_mod module, + unsigned char frame_rate_divider); + +/*! + * This function gets the AC97 command address register. + * + * @param module the module number + * @return This function returns the command address slot information. + */ +unsigned int ssi_ac97_get_command_address_register(ssi_mod module); + +/*! + * This function gets the AC97 command data register. + * + * @param module the module number + * @return This function returns the command data slot information. + */ +unsigned int ssi_ac97_get_command_data_register(ssi_mod module); + +/*! + * This function gets the AC97 tag register. + * + * @param module the module number + * @return This function returns the tag information. + */ +unsigned int ssi_ac97_get_tag_register(ssi_mod module); + +/*! + * This function controls the AC97 mode. + * + * @param module the module number + * @param state the AC97 mode state (enabled or disabled) + */ +void ssi_ac97_mode_enable(ssi_mod module, bool state); + +/*! + * This function controls the AC97 tag in FIFO behavior. + * + * @param module the module number + * @param state the tag in fifo behavior (Tag info stored in Rx FIFO 0 if TRUE, + * Tag info stored in SATAG register otherwise) + */ +void ssi_ac97_tag_in_fifo(ssi_mod module, bool state); + +/*! + * This function controls the AC97 read command. + * + * @param module the module number + * @param state the next AC97 command is a read command or not + */ +void ssi_ac97_read_command(ssi_mod module, bool state); + +/*! + * This function sets the AC97 command address register. + * + * @param module the module number + * @param address the command address slot information + */ +void ssi_ac97_set_command_address_register(ssi_mod module, + unsigned int address); + +/*! + * This function sets the AC97 command data register. + * + * @param module the module number + * @param data the command data slot information + */ +void ssi_ac97_set_command_data_register(ssi_mod module, unsigned int data); + +/*! + * This function sets the AC97 tag register. + * + * @param module the module number + * @param tag the tag information + */ +void ssi_ac97_set_tag_register(ssi_mod module, unsigned int tag); + +/*! + * This function controls the AC97 variable mode. + * + * @param module the module number + * @param state the AC97 variable mode state (enabled or disabled) + */ +void ssi_ac97_variable_mode(ssi_mod module, bool state); + +/*! + * This function controls the AC97 write command. + * + * @param module the module number + * @param state the next AC97 command is a write command or not + */ +void ssi_ac97_write_command(ssi_mod module, bool state); + +/*! + * This function controls the idle state of the transmit clock port during SSI internal gated mode. + * + * @param module the module number + * @param state the clock idle state + */ +void ssi_clock_idle_state(ssi_mod module, idle_state state); + +/*! + * This function turns off/on the ccm_ssi_clk to reduce power consumption. + * + * @param module the module number + * @param state the state for ccm_ssi_clk (true: turn off, else:turn on) + */ +void ssi_clock_off(ssi_mod module, bool state); + +/*! + * This function enables/disables the SSI module. + * + * @param module the module number + * @param state the state for SSI module + */ +void ssi_enable(ssi_mod module, bool state); + +/*! + * This function gets the data word in the Receive FIFO of the SSI module. + * + * @param module the module number + * @param fifo the Receive FIFO to read + * @return This function returns the read data. + */ +unsigned int ssi_get_data(ssi_mod module, fifo_nb fifo); + +/*! + * This function returns the status of the SSI module (SISR register) as a combination of status. + * + * @param module the module number + * @return This function returns the status of the SSI module. + */ +ssi_status_enable_mask ssi_get_status(ssi_mod module); + +/*! + * This function selects the I2S mode of the SSI module. + * + * @param module the module number + * @param mode the I2S mode + */ +void ssi_i2s_mode(ssi_mod module, mode_i2s mode); + +/*! + * This function disables the interrupts of the SSI module. + * + * @param module the module number + * @param mask the mask of the interrupts to disable + */ +void ssi_interrupt_disable(ssi_mod module, ssi_status_enable_mask mask); + +/*! + * This function enables the interrupts of the SSI module. + * + * @param module the module number + * @param mask the mask of the interrupts to enable + */ +void ssi_interrupt_enable(ssi_mod module, ssi_status_enable_mask mask); + +/*! + * This function enables/disables the network mode of the SSI module. + * + * @param module the module number + * @param state the network mode state + */ +void ssi_network_mode(ssi_mod module, bool state); + +/*! + * This function enables/disables the receive section of the SSI module. + * + * @param module the module number + * @param state the receive section state + */ +void ssi_receive_enable(ssi_mod module, bool state); + +/*! + * This function configures the SSI module to receive data word at bit position 0 or 23 in the Receive shift register. + * + * @param module the module number + * @param state the state to receive at bit 0 + */ +void ssi_rx_bit0(ssi_mod module, bool state); + +/*! + * This function controls the source of the clock signal used to clock the Receive shift register. + * + * @param module the module number + * @param direction the clock signal direction + */ +void ssi_rx_clock_direction(ssi_mod module, ssi_tx_rx_direction direction); + +/*! + * This function configures the divide-by-two divider of the SSI module for the receive section. + * + * @param module the module number + * @param state the divider state + */ +void ssi_rx_clock_divide_by_two(ssi_mod module, bool state); + +/*! + * This function controls which bit clock edge is used to clock in data. + * + * @param module the module number + * @param polarity the clock polarity + */ +void ssi_rx_clock_polarity(ssi_mod module, ssi_tx_rx_clock_polarity polarity); + +/*! + * This function configures a fixed divide-by-eight clock prescaler divider of the SSI module in series with the variable prescaler for the receive section. + * + * @param module the module number + * @param state the prescaler state + */ +void ssi_rx_clock_prescaler(ssi_mod module, bool state); + +/*! + * This function controls the early frame sync configuration. + * + * @param module the module number + * @param early the early frame sync configuration + */ +void ssi_rx_early_frame_sync(ssi_mod module, ssi_tx_rx_early_frame_sync early); + +/*! + * This function gets the number of data words in the Receive FIFO. + * + * @param module the module number + * @param fifo the fifo + * @return This function returns the number of words in the Rx FIFO. + */ +unsigned char ssi_rx_fifo_counter(ssi_mod module, fifo_nb fifo); + +/*! + * This function enables the Receive FIFO. + * + * @param module the module number + * @param fifo the fifo to enable + * @param enabled the state of the fifo, enabled or disabled + */ +void ssi_rx_fifo_enable(ssi_mod module, fifo_nb fifo, bool enabled); + +/*! + * This function controls the threshold at which the RFFx flag will be set. + * + * @param module the module number + * @param fifo the fifo to enable + * @param watermark the watermark + * @return This function returns the result of the operation (0 if successful, -1 otherwise). + */ +int ssi_rx_fifo_full_watermark(ssi_mod module, + fifo_nb fifo, unsigned char watermark); + +/*! + * This function flushes the Receive FIFOs. + * + * @param module the module number + */ +void ssi_rx_flush_fifo(ssi_mod module); + +/*! + * This function controls the direction of the Frame Sync signal for the receive section. + * + * @param module the module number + * @param direction the Frame Sync signal direction + */ +void ssi_rx_frame_direction(ssi_mod module, ssi_tx_rx_direction direction); + +/*! + * This function configures the Receive frame rate divider for the receive section. + * + * @param module the module number + * @param ratio the divide ratio from 1 to 32 + * @return This function returns the result of the operation (0 if successful, -1 otherwise). + */ +int ssi_rx_frame_rate(ssi_mod module, unsigned char ratio); + +/*! + * This function controls the Frame Sync active polarity for the receive section. + * + * @param module the module number + * @param active the Frame Sync active polarity + */ +void ssi_rx_frame_sync_active(ssi_mod module, + ssi_tx_rx_frame_sync_active active); + +/*! + * This function controls the Frame Sync length (one word or one bit long) for the receive section. + * + * @param module the module number + * @param length the Frame Sync length + */ +void ssi_rx_frame_sync_length(ssi_mod module, + ssi_tx_rx_frame_sync_length length); + +/*! + * This function configures the time slot(s) to mask for the receive section. + * + * @param module the module number + * @param mask the mask to indicate the time slot(s) masked + */ +void ssi_rx_mask_time_slot(ssi_mod module, unsigned int mask); + +/*! + * This function configures the Prescale divider for the receive section. + * + * @param module the module number + * @param divider the divide ratio from 1 to 256 + * @return This function returns the result of the operation (0 if successful, -1 otherwise). + */ +int ssi_rx_prescaler_modulus(ssi_mod module, unsigned int divider); + +/*! + * This function controls whether the MSB or LSB will be received first in a sample. + * + * @param module the module number + * @param direction the shift direction + */ +void ssi_rx_shift_direction(ssi_mod module, + ssi_tx_rx_shift_direction direction); + +/*! + * This function configures the Receive word length. + * + * @param module the module number + * @param length the word length + */ +void ssi_rx_word_length(ssi_mod module, ssi_word_length length); + +/*! + * This function sets the data word in the Transmit FIFO of the SSI module. + * + * @param module the module number + * @param fifo the FIFO number + * @param data the data to load in the FIFO + */ +void ssi_set_data(ssi_mod module, fifo_nb fifo, unsigned int data); + +/*! + * This function controls the number of wait states between the core and SSI. + * + * @param module the module number + * @param wait the number of wait state(s) + */ +void ssi_set_wait_states(ssi_mod module, ssi_wait_states wait); + +/*! + * This function enables/disables the synchronous mode of the SSI module. + * + * @param module the module number + * @param state the synchronous mode state + */ +void ssi_synchronous_mode(ssi_mod module, bool state); + +/*! + * This function allows the SSI module to output the SYS_CLK at the SRCK port. + * + * @param module the module number + * @param state the system clock state + */ +void ssi_system_clock(ssi_mod module, bool state); + +/*! + * This function enables/disables the transmit section of the SSI module. + * + * @param module the module number + * @param state the transmit section state + */ +void ssi_transmit_enable(ssi_mod module, bool state); + +/*! + * This function allows the SSI module to operate in the two channel mode. + * + * @param module the module number + * @param state the two channel mode state + */ +void ssi_two_channel_mode(ssi_mod module, bool state); + +/*! + * This function configures the SSI module to transmit data word from bit position 0 or 23 in the Transmit shift register. + * + * @param module the module number + * @param state the transmit from bit 0 state + */ +void ssi_tx_bit0(ssi_mod module, bool state); + +/*! + * This function controls the direction of the clock signal used to clock the Transmit shift register. + * + * @param module the module number + * @param direction the clock signal direction + */ +void ssi_tx_clock_direction(ssi_mod module, ssi_tx_rx_direction direction); + +/*! + * This function configures the divide-by-two divider of the SSI module for the transmit section. + * + * @param module the module number + * @param state the divider state + */ +void ssi_tx_clock_divide_by_two(ssi_mod module, bool state); + +/*! + * This function controls which bit clock edge is used to clock out data. + * + * @param module the module number + * @param polarity the clock polarity + */ +void ssi_tx_clock_polarity(ssi_mod module, ssi_tx_rx_clock_polarity polarity); + +/*! + * This function configures a fixed divide-by-eight clock prescaler divider of the SSI module in series with the variable prescaler for the transmit section. + * + * @param module the module number + * @param state the prescaler state + */ +void ssi_tx_clock_prescaler(ssi_mod module, bool state); + +/*! + * This function controls the early frame sync configuration for the transmit section. + * + * @param module the module number + * @param early the early frame sync configuration + */ +void ssi_tx_early_frame_sync(ssi_mod module, ssi_tx_rx_early_frame_sync early); + +/*! + * This function gets the number of data words in the Transmit FIFO. + * + * @param module the module number + * @param fifo the fifo + * @return This function returns the number of words in the Tx FIFO. + */ +unsigned char ssi_tx_fifo_counter(ssi_mod module, fifo_nb fifo); + +/*! + * This function controls the threshold at which the TFEx flag will be set. + * + * @param module the module number + * @param fifo the fifo to enable + * @param watermark the watermark + * @return This function returns the result of the operation (0 if successful, -1 otherwise). + */ +int ssi_tx_fifo_empty_watermark(ssi_mod module, fifo_nb fifo, + unsigned char watermark); + +/*! + * This function enables the Transmit FIFO. + * + * @param module the module number + * @param fifo the fifo to enable + * @param enable the state of the FIFO, enabled or disabled + */ +void ssi_tx_fifo_enable(ssi_mod module, fifo_nb fifo, bool enable); + +/*! + * This function flushes the Transmit FIFOs. + * + * @param module the module number + */ +void ssi_tx_flush_fifo(ssi_mod module); + +/*! + * This function controls the direction of the Frame Sync signal for the transmit section. + * + * @param module the module number + * @param direction the Frame Sync signal direction + */ +void ssi_tx_frame_direction(ssi_mod module, ssi_tx_rx_direction direction); + +/*! + * This function configures the Transmit frame rate divider. + * + * @param module the module number + * @param ratio the divide ratio from 1 to 32 + * @return This function returns the result of the operation (0 if successful, -1 otherwise). + */ +int ssi_tx_frame_rate(ssi_mod module, unsigned char ratio); + +/*! + * This function controls the Frame Sync active polarity for the transmit section. + * + * @param module the module number + * @param active the Frame Sync active polarity + */ +void ssi_tx_frame_sync_active(ssi_mod module, + ssi_tx_rx_frame_sync_active active); + +/*! + * This function controls the Frame Sync length (one word or one bit long) for the transmit section. + * + * @param module the module number + * @param length the Frame Sync length + */ +void ssi_tx_frame_sync_length(ssi_mod module, + ssi_tx_rx_frame_sync_length length); + +/*! + * This function configures the time slot(s) to mask for the transmit section. + * + * @param module the module number + * @param mask the mask to indicate the time slot(s) masked + */ +void ssi_tx_mask_time_slot(ssi_mod module, unsigned int mask); + +/*! + * This function configures the Prescale divider for the transmit section. + * + * @param module the module number + * @param divider the divide ratio from 1 to 256 + * @return This function returns the result of the operation (0 if successful, -1 otherwise). + */ +int ssi_tx_prescaler_modulus(ssi_mod module, unsigned int divider); + +/*! + * This function controls whether the MSB or LSB will be transmited first in a sample. + * + * @param module the module number + * @param direction the shift direction + */ +void ssi_tx_shift_direction(ssi_mod module, + ssi_tx_rx_shift_direction direction); + +/*! + * This function configures the Transmit word length. + * + * @param module the module number + * @param length the word length + */ +void ssi_tx_word_length(ssi_mod module, ssi_word_length length); + +#endif /* __MXC_SSI_H__ */ diff --git a/drivers/mxc/ssi/ssi_types.h b/drivers/mxc/ssi/ssi_types.h new file mode 100644 index 000000000000..015e65a6d2d2 --- /dev/null +++ b/drivers/mxc/ssi/ssi_types.h @@ -0,0 +1,367 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + + /*! + * @file ssi_types.h + * @brief This header file contains SSI types. + * + * @ingroup SSI + */ + +#ifndef __MXC_SSI_TYPES_H__ +#define __MXC_SSI_TYPES_H__ + +/*! + * This enumeration describes the FIFO number. + */ +typedef enum { + /*! + * FIFO 0 + */ + ssi_fifo_0 = 0, + /*! + * FIFO 1 + */ + ssi_fifo_1 = 1 +} fifo_nb; + +/*! + * This enumeration describes the clock idle state. + */ +typedef enum { + /*! + * Clock idle state is 1 + */ + clock_idle_state_1 = 0, + /*! + * Clock idle state is 0 + */ + clock_idle_state_0 = 1 +} idle_state; + +/*! + * This enumeration describes I2S mode. + */ +typedef enum { + /*! + * Normal mode + */ + i2s_normal = 0, + /*! + * Master mode + */ + i2s_master = 1, + /*! + * Slave mode + */ + i2s_slave = 2 +} mode_i2s; + +/*! + * This enumeration describes index for both SSI1 and SSI2 modules. + */ +typedef enum { + /*! + * SSI1 index + */ + SSI1 = 0, + /*! + * SSI2 index not present on MXC 91221 and MXC91311 + */ + SSI2 = 1 +} ssi_mod; + +/*! + * This enumeration describes the status/enable bits for interrupt source of the SSI module. + */ +typedef enum { + /*! + * SSI Transmit FIFO 0 empty bit + */ + ssi_tx_fifo_0_empty = 0x00000001, + /*! + * SSI Transmit FIFO 1 empty bit + */ + ssi_tx_fifo_1_empty = 0x00000002, + /*! + * SSI Receive FIFO 0 full bit + */ + ssi_rx_fifo_0_full = 0x00000004, + /*! + * SSI Receive FIFO 1 full bit + */ + ssi_rx_fifo_1_full = 0x00000008, + /*! + * SSI Receive Last Time Slot bit + */ + ssi_rls = 0x00000010, + /*! + * SSI Transmit Last Time Slot bit + */ + ssi_tls = 0x00000020, + /*! + * SSI Receive Frame Sync bit + */ + ssi_rfs = 0x00000040, + /*! + * SSI Transmit Frame Sync bit + */ + ssi_tfs = 0x00000080, + /*! + * SSI Transmitter underrun 0 bit + */ + ssi_transmitter_underrun_0 = 0x00000100, + /*! + * SSI Transmitter underrun 1 bit + */ + ssi_transmitter_underrun_1 = 0x00000200, + /*! + * SSI Receiver overrun 0 bit + */ + ssi_receiver_overrun_0 = 0x00000400, + /*! + * SSI Receiver overrun 1 bit + */ + ssi_receiver_overrun_1 = 0x00000800, + /*! + * SSI Transmit Data register empty 0 bit + */ + ssi_tx_data_reg_empty_0 = 0x00001000, + /*! + * SSI Transmit Data register empty 1 bit + */ + ssi_tx_data_reg_empty_1 = 0x00002000, + + /*! + * SSI Receive Data Ready 0 bit + */ + ssi_rx_data_ready_0 = 0x00004000, + /*! + * SSI Receive Data Ready 1 bit + */ + ssi_rx_data_ready_1 = 0x00008000, + /*! + * SSI Receive tag updated bit + */ + ssi_rx_tag_updated = 0x00010000, + /*! + * SSI Command data register updated bit + */ + ssi_cmd_data_reg_updated = 0x00020000, + /*! + * SSI Command address register updated bit + */ + ssi_cmd_address_reg_updated = 0x00040000, + /*! + * SSI Transmit interrupt enable bit + */ + ssi_tx_interrupt_enable = 0x00080000, + /*! + * SSI Transmit DMA enable bit + */ + ssi_tx_dma_interrupt_enable = 0x00100000, + /*! + * SSI Receive interrupt enable bit + */ + ssi_rx_interrupt_enable = 0x00200000, + /*! + * SSI Receive DMA enable bit + */ + ssi_rx_dma_interrupt_enable = 0x00400000, + /*! + * SSI Tx frame complete enable bit on MXC91221 & MXC91311 only + */ + ssi_tx_frame_complete = 0x00800000, + /*! + * SSI Rx frame complete on MXC91221 & MXC91311 only + */ + ssi_rx_frame_complete = 0x001000000 +} ssi_status_enable_mask; + +/*! + * This enumeration describes the clock edge to clock in or clock out data. + */ +typedef enum { + /*! + * Clock on rising edge + */ + ssi_clock_on_rising_edge = 0, + /*! + * Clock on falling edge + */ + ssi_clock_on_falling_edge = 1 +} ssi_tx_rx_clock_polarity; + +/*! + * This enumeration describes the clock direction. + */ +typedef enum { + /*! + * Clock is external + */ + ssi_tx_rx_externally = 0, + /*! + * Clock is generated internally + */ + ssi_tx_rx_internally = 1 +} ssi_tx_rx_direction; + +/*! + * This enumeration describes the early frame sync behavior. + */ +typedef enum { + /*! + * Frame Sync starts on the first data bit + */ + ssi_frame_sync_first_bit = 0, + /*! + * Frame Sync starts one bit before the first data bit + */ + ssi_frame_sync_one_bit_before = 1 +} ssi_tx_rx_early_frame_sync; + +/*! + * This enumeration describes the Frame Sync active value. + */ +typedef enum { + /*! + * Frame Sync is active when high + */ + ssi_frame_sync_active_high = 0, + /*! + * Frame Sync is active when low + */ + ssi_frame_sync_active_low = 1 +} ssi_tx_rx_frame_sync_active; + +/*! + * This enumeration describes the Frame Sync active length. + */ +typedef enum { + /*! + * Frame Sync is active when high + */ + ssi_frame_sync_one_word = 0, + /*! + * Frame Sync is active when low + */ + ssi_frame_sync_one_bit = 1 +} ssi_tx_rx_frame_sync_length; + +/*! + * This enumeration describes the Tx/Rx frame shift direction. + */ +typedef enum { + /*! + * MSB first + */ + ssi_msb_first = 0, + /*! + * LSB first + */ + ssi_lsb_first = 1 +} ssi_tx_rx_shift_direction; + +/*! + * This enumeration describes the wait state number. + */ +typedef enum { + /*! + * 0 wait state + */ + ssi_waitstates0 = 0x0, + /*! + * 1 wait state + */ + ssi_waitstates1 = 0x1, + /*! + * 2 wait states + */ + ssi_waitstates2 = 0x2, + /*! + * 3 wait states + */ + ssi_waitstates3 = 0x3 +} ssi_wait_states; + +/*! + * This enumeration describes the word length. + */ +typedef enum { + /*! + * 2 bits long + */ + ssi_2_bits = 0x0, + /*! + * 4 bits long + */ + ssi_4_bits = 0x1, + /*! + * 6 bits long + */ + ssi_6_bits = 0x2, + /*! + * 8 bits long + */ + ssi_8_bits = 0x3, + /*! + * 10 bits long + */ + ssi_10_bits = 0x4, + /*! + * 12 bits long + */ + ssi_12_bits = 0x5, + /*! + * 14 bits long + */ + ssi_14_bits = 0x6, + /*! + * 16 bits long + */ + ssi_16_bits = 0x7, + /*! + * 18 bits long + */ + ssi_18_bits = 0x8, + /*! + * 20 bits long + */ + ssi_20_bits = 0x9, + /*! + * 22 bits long + */ + ssi_22_bits = 0xA, + /*! + * 24 bits long + */ + ssi_24_bits = 0xB, + /*! + * 26 bits long + */ + ssi_26_bits = 0xC, + /*! + * 28 bits long + */ + ssi_28_bits = 0xD, + /*! + * 30 bits long + */ + ssi_30_bits = 0xE, + /*! + * 32 bits long + */ + ssi_32_bits = 0xF +} ssi_word_length; + +#endif /* __MXC_SSI_TYPES_H__ */ diff --git a/drivers/mxc/vpu/Kconfig b/drivers/mxc/vpu/Kconfig new file mode 100644 index 000000000000..d05c54311b36 --- /dev/null +++ b/drivers/mxc/vpu/Kconfig @@ -0,0 +1,21 @@ +# +# Codec configuration +# + +menu "MXC VPU(Video Processing Unit) support" + +config MXC_VPU + tristate "Support for MXC VPU(Video Processing Unit)" + depends on (ARCH_MX3 || ARCH_MX27 || ARCH_MXC92323) + default y + ---help--- + The VPU codec device provides codec function for H.264/MPEG4/H.263 + +config MXC_VPU_DEBUG + bool "MXC VPU debugging" + depends on MXC_VPU != n + help + This is an option for the developers; most people should + say N here. This enables MXC VPU driver debugging. + +endmenu diff --git a/drivers/mxc/vpu/Makefile b/drivers/mxc/vpu/Makefile new file mode 100644 index 000000000000..88e8f2c084a0 --- /dev/null +++ b/drivers/mxc/vpu/Makefile @@ -0,0 +1,10 @@ +# +# Makefile for the VPU drivers. +# + +obj-$(CONFIG_MXC_VPU) += vpu.o +vpu-objs := mxc_vpu.o mxc_vl2cc.o + +ifeq ($(CONFIG_MXC_VPU_DEBUG),y) +EXTRA_CFLAGS += -DDEBUG +endif diff --git a/drivers/mxc/vpu/mxc_vl2cc.c b/drivers/mxc/vpu/mxc_vl2cc.c new file mode 100644 index 000000000000..cd062d28aef5 --- /dev/null +++ b/drivers/mxc/vpu/mxc_vl2cc.c @@ -0,0 +1,123 @@ +/* + * Copyright 2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mxc_vl2cc.c + * + * @brief VL2CC initialization and flush operation implementation + * + * @ingroup VL2CC + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <asm/hardware.h> +#include <asm/io.h> + +#define VL2CC_CTRL_OFFSET (0x100) +#define VL2CC_AUXCTRL_OFFSET (0x104) +#define VL2CC_INVWAY_OFFSET (0x77C) +#define VL2CC_CLEANWAY_OFFSET (0x7BC) + +/*! VL2CC clock handle. */ +static struct clk *vl2cc_clk; +static u32 *vl2cc_base; + +/*! + * Initialization function of VL2CC. Remap the VL2CC base address. + * + * @return status 0 success. + */ +int vl2cc_init(u32 vl2cc_hw_base) +{ + vl2cc_base = ioremap(vl2cc_hw_base, SZ_8K - 1); + if (vl2cc_base == NULL) { + printk(KERN_INFO "vl2cc: Unable to ioremap\n"); + return -ENOMEM; + } + + vl2cc_clk = clk_get(NULL, "vl2cc_clk"); + if (IS_ERR(vl2cc_clk)) { + printk(KERN_INFO "vl2cc: Unable to get clock\n"); + iounmap(vl2cc_base); + return -EIO; + } + + printk(KERN_INFO "VL2CC initialized\n"); + return 0; +} + +/*! + * Enable VL2CC hardware + */ +void vl2cc_enable(void) +{ + volatile u32 reg; + + clk_enable(vl2cc_clk); + + /* Disable VL2CC */ + reg = __raw_readl(vl2cc_base + VL2CC_CTRL_OFFSET); + reg &= 0xFFFFFFFE; + __raw_writel(reg, vl2cc_base + VL2CC_CTRL_OFFSET); + + /* Set the latency for data RAM reads, data RAM writes, tag RAM and + * dirty RAM to 1 cycle - write 0x0 to AUX CTRL [11:0] and also + * configure the number of ways to 8 - write 8 to AUX CTRL [16:13] + */ + reg = __raw_readl(vl2cc_base + VL2CC_AUXCTRL_OFFSET); + reg &= 0xFFFE1000; /* Clear [16:13] too */ + reg |= (0x8 << 13); /* [16:13] = 8; */ + __raw_writel(reg, vl2cc_base + VL2CC_AUXCTRL_OFFSET); + + /* Invalidate the VL2CC ways - write 0xff to INV BY WAY and poll the + * register until its value is 0x0 + */ + __raw_writel(0xff, vl2cc_base + VL2CC_INVWAY_OFFSET); + while (__raw_readl(vl2cc_base + VL2CC_INVWAY_OFFSET) != 0x0) ; + + /* Enable VL2CC */ + reg = __raw_readl(vl2cc_base + VL2CC_CTRL_OFFSET); + reg |= 0x1; + __raw_writel(reg, vl2cc_base + VL2CC_CTRL_OFFSET); +} + +/*! + * Flush VL2CC + */ +void vl2cc_flush(void) +{ + __raw_writel(0xff, vl2cc_base + VL2CC_CLEANWAY_OFFSET); + while (__raw_readl(vl2cc_base + VL2CC_CLEANWAY_OFFSET) != 0x0) ; +} + +/*! + * Disable VL2CC + */ +void vl2cc_disable(void) +{ + __raw_writel(0, vl2cc_base + VL2CC_CTRL_OFFSET); + clk_disable(vl2cc_clk); +} + +/*! + * Cleanup VL2CC + */ +void vl2cc_cleanup(void) +{ + clk_put(vl2cc_clk); + iounmap(vl2cc_base); +} diff --git a/drivers/mxc/vpu/mxc_vpu.c b/drivers/mxc/vpu/mxc_vpu.c new file mode 100644 index 000000000000..be9e443f83a8 --- /dev/null +++ b/drivers/mxc/vpu/mxc_vpu.c @@ -0,0 +1,489 @@ +/* + * Copyright 2006-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mxc_vpu.c + * + * @brief VPU system initialization and file operation implementation + * + * @ingroup VPU + */ + +#include <linux/kernel.h> +#include <linux/mm.h> +#include <linux/interrupt.h> +#include <linux/autoconf.h> +#include <linux/ioport.h> +#include <linux/stat.h> +#include <linux/platform_device.h> +#include <linux/kdev_t.h> +#include <linux/dma-mapping.h> +#include <linux/wait.h> +#include <linux/list.h> +#include <linux/clk.h> + +#include <asm/uaccess.h> +#include <asm/io.h> +#include <asm/sizes.h> +#include <asm/dma-mapping.h> +#include <asm/hardware.h> + +#include <asm/arch/mxc_vpu.h> + +#define BIT_INT_CLEAR 0x00C +#define BIT_INT_STATUS 0x010 +#define BIT_INT_ENABLE 0x170 + +typedef struct vpu_t { + struct fasync_struct *async_queue; +} vpu_t; + +/* To track the allocated memory buffer */ +typedef struct memalloc_record { + struct list_head list; + vpu_mem_desc mem; +} memalloc_record; + +static DEFINE_SPINLOCK(vpu_lock); +static LIST_HEAD(head); + +static int vpu_major = 0; +static struct class *vpu_class; +static struct vpu_t vpu_data; +static u8 open_count = 0; +static struct clk *vpu_clk; + +/* implement the blocking ioctl */ +static int codec_done = 0; +static wait_queue_head_t vpu_queue; + +/*! + * Private function to free buffers + * @return status 0 success. + */ +static int vpu_free_buffers(void) +{ + struct memalloc_record *rec, *n; + vpu_mem_desc mem; + + spin_lock(&vpu_lock); + list_for_each_entry_safe(rec, n, &head, list) { + mem = rec->mem; + if (mem.cpu_addr != 0) { + dma_free_coherent(0, PAGE_ALIGN(mem.size), + (void *)mem.cpu_addr, mem.phy_addr); + pr_debug("[FREE] freed paddr=0x%08X\n", mem.phy_addr); + + /* delete from list */ + list_del(&rec->list); + kfree(rec); + } + } + spin_unlock(&vpu_lock); + + return 0; +} + +/*! + * @brief vpu interrupt handler + */ +static irqreturn_t vpu_irq_handler(int irq, void *dev_id) +{ + struct vpu_t *dev; + dev = (struct vpu_t *)dev_id; + __raw_readl(IO_ADDRESS(VPU_BASE_ADDR + BIT_INT_STATUS)); + __raw_writel(0x1, IO_ADDRESS(VPU_BASE_ADDR + BIT_INT_CLEAR)); + if (dev->async_queue) + kill_fasync(&dev->async_queue, SIGIO, POLL_IN); + + codec_done = 1; + wake_up_interruptible(&vpu_queue); + + return IRQ_HANDLED; +} + +/*! + * @brief vpu hardware enable function + * + * @return 0 on success or negative error code on error + */ +static int vpu_hardware_enable(void) +{ + if (cpu_is_mx32()) { + vl2cc_enable(); + } + clk_enable(vpu_clk); + return 0; +} + +/*! + * @brief vpu hardware disable function + * + * @return 0 on success or negative error code on error + */ +static int vpu_hardware_disable(void) +{ + if (cpu_is_mx32()) { + vl2cc_disable(); + } + clk_disable(vpu_clk); + return 0; + +} + +/*! + * @brief open function for vpu file operation + * + * @return 0 on success or negative error code on error + */ +static int vpu_open(struct inode *inode, struct file *filp) +{ + if (open_count++ == 0) { + filp->private_data = (void *)(&vpu_data); + vpu_hardware_enable(); + } else { + printk(KERN_ERR "VPU has already been opened.\n"); + return -EACCES; + } + + return 0; +} + +/*! + * @brief IO ctrl function for vpu file operation + * @param cmd IO ctrl command + * @return 0 on success or negative error code on error + */ +static int vpu_ioctl(struct inode *inode, struct file *filp, u_int cmd, + u_long arg) +{ + int ret = 0; + + switch (cmd) { + case VPU_IOC_PHYMEM_ALLOC: + { + struct memalloc_record *rec; + + rec = kzalloc(sizeof(*rec), GFP_KERNEL); + if (!rec) + return -ENOMEM; + + ret = copy_from_user(&(rec->mem), (vpu_mem_desc *) arg, + sizeof(vpu_mem_desc)); + if (ret) { + kfree(rec); + return -EFAULT; + } + + pr_debug("[ALLOC] mem alloc size = 0x%x\n", + rec->mem.size); + rec->mem.cpu_addr = (unsigned long) + dma_alloc_coherent(NULL, + PAGE_ALIGN(rec->mem.size), + (dma_addr_t + *) (&(rec->mem.phy_addr)), + GFP_DMA | GFP_KERNEL); + pr_debug("[ALLOC] mem alloc cpu_addr = 0x%x\n", + rec->mem.cpu_addr); + if ((void *)(rec->mem.cpu_addr) == NULL) { + kfree(rec); + printk(KERN_ERR + "Physical memory allocation error!\n"); + ret = -1; + break; + } + ret = copy_to_user((void __user *)arg, &(rec->mem), + sizeof(vpu_mem_desc)); + if (ret) { + kfree(rec); + ret = -EFAULT; + break; + } + + spin_lock(&vpu_lock); + list_add(&rec->list, &head); + spin_unlock(&vpu_lock); + + break; + } + case VPU_IOC_PHYMEM_FREE: + { + struct memalloc_record *rec, *n; + vpu_mem_desc vpu_mem; + + ret = copy_from_user(&vpu_mem, (vpu_mem_desc *) arg, + sizeof(vpu_mem_desc)); + if (ret) + return -EACCES; + + pr_debug("[FREE] mem freed cpu_addr = 0x%x\n", + vpu_mem.cpu_addr); + if ((void *)vpu_mem.cpu_addr != NULL) { + dma_free_coherent(NULL, + PAGE_ALIGN(vpu_mem.size), + (void *)vpu_mem.cpu_addr, + (dma_addr_t) vpu_mem. + phy_addr); + } + + spin_lock(&vpu_lock); + list_for_each_entry_safe(rec, n, &head, list) { + if (rec->mem.cpu_addr == vpu_mem.cpu_addr) { + /* delete from list */ + list_del(&rec->list); + kfree(rec); + break; + } + } + spin_unlock(&vpu_lock); + + break; + } + case VPU_IOC_WAIT4INT: + { + u_long timeout = (u_long) arg; + if (!wait_event_interruptible_timeout + (vpu_queue, codec_done != 0, + msecs_to_jiffies(timeout))) { + printk(KERN_WARNING "VPU blocking: timeout.\n"); + ret = -ETIME; + } else if (signal_pending(current)) { + printk(KERN_WARNING + "VPU interrupt received.\n"); + ret = -ERESTARTSYS; + } + + codec_done = 0; + break; + } + case VPU_IOC_VL2CC_FLUSH: + if (cpu_is_mx32()) { + vl2cc_flush(); + } + break; + case VPU_IOC_REG_DUMP: + break; + case VPU_IOC_PHYMEM_DUMP: + break; + default: + { + printk(KERN_ERR "No such IOCTL, cmd is %d\n", cmd); + break; + } + } + return ret; +} + +/*! + * @brief Release function for vpu file operation + * @return 0 on success or negative error code on error + */ +static int vpu_release(struct inode *inode, struct file *filp) +{ + if (--open_count == 0) { + vpu_free_buffers(); + vpu_hardware_disable(); + } + + return 0; +} + +/*! + * @brief fasync function for vpu file operation + * @return 0 on success or negative error code on error + */ +static int vpu_fasync(int fd, struct file *filp, int mode) +{ + struct vpu_t *dev = (struct vpu_t *)filp->private_data; + return fasync_helper(fd, filp, mode, &dev->async_queue); +} + +/*! + * @brief memory map function of harware registers for vpu file operation + * @return 0 on success or negative error code on error + */ +static int vpu_map_hwregs(struct file *fp, struct vm_area_struct *vm) +{ + unsigned long pfn; + + vm->vm_flags |= VM_IO | VM_RESERVED; + vm->vm_page_prot = pgprot_noncached(vm->vm_page_prot); + pfn = VPU_BASE_ADDR >> PAGE_SHIFT; + pr_debug("size=0x%x, page no.=0x%x\n", + (int)(vm->vm_end - vm->vm_start), (int)pfn); + return remap_pfn_range(vm, vm->vm_start, pfn, vm->vm_end - vm->vm_start, + vm->vm_page_prot) ? -EAGAIN : 0; +} + +/*! + * @brief memory map function of memory for vpu file operation + * @return 0 on success or negative error code on error + */ +static int vpu_map_mem(struct file *fp, struct vm_area_struct *vm) +{ + int request_size; + request_size = vm->vm_end - vm->vm_start; + + pr_debug(" start=0x%x, pgoff=0x%x, size=0x%x\n", + (unsigned int)(vm->vm_start), (unsigned int)(vm->vm_pgoff), + request_size); + + vm->vm_flags |= VM_IO | VM_RESERVED; + vm->vm_page_prot = pgprot_noncached(vm->vm_page_prot); + + return remap_pfn_range(vm, vm->vm_start, vm->vm_pgoff, + request_size, vm->vm_page_prot) ? -EAGAIN : 0; + +} + +/*! + * @brief memory map interface for vpu file operation + * @return 0 on success or negative error code on error + */ +static int vpu_mmap(struct file *fp, struct vm_area_struct *vm) +{ + if (vm->vm_pgoff) + return vpu_map_mem(fp, vm); + else + return vpu_map_hwregs(fp, vm); +} + +struct file_operations vpu_fops = { + .owner = THIS_MODULE, + .open = vpu_open, + .ioctl = vpu_ioctl, + .release = vpu_release, + .fasync = vpu_fasync, + .mmap = vpu_mmap, +}; + +/*! + * This function is called by the driver framework to initialize the vpu device. + * @param dev The device structure for the vpu passed in by the framework. + * @return 0 on success or negative error code on error + */ +static int vpu_dev_probe(struct platform_device *pdev) +{ + int err = 0; + struct class_device *temp_class; + struct resource *res; + + if (cpu_is_mx32()) { + /* Obtain VL2CC base address */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + printk(KERN_ERR "vpu: unable to get VL2CC base\n"); + return -ENOENT; + } + + err = vl2cc_init(res->start); + if (err != 0) + return err; + } + + vpu_major = register_chrdev(vpu_major, "mxc_vpu", &vpu_fops); + if (vpu_major < 0) { + printk(KERN_ERR "vpu: unable to get a major for VPU\n"); + err = -EBUSY; + goto error; + } + + vpu_class = class_create(THIS_MODULE, "mxc_vpu"); + if (IS_ERR(vpu_class)) { + err = PTR_ERR(vpu_class); + goto err_out_chrdev; + } + + temp_class = class_device_create(vpu_class, NULL, + MKDEV(vpu_major, 0), NULL, "mxc_vpu"); + if (IS_ERR(temp_class)) { + err = PTR_ERR(temp_class); + goto err_out_class; + } + + vpu_clk = clk_get(&pdev->dev, "vpu_clk"); + if (IS_ERR(vpu_clk)) { + err = -ENOENT; + goto err_out_class; + } + + err = request_irq(INT_VPU, vpu_irq_handler, 0, "VPU_CODEC_IRQ", + (void *)(&vpu_data)); + if (err) + goto err_out_class; + + printk(KERN_INFO "VPU initialized\n"); + goto out; + + err_out_class: + class_device_destroy(vpu_class, MKDEV(vpu_major, 0)); + class_destroy(vpu_class); + err_out_chrdev: + unregister_chrdev(vpu_major, "mxc_vpu"); + error: + if (cpu_is_mx32()) { + vl2cc_cleanup(); + } + out: + return err; +} + +/*! Driver definition + * + */ +static struct platform_driver mxcvpu_driver = { + .driver = { + .name = "mxc_vpu", + }, + .probe = vpu_dev_probe, +}; + +static int __init vpu_init(void) +{ + int ret = platform_driver_register(&mxcvpu_driver); + + init_waitqueue_head(&vpu_queue); + + return ret; +} + +static void __exit vpu_exit(void) +{ + free_irq(INT_VPU, (void *)(&vpu_data)); + if (vpu_major > 0) { + class_device_destroy(vpu_class, MKDEV(vpu_major, 0)); + class_destroy(vpu_class); + if (unregister_chrdev(vpu_major, "mxc_vpu") < 0) { + printk(KERN_ERR + "Failed to unregister vpu from devfs\n"); + return; + } + vpu_major = 0; + } + + if (cpu_is_mx32()) { + vl2cc_cleanup(); + } + + clk_put(vpu_clk); + + platform_driver_unregister(&mxcvpu_driver); + return; +} + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("Linux VPU driver for Freescale i.MX27"); +MODULE_LICENSE("GPL"); + +module_init(vpu_init); +module_exit(vpu_exit); diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig index 9af05a2f4af3..323f5e7c0c39 100644 --- a/drivers/net/Kconfig +++ b/drivers/net/Kconfig @@ -1807,11 +1807,11 @@ config 68360_ENET the Motorola 68360 processor. config FEC - bool "FEC ethernet controller (of ColdFire CPUs)" - depends on M523x || M527x || M5272 || M528x || M520x + tristate "FEC ethernet controller" + depends on M523x || M527x || M5272 || M528x || M520x || ARCH_MX27 || ARCH_MX33 help Say Y here if you want to use the built-in 10/100 Fast ethernet - controller on some Motorola ColdFire processors. + controller on some Motorola/Freescale processors. config FEC2 bool "Second FEC ethernet controller (on some ColdFire CPUs)" diff --git a/drivers/net/cs89x0.c b/drivers/net/cs89x0.c index 571750975137..1270ac305edc 100644 --- a/drivers/net/cs89x0.c +++ b/drivers/net/cs89x0.c @@ -194,6 +194,17 @@ static unsigned int cs8900_irq_map[] = {IRQ_IXDP2X01_CS8900, 0, 0, 0}; #define CIRRUS_DEFAULT_IRQ VH_INTC_INT_NUM_CASCADED_INTERRUPT_1 /* Event inputs bank 1 - ID 35/bit 3 */ static unsigned int netcard_portlist[] __initdata = {CIRRUS_DEFAULT_BASE, 0}; static unsigned int cs8900_irq_map[] = {CIRRUS_DEFAULT_IRQ, 0, 0, 0}; +#elif defined(CONFIG_ARCH_MXC) +/*! Null terminated portlist used to probe for the CS8900A device on ISA Bus + * Add 3 to reset the page window before probing (fixes eth probe when deployed + * using nand_boot) + */ +extern unsigned int netcard_portlist[2]; +/*! + * The CS8900A has 4 IRQ pins, which is software selectable, CS8900A interrupt + * pin 0 is used for interrupt generation. + */ +extern unsigned int cs8900_irq_map[4]; #else static unsigned int netcard_portlist[] __initdata = { 0x300, 0x320, 0x340, 0x360, 0x200, 0x220, 0x240, 0x260, 0x280, 0x2a0, 0x2c0, 0x2e0, 0}; @@ -802,7 +813,7 @@ cs89x0_probe1(struct net_device *dev, int ioaddr, int modular) } else { i = lp->isa_config & INT_NO_MASK; if (lp->chip_type == CS8900) { -#if defined(CONFIG_MACH_IXDP2351) || defined(CONFIG_ARCH_IXDP2X01) || defined(CONFIG_ARCH_PNX010X) +#if defined(CONFIG_MACH_IXDP2351) || defined(CONFIG_ARCH_IXDP2X01) || defined(CONFIG_ARCH_PNX010X) || defined(CONFIG_ARCH_MXC) i = cs8900_irq_map[0]; #else /* Translate the IRQ using the IRQ mapping table. */ @@ -1029,6 +1040,7 @@ skip_this_frame: void __init reset_chip(struct net_device *dev) { +#if !defined(CONFIG_ARCH_MXC) #if !defined(CONFIG_MACH_IXDP2351) && !defined(CONFIG_ARCH_IXDP2X01) struct net_local *lp = netdev_priv(dev); int ioaddr = dev->base_addr; @@ -1057,6 +1069,7 @@ void __init reset_chip(struct net_device *dev) reset_start_time = jiffies; while( (readreg(dev, PP_SelfST) & INIT_DONE) == 0 && jiffies - reset_start_time < 2) ; +#endif /* !CONFIG_ARCH_MXC */ } @@ -1304,7 +1317,7 @@ net_open(struct net_device *dev) else #endif { -#if !defined(CONFIG_MACH_IXDP2351) && !defined(CONFIG_ARCH_IXDP2X01) && !defined(CONFIG_ARCH_PNX010X) +#if !defined(CONFIG_MACH_IXDP2351) && !defined(CONFIG_ARCH_IXDP2X01) && !defined(CONFIG_ARCH_PNX010X) && !defined(CONFIG_ARCH_MXC) if (((1 << dev->irq) & lp->irq_map) == 0) { printk(KERN_ERR "%s: IRQ %d is not in our map of allowable IRQs, which is %x\n", dev->name, dev->irq, lp->irq_map); diff --git a/drivers/net/fec.c b/drivers/net/fec.c index 0fbf1bbbaee9..99b3efd77738 100644 --- a/drivers/net/fec.c +++ b/drivers/net/fec.c @@ -24,6 +24,9 @@ * Bug fixes and cleanup by Philippe De Muyter (phdm@macqel.be) * Copyright (c) 2004-2006 Macq Electronique SA. */ +/* + * Copyright 2006-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ #include <linux/module.h> #include <linux/kernel.h> @@ -42,6 +45,7 @@ #include <linux/spinlock.h> #include <linux/workqueue.h> #include <linux/bitops.h> +#include <linux/clk.h> #include <asm/irq.h> #include <asm/uaccess.h> @@ -55,12 +59,21 @@ #include <asm/coldfire.h> #include <asm/mcfsim.h> #include "fec.h" +#define FEC_ALIGNMENT (0x03) /*FEC needs 4bytes alignment*/ +#elif defined(CONFIG_ARCH_MXC) +#include <asm/arch/hardware.h> +#include <asm/arch/iim.h> +#include "fec.h" +#define FEC_ALIGNMENT (0x0F) /*FEC needs 128bits(32bytes) alignment*/ #else #include <asm/8xx_immap.h> #include <asm/mpc8xx.h> #include "commproc.h" +#define FEC_ALIGNMENT (0x03) /*FEC needs 4bytes alignment */ #endif +#define FEC_ADDR_ALIGNMENT(x) ((unsigned char *)(((unsigned long )(x) + (FEC_ALIGNMENT)) & (~FEC_ALIGNMENT))) + #if defined(CONFIG_FEC2) #define FEC_MAX_PORTS 2 #else @@ -82,6 +95,8 @@ static unsigned int fec_hw[] = { (MCF_MBAR+0x30000), #elif defined(CONFIG_M532x) (MCF_MBAR+0xfc030000), +#elif defined(CONFIG_ARCH_MXC) + (IO_ADDRESS(FEC_BASE_ADDR)), #else &(((immap_t *)IMAP_ADDR)->im_cpm.cp_fec), #endif @@ -159,6 +174,12 @@ typedef struct { #define FEC_ENET_MII ((uint)0x00800000) /* MII interrupt */ #define FEC_ENET_EBERR ((uint)0x00400000) /* SDMA bus error */ +#ifndef CONFIG_ARCH_MXC +#define FEC_ENET_MASK ((uint)0xffc00000) +#else +#define FEC_ENET_MASK ((uint)0xfff80000) +#endif + /* The FEC stores dest/src/type, data, and checksum for receive packets. */ #define PKT_MAXBUF_SIZE 1518 @@ -172,7 +193,7 @@ typedef struct { * account when setting it. */ #if defined(CONFIG_M523x) || defined(CONFIG_M527x) || defined(CONFIG_M528x) || \ - defined(CONFIG_M520x) || defined(CONFIG_M532x) + defined(CONFIG_M520x) || defined(CONFIG_M532x) || defined(CONFIG_ARCH_MXC) #define OPT_FRAME_SIZE (PKT_MAXBUF_SIZE << 16) #else #define OPT_FRAME_SIZE 0 @@ -195,11 +216,13 @@ struct fec_enet_private { /* The saved address of a sent-in-place packet/buffer, for skfree(). */ unsigned char *tx_bounce[TX_RING_SIZE]; struct sk_buff* tx_skbuff[TX_RING_SIZE]; + struct sk_buff* rx_skbuff[RX_RING_SIZE]; ushort skb_cur; ushort skb_dirty; /* CPM dual port RAM relative addresses. */ + void * cbd_mem_base; /* save the virtual base address of rx&tx buffer descripter */ cbd_t *rx_bd_base; /* Address of Rx and Tx buffers. */ cbd_t *tx_bd_base; cbd_t *cur_rx, *cur_tx; /* The next free ring entry */ @@ -213,6 +236,7 @@ struct fec_enet_private { uint phy_speed; phy_info_t const *phy; struct work_struct phy_task; + struct net_device *net; uint sequence_done; uint mii_phy_task_queued; @@ -224,6 +248,8 @@ struct fec_enet_private { int link; int old_link; int full_duplex; + + struct clk *clk; }; static int fec_enet_open(struct net_device *dev); @@ -238,6 +264,17 @@ static void fec_restart(struct net_device *dev, int duplex); static void fec_stop(struct net_device *dev); static void fec_set_mac_address(struct net_device *dev); +static void __inline__ fec_dcache_inv_range(void * start, void * end); +static void __inline__ fec_dcache_flush_range(void * start, void * end); + +/* + * fec_copy_threshold controls the copy when recieving ethernet frame. + * If ethernet header aligns 4bytes, the ip header and upper header will not aligns 4bytes. + * The resean is ethernet header is 14bytes. + * And the max size of tcp & ip header is 128bytes. Normally it is 40bytes. + * So I set the default value between 128 to 256. + */ +static int fec_copy_threshold = 192; /* MII processing. We keep this as simple as possible. Requests are * placed on the list (if there is room). When the request is finished @@ -346,10 +383,10 @@ fec_enet_start_xmit(struct sk_buff *skb, struct net_device *dev) * 4-byte boundaries. Use bounce buffers to copy data * and get it aligned. Ugh. */ - if (bdp->cbd_bufaddr & 0x3) { + if ((bdp->cbd_bufaddr) & FEC_ALIGNMENT) { unsigned int index; index = bdp - fep->tx_bd_base; - memcpy(fep->tx_bounce[index], (void *) bdp->cbd_bufaddr, bdp->cbd_datlen); + memcpy(fep->tx_bounce[index], (void *) skb->data, skb->len); bdp->cbd_bufaddr = __pa(fep->tx_bounce[index]); } @@ -363,8 +400,8 @@ fec_enet_start_xmit(struct sk_buff *skb, struct net_device *dev) /* Push the data cache so the CPM does not get stale memory * data. */ - flush_dcache_range((unsigned long)skb->data, - (unsigned long)skb->data + skb->len); + fec_dcache_flush_range(__va(bdp->cbd_bufaddr), __va(bdp->cbd_bufaddr) + + bdp->cbd_datlen); spin_lock_irq(&fep->lock); @@ -379,7 +416,7 @@ fec_enet_start_xmit(struct sk_buff *skb, struct net_device *dev) dev->trans_start = jiffies; /* Trigger transmission start */ - fecp->fec_x_des_active = 0; + fecp->fec_x_des_active = 0x01000000; /* If this was the last BD in the ring, start at the beginning again. */ @@ -465,7 +502,8 @@ fec_enet_interrupt(int irq, void * dev_id) /* Handle receive event in its own function. */ - if (int_events & FEC_ENET_RXF) { + if (int_events & (FEC_ENET_RXF | FEC_ENET_RXB)) { + handled = 1; fec_enet_rx(dev); } @@ -474,7 +512,7 @@ fec_enet_interrupt(int irq, void * dev_id) descriptors. FEC handles all errors, we just discover them as part of the transmit process. */ - if (int_events & FEC_ENET_TXF) { + if (int_events & (FEC_ENET_TXF | FEC_ENET_TXB)) { handled = 1; fec_enet_tx(dev); } @@ -576,6 +614,7 @@ fec_enet_rx(struct net_device *dev) struct sk_buff *skb; ushort pkt_len; __u8 *data; + int rx_index ; #ifdef CONFIG_M532x flush_cache_all(); @@ -590,7 +629,7 @@ fec_enet_rx(struct net_device *dev) bdp = fep->cur_rx; while (!((status = bdp->cbd_sc) & BD_ENET_RX_EMPTY)) { - + rx_index = bdp - fep->rx_bd_base; #ifndef final_version /* Since we have allocated space to hold a complete frame, * the last indicator should be set. @@ -634,20 +673,37 @@ while (!((status = bdp->cbd_sc) & BD_ENET_RX_EMPTY)) { pkt_len = bdp->cbd_datlen; dev->stats.rx_bytes += pkt_len; data = (__u8*)__va(bdp->cbd_bufaddr); + fec_dcache_inv_range(data, data+pkt_len -4); /* This does 16 byte alignment, exactly what we need. * The packet length includes FCS, but we don't want to * include that when passing upstream as it messes up * bridging applications. */ - skb = dev_alloc_skb(pkt_len-4); + if ((pkt_len - 4) < fec_copy_threshold) { + skb = dev_alloc_skb(pkt_len); + } else { + skb = dev_alloc_skb(FEC_ENET_RX_FRSIZE); + } if (skb == NULL) { printk("%s: Memory squeeze, dropping packet.\n", dev->name); dev->stats.rx_dropped++; } else { - skb_put(skb,pkt_len-4); /* Make room */ + if ((pkt_len - 4) < fec_copy_threshold) { + skb_reserve(skb, 2); /*skip 2bytes, so ipheader is align 4bytes*/ + skb_put(skb,pkt_len-4); /* Make room */ skb_copy_to_linear_data(skb, data, pkt_len-4); + pkt_len-4, 0); + } else { + struct sk_buff * pskb = fep->rx_skbuff[rx_index]; + + fep->rx_skbuff[rx_index] = skb; + skb->data = FEC_ADDR_ALIGNMENT(skb->data); + bdp->cbd_bufaddr = __pa(skb->data); + skb_put(pskb,pkt_len-4); /* Make room */ + skb = pskb; + } skb->protocol=eth_type_trans(skb,dev); netif_rx(skb); } @@ -674,7 +730,7 @@ while (!((status = bdp->cbd_sc) & BD_ENET_RX_EMPTY)) { * incoming frames. On a heavily loaded network, we should be * able to keep up at the expense of system resources. */ - fecp->fec_r_des_active = 0; + fecp->fec_r_des_active = 0x01000000; #endif } /* while (!((status = bdp->cbd_sc) & BD_ENET_RX_EMPTY)) */ fep->cur_rx = (cbd_t *)bdp; @@ -1234,6 +1290,21 @@ mii_link_interrupt(int irq, void * dev_id); #if defined(CONFIG_M5272) /* + * * do some initializtion based architecture of this chip + * */ +static void __inline__ fec_arch_init(void) +{ + return; +} +/* + * * do some cleanup based architecture of this chip + * */ +static void __inline__ fec_arch_exit(void) +{ + return; +} + +/* * Code specific to Coldfire 5272 setup. */ static void __inline__ fec_request_intrs(struct net_device *dev) @@ -1339,10 +1410,35 @@ static void __inline__ fec_localhw_setup(void) } /* - * Do not need to make region uncached on 5272. + * invalidate dcache related with the virtual memory range(start, end) + */ +static void __inline__ fec_dcache_inv_range(void * start, void * end) +{ + return ; +} + +/* + * flush dcache related with the virtual memory range(start, end) + */ +static void __inline__ fec_dcache_flush_range(void * start, void * end) +{ + return ; +} + +/* + * map memory space (addr, addr+size) to uncachable erea. */ -static void __inline__ fec_uncache(unsigned long addr) +static unsigned long __inline__ fec_map_uncache(unsigned long addr, int size) { + return addr; +} + +/* + * unmap memory erea started with addr from uncachable erea. + */ +static void __inline__ fec_unmap_uncache(void * addr) +{ + return ; } /* ------------------------------------------------------------------------- */ @@ -1350,6 +1446,22 @@ static void __inline__ fec_uncache(unsigned long addr) #elif defined(CONFIG_M523x) || defined(CONFIG_M527x) || defined(CONFIG_M528x) /* + * do some initializtion based architecture of this chip + */ +static void __inline__ fec_arch_init(void) +{ + return; +} + +/* + * do some cleanup based architecture of this chip + */ +static void __inline__ fec_arch_exit(void) +{ + return; +} + +/* * Code specific to Coldfire 5230/5231/5232/5234/5235, * the 5270/5271/5274/5275 and 5280/5282 setups. */ @@ -1509,17 +1621,55 @@ static void __inline__ fec_phy_ack_intr(void) static void __inline__ fec_localhw_setup(void) { } +/* + * invalidate dcache related with the virtual memory range(start, end) + */ +static void __inline__ fec_dcache_inv_range(void * start, void * end) +{ + return ; +} /* - * Do not need to make region uncached on 5272. + * flush dcache related with the virtual memory range(start, end) */ -static void __inline__ fec_uncache(unsigned long addr) +static void __inline__ fec_dcache_flush_range(void * start, void * end) { + return ; +} + +/* + * map memory space (addr, addr+size) to uncachable erea. + */ +static unsigned long __inline__ fec_map_uncache(unsigned long addr, int size) +{ + return addr; +} + +/* + * unmap memory erea started with addr from uncachable erea. + */ +static void __inline__ fec_unmap_uncache(void * addr) +{ + return ; } /* ------------------------------------------------------------------------- */ #elif defined(CONFIG_M520x) +/* + * do some initializtion based architecture of this chip + */ +static void __inline__ fec_arch_init(void) +{ + return; +} +/* + * do some cleanup based architecture of this chip + */ +static void __inline__ fec_arch_exit(void) +{ + return; +} /* * Code specific to Coldfire 520x @@ -1641,13 +1791,59 @@ static void __inline__ fec_localhw_setup(void) { } -static void __inline__ fec_uncache(unsigned long addr) +/* + * invalidate dcache related with the virtual memory range(start, end) + */ +static void __inline__ fec_dcache_inv_range(void * start, void * end) +{ + return ; +} + +/* + * flush dcache related with the virtual memory range(start, end) + */ +static void __inline__ fec_dcache_flush_range(void * start, void * end) +{ + return ; +} + +/* + * map memory space (addr, addr+size) to uncachable erea. + */ +static unsigned long __inline__ fec_map_uncache(unsigned long addr, int size) { + return addr; } +/* + * unmap memory erea started with addr from uncachable erea. + */ +static void __inline__ fec_unmap_uncache(void * addr) +{ + return ; +} + + /* ------------------------------------------------------------------------- */ #elif defined(CONFIG_M532x) + +/* + * do some initializtion based architecture of this chip + */ +static void __inline__ fec_arch_init(void) +{ + return; +} + +/* + * do some cleanup based architecture of this chip + */ +static void __inline__ fec_arch_exit(void) +{ + return; +} + /* * Code specific for M532x */ @@ -1791,16 +1987,213 @@ static void __inline__ fec_localhw_setup(void) } /* - * Do not need to make region uncached on 532x. + * invalidate dcache related with the virtual memory range(start, end) */ -static void __inline__ fec_uncache(unsigned long addr) +static void __inline__ fec_dcache_inv_range(void * start, void * end) { + return ; +} + +/* + * flush dcache related with the virtual memory range(start, end) + */ +static void __inline__ fec_dcache_flush_range(void * start, void * end) +{ + return ; +} + +/* + * map memory space (addr, addr+size) to uncachable erea. + */ +static unsigned long __inline__ fec_map_uncache(unsigned long addr, int size) +{ + return addr; +} + +/* + * unmap memory erea started with addr from uncachable erea. + */ +static void __inline__ fec_unmap_uncache(void * addr) +{ + return ; } /* ------------------------------------------------------------------------- */ +#elif defined(CONFIG_ARCH_MXC) + +extern void gpio_fec_active(void); +extern void gpio_fec_inactive(void); +extern unsigned int expio_intr_fec; + +/* + * do some initializtion based architecture of this chip + */ +static void __inline__ fec_arch_init(void) +{ + struct clk *clk; + gpio_fec_active(); + clk = clk_get(NULL, "fec_clk"); + clk_enable(clk); + clk_put(clk); + return; +} +/* + * do some cleanup based architecture of this chip + */ +static void __inline__ fec_arch_exit(void) +{ + struct clk *clk; + clk = clk_get(NULL, "fec_clk"); + clk_disable(clk); + clk_put(clk); + gpio_fec_inactive(); + return; +} + +/* + * Code specific to Freescale i.MXC + */ +static void __inline__ fec_request_intrs(struct net_device *dev) +{ + /* Setup interrupt handlers. */ + if (request_irq(INT_FEC, fec_enet_interrupt, 0, "fec", dev) != 0) + panic("FEC: Could not allocate FEC IRQ(%d)!\n", INT_FEC); + /* TODO: disable now due to CPLD issue */ + if (request_irq(expio_intr_fec, mii_link_interrupt, 0, "fec(MII)", dev) != 0) + panic("FEC: Could not allocate FEC(MII) IRQ(%d)!\n", expio_intr_fec); + disable_irq(expio_intr_fec); +} + +static void __inline__ fec_set_mii(struct net_device *dev, struct fec_enet_private *fep) +{ + u32 rate; + struct clk *clk; + volatile fec_t *fecp; + fecp = fep->hwp; + fecp->fec_r_cntrl = OPT_FRAME_SIZE | 0x04; + fecp->fec_x_cntrl = 0x00; + + /* + * Set MII speed to 2.5 MHz + */ + clk = clk_get(NULL, "fec_clk"); + rate = clk_get_rate(clk); + clk_put(clk); + + fep->phy_speed = + ((((rate / 2 + 4999999) / 2500000) / 2) & 0x3F) << 1; + fecp->fec_mii_speed = fep->phy_speed; + fec_restart(dev, 0); +} + +#define FEC_IIM_BASE IO_ADDRESS(IIM_BASE_ADDR) +static void __inline__ fec_get_mac(struct net_device *dev) +{ + struct fec_enet_private *fep = netdev_priv(dev); + volatile fec_t *fecp; + unsigned char *iap, tmpaddr[ETH_ALEN]; + int i; + unsigned long fec_mac_base = FEC_IIM_BASE + MXC_IIMKEY0; + fecp = fep->hwp; + + if (cpu_is_mx27_rev(CHIP_REV_2_0) > 0) { + fec_mac_base = FEC_IIM_BASE + MXC_IIMMAC; + } + + /* + * Get MAC address from IIM. + * If it is all 1's or 0's, use the default. + */ + for (i = 0; i < ETH_ALEN; i++) { + tmpaddr[ETH_ALEN-1-i] = __raw_readb(fec_mac_base + i * 4); + } + iap = &tmpaddr[0]; + + if ((iap[0] == 0) && (iap[1] == 0) && (iap[2] == 0) && + (iap[3] == 0) && (iap[4] == 0) && (iap[5] == 0)) + iap = fec_mac_default; + if ((iap[0] == 0xff) && (iap[1] == 0xff) && (iap[2] == 0xff) && + (iap[3] == 0xff) && (iap[4] == 0xff) && (iap[5] == 0xff)) + iap = fec_mac_default; + + memcpy(dev->dev_addr, iap, ETH_ALEN); + + /* Adjust MAC if using default MAC address */ + if (iap == fec_mac_default) + dev->dev_addr[ETH_ALEN-1] = fec_mac_default[ETH_ALEN-1] + fep->index; +} + +static void __inline__ fec_enable_phy_intr(void) +{ + enable_irq(expio_intr_fec); +} + +static void __inline__ fec_disable_phy_intr(void) +{ + disable_irq(expio_intr_fec); +} + +static void __inline__ fec_phy_ack_intr(void) +{ + disable_irq(expio_intr_fec); +} + +static void __inline__ fec_localhw_setup(void) +{ +} + +/* + * invalidate dcache related with the virtual memory range(start, end) + */ +static void __inline__ fec_dcache_inv_range(void * start, void * end) +{ + dma_sync_single(NULL, (unsigned long)__pa(start), (unsigned long) (end-start), DMA_FROM_DEVICE); + return ; +} + +/* + * flush dcache related with the virtual memory range(start, end) + */ +static void __inline__ fec_dcache_flush_range(void * start, void * end) +{ + dma_sync_single(NULL, (unsigned long)__pa(start), (unsigned long) (end-start), DMA_BIDIRECTIONAL); + return ; +} + +/* + * map memory space (addr, addr+size) to uncachable erea. + */ +static unsigned long __inline__ fec_map_uncache(unsigned long addr, int size) +{ + return (unsigned long)ioremap(__pa(addr), size); +} + +/* + * unmap memory erea started with addr from uncachable erea. + */ +static void __inline__ fec_unmap_uncache(void * addr) +{ + return iounmap(addr); +} + +/* ------------------------------------------------------------------------- */ #else +/* + * do some initializtion based architecture of this chip + */ +static void __inline__ fec_arch_init(void) +{ + return; +} +/* + * do some cleanup based architecture of this chip + */ +static void __inline__ fec_arch_exit(void) +{ + return; +} /* * Code specific to the MPC860T setup. @@ -1907,12 +2300,40 @@ static void __inline__ fec_localhw_setup(void) fecp->fec_fun_code = 0x78000000; } -static void __inline__ fec_uncache(unsigned long addr) +/* + * invalidate dcache related with the virtual memory range(start, end) + */ +static void __inline__ fec_dcache_inv_range(void * start, void * end) +{ + return ; +} + +/* + * flush dcache related with the virtual memory range(start, end) + */ +static void __inline__ fec_dcache_flush_range(void * start, void * end) +{ + return ; +} + +/* + * map memory space (addr, addr+size) to uncachable erea. + */ +static unsigned long __inline__ fec_map_uncache(unsigned long addr, int size) { pte_t *pte; pte = va_to_pte(mem_addr); pte_val(*pte) |= _PAGE_NO_CACHE; flush_tlb_page(init_mm.mmap, mem_addr); + return addr; +} + +/* + * * unmap memory erea started with addr from uncachable erea. + * */ +static void __inline__ fec_unmap_uncache(void * addr) +{ + return ; } #endif @@ -1959,6 +2380,7 @@ static void mii_display_config(struct work_struct *work) { struct fec_enet_private *fep = container_of(work, struct fec_enet_private, phy_task); struct net_device *dev = fep->netdev; + struct net_device *dev = fep->net; uint status = fep->phy_status; /* @@ -1996,6 +2418,7 @@ static void mii_relink(struct work_struct *work) { struct fec_enet_private *fep = container_of(work, struct fec_enet_private, phy_task); struct net_device *dev = fep->netdev; + struct net_device *dev = fep->net; int duplex; /* @@ -2142,10 +2565,14 @@ mii_link_interrupt(int irq, void * dev_id) #if 0 disable_irq(fep->mii_irq); /* disable now, enable later */ #endif - - mii_do_cmd(dev, fep->phy->ack_int); - mii_do_cmd(dev, phy_cmd_relink); /* restart and display status */ - + /* + * Some board will trigger phy interrupt before phy enable. + * And at that moment , fep->phy is not initialized. + */ + if (fep->phy) { + mii_do_cmd(dev, fep->phy->ack_int); + mii_do_cmd(dev, phy_cmd_relink); /* restart and display status */ + } return IRQ_HANDLED; } @@ -2190,7 +2617,6 @@ fec_enet_open(struct net_device *dev) fec_restart(dev, 1); } - netif_start_queue(dev); fep->opened = 1; return 0; /* Success */ } @@ -2203,9 +2629,9 @@ fec_enet_close(struct net_device *dev) /* Don't know what to do yet. */ fep->opened = 0; - netif_stop_queue(dev); - fec_stop(dev); - + if (fep->link) { + fec_stop(dev); + } return 0; } @@ -2316,6 +2742,7 @@ int __init fec_enet_init(struct net_device *dev) unsigned long mem_addr; volatile cbd_t *bdp; cbd_t *cbd_base; + struct sk_buff* pskb; volatile fec_t *fecp; int i, j; static int index = 0; @@ -2324,6 +2751,10 @@ int __init fec_enet_init(struct net_device *dev) if (index >= FEC_MAX_PORTS) return -ENXIO; + fep->net = dev; + + spin_lock_init(&(fep->lock)); + /* Allocate memory for buffer descriptors. */ mem_addr = __get_free_page(GFP_KERNEL); @@ -2332,6 +2763,7 @@ int __init fec_enet_init(struct net_device *dev) return -ENOMEM; } + fep->cbd_mem_base = (void *)mem_addr; /* Create an Ethernet device instance. */ fecp = (volatile fec_t *) fec_hw[index]; @@ -2353,10 +2785,14 @@ int __init fec_enet_init(struct net_device *dev) */ fec_get_mac(dev); - cbd_base = (cbd_t *)mem_addr; - /* XXX: missing check for allocation failure */ + cbd_base = (cbd_t *)fec_map_uncache(mem_addr, PAGE_SIZE); + if (cbd_base == NULL) { + free_page(mem_addr); + printk("FEC: map descriptor memory to uncacheable failed?\n"); + return -ENOMEM; + } - fec_uncache(mem_addr); + /* XXX: missing check for allocation failure */ /* Set receive and transmit descriptor base. */ @@ -2371,25 +2807,24 @@ int __init fec_enet_init(struct net_device *dev) /* Initialize the receive buffer descriptors. */ bdp = fep->rx_bd_base; - for (i=0; i<FEC_ENET_RX_PAGES; i++) { - - /* Allocate a page. - */ - mem_addr = __get_free_page(GFP_KERNEL); - /* XXX: missing check for allocation failure */ - - fec_uncache(mem_addr); - - /* Initialize the BD for every fragment in the page. - */ - for (j=0; j<FEC_ENET_RX_FRPPG; j++) { - bdp->cbd_sc = BD_ENET_RX_EMPTY; - bdp->cbd_bufaddr = __pa(mem_addr); - mem_addr += FEC_ENET_RX_FRSIZE; - bdp++; + for (i=0; i<RX_RING_SIZE; i++, bdp++) { + pskb = dev_alloc_skb(FEC_ENET_RX_FRSIZE); + if(pskb == NULL) { + for(; i>0; i--) { + if( fep->rx_skbuff[i-1] ) { + kfree_skb(fep->rx_skbuff[i-1]); + fep->rx_skbuff[i-1] = NULL; + } + } + printk("FEC: allocate skb fail when initializing rx buffer \n"); + free_page(mem_addr); + return -ENOMEM; } + fep->rx_skbuff[i] = pskb; + pskb->data = FEC_ADDR_ALIGNMENT(pskb->data); + bdp->cbd_sc = BD_ENET_RX_EMPTY; + bdp->cbd_bufaddr = __pa(pskb->data); } - /* Set the last buffer to wrap. */ bdp--; @@ -2422,19 +2857,23 @@ int __init fec_enet_init(struct net_device *dev) /* Set receive and transmit descriptor base. */ - fecp->fec_r_des_start = __pa((uint)(fep->rx_bd_base)); - fecp->fec_x_des_start = __pa((uint)(fep->tx_bd_base)); + fecp->fec_r_des_start = __pa((uint)(fep->cbd_mem_base)); + fecp->fec_x_des_start = __pa((uint)(fep->cbd_mem_base + RX_RING_SIZE*sizeof(cbd_t))); /* Install our interrupt handlers. This varies depending on * the architecture. */ fec_request_intrs(dev); + /* Clear and enable interrupts */ + fecp->fec_ievent = FEC_ENET_MASK; + fecp->fec_imask = FEC_ENET_TXF | FEC_ENET_TXB | FEC_ENET_RXF | FEC_ENET_RXB | FEC_ENET_MII; + fecp->fec_hash_table_high = 0; fecp->fec_hash_table_low = 0; fecp->fec_r_buff_size = PKT_MAXBLR_SIZE; fecp->fec_ecntrl = 2; - fecp->fec_r_des_active = 0; + fecp->fec_r_des_active = 0x01000000; dev->base_addr = (unsigned long)fecp; @@ -2453,11 +2892,6 @@ int __init fec_enet_init(struct net_device *dev) /* setup MII interface */ fec_set_mii(dev, fep); - /* Clear and enable interrupts */ - fecp->fec_ievent = 0xffc00000; - fecp->fec_imask = (FEC_ENET_TXF | FEC_ENET_TXB | - FEC_ENET_RXF | FEC_ENET_RXB | FEC_ENET_MII); - /* Queue up command to detect the PHY and initialize the * remainder of the interface. */ @@ -2489,9 +2923,15 @@ fec_restart(struct net_device *dev, int duplex) fecp->fec_ecntrl = 1; udelay(10); + /* Enable interrupts we wish to service. + */ + fecp->fec_imask = FEC_ENET_TXF | FEC_ENET_TXB | FEC_ENET_RXF | FEC_ENET_RXB | FEC_ENET_MII; + /* Clear any outstanding interrupt. - */ - fecp->fec_ievent = 0xffc00000; + * + */ + fecp->fec_ievent = FEC_ENET_MASK; + fec_enable_phy_intr(); /* Set station address. @@ -2511,8 +2951,8 @@ fec_restart(struct net_device *dev, int duplex) /* Set receive and transmit descriptor base. */ - fecp->fec_r_des_start = __pa((uint)(fep->rx_bd_base)); - fecp->fec_x_des_start = __pa((uint)(fep->tx_bd_base)); + fecp->fec_r_des_start = __pa((uint)(fep->cbd_mem_base)); + fecp->fec_x_des_start = __pa((uint)(fep->cbd_mem_base + RX_RING_SIZE*sizeof(cbd_t))); fep->dirty_tx = fep->cur_tx = fep->tx_bd_base; fep->cur_rx = fep->rx_bd_base; @@ -2579,12 +3019,9 @@ fec_restart(struct net_device *dev, int duplex) /* And last, enable the transmit and receive processing. */ fecp->fec_ecntrl = 2; - fecp->fec_r_des_active = 0; + fecp->fec_r_des_active = 0x01000000; - /* Enable interrupts we wish to service. - */ - fecp->fec_imask = (FEC_ENET_TXF | FEC_ENET_TXB | - FEC_ENET_RXF | FEC_ENET_RXB | FEC_ENET_MII); + netif_start_queue(dev); } static void @@ -2593,6 +3030,8 @@ fec_stop(struct net_device *dev) volatile fec_t *fecp; struct fec_enet_private *fep; + netif_stop_queue(dev); + fep = netdev_priv(dev); fecp = fep->hwp; @@ -2628,6 +3067,7 @@ static int __init fec_enet_module_init(void) DECLARE_MAC_BUF(mac); printk("FEC ENET Version 0.2\n"); + fec_arch_init(); for (i = 0; (i < FEC_MAX_PORTS); i++) { dev = alloc_etherdev(sizeof(struct fec_enet_private)); diff --git a/drivers/net/fec.h b/drivers/net/fec.h index 1d421606984f..9a35c5b7bac8 100644 --- a/drivers/net/fec.h +++ b/drivers/net/fec.h @@ -14,7 +14,7 @@ /****************************************************************************/ #if defined(CONFIG_M523x) || defined(CONFIG_M527x) || defined(CONFIG_M528x) || \ - defined(CONFIG_M520x) || defined(CONFIG_M532x) + defined(CONFIG_M520x) || defined(CONFIG_M532x) || defined(CONFIG_ARCH_MXC) /* * Just figures, Motorola would have to change the offsets for * registers in the same peripheral device on different models @@ -103,12 +103,22 @@ typedef struct fec { /* * Define the buffer descriptor structure. */ +/* Please see "Receive Buffer Descriptor Field Definitions" in Specification. + * It's LE. + */ +#ifdef CONFIG_ARCH_MXC +typedef struct bufdesc { + unsigned short cbd_datlen; /* Data length */ + unsigned short cbd_sc; /* Control and status info */ + unsigned long cbd_bufaddr; /* Buffer address */ +} cbd_t; +#else typedef struct bufdesc { unsigned short cbd_sc; /* Control and status info */ unsigned short cbd_datlen; /* Data length */ unsigned long cbd_bufaddr; /* Buffer address */ } cbd_t; - +#endif /* * The following definitions courtesy of commproc.h, which where diff --git a/drivers/net/irda/Kconfig b/drivers/net/irda/Kconfig index 65806956728a..2b635857b354 100644 --- a/drivers/net/irda/Kconfig +++ b/drivers/net/irda/Kconfig @@ -482,5 +482,9 @@ config MCS_FIR To compile it as a module, choose M here: the module will be called mcs7780. +config MXC_FIR + tristate "Freescale MXC FIR driver" + depends on ARCH_MXC && IRDA + endmenu diff --git a/drivers/net/irda/Makefile b/drivers/net/irda/Makefile index fefbb5909081..21db102d16bd 100644 --- a/drivers/net/irda/Makefile +++ b/drivers/net/irda/Makefile @@ -20,6 +20,7 @@ obj-$(CONFIG_VLSI_FIR) += vlsi_ir.o obj-$(CONFIG_VIA_FIR) += via-ircc.o obj-$(CONFIG_PXA_FICP) += pxaficp_ir.o obj-$(CONFIG_MCS_FIR) += mcs7780.o +obj-$(CONFIG_MXC_FIR) += mxc_ir.o # Old dongle drivers for old SIR drivers obj-$(CONFIG_ESI_DONGLE_OLD) += esi.o obj-$(CONFIG_TEKRAM_DONGLE_OLD) += tekram.o diff --git a/drivers/net/irda/mxc_ir.c b/drivers/net/irda/mxc_ir.c new file mode 100644 index 000000000000..d7d5d07eb38e --- /dev/null +++ b/drivers/net/irda/mxc_ir.c @@ -0,0 +1,1777 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * Based on sa1100_ir.c - Copyright 2000-2001 Russell King + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mxc_ir.c + * + * @brief Driver for the Freescale Semiconductor MXC FIRI. + * + * This driver is based on drivers/net/irda/sa1100_ir.c, by Russell King. + * + * @ingroup FIRI + */ + +/* + * Include Files + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/netdevice.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <linux/dma-mapping.h> +#include <linux/clk.h> + +#include <net/irda/irda.h> +#include <net/irda/wrapper.h> +#include <net/irda/irda_device.h> + +#include <asm/irq.h> +#include <asm/dma.h> +#include <asm/hardware.h> +#include <asm/arch/mxc_uart.h> +#include "mxc_ir.h" + +#define IS_SIR(mi) ( (mi)->speed <= 115200 ) +#define IS_MIR(mi) ( (mi)->speed < 4000000 && (mi)->speed >= 576000 ) +#define IS_FIR(mi) ( (mi)->speed >= 4000000 ) + +#define SDMA_START_DELAY() { \ + volatile int j,k;\ + int i;\ + for(i=0;i<10000;i++)\ + k=j;\ + } + +#define IRDA_FRAME_SIZE_LIMIT 2047 +#define UART_BUFF_SIZE 14384 + +#define UART4_UFCR_TXTL 16 +#define UART4_UFCR_RXTL 1 + +#define FIRI_SDMA_TX +#define FIRI_SDMA_RX + +/*! + * This structure is a way for the low level driver to define their own + * \b mxc_irda structure. This structure includes SK buffers, DMA buffers. + * and has other elements that are specifically required by this driver. + */ +struct mxc_irda { + /*! + * This keeps track of device is running or not + */ + unsigned char open; + + /*! + * This holds current FIRI communication speed + */ + int speed; + + /*! + * This holds FIRI communication speed for next packet + */ + int newspeed; + + /*! + * SK buffer for transmitter + */ + struct sk_buff *txskb; + + /*! + * SK buffer for receiver + */ + struct sk_buff *rxskb; + +#ifdef FIRI_SDMA_RX + /*! + * SK buffer for tasklet + */ + struct sk_buff *tskb; +#endif + + /*! + * DMA address for transmitter + */ + dma_addr_t dma_rx_buff_phy; + + /*! + * DMA address for receiver + */ + dma_addr_t dma_tx_buff_phy; + + /*! + * DMA Transmit buffer length + */ + unsigned int dma_tx_buff_len; + + /*! + * DMA channel for transmitter + */ + int txdma_ch; + + /*! + * DMA channel for receiver + */ + int rxdma_ch; + + /*! + * IrDA network device statistics + */ + struct net_device_stats stats; + + /*! + * The device structure used to get FIRI information + */ + struct device *dev; + + /*! + * Resource structure for UART, which will maintain base addresses and IRQs. + */ + struct resource *uart_res; + + /*! + * Base address of UART, used in readl and writel. + */ + void *uart_base; + + /*! + * Resource structure for FIRI, which will maintain base addresses and IRQs. + */ + struct resource *firi_res; + + /*! + * Base address of FIRI, used in readl and writel. + */ + void *firi_base; + + /*! + * UART IRQ number. + */ + int uart_irq; + + /*! + * Second UART IRQ number in case the interrupt lines are not muxed. + */ + int uart_irq1; + + /*! + * UART clock needed for baud rate calculations + */ + struct clk *uart_clk; + + /*! + * UART clock needed for baud rate calculations + */ + unsigned long uart_clk_rate; + + /*! + * FIRI clock needed for baud rate calculations + */ + struct clk *firi_clk; + + /*! + * FIRI IRQ number. + */ + int firi_irq; + + /*! + * IrLAP layer instance + */ + struct irlap_cb *irlap; + + /*! + * Driver supported baudrate capabilities + */ + struct qos_info qos; + + /*! + * Temporary transmit buffer used by the driver + */ + iobuff_t tx_buff; + + /*! + * Temporary receive buffer used by the driver + */ + iobuff_t rx_buff; + + /*! + * Pointer to platform specific data structure. + */ + struct mxc_ir_platform_data *mxc_ir_plat; + + /*! + * This holds the power management status of this module. + */ + int suspend; + +}; + +extern void gpio_firi_active(void *, unsigned int); +extern void gpio_firi_inactive(void); +extern void gpio_firi_init(void); + +void mxc_irda_firi_init(struct mxc_irda *si); +#ifdef FIRI_SDMA_RX +static void mxc_irda_fir_dma_rx_irq(void *id, int error_status, + unsigned int count); +#endif +#ifdef FIRI_SDMA_TX +static void mxc_irda_fir_dma_tx_irq(void *id, int error_status, + unsigned int count); +#endif + +/*! + * This function allocates and maps the receive buffer, + * unless it is already allocated. + * + * @param si FIRI device specific structure. + * @return The function returns 0 on success and a non-zero value on + * failure. + */ +static int mxc_irda_rx_alloc(struct mxc_irda *si) +{ +#ifdef FIRI_SDMA_RX + mxc_dma_requestbuf_t dma_request; +#endif + if (si->rxskb) { + return 0; + } + + si->rxskb = alloc_skb(IRDA_FRAME_SIZE_LIMIT + 1, GFP_ATOMIC); + + if (!si->rxskb) { + dev_err(si->dev, "mxc_ir: out of memory for RX SKB\n"); + return -ENOMEM; + } + + /* + * Align any IP headers that may be contained + * within the frame. + */ + skb_reserve(si->rxskb, 1); + +#ifdef FIRI_SDMA_RX + si->dma_rx_buff_phy = + dma_map_single(si->dev, si->rxskb->data, IRDA_FRAME_SIZE_LIMIT, + DMA_FROM_DEVICE); + + dma_request.num_of_bytes = IRDA_FRAME_SIZE_LIMIT; + dma_request.dst_addr = si->dma_rx_buff_phy; + dma_request.src_addr = si->firi_res->start; + + mxc_dma_config(si->rxdma_ch, &dma_request, 1, MXC_DMA_MODE_READ); +#endif + return 0; +} + +/*! + * This function is called to disable the FIRI dma + * + * @param si FIRI port specific structure. + */ +static void mxc_irda_disabledma(struct mxc_irda *si) +{ + /* Stop all DMA activity. */ +#ifdef FIRI_SDMA_TX + mxc_dma_disable(si->txdma_ch); +#endif +#ifdef FIRI_SDMA_RX + mxc_dma_disable(si->rxdma_ch); +#endif +} + +/*! + * This function is called to set the IrDA communications speed. + * + * @param si FIRI specific structure. + * @param speed new Speed to be configured for. + * + * @return The function returns 0 on success and a non-zero value on + * failure. + */ +static int mxc_irda_set_speed(struct mxc_irda *si, int speed) +{ + unsigned long flags; + int ret = 0; + unsigned int num, denom, baud; + unsigned int cr; + + dev_dbg(si->dev, "speed:%d\n", speed); + switch (speed) { + case 9600: + case 19200: + case 38400: + case 57600: + case 115200: + dev_dbg(si->dev, "starting SIR\n"); + baud = speed; + if (IS_FIR(si)) { +#ifdef FIRI_SDMA_RX + mxc_dma_disable(si->rxdma_ch); +#endif + cr = readl(si->firi_base + FIRITCR); + cr &= ~FIRITCR_TE; + writel(cr, si->firi_base + FIRITCR); + + cr = readl(si->firi_base + FIRIRCR); + cr &= ~FIRIRCR_RE; + writel(cr, si->firi_base + FIRIRCR); + + } + local_irq_save(flags); + + /* Disable Tx and Rx */ + cr = readl(si->uart_base + MXC_UARTUCR2); + cr &= ~(MXC_UARTUCR2_RXEN | MXC_UARTUCR2_TXEN); + writel(cr, si->uart_base + MXC_UARTUCR2); + + gpio_firi_inactive(); + + num = baud / 100 - 1; + denom = si->uart_clk_rate / 1600 - 1; + if ((denom < 65536) && (si->uart_clk_rate > 1600)) { + writel(num, si->uart_base + MXC_UARTUBIR); + writel(denom, si->uart_base + MXC_UARTUBMR); + } + + si->speed = speed; + + writel(0xFFFF, si->uart_base + MXC_UARTUSR1); + writel(0xFFFF, si->uart_base + MXC_UARTUSR2); + + /* Enable Receive Overrun and Data Ready interrupts. */ + cr = readl(si->uart_base + MXC_UARTUCR4); + cr |= (MXC_UARTUCR4_OREN | MXC_UARTUCR4_DREN); + writel(cr, si->uart_base + MXC_UARTUCR4); + + cr = readl(si->uart_base + MXC_UARTUCR2); + cr |= (MXC_UARTUCR2_RXEN | MXC_UARTUCR2_TXEN); + writel(cr, si->uart_base + MXC_UARTUCR2); + + local_irq_restore(flags); + break; + case 4000000: + local_irq_save(flags); + + /* Disable Receive Overrun and Data Ready interrupts. */ + cr = readl(si->uart_base + MXC_UARTUCR4); + cr &= ~(MXC_UARTUCR4_OREN | MXC_UARTUCR4_DREN); + writel(cr, si->uart_base + MXC_UARTUCR4); + + /* Disable Tx and Rx */ + cr = readl(si->uart_base + MXC_UARTUCR2); + cr &= ~(MXC_UARTUCR2_RXEN | MXC_UARTUCR2_TXEN); + writel(cr, si->uart_base + MXC_UARTUCR2); + + /* + * FIR configuration + */ + mxc_irda_disabledma(si); + + cr = readl(si->firi_base + FIRITCR); + cr &= ~FIRITCR_TE; + writel(cr, si->firi_base + FIRITCR); + + gpio_firi_active(si->firi_base + FIRITCR, FIRITCR_TPP); + + si->speed = speed; + + cr = readl(si->firi_base + FIRIRCR); + cr |= FIRIRCR_RE; + writel(cr, si->firi_base + FIRIRCR); + + dev_dbg(si->dev, "Going for fast IRDA ...\n"); + ret = mxc_irda_rx_alloc(si); + + /* clear RX status register */ + writel(0xFFFF, si->firi_base + FIRIRSR); +#ifdef FIRI_SDMA_RX + if (si->rxskb) { + mxc_dma_enable(si->rxdma_ch); + } +#endif + local_irq_restore(flags); + + break; + default: + dev_err(si->dev, "speed not supported by FIRI\n"); + break; + } + + return ret; +} + +/*! + * This function is called to set the IrDA communications speed. + * + * @param si FIRI specific structure. + * + * @return The function returns 0 on success and a non-zero value on + * failure. + */ +static inline int mxc_irda_fir_error(struct mxc_irda *si) +{ + struct sk_buff *skb = si->rxskb; + unsigned int dd_error, crc_error, overrun_error; + unsigned int sr; + + if (!skb) { + dev_err(si->dev, "no skb!\n"); + return -1; + } + + sr = readl(si->firi_base + FIRIRSR); + dd_error = sr & FIRIRSR_DDE; + crc_error = sr & FIRIRSR_CRCE; + overrun_error = sr & FIRIRSR_RFO; + + if (!(dd_error | crc_error | overrun_error)) { + return 0; + } + dev_err(si->dev, "dde,crce,rfo=%d,%d,%d.\n", dd_error, crc_error, + overrun_error); + si->stats.rx_errors++; + if (crc_error) { + si->stats.rx_crc_errors++; + } + if (dd_error) { + si->stats.rx_frame_errors++; + } + if (overrun_error) { + si->stats.rx_frame_errors++; + } + writel(sr, si->firi_base + FIRIRSR); + + return -1; +} + +#ifndef FIRI_SDMA_RX +/*! + * FIR interrupt service routine to handle receive. + * + * @param dev pointer to the net_device structure + */ +void mxc_irda_fir_irq_rx(struct net_device *dev) +{ + struct mxc_irda *si = dev->priv; + struct sk_buff *skb = si->rxskb; + unsigned int sr, len; + int i; + unsigned char *p = skb->data; + + /* + * Deal with any receive errors. + */ + if (mxc_irda_fir_error(si) != 0) { + return; + } + + sr = readl(si->firi_base + FIRIRSR); + + if (!(sr & FIRIRSR_RPE)) { + return; + } + + /* + * Coming here indicates that fir rx packet has been successfully recieved. + * And No error happened so far. + */ + writel(sr | FIRIRSR_RPE, si->firi_base + FIRIRSR); + + len = (sr & FIRIRSR_RFP) >> 8; + + /* 4 bytes of CRC */ + len -= 4; + + skb_put(skb, len); + + for (i = 0; i < len; i++) { + *p++ = readb(si->firi_base + FIRIRXFIFO); + } + + /* Discard the four CRC bytes */ + for (i = 0; i < 4; i++) { + readb(si->firi_base + FIRIRXFIFO); + } + + /* + * Deal with the case of packet complete. + */ + skb->dev = dev; + skb->mac.raw = skb->data; + skb->protocol = htons(ETH_P_IRDA); + si->stats.rx_packets++; + si->stats.rx_bytes += len; + netif_rx(skb); + + si->rxskb = NULL; + mxc_irda_rx_alloc(si); + + writel(0xFFFF, si->firi_base + FIRIRSR); + +} +#endif + +/*! + * FIR interrupt service routine to handle transmit. + * + * @param dev pointer to the net_device structure + */ +void mxc_irda_fir_irq_tx(struct net_device *dev) +{ + struct mxc_irda *si = dev->priv; + struct sk_buff *skb = si->txskb; + unsigned int cr, sr; + + sr = readl(si->firi_base + FIRITSR); + writel(sr, si->firi_base + FIRITSR); + + if (sr & FIRITSR_TC) { + +#ifdef FIRI_SDMA_TX + mxc_dma_disable(si->txdma_ch); +#endif + cr = readl(si->firi_base + FIRITCR); + cr &= ~(FIRITCR_TCIE | FIRITCR_TE); + writel(cr, si->firi_base + FIRITCR); + + if (si->newspeed) { + mxc_irda_set_speed(si, si->newspeed); + si->newspeed = 0; + } + si->txskb = NULL; + + cr = readl(si->firi_base + FIRIRCR); + cr |= FIRIRCR_RE; + writel(cr, si->firi_base + FIRIRCR); + + writel(0xFFFF, si->firi_base + FIRIRSR); + /* + * Account and free the packet. + */ + if (skb) { +#ifdef FIRI_SDMA_TX + dma_unmap_single(si->dev, si->dma_tx_buff_phy, skb->len, + DMA_TO_DEVICE); +#endif + si->stats.tx_packets++; + si->stats.tx_bytes += skb->len; + dev_kfree_skb_irq(skb); + } + /* + * Make sure that the TX queue is available for sending + * (for retries). TX has priority over RX at all times. + */ + netif_wake_queue(dev); + } +} + +/*! + * This is FIRI interrupt handler. + * + * @param dev pointer to the net_device structure + */ +void mxc_irda_fir_irq(struct net_device *dev) +{ + struct mxc_irda *si = dev->priv; + unsigned int sr1, sr2; + + sr1 = readl(si->firi_base + FIRIRSR); + sr2 = readl(si->firi_base + FIRITSR); + + if (sr2 & FIRITSR_TC) + mxc_irda_fir_irq_tx(dev); +#ifndef FIRI_SDMA_RX + if (sr1 & (FIRIRSR_RPE | FIRIRSR_RFO)) + mxc_irda_fir_irq_rx(dev); +#endif + +} + +/*! + * This is the SIR transmit routine. + * + * @param si FIRI specific structure. + * + * @param dev pointer to the net_device structure + * + * @return The function returns 0 on success and a non-zero value on + * failure. + */ +static int mxc_irda_sir_txirq(struct mxc_irda *si, struct net_device *dev) +{ + unsigned int sr1, sr2, cr; + unsigned int status; + + sr1 = readl(si->uart_base + MXC_UARTUSR1); + sr2 = readl(si->uart_base + MXC_UARTUSR2); + cr = readl(si->uart_base + MXC_UARTUCR2); + + /* + * Echo cancellation for IRDA Transmit chars + * Disable the receiver and enable Transmit complete. + */ + cr &= ~MXC_UARTUCR2_RXEN; + writel(cr, si->uart_base + MXC_UARTUCR2); + cr = readl(si->uart_base + MXC_UARTUCR4); + cr |= MXC_UARTUCR4_TCEN; + writel(cr, si->uart_base + MXC_UARTUCR4); + + while ((sr1 & MXC_UARTUSR1_TRDY) && si->tx_buff.len) { + + writel(*si->tx_buff.data++, si->uart_base + MXC_UARTUTXD); + si->tx_buff.len -= 1; + sr1 = readl(si->uart_base + MXC_UARTUSR1); + } + + if (si->tx_buff.len == 0) { + si->stats.tx_packets++; + si->stats.tx_bytes += si->tx_buff.data - si->tx_buff.head; + + /*Yoohoo...we are done...Lets stop Tx */ + cr = readl(si->uart_base + MXC_UARTUCR1); + cr &= ~MXC_UARTUCR1_TRDYEN; + writel(cr, si->uart_base + MXC_UARTUCR1); + + do { + status = readl(si->uart_base + MXC_UARTUSR2); + } while (!(status & MXC_UARTUSR2_TXDC)); + + if (si->newspeed) { + mxc_irda_set_speed(si, si->newspeed); + si->newspeed = 0; + } + /* I'm hungry! */ + netif_wake_queue(dev); + + /* Is the transmit complete to reenable the receiver? */ + if (status & MXC_UARTUSR2_TXDC) { + + cr = readl(si->uart_base + MXC_UARTUCR2); + cr |= MXC_UARTUCR2_RXEN; + writel(cr, si->uart_base + MXC_UARTUCR2); + /* Disable the Transmit complete interrupt bit */ + cr = readl(si->uart_base + MXC_UARTUCR4); + cr &= ~MXC_UARTUCR4_TCEN; + writel(cr, si->uart_base + MXC_UARTUCR4); + } + } + + return 0; +} + +/*! + * This is the SIR receive routine. + * + * @param si FIRI specific structure. + * + * @param dev pointer to the net_device structure + * + * @return The function returns 0 on success and a non-zero value on + * failure. + */ +static int mxc_irda_sir_rxirq(struct mxc_irda *si, struct net_device *dev) +{ + unsigned int data, status; + volatile unsigned int sr2; + + sr2 = readl(si->uart_base + MXC_UARTUSR2); + while ((sr2 & MXC_UARTUSR2_RDR) == 1) { + data = readl(si->uart_base + MXC_UARTURXD); + status = data & 0xf400; + if (status & MXC_UARTURXD_ERR) { + dev_err(si->dev, "Receive an incorrect data =0x%x.\n", + data); + si->stats.rx_errors++; + if (status & MXC_UARTURXD_OVRRUN) { + si->stats.rx_fifo_errors++; + dev_err(si->dev, "Rx overrun.\n"); + } + if (status & MXC_UARTURXD_FRMERR) { + si->stats.rx_frame_errors++; + dev_err(si->dev, "Rx frame error.\n"); + } + if (status & MXC_UARTURXD_PRERR) { + dev_err(si->dev, "Rx parity error.\n"); + } + /* Other: it is the Break char. + * Do nothing for it. throw out the data. + */ + async_unwrap_char(dev, &si->stats, &si->rx_buff, + (data & 0xFF)); + } else { + /* It is correct data. */ + data &= 0xFF; + async_unwrap_char(dev, &si->stats, &si->rx_buff, data); + + dev->last_rx = jiffies; + } + sr2 = readl(si->uart_base + MXC_UARTUSR2); + + writel(0xFFFF, si->uart_base + MXC_UARTUSR1); + writel(0xFFFF, si->uart_base + MXC_UARTUSR2); + } /*while */ + return 0; + +} + +static irqreturn_t mxc_irda_irq(int irq, void *dev_id) +{ + struct net_device *dev = dev_id; + struct mxc_irda *si = dev->priv; + + if (IS_FIR(si)) { + mxc_irda_fir_irq(dev); + return IRQ_HANDLED; + } + + if (readl(si->uart_base + MXC_UARTUCR2) & MXC_UARTUCR2_RXEN) { + mxc_irda_sir_rxirq(si, dev); + } + if ((readl(si->uart_base + MXC_UARTUCR1) & MXC_UARTUCR1_TRDYEN) && + (readl(si->uart_base + MXC_UARTUSR1) & MXC_UARTUSR1_TRDY)) { + mxc_irda_sir_txirq(si, dev); + } + + return IRQ_HANDLED; +} + +static irqreturn_t mxc_irda_tx_irq(int irq, void *dev_id) +{ + + struct net_device *dev = dev_id; + struct mxc_irda *si = dev->priv; + + mxc_irda_sir_txirq(si, dev); + + return IRQ_HANDLED; +} + +static irqreturn_t mxc_irda_rx_irq(int irq, void *dev_id) +{ + + struct net_device *dev = dev_id; + struct mxc_irda *si = dev->priv; + + /* Clear the aging timer bit */ + writel(MXC_UARTUSR1_AGTIM, si->uart_base + MXC_UARTUSR1); + + mxc_irda_sir_rxirq(si, dev); + + return IRQ_HANDLED; +} + +#ifdef FIRI_SDMA_RX +struct tasklet_struct dma_rx_tasklet; + +static void mxc_irda_rx_task(unsigned long tparam) +{ + struct mxc_irda *si = (struct mxc_irda *)tparam; + struct sk_buff *lskb = si->tskb; + + si->tskb = NULL; + if (lskb) { + lskb->mac_header = lskb->data; + lskb->protocol = htons(ETH_P_IRDA); + netif_rx(lskb); + } +} + +/*! + * Receiver DMA callback routine. + * + * @param id pointer to network device structure + * @param error_status used to pass error status to this callback function + * @param count number of bytes received + */ +static void mxc_irda_fir_dma_rx_irq(void *id, int error_status, + unsigned int count) +{ + struct net_device *dev = id; + struct mxc_irda *si = dev->priv; + struct sk_buff *skb = si->rxskb; + unsigned int cr; + unsigned int len; + + cr = readl(si->firi_base + FIRIRCR); + cr &= ~FIRIRCR_RE; + writel(cr, si->firi_base + FIRIRCR); + cr = readl(si->firi_base + FIRIRCR); + cr |= FIRIRCR_RE; + writel(cr, si->firi_base + FIRIRCR); + len = count - 4; /* remove 4 bytes for CRC */ + skb_put(skb, len); + skb->dev = dev; + si->tskb = skb; + tasklet_schedule(&dma_rx_tasklet); + + if (si->dma_rx_buff_phy != 0) + dma_unmap_single(si->dev, si->dma_rx_buff_phy, + IRDA_FRAME_SIZE_LIMIT, DMA_FROM_DEVICE); + + si->rxskb = NULL; + mxc_irda_rx_alloc(si); + + SDMA_START_DELAY(); + writel(0xFFFF, si->firi_base + FIRIRSR); + + if (si->rxskb) { + mxc_dma_enable(si->rxdma_ch); + } +} +#endif + +#ifdef FIRI_SDMA_TX +/*! + * This function is called by SDMA Interrupt Service Routine to indicate + * requested DMA transfer is completed. + * + * @param id pointer to network device structure + * @param error_status used to pass error status to this callback function + * @param count number of bytes sent + */ +static void mxc_irda_fir_dma_tx_irq(void *id, int error_status, + unsigned int count) +{ + struct net_device *dev = id; + struct mxc_irda *si = dev->priv; + + mxc_dma_disable(si->txdma_ch); +} +#endif + +/*! + * This function is called by Linux IrDA network subsystem to + * transmit the Infrared data packet. The TX DMA channel is configured + * to transfer SK buffer data to FIRI TX FIFO along with DMA transfer + * completion routine. + * + * @param skb The packet that is queued to be sent + * @param dev net_device structure. + * + * @return The function returns 0 on success and a negative value on + * failure. + */ +static int mxc_irda_hard_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct mxc_irda *si = dev->priv; + int speed = irda_get_next_speed(skb); + unsigned int cr; + + /* + * Does this packet contain a request to change the interface + * speed? If so, remember it until we complete the transmission + * of this frame. + */ + if (speed != si->speed && speed != -1) { + si->newspeed = speed; + } + + /* If this is an empty frame, we can bypass a lot. */ + if (skb->len == 0) { + if (si->newspeed) { + si->newspeed = 0; + mxc_irda_set_speed(si, speed); + } + dev_kfree_skb(skb); + return 0; + } + + /* We must not be transmitting... */ + netif_stop_queue(dev); + if (IS_SIR(si)) { + + si->tx_buff.data = si->tx_buff.head; + si->tx_buff.len = async_wrap_skb(skb, si->tx_buff.data, + si->tx_buff.truesize); + cr = readl(si->uart_base + MXC_UARTUCR1); + cr |= MXC_UARTUCR1_TRDYEN; + writel(cr, si->uart_base + MXC_UARTUCR1); + dev_kfree_skb(skb); + } else { + unsigned int mtt = irda_get_mtt(skb); + unsigned char *p = skb->data; + unsigned int skb_len = skb->len; +#ifdef FIRI_SDMA_TX + mxc_dma_requestbuf_t dma_request; +#else + unsigned int i, sr; +#endif + + skb_len = skb_len + ((4 - (skb_len % 4)) % 4); + + if (si->txskb) { + BUG(); + } + si->txskb = skb; + + /* + * If we have a mean turn-around time, impose the specified + * specified delay. We could shorten this by timing from + * the point we received the packet. + */ + if (mtt) { + udelay(mtt); + } + + cr = readl(si->firi_base + FIRIRCR); + cr &= ~FIRIRCR_RE; + writel(cr, si->firi_base + FIRIRCR); + + writel(skb->len - 1, si->firi_base + FIRITCTR); + +#ifdef FIRI_SDMA_TX + /* + * Configure DMA Tx Channel for source and destination addresses, + * Number of bytes in SK buffer to transfer and Transfer complete + * callback function. + */ + si->dma_tx_buff_len = skb_len; + si->dma_tx_buff_phy = + dma_map_single(si->dev, p, skb_len, DMA_TO_DEVICE); + + dma_request.num_of_bytes = skb_len; + dma_request.dst_addr = si->firi_res->start + FIRITXFIFO; + dma_request.src_addr = si->dma_tx_buff_phy; + + mxc_dma_config(si->txdma_ch, &dma_request, 1, + MXC_DMA_MODE_WRITE); + + mxc_dma_enable(si->txdma_ch); +#endif + cr = readl(si->firi_base + FIRITCR); + cr |= FIRITCR_TCIE; + writel(cr, si->firi_base + FIRITCR); + + cr |= FIRITCR_TE; + writel(cr, si->firi_base + FIRITCR); + +#ifndef FIRI_SDMA_TX + for (i = 0; i < skb->len;) { + sr = readl(si->firi_base + FIRITSR); + /* TFP = number of bytes in the TX FIFO for the + * Transmitter + * */ + if ((sr >> 8) < 128) { + writeb(*p, si->firi_base + FIRITXFIFO); + p++; + i++; + } + } +#endif + } + + dev->trans_start = jiffies; + return 0; +} + +/*! + * This function handles network interface ioctls passed to this driver.. + * + * @param dev net device structure + * @param ifreq user request data + * @param cmd command issued + * + * @return The function returns 0 on success and a non-zero value on + * failure. + */ +static int mxc_irda_ioctl(struct net_device *dev, struct ifreq *ifreq, int cmd) +{ + struct if_irda_req *rq = (struct if_irda_req *)ifreq; + struct mxc_irda *si = dev->priv; + int ret = -EOPNOTSUPP; + + switch (cmd) { + /* This function will be used by IrLAP to change the speed */ + case SIOCSBANDWIDTH: + dev_dbg(si->dev, "%s:with cmd SIOCSBANDWIDTH\n", __FUNCTION__); + if (capable(CAP_NET_ADMIN)) { + /* + * We are unable to set the speed if the + * device is not running. + */ + if (si->open) { + ret = mxc_irda_set_speed(si, rq->ifr_baudrate); + } else { + dev_err(si->dev, "mxc_ir_ioctl: SIOCSBANDWIDTH:\ + !netif_running\n"); + ret = 0; + } + } + break; + case SIOCSMEDIABUSY: + dev_dbg(si->dev, "%s:with cmd SIOCSMEDIABUSY\n", __FUNCTION__); + ret = -EPERM; + if (capable(CAP_NET_ADMIN)) { + irda_device_set_media_busy(dev, TRUE); + ret = 0; + } + break; + case SIOCGRECEIVING: + rq->ifr_receiving = + IS_SIR(si) ? si->rx_buff.state != OUTSIDE_FRAME : 0; + ret = 0; + break; + default: + break; + } + return ret; +} + +/*! + * Kernel interface routine to get current statistics of the device + * which includes the number bytes/packets transmitted/received, + * receive errors, CRC errors, framing errors etc. + * + * @param dev the net_device structure + * + * @return This function returns IrDA network statistics + */ +static struct net_device_stats *mxc_irda_stats(struct net_device *dev) +{ + struct mxc_irda *si = dev->priv; + return &si->stats; +} + +/*! + * FIRI init function + * + * @param si FIRI device specific structure. + */ +void mxc_irda_firi_init(struct mxc_irda *si) +{ + unsigned int firi_baud, osf = 6; + unsigned int tcr, rcr, cr; + + si->firi_clk = clk_get(si->dev, "firi_clk"); + firi_baud = clk_round_rate(si->firi_clk, 48004500); + if ((firi_baud < 47995500) || + (clk_set_rate(si->firi_clk, firi_baud) < 0)) { + dev_err(si->dev, "Unable to set FIR clock to 48MHz.\n"); + return; + } + clk_enable(si->firi_clk); + + writel(0xFFFF, si->firi_base + FIRITSR); + writel(0xFFFF, si->firi_base + FIRIRSR); + writel(0x00, si->firi_base + FIRITCR); + writel(0x00, si->firi_base + FIRIRCR); + + /* set _BL & _OSF */ + cr = (osf - 1) | (16 << 5); + writel(cr, si->firi_base + FIRICR); + +#ifdef FIRI_SDMA_TX + tcr = + FIRITCR_TDT_FIR | FIRITCR_TM_FIR | FIRITCR_TCIE | + FIRITCR_PCF | FIRITCR_PC; +#else + tcr = FIRITCR_TM_FIR | FIRITCR_TCIE | FIRITCR_PCF | FIRITCR_PC; +#endif + +#ifdef FIRI_SDMA_RX + rcr = + FIRIRCR_RPEDE | FIRIRCR_RM_FIR | FIRIRCR_RDT_FIR | + FIRIRCR_RPA | FIRIRCR_RPP; +#else + rcr = + FIRIRCR_RPEDE | FIRIRCR_RM_FIR | FIRIRCR_RDT_FIR | FIRIRCR_RPEIE | + FIRIRCR_RPA | FIRIRCR_PAIE | FIRIRCR_RFOIE | FIRIRCR_RPP; +#endif + + writel(tcr, si->firi_base + FIRITCR); + writel(rcr, si->firi_base + FIRIRCR); + cr = 0; + writel(cr, si->firi_base + FIRITCTR); +} + +/*! + * This function initialises the UART. + * + * @param si FIRI port specific structure. + * + * @return The function returns 0 on success. + */ +static int mxc_irda_uart_init(struct mxc_irda *si) +{ + unsigned int per_clk; + unsigned int num, denom, baud, ufcr = 0; + unsigned int cr; + int d = 1; + int uart_ir_mux = 0; + + if (si->mxc_ir_plat) + uart_ir_mux = si->mxc_ir_plat->uart_ir_mux; + /* + * Clear Status Registers 1 and 2 + **/ + writel(0xFFFF, si->uart_base + MXC_UARTUSR1); + writel(0xFFFF, si->uart_base + MXC_UARTUSR2); + + /* Configure the IOMUX for the UART */ + gpio_firi_init(); + + per_clk = clk_get_rate(si->uart_clk); + baud = per_clk / 16; + if (baud > 1500000) { + baud = 1500000; + d = per_clk / ((baud * 16) + 1000); + if (d > 6) { + d = 6; + } + } + clk_enable(si->uart_clk); + + si->uart_clk_rate = per_clk / d; + writel(si->uart_clk_rate / 1000, si->uart_base + MXC_UARTONEMS); + + writel(si->mxc_ir_plat->ir_rx_invert | MXC_UARTUCR4_IRSC, + si->uart_base + MXC_UARTUCR4); + + if (uart_ir_mux) { + writel(MXC_UARTUCR3_RXDMUXSEL | si->mxc_ir_plat->ir_tx_invert | + MXC_UARTUCR3_DSR, si->uart_base + MXC_UARTUCR3); + } else { + writel(si->mxc_ir_plat->ir_tx_invert | MXC_UARTUCR3_DSR, + si->uart_base + MXC_UARTUCR3); + } + + writel(MXC_UARTUCR2_IRTS | MXC_UARTUCR2_CTS | MXC_UARTUCR2_WS | + MXC_UARTUCR2_ATEN | MXC_UARTUCR2_TXEN | MXC_UARTUCR2_RXEN, + si->uart_base + MXC_UARTUCR2); + /* Wait till we are out of software reset */ + do { + cr = readl(si->uart_base + MXC_UARTUCR2); + } while (!(cr & MXC_UARTUCR2_SRST)); + + ufcr |= (UART4_UFCR_TXTL << MXC_UARTUFCR_TXTL_OFFSET) | + ((6 - d) << MXC_UARTUFCR_RFDIV_OFFSET) | UART4_UFCR_RXTL; + writel(ufcr, si->uart_base + MXC_UARTUFCR); + + writel(MXC_UARTUCR1_UARTEN | MXC_UARTUCR1_IREN, + si->uart_base + MXC_UARTUCR1); + + baud = 9600; + num = baud / 100 - 1; + denom = si->uart_clk_rate / 1600 - 1; + + if ((denom < 65536) && (si->uart_clk_rate > 1600)) { + writel(num, si->uart_base + MXC_UARTUBIR); + writel(denom, si->uart_base + MXC_UARTUBMR); + } + + writel(0x0000, si->uart_base + MXC_UARTUTS); + return 0; + +} + +/*! + * This function enables FIRI port. + * + * @param si FIRI port specific structure. + * + * @return The function returns 0 on success and a non-zero value on + * failure. + */ +static int mxc_irda_startup(struct mxc_irda *si) +{ + int ret = 0; + + mxc_irda_uart_init(si); + mxc_irda_firi_init(si); + + /* configure FIRI device for speed */ + ret = mxc_irda_set_speed(si, si->speed = 9600); + + return ret; +} + +/*! + * When an ifconfig is issued which changes the device flag to include + * IFF_UP this function is called. It is only called when the change + * occurs, not when the interface remains up. The function grabs the interrupt + * resources and registers FIRI interrupt service routines, requests for DMA + * channels, configures the DMA channel. It then initializes the IOMUX + * registers to configure the pins for FIRI signals and finally initializes the + * various FIRI registers and enables the port for reception. + * + * @param dev net device structure that is being opened + * + * @return The function returns 0 for a successful open and non-zero value + * on failure. + */ +static int mxc_irda_start(struct net_device *dev) +{ + struct mxc_irda *si = dev->priv; + int err; + int ints_muxed = 0; + mxc_dma_device_t dev_id = 0; + + if (si->uart_irq == si->uart_irq1) + ints_muxed = 1; + + si->speed = 9600; + + if (si->uart_irq == si->firi_irq) { + err = + request_irq(si->uart_irq, mxc_irda_irq, 0, dev->name, dev); + if (err) { + dev_err(si->dev, "%s:Failed to request the IRQ\n", + __FUNCTION__); + return err; + } + /* + * The interrupt must remain disabled for now. + */ + disable_irq(si->uart_irq); + } else { + err = + request_irq(si->firi_irq, mxc_irda_irq, 0, dev->name, dev); + if (err) { + dev_err(si->dev, "%s:Failed to request FIRI IRQ\n", + __FUNCTION__); + return err; + } + /* + * The interrupt must remain disabled for now. + */ + disable_irq(si->firi_irq); + if (ints_muxed) { + + err = request_irq(si->uart_irq, mxc_irda_irq, 0, + dev->name, dev); + if (err) { + dev_err(si->dev, + "%s:Failed to request UART IRQ\n", + __FUNCTION__); + goto err_irq1; + } + /* + * The interrupt must remain disabled for now. + */ + disable_irq(si->uart_irq); + } else { + err = request_irq(si->uart_irq, mxc_irda_tx_irq, 0, + dev->name, dev); + if (err) { + dev_err(si->dev, + "%s:Failed to request UART IRQ\n", + __FUNCTION__); + goto err_irq1; + } + err = request_irq(si->uart_irq1, mxc_irda_rx_irq, 0, + dev->name, dev); + if (err) { + dev_err(si->dev, + "%s:Failed to request UART1 IRQ\n", + __FUNCTION__); + goto err_irq2; + } + /* + * The interrupts must remain disabled for now. + */ + disable_irq(si->uart_irq); + disable_irq(si->uart_irq1); + } + } +#ifdef FIRI_SDMA_RX + dev_id = MXC_DMA_FIR_RX; + si->rxdma_ch = mxc_dma_request(dev_id, "MXC FIRI RX"); + if (si->rxdma_ch < 0) { + dev_err(si->dev, "Cannot allocate FIR DMA channel\n"); + goto err_rx_dma; + } + mxc_dma_callback_set(si->rxdma_ch, mxc_irda_fir_dma_rx_irq, + (void *)dev_get_drvdata(si->dev)); +#endif +#ifdef FIRI_SDMA_TX + + dev_id = MXC_DMA_FIR_TX; + si->txdma_ch = mxc_dma_request(dev_id, "MXC FIRI TX"); + if (si->txdma_ch < 0) { + dev_err(si->dev, "Cannot allocate FIR DMA channel\n"); + goto err_tx_dma; + } + mxc_dma_callback_set(si->txdma_ch, mxc_irda_fir_dma_tx_irq, + (void *)dev_get_drvdata(si->dev)); +#endif + /* Setup the serial port port for the initial speed. */ + err = mxc_irda_startup(si); + if (err) { + goto err_startup; + } + + /* Open a new IrLAP layer instance. */ + si->irlap = irlap_open(dev, &si->qos, "mxc"); + err = -ENOMEM; + if (!si->irlap) { + goto err_irlap; + } + + /* Now enable the interrupt and start the queue */ + si->open = 1; + si->suspend = 0; + + if (si->uart_irq == si->firi_irq) { + enable_irq(si->uart_irq); + } else { + enable_irq(si->firi_irq); + if (ints_muxed == 1) { + enable_irq(si->uart_irq); + } else { + enable_irq(si->uart_irq); + enable_irq(si->uart_irq1); + } + } + + netif_start_queue(dev); + return 0; + + err_irlap: + si->open = 0; + mxc_irda_disabledma(si); + err_startup: +#ifdef FIRI_SDMA_TX + mxc_dma_free(si->txdma_ch); + err_tx_dma: +#endif +#ifdef FIRI_SDMA_RX + mxc_dma_free(si->rxdma_ch); + err_rx_dma: +#endif + if (si->uart_irq1 && !ints_muxed) + free_irq(si->uart_irq1, dev); + err_irq2: + if (si->uart_irq != si->firi_irq) + free_irq(si->uart_irq, dev); + err_irq1: + if (si->firi_irq) + free_irq(si->firi_irq, dev); + return err; +} + +/*! + * This function is called when IFF_UP flag has been cleared by the user via + * the ifconfig irda0 down command. This function stops any further + * transmissions being queued, and then disables the interrupts. + * Finally it resets the device. + * @param dev the net_device structure + * + * @return int the function always returns 0 indicating a success. + */ +static int mxc_irda_stop(struct net_device *dev) +{ + struct mxc_irda *si = netdev_priv(dev); + unsigned long flags; + + /* Stop IrLAP */ + if (si->irlap) { + irlap_close(si->irlap); + si->irlap = NULL; + } + + netif_stop_queue(dev); + + /*Save flags and disable the FIRI interrupts.. */ + if (si->open) { + local_irq_save(flags); + disable_irq(si->uart_irq); + free_irq(si->uart_irq, dev); + if (si->uart_irq != si->firi_irq) { + disable_irq(si->firi_irq); + free_irq(si->firi_irq, dev); + if (si->uart_irq1 != si->uart_irq) { + disable_irq(si->uart_irq1); + free_irq(si->uart_irq1, dev); + } + } + local_irq_restore(flags); + si->open = 0; + } +#ifdef FIRI_SDMA_RX + if (si->rxdma_ch) { + mxc_dma_disable(si->rxdma_ch); + mxc_dma_free(si->rxdma_ch); + if (si->dma_rx_buff_phy) { + dma_unmap_single(si->dev, si->dma_rx_buff_phy, + IRDA_FRAME_SIZE_LIMIT, + DMA_FROM_DEVICE); + si->dma_rx_buff_phy = 0; + } + si->rxdma_ch = 0; + } + tasklet_kill(&dma_rx_tasklet); +#endif +#ifdef FIRI_SDMA_TX + if (si->txdma_ch) { + mxc_dma_disable(si->txdma_ch); + mxc_dma_free(si->txdma_ch); + if (si->dma_tx_buff_phy) { + dma_unmap_single(si->dev, si->dma_tx_buff_phy, + si->dma_tx_buff_len, DMA_TO_DEVICE); + si->dma_tx_buff_phy = 0; + } + si->txdma_ch = 0; + } +#endif + return 0; +} + +#ifdef CONFIG_PM +/*! + * This function is called to put the FIRI in a low power state. Refer to the + * document driver-model/driver.txt in the kernel source tree for more + * information. + * + * @param pdev the device structure used to give information on which FIRI + * to suspend + * @param state the power state the device is entering + * + * @return The function always returns 0. + */ +static int mxc_irda_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct net_device *ndev = platform_get_drvdata(pdev); + struct mxc_irda *si = netdev_priv(ndev); + unsigned int cr; + unsigned long flags; + + if (!si) { + return 0; + } + if (si->suspend == 1) { + dev_err(si->dev, + " suspend - Device is already suspended ... \n"); + return 0; + } + if (si->open) { + + netif_device_detach(ndev); + mxc_irda_disabledma(si); + + /*Save flags and disable the FIRI interrupts.. */ + local_irq_save(flags); + disable_irq(si->uart_irq); + if (si->uart_irq != si->firi_irq) { + disable_irq(si->firi_irq); + if (si->uart_irq != si->uart_irq1) { + disable_irq(si->uart_irq1); + } + } + local_irq_restore(flags); + + /* Disable Tx and Rx and then disable the UART clock */ + cr = readl(si->uart_base + MXC_UARTUCR2); + cr &= ~(MXC_UARTUCR2_TXEN | MXC_UARTUCR2_RXEN); + writel(cr, si->uart_base + MXC_UARTUCR2); + cr = readl(si->uart_base + MXC_UARTUCR1); + cr &= ~MXC_UARTUCR1_UARTEN; + writel(cr, si->uart_base + MXC_UARTUCR1); + clk_disable(si->uart_clk); + + /*Disable Tx and Rx for FIRI and then disable the FIRI clock.. */ + cr = readl(si->firi_base + FIRITCR); + cr &= ~FIRITCR_TE; + writel(cr, si->firi_base + FIRITCR); + cr = readl(si->firi_base + FIRIRCR); + cr &= ~FIRIRCR_RE; + writel(cr, si->firi_base + FIRIRCR); + clk_disable(si->firi_clk); + + gpio_firi_inactive(); + + si->suspend = 1; + si->open = 0; + } + return 0; +} + +/*! + * This function is called to bring the FIRI back from a low power state. Refer + * to the document driver-model/driver.txt in the kernel source tree for more + * information. + * + * @param pdev the device structure used to give information on which FIRI + * to resume + * + * @return The function always returns 0. + */ +static int mxc_irda_resume(struct platform_device *pdev) +{ + struct net_device *ndev = platform_get_drvdata(pdev); + struct mxc_irda *si = netdev_priv(ndev); + unsigned long flags; + + if (!si) { + return 0; + } + + if (si->suspend == 1 && !si->open) { + + /*Initialise the UART first */ + clk_enable(si->uart_clk); + + /*Now init FIRI */ + gpio_firi_active(si->firi_base + FIRITCR, FIRITCR_TPP); + mxc_irda_startup(si); + + /* Enable the UART and FIRI interrupts.. */ + local_irq_save(flags); + enable_irq(si->uart_irq); + if (si->uart_irq != si->firi_irq) { + enable_irq(si->firi_irq); + if (si->uart_irq != si->uart_irq1) { + enable_irq(si->uart_irq1); + } + } + local_irq_restore(flags); + + /* Let the kernel know that we are alive and kicking.. */ + netif_device_attach(ndev); + + si->suspend = 0; + si->open = 1; + } + return 0; +} +#else +#define mxc_irda_suspend NULL +#define mxc_irda_resume NULL +#endif + +static int mxc_irda_init_iobuf(iobuff_t * io, int size) +{ + io->head = kmalloc(size, GFP_KERNEL | GFP_DMA); + if (io->head != NULL) { + io->truesize = size; + io->in_frame = FALSE; + io->state = OUTSIDE_FRAME; + io->data = io->head; + } + return io->head ? 0 : -ENOMEM; + +} + +/*! + * This function is called during the driver binding process. + * This function requests for memory, initializes net_device structure and + * registers with kernel. + * + * @param pdev the device structure used to store device specific + * information that is used by the suspend, resume and remove + * functions + * + * @return The function returns 0 on success and a non-zero value on failure + */ +static int mxc_irda_probe(struct platform_device *pdev) +{ + struct net_device *dev; + struct mxc_irda *si; + struct resource *uart_res, *firi_res; + int uart_irq, firi_irq, uart_irq1; + unsigned int baudrate_mask = 0; + int err; + + uart_res = &pdev->resource[0]; + uart_irq = pdev->resource[1].start; + + firi_res = &pdev->resource[2]; + firi_irq = pdev->resource[3].start; + + uart_irq1 = pdev->resource[4].start; + + if (!uart_res || uart_irq == NO_IRQ || !firi_res || firi_irq == NO_IRQ) { + dev_err(&pdev->dev, "Unable to find resources\n"); + return -ENXIO; + } + + err = + request_mem_region(uart_res->start, SZ_16K, + "MXC_IRDA") ? 0 : -EBUSY; + if (err) { + dev_err(&pdev->dev, "Failed to request UART memory region\n"); + return -ENOMEM; + } + + err = + request_mem_region(firi_res->start, SZ_16K, + "MXC_IRDA") ? 0 : -EBUSY; + if (err) { + dev_err(&pdev->dev, "Failed to request FIRI memory region\n"); + goto err_mem_1; + } + + dev = alloc_irdadev(sizeof(struct mxc_irda)); + if (!dev) { + goto err_mem_2; + } + + si = netdev_priv(dev); + si->dev = &pdev->dev; + + si->mxc_ir_plat = pdev->dev.platform_data; + si->uart_clk = si->mxc_ir_plat->uart_clk; + + si->uart_res = uart_res; + si->firi_res = firi_res; + si->uart_irq = uart_irq; + si->firi_irq = firi_irq; + si->uart_irq1 = uart_irq1; + + si->uart_base = ioremap(uart_res->start, SZ_16K); + si->firi_base = ioremap(firi_res->start, SZ_16K); + + if (!(si->uart_base || si->firi_base)) { + err = -ENOMEM; + goto err_mem_3; + } + + /* + * Initialise the SIR buffers + */ + err = mxc_irda_init_iobuf(&si->rx_buff, UART_BUFF_SIZE); + if (err) { + goto err_mem_4; + } + + err = mxc_irda_init_iobuf(&si->tx_buff, UART_BUFF_SIZE); + if (err) { + goto err_mem_5; + } + + dev->hard_start_xmit = mxc_irda_hard_xmit; + dev->open = mxc_irda_start; + dev->stop = mxc_irda_stop; + dev->do_ioctl = mxc_irda_ioctl; + dev->get_stats = mxc_irda_stats; + + irda_init_max_qos_capabilies(&si->qos); + + /* + * We support + * SIR(9600, 19200,38400, 57600 and 115200 bps) + * FIR(4 Mbps) + * Min Turn Time set to 1ms or greater. + */ + baudrate_mask |= IR_9600 | IR_19200 | IR_38400 | IR_57600 | IR_115200; + baudrate_mask |= IR_4000000 << 8; + + si->qos.baud_rate.bits &= baudrate_mask; + si->qos.min_turn_time.bits = 0x7; + + irda_qos_bits_to_value(&si->qos); + +#ifdef FIRI_SDMA_RX + si->tskb = NULL; + tasklet_init(&dma_rx_tasklet, mxc_irda_rx_task, (unsigned long)si); +#endif + err = register_netdev(dev); + if (err == 0) { + platform_set_drvdata(pdev, dev); + } else { + kfree(si->tx_buff.head); + err_mem_5: + kfree(si->rx_buff.head); + err_mem_4: + iounmap(si->uart_base); + iounmap(si->firi_base); + err_mem_3: + free_netdev(dev); + err_mem_2: + release_mem_region(firi_res->start, SZ_16K); + err_mem_1: + release_mem_region(uart_res->start, SZ_16K); + } + return err; +} + +/*! + * Dissociates the driver from the FIRI device. Removes the appropriate FIRI + * port structure from the kernel. + * + * @param pdev the device structure used to give information on which FIRI + * to remove + * + * @return The function always returns 0. + */ +static int mxc_irda_remove(struct platform_device *pdev) +{ + struct net_device *dev = platform_get_drvdata(pdev); + struct mxc_irda *si = netdev_priv(dev); + + if (si->uart_base) + iounmap(si->uart_base); + if (si->firi_base) + iounmap(si->firi_base); + if (si->firi_res->start) + release_mem_region(si->firi_res->start, SZ_16K); + if (si->uart_res->start) + release_mem_region(si->uart_res->start, SZ_16K); + if (si->tx_buff.head) + kfree(si->tx_buff.head); + if (si->rx_buff.head) + kfree(si->rx_buff.head); + + platform_set_drvdata(pdev, NULL); + unregister_netdev(dev); + free_netdev(dev); + + return 0; +} + +/*! + * This structure contains pointers to the power management callback functions. + */ +static struct platform_driver mxcir_driver = { + .driver = { + .name = "mxcir", + }, + .probe = mxc_irda_probe, + .remove = mxc_irda_remove, + .suspend = mxc_irda_suspend, + .resume = mxc_irda_resume, +}; + +/*! + * This function is used to initialize the FIRI driver module. The function + * registers the power management callback functions with the kernel and also + * registers the FIRI callback functions. + * + * @return The function returns 0 on success and a non-zero value on failure. + */ +static int __init mxc_irda_init(void) +{ + return platform_driver_register(&mxcir_driver); +} + +/*! + * This function is used to cleanup all resources before the driver exits. + */ +static void __exit mxc_irda_exit(void) +{ + platform_driver_unregister(&mxcir_driver); +} + +module_init(mxc_irda_init); +module_exit(mxc_irda_exit); + +MODULE_AUTHOR("Freescale Semiconductor"); +MODULE_DESCRIPTION("MXC IrDA(SIR/FIR) driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/irda/mxc_ir.h b/drivers/net/irda/mxc_ir.h new file mode 100644 index 000000000000..e8d77e86a548 --- /dev/null +++ b/drivers/net/irda/mxc_ir.h @@ -0,0 +1,133 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#ifndef __MXC_FIRI_REG_H__ +#define __MXC_FIRI_REG_H__ + +#include <asm/hardware.h> + +/*! + * @defgroup FIRI Fast IR Driver + */ + +/*! + * @file mxc_ir.h + * + * @brief MXC FIRI header file + * + * This file defines base address and bits of FIRI registers + * + * @ingroup FIRI + */ + +/*! + * FIRI maximum packet length + */ +#define FIR_MAX_RXLEN 2047 + +/* + * FIRI Transmitter Control Register + */ +#define FIRITCR 0x00 +/* + * FIRI Transmitter Count Register + */ +#define FIRITCTR 0x04 +/* + * FIRI Receiver Control Register + */ +#define FIRIRCR 0x08 +/* + * FIRI Transmitter Status Register + */ +#define FIRITSR 0x0C +/* + * FIRI Receiver Status Register + */ +#define FIRIRSR 0x10 +/* + * FIRI Transmitter FIFO + */ +#define FIRITXFIFO 0x14 +/* + * FIRI Receiver FIFO + */ +#define FIRIRXFIFO 0x18 +/* + * FIRI Control Register + */ +#define FIRICR 0x1C + +/* + * Bit definitions of Transmitter Controller Register + */ +#define FIRITCR_HAG (1<<24) /* H/W address generator */ +#define FIRITCR_SRF_FIR (0<<13) /* Start field repeat factor */ +#define FIRITCR_SRF_MIR (1<<13) /* Start field Repeat Factor */ +#define FIRITCR_TDT_MIR (2<<10) /* TX trigger for MIR is set to 32 bytes) */ +#define FIRITCR_TDT_FIR (1<<10) /* TX trigger for FIR is set to 16 bytes) */ +#define FIRITCR_TCIE (1<<9) /* TX Complete Interrupt Enable */ +#define FIRITCR_TPEIE (1<<8) /* TX Packet End Interrupt Enable */ +#define FIRITCR_TFUIE (1<<7) /* TX FIFO Under-run Interrupt Enable */ +#define FIRITCR_PCF (1<<6) /* Packet Complete by FIFO */ +#define FIRITCR_PC (1<<5) /* Packet Complete */ +#define FIRITCR_SIP (1<<4) /* TX Enable of SIP */ +#define FIRITCR_TPP (1<<3) /* TX Pulse Polarity bit */ +#define FIRITCR_TM_FIR (0<<1) /* TX Mode 4 Mbps */ +#define FIRITCR_TM_MIR1 (1<<1) /* TX Mode 0.576 Mbps */ +#define FIRITCR_TM_MIR2 (1<<2) /* TX Mode 1.152 Mbps */ +#define FIRITCR_TE (1<<0) /* TX Enable */ + +/* + * Bit definitions of Transmitter Count Register + */ +#define FIRITCTR_TPL 511 /* TX Packet Length set to 512 bytes */ + +/* + * Bit definitions of Receiver Control Register + */ +#define FIRIRCR_RAM (1<<24) /* RX Address Match */ +#define FIRIRCR_RPEDE (1<<11) /* Packet End DMA request Enable */ +#define FIRIRCR_RDT_MIR (2<<8) /* DMA Trigger level(64 bytes in RXFIFO) */ +#define FIRIRCR_RDT_FIR (1<<8) /* DMA Trigger level(16 bytes in RXFIFO) */ +#define FIRIRCR_RPA (1<<7) /* RX Packet Abort */ +#define FIRIRCR_RPEIE (1<<6) /* RX Packet End Interrupt Enable */ +#define FIRIRCR_PAIE (1<<5) /* Packet Abort Interrupt Enable */ +#define FIRIRCR_RFOIE (1<<4) /* RX FIFO Overrun Interrupt Enable */ +#define FIRIRCR_RPP (1<<3) /* RX Pulse Polarity bit */ +#define FIRIRCR_RM_FIR (0<<1) /* 4 Mbps */ +#define FIRIRCR_RM_MIR1 (1<<1) /* 0.576 Mbps */ +#define FIRIRCR_RM_MIR2 (1<<2) /* 1.152 Mbps */ +#define FIRIRCR_RE (1<<0) /* RX Enable */ + +/* Transmitter Status Register */ +#define FIRITSR_TFP 0xFF00 /* Mask for available bytes in TX FIFO */ +#define FIRITSR_TC (1<<3) /* Transmit Complete bit */ +#define FIRITSR_SIPE (1<<2) /* SIP End bit */ +#define FIRITSR_TPE (1<<1) /* Transmit Packet End */ +#define FIRITSR_TFU (1<<0) /* TX FIFO Under-run */ + +/* Receiver Status Register */ +#define FIRIRSR_RFP 0xFF00 /* mask for available bytes RX FIFO */ +#define FIRIRSR_PAS (1<<5) /* preamble search */ +#define FIRIRSR_RPE (1<<4) /* RX Packet End */ +#define FIRIRSR_RFO (1<<3) /* RX FIFO Overrun */ +#define FIRIRSR_BAM (1<<2) /* Broadcast Address Match */ +#define FIRIRSR_CRCE (1<<1) /* CRC error */ +#define FIRIRSR_DDE (1<<0) /* Address, control or data field error */ + +/* FIRI Control Register */ +#define FIRICR_BL (32<<5) /* Burst Length is set to 32 */ +#define FIRICR_OSF (0<<1) /* Over Sampling Factor */ + +#endif /* __MXC_FIRI_REG_H__ */ diff --git a/drivers/otg/Kconfig b/drivers/otg/Kconfig new file mode 100644 index 000000000000..be5fcbf1bfc2 --- /dev/null +++ b/drivers/otg/Kconfig @@ -0,0 +1,52 @@ +# @(#) sl@belcarra.com/whiskey.enposte.net|otg/.Makefiles/Kconfig-freescale|20070525074749|60734 +# Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp +menu "On-The-Go and USB Peripheral Support" + + config OTG + tristate "Support for On-The-Go and USB Peripheral Support" + ---help--- + Configure all or part of the Belcarra OTG Stack + + + comment "" + + menu "On-The-Go Support Platform Selection" + depends on OTG + + source "drivers/otg/hardware/Kconfig-zasevb" + source "drivers/otg/hardware/Kconfig-zasevb-arc" + source "drivers/otg/hardware/Kconfig-imx31ads" + source "drivers/otg/hardware/Kconfig-isp1301" + + endmenu + + source "drivers/otg/Kconfig-otg" + + if ( OTG_ZASEVB != 'n') + + config OTG_NEW_TX_CACHE + bool 'Use new macro to synchronize TX cache' + default n + ---help--- + Newer kernels need this set. Turn this off if you get TX_CACHE build errors + + endif + + + + menu "Targeted Peripherals List (USB Peripheral Function Drivers)" + depends on OTG_PLATFORM_OTG || OTG_PLATFORM_USBD + #---help--- + #A list of USB peripheral types that this device + #can emulate when it is acting as a peripheral. + source "drivers/otg/functions/generic/Kconfig" + source "drivers/otg/functions/acm/Kconfig" + source "drivers/otg/functions/mouse/Kconfig" + source "drivers/otg/functions/msc/Kconfig" + source "drivers/otg/functions/network/Kconfig" +# source "drivers/otg/functions/belcarra/Kconfig" +# source "drivers/otg/functions/motorola/Kconfig" +# source "drivers/otg/functions/isotest/Kconfig" + endmenu + +endmenu diff --git a/drivers/otg/Kconfig-otg b/drivers/otg/Kconfig-otg new file mode 100644 index 000000000000..b8ebebdabe71 --- /dev/null +++ b/drivers/otg/Kconfig-otg @@ -0,0 +1,243 @@ +# +# @(#) balden@belcarra.com/seth2.rillanon.org|otg/Kconfig-otg|20070531215019|62203 +# Copyright (c) 2005 Belcarra Technologies Corp +# Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp +# +# The platform configuration must define the following capability flags to +# allow this common selection to work. + +# This implements selection of the appropriate role that the software should +# implement: +# + +# Device and Platform Capabilities +# Supports Acting as a: +# OTG_PLATFORM_USBD standard USB peripheral +# OTG_PLATFORM_USB standard USB host +# OTG_PLATFORM_OTG OTG Device +# +# OTG_HIGH_SPEED_CAPABLE High Speed Capable Device. +# OTG_REMOTE_WAKEUP_CAPABLE Device can do Remote Wakeup. +# +# OTG_BUS_POWERED_CAPABLE Platform can be bus powered. +# + +# Role Configuration +# Implements: +# OTG_USB_PERIPHERAL USB peripheral +# OTG_USB_HOST USB host +# OTG_USB_PERIPHERAL_OR_HOST USB peripheral or USB host +# OTG_BDEVICE_WITH_SRP OTG SRP capable B-Device +# OTG_DEVICE OTG Device +# +# + + comment "" + + menu "On-The-Go Options" + + depends on OTG_PLATFORM_OTG|| OTG_PLATFORM_USBD + + + # USBOTG Role Configuration + choice + prompt "On-The-Go or USB Device Role Configuration" + + config OTG_USB_PERIPHERAL + bool "USB Peripheral (only)" + depends on OTG_PLATFORM_USBD + ---help--- + Implement a standard USB Peripheral only. + + config OTG_USB_WIRED_DEVICE + bool "USB Wired Device (only)" + depends on OTG_PLATFORM_WUSBD + ---help--- + Implement a Wired USB Device only. + + #config OTG_USB_HOST + # bool "USB Host (only)" + # depends on OTG_PLATFORM_USB + # ---help--- + # Implement a standard USB Host only. + + #config OTG_USB_WIRED_HOST + # bool "USB Host (only)" + # depends on OTG_PLATFORM_WUSB + # ---help--- + # Implement a Wired USB Host only. + + config OTG_USB_PERIPHERAL_OR_HOST + bool "USB Peripheral or Host" + depends on OTG_PLATFORM_USB && OTG_PLATFORM_USBD && USB + ---help--- + Implement a standard USB Peripheral and Host. + Support for this is platform-dependent. + + config OTG_USB_WIRED_DEVICE_OR_HOST + bool "USB Wired Device or Host" + depends on OTG_PLATFORM_WUSB && OTG_PLATFORM_WUSBD && USB + ---help--- + Implement a Wired USB Peripheral and Host. + Support for this is platform-dependent. + + #config OTG_BDEVICE_WITH_SRP + # bool "SRP Capable B-Device (Only)" + # depends on OTG_PLATFORM_OTG + # ---help--- + # Implement an On-The-Go Peripheral-Only SRP-capable device. This + # is similar to a Traditional USB Peripheral but enables + # On-The-Go features such as Service Request Protocol (SRP). + + config OTG_DEVICE + bool "OTG Device - can act as A or B Device" + depends on OTG_PLATFORM_OTG && USB + ---help--- + Implement full On-The-Go Device support for a platform that + supports implementation of A and B Devices that implement + Service Request Protocol (SRP) and Host Negotiation Protocol + (HNP). + + endchoice + + # High Speed + if ( OTG_HIGH_SPEED_CAPABLE = 'y') + # we may want to make this conditional + config OTG_HIGH_SPEED + bool 'Enable High Speed for this device' + default OTG_HIGH_SPEED_CAPABLE + ---help--- + This device is capable of USB 2.0 High Speed connections. + This option can be used to limit connections to Full Speed + only. Generally this should not be neccessary. + endif + + # Device that can be bus powered + if ( OTG_BUS_POWERED_CAPABLE = 'y') + + config OTG_SELF_POWERED + bool 'Enable if device is Self-Powered' + default y + ---help--- + This device is capable of being bus powered or self powered. + Enable this option if it is self powered. + + if ( OTG_SELF_POWERED = 'n') + + config OTG_BMAXPOWER + int 'bMaxPower - 2mA units' + default 1 + ---help--- + The amount of power the device will draw in 2mA units. + + endif + + # N.B. bMaxPower must be 1 even for self powered devices. + if ( OTG_SELF_POWERED != 'n') + config OTG_BMAXPOWER + int + default 1 + endif + endif + + # Device that can only be self-powered, + # N.B. bMaxPower must be 1 even for self powered devices. + if ( OTG_BUS_POWERED_CAPABLE != 'y') + + config OTG_SELF_POWERED + bool + default y + + config OTG_BMAXPOWER + int + default 1 + endif + + # Device that can implement Remote Wakeup + if ( OTG_REMOTE_WAKEUP_CAPABLE = 'y') + + config OTG_REMOTE_WAKEUP + bool 'Enable Remote Wakeup' + default y + ---help--- + This device can initiate a Remote Wakeup. Do you wish + to enable this capability. This will add the Remote Wakeup + bit to bmAttributes. + + Note that Chapter 9 tests will require + that you use the usbadmin (or similiar) program to test + this. + + endif + + + config OTG_TR_AUTO + bool 'Enable Auto-Start' + default OTG_USB_PERIPHERAL || OTG_USB_PERIPHERAL_OR_HOST || OTG_USB_HOST + ---help--- + Automatically start and enable standard USB Device or USB + Host. If disabled, a USBOTG management application will need + to enable the OTG software before the device can be used. + + #comment "" + #comment "OTG Support In Linux USB Core" + # + #config OTG_HOST + # boolean "USB Host - OTG Support" + # depends on OTG_DEVICE && USB + # default OTG_DEVICE + # ---help--- + # This option adds OTG options in the standard Linux + # USB Core to support implmentation of On-The-Go Devices. + # This should be enabled if implementing an OTG Device. + + comment "" + comment "Language" + + config OTG_LANGID + hex "Language ID)" + depends on OTG + default "0x0904" + ---help--- + This option sets the default language ID. Typical + values are 0x0904 for US English, or 0x0903 for English. + + + comment "Testing and Portability" + + config OTG_TRACE + bool 'OTG Fast Tracing' + depends on OTG!=n + ---help--- + This option implements register trace to support + driver debugging; do not enable in production devices. + + config OTG_TRACE_PACKED + bool 'OTG Fast Tracing -- packed option' + depends on (OTG !=n) && (OTG_TRACE != n) + default n + ---help--- + This option is experimental. Do not enable for normal use + config OTG_LATENCY_CHECK + bool 'OTG Latency Check' + depends on (OTG !=n) && (OTG_TRACE != n) + default n + ---help--- + This option is experimental. Do not enable for normal use + + + config OTG_NOC99 + bool 'Disable C99 initializers' + ---help--- + Enable this if your compiler does not allow a structure to + be initialized as .element_name=value + + config OTG_INTERNAL_TESTING + bool 'Enable internal testing modes' + depends on (OTG != n) + default n + ---help--- + This option enables Belcara internal testing options. + Do not enable for normal use. + + endmenu diff --git a/drivers/otg/Makefile b/drivers/otg/Makefile new file mode 100644 index 000000000000..c029a1f545bd --- /dev/null +++ b/drivers/otg/Makefile @@ -0,0 +1,30 @@ +# +# Belcarra OTG - On-The-Go +# @(#) balden@belcarra.com/seth2.rillanon.org|otg/.Makefiles/Makefile-freescale|20070530040553|49544 +# +# Copyright (c) 2004-2005 Belcarra Technologies Corp +# Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + +TOPDIR ?= ../.. + +obj-y += +obj-$(CONFIG_OTG) += otgcore/ + +EXTRA_CFLAGS += -Wno-format -Wall +# Function Drivers +obj-$(CONFIG_OTG_GENERIC) += functions/generic/ +obj-$(CONFIG_OTG_ACM) += functions/acm/ +obj-$(CONFIG_OTG_MSC) += functions/msc/ +obj-$(CONFIG_OTG_MOUSE) += functions/mouse/ +obj-$(CONFIG_OTG_NETWORK) += functions/network/ + +ifeq ($(CONFIG_OTG),m) +obj-m += hardware/ +ifeq ($(CONFIG_OTG_NETWORK),m) +obj-y += functions/network/ +endif +endif + +ifeq ($(CONFIG_OTG),y) +obj-y += hardware/ +endif diff --git a/drivers/otg/functions/acm/Kconfig b/drivers/otg/functions/acm/Kconfig new file mode 100644 index 000000000000..4c648a9ddd39 --- /dev/null +++ b/drivers/otg/functions/acm/Kconfig @@ -0,0 +1,19 @@ +# @(#) tt/root@belcarra.com/debian286.bbb|otg/functions/acm/Kconfig|20070628094342|31231 + +# Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + +menu "OTG ACM Function" + +config OTG_ACM + tristate "CDC ACM Function" + depends on OTG + default OTG + +config OTG_ACM_PIPES_TEST + bool "Pipes Test Only" + depends on OTG && OTG_ACM && OTG_INTERNAL_TESTING + default n + ---help--- + Setting this allows the pipes to be tested separately. + +endmenu diff --git a/drivers/otg/functions/acm/Makefile b/drivers/otg/functions/acm/Makefile new file mode 100644 index 000000000000..ccb7f855fea3 --- /dev/null +++ b/drivers/otg/functions/acm/Makefile @@ -0,0 +1,15 @@ +# Function driver for a CDC ACM OTG Device +# @(#) balden@belcarra.com|otg/functions/acm/Makefile-l26|20060419204257|06816 +# +# Copyright (c) 2004 Belcarra Technologies Corp +# Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + +tty_if-objs := acm-l26.o acm.o tty-l26-os.o tty-if.o + +obj-$(CONFIG_OTG_ACM) += tty_if.o + +OTG=$(TOPDIR)/drivers/otg +ACMD=$(OTG)/functions/acm +USBDCORE_DIR=$(OTG)/usbdcore +EXTRA_CFLAGS += -I$(ACMD) -I$(OTG) -Wno-unused -Wno-format -I$(USBDCORE_DIR) +EXTRA_CFLAGS_nostdinc += -I$(ACMD) -I$(OTG) -Wno-unused -Wno-format -I$(USBDCORE_DIR) diff --git a/drivers/otg/functions/acm/acm-l26.c b/drivers/otg/functions/acm/acm-l26.c new file mode 100644 index 000000000000..f445569c8846 --- /dev/null +++ b/drivers/otg/functions/acm/acm-l26.c @@ -0,0 +1,132 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/functions/acm/acm-l24-os.c + * @(#) tt/root@belcarra.com/debian286.bbb|otg/functions/acm/acm-l26.c|20070911235624|22864 + * + * Copyright (c) 2003-2005 Belcarra Technologies Corp + * Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + * + * By: + * Stuart Lynne <sl@belcarra.com>, + * Bruce Balden <balden@belcarra.com> + * + */ + +/*! + * @file otg/functions/acm/acm-l26.c + * @brief ACM Function Driver private defines + * + * This is the linux module wrapper for the tty-if function driver. + * + * TTY + * Interface + * Upper +----------+ + * Edge | tty-l26 | + * Implementation +----------+ + * + * + * Function +----------+ + * Descriptors | tty-if | + * Registration +----------+ + * + * + * Function +----------+ + * I/O | acm | + * Implementation +----------+ + * + * + * Module +----------+ + * Loading | acm-l26 | <----- + * +----------+ + * + * @ingroup ACMFunction + */ + + +//#include <otg/osversion.h> +#include <otg/otg-compat.h> +#include <otg/otg-module.h> + +MOD_AUTHOR ("sl@belcarra.com"); + +MOD_DESCRIPTION ("Belcarra TTY Function"); +EMBED_LICENSE(); + +#include <linux/init.h> +#include <asm/uaccess.h> +#include <linux/ctype.h> +#include <linux/timer.h> +#include <linux/interrupt.h> +#include <asm/atomic.h> +#include <linux/tty.h> +#include <linux/tty_driver.h> +#include <linux/tty_flip.h> +#include <linux/smp_lock.h> +#include <linux/slab.h> +#include <linux/serial.h> + +#include <otg/usbp-chap9.h> +#include <otg/usbp-cdc.h> +#include <otg/usbp-func.h> + +#include <linux/capability.h> +#include <otg/otg-trace.h> +#include "acm.h" +#include "tty.h" + +MOD_PARM_INT (vendor_id, "Device Vendor ID", 0); +MOD_PARM_INT (product_id, "Device Product ID", 0); +MOD_PARM_INT (max_queued_urbs, "Maximum TX Queued Urbs", 0); +MOD_PARM_INT (max_queued_bytes, "Maximum TX Queued Bytes", 0); + +/*! acm_l26_modinit - module init + * + * This is called immediately after the module is loaded or during + * the kernel driver initialization if linked into the kernel. + * + */ +STATIC int acm_l26_modinit (void) +{ + BOOL tty_l26 = FALSE, tty_if = FALSE; + + /* register tty and usb interface function drivers + */ + TTY = otg_trace_obtain_tag(NULL, "acm"); + THROW_UNLESS(tty_l26 = BOOLEAN(!tty_l26_init("tty_if", 2)), error); + THROW_UNLESS(tty_if = BOOLEAN(!tty_if_init()), error); + + CATCH(error) { + if (tty_l26) tty_l26_exit(); + if (tty_if) tty_if_exit(); + otg_trace_invalidate_tag(TTY); + return -EINVAL; + } + return 0; +} + + +/*! acm_l26_modexit - module cleanup + * + * This is called prior to the module being unloaded. + */ +STATIC void acm_l26_modexit (void) +{ + /* de-register as tty and usb drivers */ + tty_l26_exit(); + tty_if_exit(); + otg_trace_invalidate_tag(TTY); +} + +module_init (acm_l26_modinit); +module_exit (acm_l26_modexit); diff --git a/drivers/otg/functions/acm/acm.c b/drivers/otg/functions/acm/acm.c new file mode 100644 index 000000000000..5c227800e003 --- /dev/null +++ b/drivers/otg/functions/acm/acm.c @@ -0,0 +1,1478 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/* + * otg/functions/acm/acm-fd.c + * @(#) tt/root@belcarra.com/debian286.bbb|otg/functions/acm/acm.c|20070912103628|42749 + * + * Copyright (c) 2003-2005 Belcarra Technologies Corp + * Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + * + * By: + * Stuart Lynne <sl@belcarra.com>, + * Bruce Balden <balden@belcarra.com> + */ +/*! + * @file otg/functions/acm/acm.c + * @brief ACM Function Driver private defines + * + * This is the ACM Protocol specific portion of the + * ACM driver. + * + * TTY + * Interface + * Upper +----------+ + * Edge | tty-l26 | + * Implementation +----------+ + * + * + * Function +----------+ + * Descriptors | tty-if | + * Registration +----------+ + * + * + * Function +----------+ + * I/O | acm | <----- + * Implementation +----------+ + * + * + * Module +----------+ + * Loading | acm-l26 | + * +----------+ + * + * @ingroup ACMFunction + */ + + +#include <otg/otg-compat.h> +#include <otg/otg-module.h> + +#include <linux/init.h> +#include <asm/uaccess.h> +#include <linux/ctype.h> +#include <linux/timer.h> +#include <linux/interrupt.h> +#include <asm/atomic.h> +#include <linux/smp_lock.h> +#include <linux/slab.h> +#include <linux/smp_lock.h> +#include <linux/wait.h> + +#include <otg/usbp-chap9.h> +#include <otg/usbp-cdc.h> +#include <otg/usbp-func.h> +#include <otg/usbp-mcpc.h> + +#include <otg/otg-trace.h> +#include <otg/otg-api.h> +#include "acm.h" + +otg_tag_t acm_trace_tag; + +/* ********************************************************************************************* */ +/* ********************************************************************************************* */ +/*! acm_urb_sent_ep0 - called to indicate ep0 URB transmit finished + * + * @param urb pointer to struct usbd_urb + * @param urb_rc result + * @return non-zero for error + */ +int acm_urb_sent_ep0 (struct usbd_urb *urb, int urb_rc) +{ + struct usbd_function_instance *function_instance = urb->function_instance; + struct acm_private *acm = function_instance ? function_instance->privdata : NULL; + + //otg_trace_msg(TTY, __FUNCTION__, + // "URB SENT acm: %x urb: %x urb_rc: %d length: %d", + // (u32)acm, (u32)urb, urb_rc, urb->actual_length); + + TRACE_MSG4(TTY,"URB SENT acm: %x urb: %x urb_rc: %d length: %d", (u32)acm, (u32)urb, urb_rc, urb->actual_length); + + RETURN_EINVAL_UNLESS(acm); + + TRACE_MSG1(TTY,"urb: %x", urb); + TRACE_MSG1(TTY, "length: %d", urb->actual_length); + + urb->function_privdata = NULL; + usbd_free_urb(urb); + return 0; +} + +/*! acm_line_coding_urb_received - callback for sent URB + * @brief Handles notification that an urb has been sent (successfully or otherwise). + * + * @param urb + * @param urb_rc + * @return non-zero for failure. + */ +int acm_line_coding_urb_received (struct usbd_urb *urb, int urb_rc) +{ + struct usbd_function_instance *function_instance = urb->function_instance; + struct acm_private *acm = function_instance ? function_instance->privdata : NULL; + + TRACE_MSG5(TTY,"URB RECEIVED acm: %x urb: %x urb_rc: %d length: %d %d", acm, urb, urb_rc, + urb->actual_length, sizeof(struct cdc_acm_line_coding)); + + RETURN_EINVAL_UNLESS(acm); + RETURN_EINVAL_IF (USBD_URB_OK != urb_rc); + RETURN_EINVAL_IF (urb->actual_length < sizeof(struct cdc_acm_line_coding)); + + // something changed, copy and notify + + memcpy(&acm->line_coding, urb->buffer, sizeof(struct cdc_acm_line_coding)); + + // XXX notify application if baudrate has changed + // we need an os layer function to call here.... it should decode line_coding + // and if possible notify posix layer that things have changed. + + usbd_free_urb(urb); + + if (acm->ops->line_coding) + acm->ops->line_coding(function_instance); + + return 0; +} + + +/*! + * @brief acm_set_link_urb_received - callback for sent URB + * @param urb + * @param urb_rc result + * @return non-zero for error + */ +int acm_set_link_urb_received (struct usbd_urb *urb, int urb_rc) +{ + struct usbd_function_instance *function_instance = urb->function_instance; + struct acm_private *acm = function_instance ? function_instance->privdata : NULL; + TRACE_MSG4(TTY,"URB RECEIVED acm: %x urb: %x urb_rc: %d length: %d", acm, urb, urb_rc, urb->actual_length); + + usbd_free_urb(urb); + return 0; +} + +/* ********************************************************************************************* */ +/* ********************************************************************************************* */ + +/*! + * acm_start_out_urb - called to queue out urb on ep0 to receive device request OUT data + * @param function + * @param len + * @param callback + * @return non-zero if error + */ +int acm_start_out_urb_ep0(struct usbd_function_instance *function, int len, int (*callback) (struct usbd_urb *, int)) +{ + struct acm_private *acm = function->privdata; + struct usbd_urb *urb; + RETURN_ZERO_UNLESS(len); + RETURN_ENOMEM_UNLESS((urb = usbd_alloc_urb_ep0(function, len, callback))); + urb->function_privdata = acm; + urb->function_instance = function; + RETURN_ZERO_UNLESS (usbd_start_out_urb(urb)); + usbd_free_urb(urb); // de-alloc if error + return -EINVAL; +} + +/*! + * acm_device_request - called to process a request to endpoint or interface + * @param function + * @param request + * @return non-zero if error + */ +int acm_device_request (struct usbd_function_instance *function, struct usbd_device_request *request) +{ + struct usbd_interface_instance *interface_instance = (struct usbd_interface_instance *)function; + struct acm_private *acm = interface_instance->function.privdata; + + TRACE_SETUP(TTY, request); + + // determine the request direction and process accordingly + switch (request->bmRequestType & (USB_REQ_DIRECTION_MASK | USB_REQ_TYPE_MASK)) { + + case USB_REQ_HOST2DEVICE | USB_REQ_TYPE_CLASS: + TRACE_MSG1(TTY, "H2D CLASS bRequest: %02x", request->bRequest); + switch (request->bRequest) { + /* CDC */ + case CDC_CLASS_REQUEST_SEND_ENCAPSULATED: break; + case CDC_CLASS_REQUEST_SET_COMM_FEATURE: break; + case CDC_CLASS_REQUEST_CLEAR_COMM_FEATURE: break; + + case CDC_CLASS_REQUEST_SET_LINE_CODING: /* 0x20 */ + return acm_start_out_urb_ep0(function, le16_to_cpu(request->wLength), acm_line_coding_urb_received); + + case CDC_CLASS_REQUEST_SET_CONTROL_LINE_STATE: { /* 0x22 */ + unsigned int prev_bmLineState = acm->bmLineState; + acm->bmLineState = le16_to_cpu(request->wValue); + + // schedule writers or hangup IFF open + TRACE_MSG5(TTY, "acm: %x interface: %x set control state, bmLineState: %04x previous: %04x changed: %04x", + acm, interface_instance, + acm->bmLineState, prev_bmLineState, acm->bmLineState ^ prev_bmLineState); + + /* make sure there really is a state change in D0 */ + if ((acm->bmLineState ^ prev_bmLineState) & CDC_LINESTATE_D0_DTR) { + + TRACE_MSG1(TTY, "DTR state changed -> %x", + (acm->bmLineState & CDC_LINESTATE_D0_DTR)); + + if (acm->bmLineState & CDC_LINESTATE_D0_DTR) { + if (acm->flags & ACM_OPENED) + acm->ops->schedule_wakeup_writers(function); + + //acm_schedule_recv_restart(function); + //acm->ops->recv_start_bh(function); + acm_start_recv_urbs(function); + + } + #if 0 + else { + if (acm->flags & ACM_OPENED) + acm->ops->schedule_hangup(function); + + acm_flush(function); + } + #endif + + /* wake up blocked opens */ + //acm->ops->wakeup_opens(function); + + /* wake up blocked ioctls */ + acm->ops->wakeup_state(function); + + } + + /* make sure there really is a state change in D1 */ + if ((acm->bmLineState ^ prev_bmLineState) & CDC_LINESTATE_D1_RTS) { + + TRACE_MSG1(TTY, "DCD state changed -> %x", + (acm->bmLineState & CDC_LINESTATE_D1_RTS)); + + /* wake up blocked opens */ + acm->ops->wakeup_opens(function); + + /* wake up blocked ioctls */ + acm->ops->wakeup_state(function); + + } + + /* check for hangup - both D0 and D1 not set */ + if ((acm->bmLineState ^ prev_bmLineState) & (CDC_LINESTATE_D0_DTR | CDC_LINESTATE_D1_RTS)) { + UNLESS ((acm->bmLineState & (CDC_LINESTATE_D0_DTR | CDC_LINESTATE_D1_RTS))) + // XXX check clocal + //if (acm->flags & ACM_OPENED) + if ((acm->flags & ACM_OPENED) && !(acm->flags & ACM_LOCAL)) + acm->ops->schedule_hangup(function); + } + + + /* send notification if we have DCD */ + TRACE_MSG1(TTY, "bmUARTState: %04x sending (DCD|DSR) notification", acm->bmUARTState); + + acm_send_int_notification(function, CDC_NOTIFICATION_SERIAL_STATE, acm->bmUARTState); + break; + } + + case CDC_CLASS_REQUEST_SEND_BREAK: break; + + default: + return -EINVAL; + } + return 0; + + case USB_REQ_DEVICE2HOST | USB_REQ_TYPE_CLASS: + TRACE_MSG1(TTY, "D2H CLASS bRequest: %02x", request->bRequest); + switch (request->bRequest) { + /* CDC */ + case CDC_CLASS_REQUEST_GET_ENCAPSULATED: break; + case CDC_CLASS_REQUEST_GET_COMM_FEATURE: break; + case CDC_CLASS_REQUEST_GET_LINE_CODING: { + struct usbd_urb *urb; + + // XXX should check wLength + RETURN_ENOMEM_IF (!(urb = usbd_alloc_urb_ep0(function, sizeof(struct cdc_acm_line_coding), + acm_urb_sent_ep0))); + urb->function_instance = function; + urb->function_privdata = acm; + + memcpy(urb->buffer, &acm->line_coding, sizeof(struct cdc_acm_line_coding)); + + urb->actual_length = sizeof(struct cdc_acm_line_coding); + + TRACE_MSG1(TTY, "sending line coding urb: %p",(u32)(void*)urb); + RETURN_ZERO_UNLESS(usbd_start_in_urb(urb)); + usbd_free_urb(urb); + TRACE_MSG0(TTY, "(send failed)--> -EINVAL"); + return -EINVAL; + } + default: + TRACE_MSG1(TTY, "UNKNOWN D2H CLASS bRequest: %02x", request->bRequest); + return -EINVAL; + } + return 0; + + /* MCPC */ + case USB_REQ_HOST2DEVICE | USB_REQ_TYPE_VENDOR: + TRACE_MSG1(TTY, "H2D Vendor bRequest: %02x", request->bRequest); + switch (request->bRequest) { + /* MCPC */ + case MCPC_REQ_ACTIVATE_MODE: + TRACE_MSG1(TTY, "H2D Vendor bRequest: %02x MCPC_REQ_ACTIVATE_MODE", request->bRequest); + return 0; + + case MCPC_REQ_SET_LINK: + return acm_start_out_urb_ep0(function, le16_to_cpu(request->wLength), acm_set_link_urb_received); + + case MCPC_REQ_CLEAR_LINK: + TRACE_MSG1(TTY, "H2D Vendor bRequest: %02x MCPC_REQ_CLEAR_LINK", request->bRequest); + return 0; + + case MCPC_REQ_MODE_SPECIFIC_REQUEST: + TRACE_MSG1(TTY, "H2D Vendor bRequest: %02x MCPC_REQ_SPECIFIC_REQUEST", request->bRequest); + return 0; + default: + TRACE_MSG1(TTY, "UNKNOWN H2D CLASS bRequest: %02x XXXX", request->bRequest); + return -EINVAL; + } + break; + + case USB_REQ_DEVICE2HOST | USB_REQ_TYPE_VENDOR: + TRACE_MSG1(TTY, "D2H Vendor bRequest: %02x", request->bRequest); + switch (request->bRequest) { + case MCPC_REQ_GET_MODETABLE: + TRACE_MSG1(TTY, "D2H Vendor bRequest: %02x MCPC_REQ_GET_MODETABLE", request->bRequest); + return 0; + case MCPC_REQ_MODE_SPECIFIC_REQUEST: + TRACE_MSG1(TTY, "D2H Vendor bRequest: %02x MCPC_REQ_SPECIFIC_REQUEST", request->bRequest); + return 0; + default: + TRACE_MSG1(TTY, "UNKNOWN D2H Vendor bRequest: %02x", request->bRequest); + return -EINVAL; + } + break; + + default: + TRACE_MSG2(TTY, "unknown bmRequestType: %02x bRequest: %02x", request->bmRequestType, request->bRequest); + return -EINVAL; + } + return 0; +} + +/*! @brief acm_endpoint_cleared - called by the USB Device Core when endpoint cleared + * @param function_instance The function instance for this driver + * @param bEndpointAddress + * @return none + */ +void acm_endpoint_cleared (struct usbd_function_instance *function_instance, int bEndpointAddress) +{ + struct usbd_interface_instance *interface_instance = (struct usbd_interface_instance *)function_instance; + struct acm_private *acm = function_instance->privdata; + TRACE_MSG1(TTY,"acm: %x", (int)acm); + + TRACE_MSG1(TTY, "CLEARED bEndpointAddress: %02x", bEndpointAddress); +} + + +/* ******************************************************************************************* */ +/*! acm_open - + * @param function_instance + * @param chan + * @return int + */ +int acm_open(struct usbd_function_instance *function_instance, minor_chan_t chan) +{ + struct acm_private *acm = function_instance ? function_instance->privdata : NULL; + TRACE_MSG1(TTY,"acm: %x", (int)acm); + RETURN_EINVAL_UNLESS(acm); + + TRACE_MSG0(TTY,"OPEN"); + acm->flags |= ACM_OPENED; + // XXX config option for old behavior + acm->bmUARTState = /* CDC_UARTSTATE_BRXCARRIER_DCD | */ CDC_UARTSTATE_BTXCARRIER_DSR; + + //acm_schedule_recv_restart(function_instance); + // acm->ops->recv_start_bh(function_instance); + acm_start_recv_urbs(function_instance); + + TRACE_MSG1(TTY,"bmUARTState: %04x", acm->bmUARTState); + acm_send_int_notification(function_instance, CDC_NOTIFICATION_SERIAL_STATE, acm->bmUARTState); + return 0; +} + +/*! acm_flush + * @brief flush BLUK_IN, BLUK_OUT endpoints + * @param function_instance + * @return + */ +void acm_flush(struct usbd_function_instance *function_instance) +{ + struct acm_private *acm = function_instance ? function_instance->privdata : NULL; + TRACE_MSG1(TTY,"acm: %x", (int)acm); + RETURN_UNLESS(acm); + + RETURN_UNLESS(acm); + atomic_set(&acm->bytes_received, 0); + atomic_set(&acm->bytes_forwarded, 0); + // XXX config option for old behavior + acm->bmUARTState = /* CDC_UARTSTATE_BRXCARRIER_DCD |*/ CDC_UARTSTATE_BTXCARRIER_DSR; + TRACE_MSG1(TTY,"bmUARTState: %04x", acm->bmUARTState); + + acm->line_coding.dwDTERate = cpu_to_le32(0x1c200); // 115200 + acm->line_coding.bDataBits = 0x08; + + RETURN_IF(usbd_get_device_status(function_instance) == USBD_CLOSING); + RETURN_IF(usbd_get_device_status(function_instance) != USBD_OK); + RETURN_IF(usbd_get_device_state(function_instance) != STATE_CONFIGURED); + usbd_flush_endpoint_index(function_instance, BULK_IN); + usbd_flush_endpoint_index(function_instance, BULK_OUT); +} + +/*!acm_close + * @brief flush queued urbs and close connection + * @param function_instance + * @param chan + * @return 0 for success, EINVAL for error + */ +int acm_close(struct usbd_function_instance *function_instance, minor_chan_t chan) +{ + struct acm_private *acm = function_instance ? function_instance->privdata : NULL; + unsigned long flags; + + TRACE_MSG1(TTY,"acm: %x XXXXXX", (int)acm); + RETURN_EINVAL_UNLESS(acm); + + switch(chan) { + case data_chan: + break; + case command_chan: + default: + break; + } + + otg_pthread_mutex_lock(&acm->mutex); + + acm->flags &= ~ACM_OPENED; + + //TRACE_MSG1(TTY,"queued: %d", (u32)(acm->queued_urbs)); + + if (atomic_read(&acm->queued_urbs)) { + acm->flags |= ACM_CLOSING; + TRACE_MSG1(TTY, "setting ACM_CLOSING flags: %04x", acm->flags); + } + + otg_pthread_mutex_unlock(&acm->mutex); + + /* XXX Should we wait here for data to flush... + */ + RETURN_ZERO_IF(acm->flags & ACM_CLOSING); + + TRACE_MSG1(TTY, "normal close flags: %04x", acm->flags); + + /* Normal Close - no data pending + */ + acm_flush(function_instance); + acm->bmUARTState = 0; + TRACE_MSG1(TTY,"bmUARTState: %04x", acm->bmUARTState); + acm_send_int_notification(function_instance, CDC_NOTIFICATION_SERIAL_STATE, acm->bmUARTState); + return 0; +} + + +/*! acm_ready() + * @param function_instance + */ +STATIC int acm_ready(struct usbd_function_instance *function_instance) +{ + struct acm_private *acm = function_instance ? function_instance->privdata : NULL; + RETURN_ZERO_UNLESS(acm); + + TRACE_MSG6(TTY,"FLAGS: %08x CONFIGURED: %x OPENED: %x LOCAL: %x d0_dtr: %x d1_dcd: %x", + acm->flags, + BOOLEAN(acm->flags & ACM_CONFIGURED), + BOOLEAN(acm->flags & ACM_OPENED), + BOOLEAN(acm->flags & ACM_LOCAL), + acm->bmLineState & CDC_LINESTATE_D0_DTR, + acm->bmLineState & CDC_LINESTATE_D1_RTS); + + return (acm->flags & ACM_CONFIGURED) && + (acm->flags & ACM_OPENED) && + ((acm->flags & ACM_LOCAL) ? 1 : (acm->bmLineState & CDC_LINESTATE_D1_RTS) ) + ; + +} + +/*! acm_start_recv_urbs + * @brief called to receive urbs + * @param function_instance + * @return number of urbs in queue + */ +int acm_start_recv_urbs(struct usbd_function_instance *function_instance) +{ + struct acm_private *acm = function_instance ? function_instance->privdata : NULL; + struct usbd_urb *urb; + /* + * Queue as many receive urbs as the OS layer has room for. Return + * the number in the queue (may be more than we queue here). + */ + // XXX unsigned long flags; + int num_in_queue = 0; + int recv_space_available; +#if defined(CONFIG_OTG_ACM_PIPES_TEST) + recv_space_available=512; +#else + RETURN_ZERO_UNLESS(acm); + RETURN_ZERO_IF((acm->flags & ACM_THROTTLED)); + + UNLESS (acm_ready(function_instance)) { + //printk(KERN_INFO"%s: NOT READY\n", __FUNCTION__); + TRACE_MSG0(TTY, "START RECV: NOT READY"); + return -1; + } + + + /* if configured, opened, not throttled and we have carrier or local then + * we can queue recv urbs. + */ + + + // TRACE_MSG2(TTY,"START RECV: recv_urbs: %d space_avail: %d", atomic_read(&acm->recv_urbs), + // acm->ops->recv_space_available(function_instance, data_chan)); + + //recv_space_available = acm->ops->recv_space_available(function_instance, data_chan); +#endif + +#if 0 + while (((atomic_read(&acm->recv_urbs) + 1) * acm->readsize) <= recv_space_available) { + struct usbd_urb *urb; + + BREAK_IF(!(urb = usbd_alloc_urb((struct usbd_function_instance *) function_instance, + BULK_OUT, acm->readsize, acm_recv_urb))); + atomic_inc(&acm->recv_urbs); + urb->function_privdata = acm; + TRACE_MSG3(TTY, "START RECV: %d urb: %p privdata: %p", atomic_read(&acm->recv_urbs), urb,acm); + CONTINUE_UNLESS (usbd_start_out_urb(urb)); + atomic_dec(&acm->recv_urbs); + TRACE_MSG1(TTY, "FAILED START RECV: %d", atomic_read(&acm->recv_urbs)); + usbd_free_urb(urb); + break; + } + + /* There needs to be at least one recv urb queued in order + * to keep driving the push to the OS layer, so return how + * many there are in case the OS layer must try again later. + */ + num_in_queue = atomic_read(&acm->recv_urbs); + return num_in_queue; +#else + + + urb = usbd_alloc_urb((struct usbd_function_instance *) function_instance, + BULK_OUT, 512, acm_recv_urb); + atomic_inc(&acm->recv_urbs); + urb->function_privdata = acm; + TRACE_MSG3(TTY, "START RECV: %d urb: %p privdata: %p", atomic_read(&acm->recv_urbs), urb,acm); + RETURN_ZERO_UNLESS (usbd_start_out_urb(urb)); + atomic_dec(&acm->recv_urbs); + TRACE_MSG1(TTY, "FAILED START RECV: %d", atomic_read(&acm->recv_urbs)); + usbd_free_urb(urb); + + num_in_queue = atomic_read(&acm->recv_urbs); + return 1; + +#endif + +} + + +#if 0 +/*! @brief acm_start_recv_bh() + * @param data + * @return none + */ +STATIC void acm_start_recv_bh(void *data) +{ + struct usbd_function_instance *function_instance = data; + RETURN_UNLESS(function_instance); + + /* keep trying to start urbs until we have enough or -1 (not ready) + */ + while (!acm_start_recv_urbs(function_instance)) { + struct acm_private *acm = function_instance ? function_instance->privdata : NULL; + TRACE_MSG0(TTY, "SLEEP START"); + interruptible_sleep_on_timeout(&acm->recv_wait, 1); + TRACE_MSG0(TTY, "SLEEP FINISH"); + } +} + +/*! @brief acm_restart_recv() + * @param data + * @return none + */ +void acm_restart_recv(struct usbd_function_instance *function_instance) +{ + struct acm_private *acm = function_instance ? function_instance->privdata : NULL; + RETURN_UNLESS(function_instance); + + TRACE_MSG0(TTY, "SLEEP WAKEUP"); + wake_up_interruptible(&acm->recv_wait); +} + +/*! acm_schedule_recv_restart() + * @param function_instance + */ +void acm_schedule_recv_restart(struct usbd_function_instance *function_instance) +{ + unsigned long flags; + struct acm_private *acm = function_instance ? function_instance->privdata : NULL; + TRACE_MSG1(TTY,"acm: %x", (int)acm); + RETURN_UNLESS(acm); + //otg_led(LED1, TRUE); + SET_WORK_ARG(acm->recv_bh, function_instance); + SCHEDULE_WORK(acm->recv_bh); + //otg_led(LED1, FALSE); +} +#endif + +/* Transmit INTERRUPT ************************************************************************** */ + +/*! + *@brief acm_send_int_notfication() + * + * Generates a response urb on the notification (INTERRUPT) endpoint. + * CALLED from interrupt context. + * @param function_instance + * @param bnotification + * @param data + * @return non-zero if error + */ +STATIC int acm_send_int_notification(struct usbd_function_instance *function_instance, int bnotification, int data) +{ + struct usbd_urb *urb = NULL; + struct cdc_notification_descriptor *notification; + int rc = 0; + struct acm_private *acm = function_instance ? function_instance->privdata : NULL; + TRACE_MSG1(TTY,"acm: %x", (int)acm); + RETURN_EINVAL_UNLESS(acm); + + RETURN_ZERO_UNLESS((acm->flags & ACM_CONFIGURED)); + + do { + BREAK_IF(!(urb = usbd_alloc_urb((struct usbd_function_instance *)function_instance, + INT_IN, + sizeof(struct cdc_notification_descriptor), + acm_urb_sent_int))); + + urb->function_privdata = acm; + urb->function_instance = function_instance; + + memset(urb->buffer, 0, urb->buffer_length); + urb->actual_length = sizeof(struct cdc_notification_descriptor); + + /* fill in notification structure */ + notification = (struct cdc_notification_descriptor *) urb->buffer; + + notification->bmRequestType = USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE; + notification->bNotification = bnotification; + + switch (bnotification) { + case CDC_NOTIFICATION_NETWORK_CONNECTION: + notification->wValue = data; + break; + case CDC_NOTIFICATION_SERIAL_STATE: + notification->wLength = cpu_to_le16(2); + *((unsigned short *)notification->data) = cpu_to_le16(data); + break; + } + + BREAK_IF(!(rc = usbd_start_in_urb (urb))); + + urb->function_privdata = NULL; + usbd_free_urb (urb); + + TRACE_MSG1(TTY,"urb: %x", urb); + + } while(0); + + return rc; +} + +/* Callback functions for TX urb completion (function chosen when urb is allocated) ***********/ + + + +/*! acm_closing + */ +int acm_closing(struct usbd_function_instance *function_instance) +{ + struct acm_private *acm = function_instance ? function_instance->privdata : NULL; + RETURN_ZERO_UNLESS(acm->flags & ACM_CLOSING); + + acm->flags &= ~ACM_CLOSING; + + /* No more data to send - complete close + */ + acm_flush(function_instance); + acm->bmUARTState = 0; + TRACE_MSG1(TTY,"bmUARTState: %04x", acm->bmUARTState); + acm_send_int_notification(function_instance, CDC_NOTIFICATION_SERIAL_STATE, acm->bmUARTState); + + return 0; +} + +#if 1 +/*! acm_urb_sent_zle - callback for completed BULK ZLE URB + * + * Handles notification that an ZLE urb has been sent (successfully or otherwise). + * + * @param urb Pointer to the urb that has been sent. + * @param urb_rc Result code from the send operation. + * + * @return non-zero for failure. + */ +int acm_urb_sent_zle (struct usbd_urb *urb, int urb_rc) +{ + struct usbd_function_instance *function_instance = urb->function_instance; + struct acm_private *acm = function_instance ? function_instance->privdata : NULL; + + void *buf; + int rc = -EINVAL; + int in_pkt_sz; + + //TRACE_MSG4(TTY,"urb: %p urb_rc: %d length: %d queue: %d", + // urb, urb_rc, urb->actual_length, acm->queued_urbs); + + usbd_free_urb (urb); + rc = 0; + return acm_closing(function_instance); +} +#endif + + +/*! acm_urb_sent_bulk - called to indicate bulk URB transmit finished + * @param urb pointer to struct usbd_urb + * @param rc result + * @return non-zero for error + */ +STATIC int acm_urb_sent_bulk (struct usbd_urb *urb, int rc) +{ + struct usbd_function_instance *function_instance = urb->function_instance; + struct acm_private *acm = function_instance ? function_instance->privdata : NULL; + int in_pkt_sz = usbd_endpoint_wMaxPacketSize(function_instance, BULK_IN, usbd_high_speed(function_instance)); + struct usbd_urb *tx_urb; + static int count=1; + + TRACE_MSG5(TTY,"acm: %x urb: %x length: %d flags: %x queued_bytes: %d", + acm, urb, urb->actual_length, + acm ? acm->flags : 0, + acm ? atomic_read(&acm->queued_bytes) : 0 + ); + RETURN_ZERO_UNLESS(acm); + +#if defined(CONFIG_OTG_ACM_PIPES_TEST) + RETURN_EINVAL_UNLESS(tx_urb = usbd_alloc_urb ((struct usbd_function_instance *) function_instance, + BULK_IN, count+1, acm_urb_sent_bulk)); + + memset ((void *)tx_urb->buffer, count & 0xff, count); + tx_urb->function_privdata = acm; + tx_urb->actual_length = count; + if (count <1024) count+=1; + RETURN_ZERO_UNLESS(usbd_start_in_urb (tx_urb)); + tx_urb->function_privdata = NULL; + usbd_free_urb (tx_urb); + return 0; + +#else + + atomic_sub(urb->actual_length, &acm->queued_bytes); + atomic_dec(&acm->queued_urbs); + + TRACE_MSG3(TTY, "queued_bytes: %d queude_urbs: %d opened: %d", + atomic_read(&acm->queued_bytes), atomic_read(&acm->queued_urbs), BOOLEAN(acm->flags & ACM_OPENED)); + + urb->function_privdata = NULL; + usbd_free_urb (urb); + + /* wakeup upper layers to send more data */ + if (acm->flags & ACM_OPENED) + acm->ops->schedule_wakeup_writers(function_instance); + + /* check if there are still pending send urbs */ + RETURN_ZERO_IF(atomic_read(&acm->queued_urbs)); + + /* check if we should send a zlp, send ZLP */ + #if 1 + UNLESS (urb->actual_length % in_pkt_sz) { + if ((urb = usbd_alloc_urb (function_instance, BULK_IN, 0, acm_urb_sent_zle ))) { + urb->actual_length = 0; + TRACE_MSG1(TTY, "Sending ZLP: length: %d", urb->actual_length); + urb->flags |= USBD_URB_SENDZLP; + + if ((rc = usbd_start_in_urb (urb))) { + TRACE_MSG1(TTY,"FAILED: %d", rc); + printk(KERN_ERR"%s: FAILED: %d\n", __FUNCTION__, rc); + urb->function_privdata = NULL; + usbd_free_urb (urb); + } + } + return 0; + } + #endif + return acm_closing(function_instance); +#endif +} + +/*! acm_urb_sent_int - called to indicate int URB transmit finished + * @param urb pointer to struct usbd_urb + * @param rc result + * @return non-zero for error + */ +int acm_urb_sent_int (struct usbd_urb *urb, int rc) +{ + struct usbd_function_instance *function_instance = urb->function_instance; + struct acm_private *acm = function_instance ? function_instance->privdata : NULL; + TRACE_MSG1(TTY,"acm: %x", (int)acm); + RETURN_ZERO_UNLESS(acm); + + TRACE_MSG1(TTY,"urb: %x", urb); + TRACE_MSG1(TTY,"INT length=%d",urb->actual_length); + + urb->function_privdata = NULL; + usbd_free_urb(urb); + return 0; +} + +/* USB Device Functions ************************************************************************ */ + + +/*!acm_write_room + * @brief get available room for queuing receiving urbs + * @param function_instance + * @param chan + * @return non-zero if error + */ +STATIC int acm_write_room(struct usbd_function_instance *function_instance, minor_chan_t chan) +{ + struct acm_private *acm = function_instance ? function_instance->privdata : NULL; + + TRACE_MSG2(TTY,"acm: %x queued_bytes: %d", acm, acm ? atomic_read(&acm->queued_bytes) : 0); + RETURN_ZERO_UNLESS(acm); + + switch(chan) { + case data_chan: + break; + case command_chan: + default: + break; + } + + return (acm->max_queued_urbs <= atomic_read(&acm->queued_urbs) || + acm->max_queued_bytes <= atomic_read(&acm->queued_bytes) ) ? 0 : acm->writesize ; +} + +/*!acm_chars_in_buffer + * @brief get number of of queued chars + * @param function_instance + * @param chan + * @return number of chars, zero if error + */ +STATIC int acm_chars_in_buffer(struct usbd_function_instance *function_instance, minor_chan_t chan) +{ + int rc; + struct acm_private *acm = function_instance ? function_instance->privdata : NULL; + + TRACE_MSG2(TTY,"acm: %x queued_bytes: %d", (int)acm, acm ? atomic_read(&acm->queued_bytes) : 0); + RETURN_ZERO_UNLESS(acm); + + switch(chan) { + case data_chan: + break; + case command_chan: + default: + break; + } + + return atomic_read(&acm->queued_bytes); +} + +static int throttle_count = 0; +static int unthrottle_count = 0; + + +/*! acm_xmit_chars + * @brief - transmit data to host + * @param function_instance + * @param chan + * @param count + * @param from_user + * @param buf + * @return number of bytes sent. + */ +int acm_xmit_chars(struct usbd_function_instance *function_instance, minor_chan_t chan, + int count, int from_user, const unsigned char *buf) +{ + struct acm_private *acm = function_instance ? function_instance->privdata : NULL; + struct usbd_urb *urb; + int in_pkt_sz = usbd_endpoint_wMaxPacketSize(function_instance, BULK_IN, usbd_high_speed(function_instance)); + int rc=0; + + TRACE_MSG4(TTY,"acm[%d]: %x flags: %x queued_bytes: %d", chan, acm, + acm ? acm->flags: 0, acm ? atomic_read(&acm->queued_bytes) : 0); + RETURN_ZERO_UNLESS(acm); + RETURN_ZERO_UNLESS((acm->flags & ACM_CONFIGURED) && + ((acm->flags & ACM_LOCAL) ? 1 : (acm->bmLineState & CDC_LINESTATE_D1_RTS))); + + //TRACE_MSG5(TTY, "count: %d from_user: %d queued: %d max: %d writesize: %d", + // count, from_user, acm->queued_urbs, acm->max_queued_urbs, acm->writesize); + + switch(chan) { + case data_chan: + // sanity check and are we connect + // XXX RETURN_ZERO_UNLESS(atomic_read(&acm->used)); + TRACE_MSG0(TTY,"DATA"); + RETURN_ZERO_UNLESS (count); + RETURN_ZERO_UNLESS (acm->flags & ACM_CONFIGURED); // XXX acm_ready() ?? + TRACE_MSG0(TTY,"connected OK"); + RETURN_ZERO_IF(acm->max_queued_urbs <= atomic_read(&acm->queued_urbs)); + TRACE_MSG0(TTY,"acm->max_queued_urbs OK"); + + // allocate a write urb + count = MIN(count, acm->writesize); + + RETURN_ZERO_UNLESS ((urb = usbd_alloc_urb ((struct usbd_function_instance *) function_instance, + BULK_IN, count, acm_urb_sent_bulk))); + + if (from_user) + rc=copy_from_user ((void *)urb->buffer, (void *)buf, count); + else + memcpy ((void *)urb->buffer, (void *)buf, count); + + + #if 0 + { + u8 *cp = urb->buffer; + int len; + for (len = 0; len < count; len += 8, cp += 8) { + TRACE_MSG8(TTY, "SEND [%02x %02x %02x %02x %02x %02x %02x %02x]", + cp[0], cp[1], cp[2], cp[3], cp[4], cp[5], cp[6], cp[7]); + } + } + #endif + + urb->function_privdata = acm; + urb->actual_length = count; + + #if 0 + /* force ZLP if multiple of packet size */ + UNLESS(count % in_pkt_sz) { + urb->flags |= USBD_URB_SENDZLP; + } + #endif + + atomic_add(count, &acm->queued_bytes); + atomic_inc(&acm->queued_urbs); + + UNLESS (usbd_start_in_urb (urb)) return count; + + usbd_free_urb (urb); + atomic_sub(count, &acm->queued_bytes); + atomic_dec(&acm->queued_urbs); + return 0; + + case command_chan: + TRACE_MSG0(TTY,"COMMAND"); + default: + break; + } + return 0; +} + +/*! acm_recv_urb - callback for urb received + * @param urb + * @param rc + * @return non-zero if error + */ +int acm_recv_urb (struct usbd_urb *urb, int rc) +{ + struct usbd_function_instance *function_instance = urb->function_instance; + struct acm_private *acm = function_instance->privdata; + + /* Return 0 if urb has been accepted, + * return 1 and expect caller to deal with urb release otherwise. + */ + + TRACE_MSG2(TTY, "acm: %x len: %d", acm, urb->actual_length); + + atomic_dec(&acm->recv_urbs); // this could probably be atomic operation.... + + if (USBD_URB_CANCELLED == rc) { + TRACE_MSG1(TTY,"cancelled URB=%p",urb); + return -EINVAL; + } + if (USBD_URB_OK != rc) { + TRACE_MSG2(TTY,"rejected URB=%p rc=%d",urb,rc); + usbd_free_urb(urb); + + //acm_schedule_recv_restart(function_instance); + // acm->ops->recv_start_bh(function_instance); + + acm_start_recv_urbs(function_instance); + return 0; + } +#if defined(CONFIG_OTG_ACM_PIPES_TEST) + acm_start_recv_urbs(function_instance); + +#else + /* loopback mode + */ + if (acm->flags & ACM_LOOPBACK) + acm_xmit_chars(function_instance, data_chan, urb->actual_length, 0, urb->buffer); + + /* acm_start_recv_urbs() will never queue more urbs than there is currently + * room in the upper layer buffer for. So we are guaranteed that any data actually + * received can be given to the upper layers without worrying if we will + * actually have room. + */ + else + if ((rc = acm->ops->recv_chars(function_instance, urb->buffer, urb->actual_length))) + return rc; // XXX + + + atomic_add(urb->actual_length, &acm->bytes_received); + + TRACE_MSG1(TTY,"bytes_received: %d", atomic_read(&acm->bytes_received)); + usbd_free_urb(urb); + + //acm_schedule_recv_restart(function_instance); + // acm->ops->recv_start_bh(function_instance); + acm_start_recv_urbs(function_instance); + return 0; +#endif +} + +/*! acm_wait_task - delay task to perform + * @param function_instance + * @param queue + */ +void acm_wait_task(struct usbd_function_instance *function_instance, OLD_WORK_ITEM *queue) +{ + struct acm_private *acm = function_instance ? function_instance->privdata : NULL; + TRACE_MSG1(TTY,"acm: %x", (int)acm); + RETURN_UNLESS(acm); + + +} + +/* ********************************************************************************************* */ +/*! acm_get_d0_dtr + * @brief get d0 DTR status + * Get DTR status. + * @param function_instance + * @return int + */ +int acm_get_d0_dtr(struct usbd_function_instance *function_instance) +{ + struct acm_private *acm = function_instance ? function_instance->privdata : NULL; + TRACE_MSG1(TTY,"acm: %x", (int)acm); + RETURN_ZERO_UNLESS(acm); + TRACE_MSG2(TTY,"bmLineState: %04x d0_dtr: %x", acm->bmLineState, (acm->bmLineState & CDC_LINESTATE_D0_DTR) ? 1 : 0); + return (acm->bmLineState & CDC_LINESTATE_D0_DTR) ? 1 : 0; +} + +/*! + * @brief acm_get_d1_rts + * Get DSR status + * @param function_instance + * @return int + */ +int acm_get_d1_rts(struct usbd_function_instance *function_instance) +{ + struct acm_private *acm = function_instance ? function_instance->privdata : NULL; + TRACE_MSG1(TTY,"acm: %x", (int)acm); + RETURN_ZERO_UNLESS(acm); + TRACE_MSG2(TTY,"bmLineState: %04x d1_rts: %x", acm->bmLineState, (acm->bmLineState & CDC_LINESTATE_D1_RTS) ? 1 : 0); + return (acm->bmLineState & CDC_LINESTATE_D1_RTS) ? 1 : 0; +} + + +/* ********************************************************************************************* */ +/*! + * @brief acm_get_bRxCarrier + * Get bRxCarrier (DCD) status + * @param function_instance + @ @return int + */ +int acm_get_bRxCarrier(struct usbd_function_instance *function_instance) +{ + struct acm_private *acm = function_instance ? function_instance->privdata : NULL; + TRACE_MSG1(TTY,"acm: %x", (int)acm); + RETURN_ZERO_UNLESS(acm); + TRACE_MSG2(TTY,"bmLineState: %04x bRxCarrier: %x", + acm->bmLineState, (acm->bmUARTState & CDC_UARTSTATE_BRXCARRIER_DCD) ? 1 : 0); + return (acm->bmUARTState & CDC_UARTSTATE_BRXCARRIER_DCD) ? 1 : 0; +} + +/*! + * @brief acm_get_bTxCarrier + * Get bTxCarrier (DSR) status + * @param function_instance + @ @return int + */ +int acm_get_bTxCarrier(struct usbd_function_instance *function_instance) +{ + struct acm_private *acm = function_instance ? function_instance->privdata : NULL; + TRACE_MSG1(TTY,"acm: %x", (int)acm); + RETURN_ZERO_UNLESS(acm); + TRACE_MSG2(TTY,"bmLineState: %04x bTxCarrier: %x", + acm->bmLineState, (acm->bmUARTState & CDC_UARTSTATE_BTXCARRIER_DSR) ? 1 : 0); + return (acm->bmUARTState & CDC_UARTSTATE_BTXCARRIER_DSR) ? 1 : 0; +} + + +/*! @brief acm_set_bRxCarrier + * Get bRxCarrier (DCD) status + * @param function_instance + * @param value + * @return none + */ +void acm_set_bRxCarrier(struct usbd_function_instance *function_instance, int value) +{ + struct acm_private *acm = function_instance ? function_instance->privdata : NULL; + TRACE_MSG2(TTY,"acm: %x value: %x", (int)acm, value); + RETURN_UNLESS(acm); + acm->bmUARTState &= ~CDC_UARTSTATE_BRXCARRIER_DCD; + acm->bmUARTState |= value ? CDC_UARTSTATE_BRXCARRIER_DCD : 0; + acm_send_int_notification(function_instance, CDC_NOTIFICATION_SERIAL_STATE, acm->bmUARTState); +} + +/*! @brief acm_set_bTxCarrier + * Set bTxCarrier (DSR) status + * @param function_instance + * @param value + * @return none + */ +void acm_set_bTxCarrier(struct usbd_function_instance *function_instance, int value) +{ + struct acm_private *acm = function_instance ? function_instance->privdata : NULL; + TRACE_MSG2(TTY,"acm: %x value: %x", (int)acm, value); + RETURN_UNLESS(acm); + acm->bmUARTState &= ~CDC_UARTSTATE_BTXCARRIER_DSR; + acm->bmUARTState |= value ? CDC_UARTSTATE_BTXCARRIER_DSR : 0; + acm_send_int_notification(function_instance, CDC_NOTIFICATION_SERIAL_STATE, acm->bmUARTState); +} + + +/* ********************************************************************************************* */ + +/*! acm_bRingSignal + * Indicate Ring signal to host. + */ +void acm_bRingSignal(struct usbd_function_instance *function_instance) +{ + struct acm_private *acm = function_instance ? function_instance->privdata : NULL; + TRACE_MSG1(TTY,"acm: %x", (int)acm); + RETURN_UNLESS(acm); + acm->bmUARTState |= CDC_UARTSTATE_BRINGSIGNAL; + acm_send_int_notification(function_instance, CDC_NOTIFICATION_SERIAL_STATE, acm->bmUARTState); + acm->bmUARTState &= ~CDC_UARTSTATE_BRINGSIGNAL; +} + +/*! acm_bBreak + * Indicate Break signal to host. + */ +void acm_bBreak(struct usbd_function_instance *function_instance) +{ + struct acm_private *acm = function_instance ? function_instance->privdata : NULL; + TRACE_MSG1(TTY,"acm: %x", (int)acm); + RETURN_UNLESS(acm); + acm->bmUARTState |= CDC_UARTSTATE_BBREAK; + acm_send_int_notification(function_instance, CDC_NOTIFICATION_SERIAL_STATE, acm->bmUARTState); + acm->bmUARTState &= ~CDC_UARTSTATE_BBREAK; +} + +/*! acm_bOverrun + * Indicate Overrun signal to host. + */ +void acm_bOverrun(struct usbd_function_instance *function_instance) +{ + struct acm_private *acm = function_instance ? function_instance->privdata : NULL; + TRACE_MSG1(TTY,"acm: %x", (int)acm); + RETURN_UNLESS(acm); + acm->bmUARTState |= CDC_UARTSTATE_BOVERRUN; + acm_send_int_notification(function_instance, CDC_NOTIFICATION_SERIAL_STATE, acm->bmUARTState); + acm->bmUARTState &= ~CDC_UARTSTATE_BOVERRUN; +} + +/*! acm_set_local + * Set LOCAL status + * @param function_instance + * @param value + */ +void acm_set_local(struct usbd_function_instance *function_instance, int value) +{ + struct acm_private *acm = function_instance ? function_instance->privdata : NULL; + + TRACE_MSG2(TTY,"acm: %x value: %x", acm, value); + RETURN_UNLESS(acm); + acm->flags &= ~ACM_LOCAL; + acm->flags |= value ? ACM_LOCAL : 0; + acm_start_recv_urbs(function_instance); +} + +/*! @ brief acm_set_loopback + * Set LOOPBACK status + * @param function_instance + * @param value + */ +void acm_set_loopback(struct usbd_function_instance *function_instance, int value) +{ + struct acm_private *acm = function_instance ? function_instance->privdata : NULL; + TRACE_MSG2(TTY,"acm: %x value: %d", (int)acm, value); + RETURN_UNLESS(acm); + acm->flags &= ~ACM_LOOPBACK; + acm->flags |= value ? ACM_LOOPBACK : 0; + acm_start_recv_urbs(function_instance); +} + + +/*! @ brief acm_set_throttle + * Set LOOPBACK status + * @param function_instance + * @param value + */ +void acm_set_throttle(struct usbd_function_instance *function_instance, int value) +{ + struct acm_private *acm = function_instance ? function_instance->privdata : NULL; + TRACE_MSG2(TTY,"acm: %x value: %d", (int)acm, value); + RETURN_UNLESS(acm); + acm->flags &= ~ACM_THROTTLED; + acm->flags |= value ? ACM_THROTTLED : 0; + acm_start_recv_urbs(function_instance); +} + +/*! @ brief acm_get_throttled + * Set LOOPBACK status + * @param function_instance + */ +int acm_get_throttled(struct usbd_function_instance *function_instance) +{ + struct acm_private *acm = function_instance ? function_instance->privdata : NULL; + TRACE_MSG1(TTY,"acm: %x", (int)acm); + RETURN_ZERO_UNLESS(acm); + + return (acm->flags & ACM_THROTTLED) ? 1 : 0; +} + + +void acm_throttle (struct usbd_function_instance *function_instance, minor_chan_t xx){ + + struct acm_private *acm = function_instance ? function_instance->privdata : NULL; + TRACE_MSG1(TTY,"sos acm: %x", (int)acm); + RETURN_UNLESS(acm); + acm->flags |= ACM_THROTTLED; + usbd_flush_endpoint_index(function_instance,BULK_OUT); + TRACE_MSG1(TTY,"sos acm111: %x", (int)acm); +} + +void acm_unthrottle (struct usbd_function_instance *function_instance, minor_chan_t xx) +{ + struct acm_private *acm = function_instance ? function_instance->privdata : NULL; + TRACE_MSG1(TTY,"acm: %x", (int)acm); + RETURN_UNLESS(acm); + acm->flags &= ~ACM_THROTTLED; + acm_start_recv_urbs(function_instance); + +} + +/*! @brief acm_function_enable - called by USB Device Core to enable the driver + * @param function_instance The function instance for this driver to use. + * @param tty_type + * @param os_ops + * @param max_urbs + * @param max_bytes + * @return non-zero if error. + */ +int acm_function_enable (struct usbd_function_instance *function_instance, tty_type_t tty_type, + struct acm_os_ops *os_ops, int max_urbs, int max_bytes) +{ + struct acm_private *acm = NULL; + + RETURN_EINVAL_UNLESS((acm = CKMALLOC(sizeof(struct acm_private)))); + printk(KERN_INFO "acm_function_enable and acm = CKMALLOC()\n"); + function_instance->privdata = acm; + + acm->tty_type = tty_type; + acm->ops = os_ops; + acm->max_queued_urbs = max_urbs; + acm->max_queued_bytes = max_bytes; + + switch(tty_type) { + case tty_if: + case dun_if: + acm->minors = 2; + break; + default: + acm->minors = 1; + break; + } + acm->line_coding.dwDTERate = __constant_cpu_to_le32(0x1c200); + acm->line_coding.bDataBits = 0x08; + //PREPARE_WORK_ITEM(acm->recv_bh, acm_start_recv_bh, function_instance); + + init_waitqueue_head(&acm->recv_wait); + + TRACE_MSG1(TTY, "ENABLED writesize: %d", acm->writesize); + + return acm->ops->enable(function_instance); +} + +/*! @brief acm_function_disable - called by the USB Device Core to disable the driver + * @param function The function instance for this driver + */ +void acm_function_disable (struct usbd_function_instance *function) +{ + struct usbd_interface_instance *interface_instance = (struct usbd_interface_instance *)function; + struct acm_private *acm = function ? function->privdata : NULL; + + TRACE_MSG0(TTY, "DISABLED"); + acm->ops->disable(function); + function->privdata = NULL; + printk(KERN_INFO "acm_function_disable and LKFREE(acm)\n"); + LKFREE(acm); +} + +/*! acm_set_configuration + * @brief called to configure this function instance + * @param function_instance + * @param configuration index to set + * @return int + */ +int acm_set_configuration (struct usbd_function_instance *function_instance, int configuration) +{ + struct usbd_interface_instance *interface_instance = (struct usbd_interface_instance *)function_instance; + struct acm_private *acm = function_instance->privdata; + struct usbd_urb *tx_urb; + + TRACE_MSG4(TTY, "CONFIGURED acm: %x wIndex:%d %x cfg: %d ", acm, + interface_instance->wIndex, function_instance, configuration); + + // XXX Need to differentiate between non-zero, zero and non-zero done twice + + /* speead and size cannot be set until now as we don't know waht speed we are + * enumerated at + */ + acm->hs = usbd_high_speed((struct usbd_function_instance *) function_instance); + acm->readsize = usbd_endpoint_transferSize(function_instance, BULK_OUT, acm->hs); + acm->writesize = usbd_endpoint_transferSize(function_instance, BULK_IN, acm->hs); + acm->flags |= ACM_CONFIGURED; + + TRACE_MSG2(TTY, "configured readsize: %d writesize: %d", acm->readsize, acm->writesize); + +#if defined(CONFIG_OTG_ACM_PIPES_TEST) + + acm_start_recv_urbs(function_instance); + + RETURN_EINVAL_UNLESS(tx_urb = usbd_alloc_urb ((struct usbd_function_instance *) function_instance, + BULK_IN, 1, acm_urb_sent_bulk)); + + memset ((void *)tx_urb->buffer, 1, 1); + tx_urb->function_privdata = acm; + tx_urb->actual_length = 1; + + RETURN_ZERO_UNLESS(usbd_start_in_urb (tx_urb)); + usbd_free_urb (tx_urb); + return -EINVAL; + +#else /*defined(CONFIG_OTG_MSC_PIPES_TEST) */ + acm_start_recv_urbs(function_instance); +#endif + + + + return 0; +} + +/*!acm_set_interface + * @brief set interface from function instance + * @param function_instance + * @param wIndex + * @param altsetting + * @return int + */ +int acm_set_interface (struct usbd_function_instance *function_instance, int wIndex, int altsetting) +{ + struct usbd_interface_instance *interface_instance = (struct usbd_interface_instance *)function_instance; + struct acm_private *acm = function_instance->privdata; + TRACE_MSG0(TTY, "SET INTERFACE"); + return 0; +} + +/*! + * @brief acm_reset - called to indicate bus reset + * @param function_instance + * @return int + */ +int acm_reset (struct usbd_function_instance *function_instance) +{ + struct usbd_interface_instance *interface_instance = (struct usbd_interface_instance *)function_instance; + struct acm_private *acm = function_instance->privdata; + + TRACE_MSG0(TTY, "RESET"); + + /* if configured and open then schedule hangup + */ + if ((acm->flags & ACM_OPENED) && (acm->flags & ACM_CONFIGURED && !(acm->flags & ACM_LOCAL))) + acm->ops->schedule_hangup(function_instance); + + acm->flags &= ~ACM_CONFIGURED; + TRACE_MSG0(TTY, "RESET continue"); + + // XXX flush + // Release any queued urbs + return 0; +} + +/*! + * @brief acm_suspended - called to indicate urb has been received + * @param function_instance + * @return int + */ +int acm_suspended (struct usbd_function_instance *function_instance) +{ + struct usbd_interface_instance *interface_instance = (struct usbd_interface_instance *)function_instance; + struct acm_private *acm = function_instance->privdata; + TRACE_MSG0(TTY, "SUSPENDED"); + return 0; +} + +/*! + * acm_resumed - called to indicate urb has been received + * @param function_instance the function instance for this driver + * @return int + */ +int acm_resumed (struct usbd_function_instance *function_instance) +{ + struct usbd_interface_instance *interface_instance = (struct usbd_interface_instance *)function_instance; + struct acm_private *acm = function_instance->privdata; + TRACE_MSG0(TTY, "RESUMED"); + return 0; +} + +/*! acm_send_queued + * @brief to check how many urbs in queue waiting to send + * @param function_instance The function instance for this driver + * @return number of queued urbs + */ +int acm_send_queued (struct usbd_function_instance *function_instance) +{ + struct acm_private *acm = function_instance->privdata; + return atomic_read(&acm->queued_urbs); +} diff --git a/drivers/otg/functions/acm/acm.h b/drivers/otg/functions/acm/acm.h new file mode 100644 index 000000000000..0cf333b4e5c4 --- /dev/null +++ b/drivers/otg/functions/acm/acm.h @@ -0,0 +1,510 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/functions/acm/acm.h + * @(#) tt/root@belcarra.com/debian286.bbb|otg/functions/acm/acm.h|20070911235624|28546 + * + * Copyright (c) 2003-2005 Belcarra Technologies Corp + * Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + * + * By: + * Stuart Lynne <sl@belcarra.com>, + * Bruce Balden <balden@belcarra.com> + * + */ + +/*! + * @defgroup ACMFunction Serial ACM Interface Function + * @ingroup InterfaceFunctions + */ + +/*! + * @file otg/functions/acm/acm.h + * @brief ACM Function Driver private defines + * + * This is an ACM Function Driver. The upper edge is exposed + * to the hosting OS as a Posix type character device. The lower + * edge implements the USB Device Stack API. + * + * This driver implements the CDC ACM driver model and uses the CDC ACM + * protocols. + * + * Note that it appears to be impossible to determine the end of a receive + * bulk transfer larger than wMaxPacketsize. The host is free to send + * wMaxPacketsize chars in a single transfer. This means that we cannot + * queue receive urbs larger than wMaxPacketsize (typically 64 bytes.) + * + * This does not however prevent queuing transmit urbs with larger amounts + * of data. It is interpreted at the receiving (host) end as a series of + * wMaxPacketsize transfers but because there is no interpretation to the + * amounts of data sent it does affect anything if we treat the data as a + * single larger transfer. + * + * @ingroup ACMFunction + */ + + +// Endpoint indexes in acm_endpoint_requests[] and the endpoint map. +#define INT_IN 0x00 +#define BULK_IN 0x01 +#define BULK_OUT 0x02 +#define ENDPOINTS 0x03 + +#define COMM_INTF 0x00 +#define DATA_INTF 0x01 + + + +// Most hosts don't care about BMAXPOWER, but the UUT tests want it to be 1 +#define BMAXPOWER 1 +#define BMATTRIBUTE 0 + +#define STATIC +//#define STATIC static +// + +/*! @name ACM Flags + * @{ + */ +#define ACM_OPENED (1 << 0) /*!< upper layer has opened the device port */ +#define ACM_CONFIGURED (1 << 1) /*!< device is configured */ +#define ACM_THROTTLED (1 << 2) /*!< upper layer doesn't want to receive data */ +//#define ACM_CARRIER (1 << 3) /*!< host has set DTR, i.e. host has opened com device */ +#define ACM_LOOPBACK (1 << 4) /*!< upper layer wants local loopback */ +#define ACM_LOCAL (1 << 5) /*!< upper layer specified LOCAL mode */ + +#define ACM_CLOSING (1 << 6) /*!< upper layer has asked us to close */ + +/*! @} */ + +/*! @name tty type + * @{ + */ +/*! @var typedef enum tty_type tty_type_t + */ +typedef enum tty_type { + tty_if, + dun_if, + obex_if, + atcom_if, +} tty_type_t; + + +/*! @} */ + +/*! @name data channel + * @{ + */ +typedef enum minor_chan { + data_chan, + command_chan, +} minor_chan_t; + +#define CHAN(x) ((x&1)?command_chan : data_chan) + +/*! @} */ + +/*! @struct acm_os_ops acm.h "otg/functions/acm/acm.h" + * + * @brief This structure lists the operations implemented in the upper os layer. + */ +struct acm_os_ops { + /*! enable + * Enable the function driver. + */ + int (*enable)(struct usbd_function_instance *); + + /*! disable + * Disable the function driver. + */ + void (*disable)(struct usbd_function_instance *); + + /*! wakeup_opens + * Wakeup processes waiting for DTR. + */ + void (*wakeup_opens)(struct usbd_function_instance *); + + /*! wakeup_opens + * Wakeup processes waiting for state change. + */ + void (*wakeup_state)(struct usbd_function_instance *); + + /*! wakeup writers + * Wakeup pending writes. + */ + void (*schedule_wakeup_writers)(struct usbd_function_instance *); + + /*! recv_space_available + * Check for amount of receive space that is available, controls + * amount of receive urbs that will be queued. + */ + int (*recv_space_available)(struct usbd_function_instance *, minor_chan_t); + + /*! recv_chars + * Process chars received on specified interface. + */ + int (*recv_chars)(struct usbd_function_instance *, u8 *cp, int n); + + + /*! schedule_hangup + * Schedule a work item that will perform a hangup. + */ + void (*schedule_hangup)(struct usbd_function_instance *); + + /*! comm_feature + * Tell function that comm feature has changed. + */ + //void (*comm_feature)(struct usbd_function_instance *); + + /*! line_coding + * Tell function that line coding has changed. + */ + void (*line_coding)(struct usbd_function_instance *); + + /*! control_state + * Tell function that control state has changed. + */ + //void (*control_state)(struct usbd_function_instance *); + + /*! send_break + * Tell function to send a break signal. + */ + void (*send_break)(struct usbd_function_instance *); + + /*! schedule_recv_start_bh + * Tell function to send a break signal. + */ + void (*recv_start_bh)(struct usbd_function_instance *); + +}; + + +/*! @struct acm_private acm.h "otg/functions/acm/acm.h" + * + * @brief encapsulation of acm instance global variables + * + */ +struct acm_private { + struct usbd_function_instance *function_instance; + otg_tag_t trace_tag; + + tty_type_t tty_type; + + int index; + int minors; + + u32 flags; + + otg_pthread_mutex_t mutex; + otg_atomic_t recv_urbs; + //otg_atomic_t used; + otg_atomic_t queued_bytes; + otg_atomic_t queued_urbs; + + BOOL hs; + int writesize; + int readsize; + + /*TBR debug receive flow control */ + otg_atomic_t bytes_received; + otg_atomic_t bytes_forwarded; + /*TBR end debug */ + + struct cdc_acm_line_coding line_coding; // C.f. Table 50 - [GS]ET_LINE_CODING Device Request (to/from host) + cdc_acm_bmUARTState bmUARTState; // C.f. Table 51 - SET_CONTROL_LINE_STATE Device Request (from host) + cdc_acm_bmLineState bmLineState; // C.f. Table 69 - SERIAL_STATE Notification (to host) + + /*for tiocmget and tiocmset functions */ + int msr; + int mcr; + + int max_queued_urbs; + int max_queued_bytes; + + + OLD_WORK_STRUCT recv_bh; + wait_queue_head_t recv_wait; /*! wait queue for blocking open*/ + + + //void *privdata; + + struct acm_os_ops *ops; + + + //mcpc_mode_t mcpc_mode; + u8 mode; // mode if mcpc_activated + + u8 modes; // sizeof received modetable + u8 *modetable; // most recent received mode table + +}; + + +#define TTY acm_trace_tag +extern otg_tag_t acm_trace_tag; + + +/*! acm_fd_init - called to initialize the acm_fd library + * This call initializs the acm_fd library and registers the function driver + * with the USB Peripheral Stack. + * + */ +int acm_fd_init (struct usbd_function_instance *, char *inf, u32 vendor_id, u32 product_id, u32 wmax_urbs, u32 wmax_bytes); + +/*! acm_fd_exit - called before exiting + * This call will cause the function driver to be de-registered. + */ +void acm_fd_exit (struct usbd_function_instance *); + +/*! acm_open - device open + * This is called the device is opened (first open only if non-exclusive opens allowed). + */ +int acm_open (struct usbd_function_instance *, minor_chan_t chan); + +/*! acm_close - Device close + * This is called when the device closed (last close only if non-exclusive opens allowed.) + */ +int acm_close (struct usbd_function_instance *, minor_chan_t chan); + + +/*! acm_flush - flush data urbs. + * Cancel outstanding data urbs. + */ +void acm_flush (struct usbd_function_instance *); + + /*! acm_schedule_recv - queue as many data receive urbs as possible + * This will schedule a bottom half hander that will will start as + * many receive data urbs as are allowed given the amount of room + * available in the upper layer. If no urbs are queued by the + * bottom half handler it will re-schedule itself. + */ +void acm_schedule_recv_restart (struct usbd_function_instance *); + + /*! acm_throttle - set throttle flag for specified interface + * Receive urbs will not be queued when throttled. + */ +void acm_throttle (struct usbd_function_instance *, minor_chan_t); + +/*! acm_unthrottle - reset throttle flag for specified interface + * Receive urbs are allowed to be queued. If no urbs are queued a + * bottom half handler will be scheduled to queue them. + */ + void acm_unthrottle (struct usbd_function_instance *, minor_chan_t); + + +/*!acm_xmit_chars - send data via specified interface + * This will start a transmit urb to send the specified data. The + * number of characters sent will be returned. + */ +int acm_xmit_chars (struct usbd_function_instance *, minor_chan_t chan, int count, int from_user, const unsigned char *buf); + +/*! acm_write_room + * Return amount of data that could be queued for sending. + */ + int acm_write_room (struct usbd_function_instance *, minor_chan_t); + +/*! acm_chars_in_buffer + * Return number of chars in xmit buffer. + */ +int acm_chars_in_buffer (struct usbd_function_instance *, minor_chan_t); + + +/*! acm_send_int_notification - send notification via interrupt endpoint + * This can be used to queue network, serial state change notifications. + */ + int acm_send_int_notification (struct usbd_function_instance *, int bnotification, int data); + +/*! acm_wait_task - wait for task to complete. + */ +void acm_wait_task (struct usbd_function_instance *, OLD_WORK_ITEM *queue); +//void acm_wait_task (struct usbd_function_instance *, struct otg_workitem *work); +/*! acm_ready - return true if connected and carrier + */ +int acm_ready (struct usbd_function_instance *); + +/*! acm_get_d1_rts + * Get DSR status + */ +int acm_get_d1_rts (struct usbd_function_instance *); + +/*! acm_get_d0_dtr + * Get DTR status. + */ +int acm_get_d0_dtr (struct usbd_function_instance *); + + +/*! acm_get_bRxCarrier + * Get bRxCarrier (DCD) status + */ +int acm_get_bRxCarrier(struct usbd_function_instance *function_instance); + +/*! acm_set_bRxCarrier + * set bRxCarrier (DCD) status + */ +void acm_set_bRxCarrier (struct usbd_function_instance *, int); + +/*! acm_get_bTxCarrier + * Get bTxCarrier (DSR) status + */ +int acm_get_bTxCarrier (struct usbd_function_instance *); + +/*! acm_set_bTxCarrier + * Set bTxCarrier (DSR) status + */ +void acm_set_bTxCarrier(struct usbd_function_instance *function_instance, int value); + +/*! acm_bRingSignal + * Indicate Ring signal to host. + */ +void acm_bRingSignal (struct usbd_function_instance *); + +/*! acm_bBreak + * Indicate Break signal to host. + */ +void acm_bBreak (struct usbd_function_instance *); + +/*! acm_bOverrun + * Indicate Overrun signal to host. + */ +void acm_bOverrun (struct usbd_function_instance *); + +/*! acm_set_throttle + * Set LOOPBACK status + */ +void acm_set_throttle (struct usbd_function_instance *, int value); + +/*! acm_get_throttled + * Set LOOPBACK status + */ +int acm_get_throttled(struct usbd_function_instance *function_instance); + + +/*! acm_set_local + * Set LOCAL status + */ +void acm_set_local (struct usbd_function_instance *, int value); + +/*! set_loopback - set loopback mode + * Sets LOOP flag, data received from the host will be immediately + * returned without passing to the upper layer. + */ +void acm_set_loopback (struct usbd_function_instance *, int value); + + + +/*! acm_start_recv_urbs + */ +void acm_restart_recv(struct usbd_function_instance *); +int acm_start_recv_urbs(struct usbd_function_instance *); + + +/*! acm_line_coding_urb_received - callback for sent URB + * @param urb + * @param rc + * @return int value + */ +int acm_line_coding_urb_received (struct usbd_urb *urb, int urb_rc); + +/*! acm_urb_sent_ep0 - called to indicate ep0 URB transmit finished + * @param urb + * @param rc + * @return int value + */ +int acm_urb_sent_ep0 (struct usbd_urb *urb, int rc); + +/*! acm_recv_urb + * @param urb + * @param rc + * @return int value + */ +int acm_recv_urb (struct usbd_urb *urb, int rc); + +/*! acm_urb_sent_int - called to indicate int URB transmit finished + * @param urb usb request block pointer + * @param result code + */ +int acm_urb_sent_int (struct usbd_urb *urb, int rc); + +/*! acm_function_enable - called by USB Device Core to enable the driver + * @param function_instance The function instance for this driver to use. + * @param tty_type + * @param os_ops + * @param mac_urbs + * @param max_bytes + * @return non-zero if error. + */ +int acm_function_enable (struct usbd_function_instance *function_instance, tty_type_t tty_type, + struct acm_os_ops *os_ops, int max_urbs, int max_bytes); + +/*! acm_function_disable - called by the USB Device Core to disable the driver + * @param function */ +void acm_function_disable (struct usbd_function_instance *function); + +/*! acm_set_configuration - called to indicate urb has been received + * @param function_instance + * @param configuration + */ +int acm_set_configuration (struct usbd_function_instance *function_instance, int configuration); + +/*! acm_set_interface - called to indicate urb has been received + * @param function ++ * @param wIndex ++ * @param altsetting + */ +int acm_set_interface (struct usbd_function_instance *function, int wIndex, int altsetting); + +/*! acm_reset - called to indicate urb has been received + * @param function + */ +int acm_reset (struct usbd_function_instance *function); + +/*! acm_suspended - called to indicate urb has been received + * @param function + */ +int acm_suspended (struct usbd_function_instance *function); + +/*! acm_resumed - called to indicate urb has been received + * @param function + */ +int acm_resumed (struct usbd_function_instance *function); + + +/*! acm_device_request - called to process a request to endpoint or interface + * @param function + * @param request + * @return non-zero if error + */ +int acm_device_request (struct usbd_function_instance *function, struct usbd_device_request *request); + + +/*! acm_endpoint_cleared - called by the USB Device Core when endpoint cleared + * @param function_instance The function instance for this driver ++ * @param bEndpointAddress Endpoint address ++ * @return none + */ +void acm_endpoint_cleared (struct usbd_function_instance *function_instance, int bEndpointAddress); + +/*! acm_send_queued - called by the USB Device Core when endpoint cleared + * @param function_instance The function instance for this driver + */ +int acm_send_queued (struct usbd_function_instance *function_instance); + + + +/* + * otg-trace tag. + */ +extern otg_tag_t acm_fd_trace_tag; + +#define MAX_QUEUED_BYTES 512 +#define MAX_QUEUED_URBS 32 // Max for write diff --git a/drivers/otg/functions/acm/otg-config.h b/drivers/otg/functions/acm/otg-config.h new file mode 100644 index 000000000000..f13f7cc0eee8 --- /dev/null +++ b/drivers/otg/functions/acm/otg-config.h @@ -0,0 +1,21 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * @(#) balden@belcarra.com|otg/functions/acm/otg-config.h|20060419204257|20815 + * + */ + +/* + * tristate " CDC ACM Function" + */ +/* #define CONFIG_OTG_ACM */ diff --git a/drivers/otg/functions/acm/tty-if.c b/drivers/otg/functions/acm/tty-if.c new file mode 100644 index 000000000000..00e37c82776b --- /dev/null +++ b/drivers/otg/functions/acm/tty-if.c @@ -0,0 +1,265 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/functions/acm/tty-if.c + * @(#) tt/root@belcarra.com/debian286.bbb|otg/functions/acm/tty-if.c|20070911235624|35439 + * + * Copyright (c) 2003-2005 Belcarra + * Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + * + * By: + * Stuart Lynne <sl@belcarra.com>, + * Bruce Balden <balden@belcarra.com> + */ +/*! + * @file otg/functions/acm/tty-if.c + * @brief ACM-TTY Descriptor Set + * + * This is the generic portion of the TTY version of the + * ACM driver. + * + * TTY + * Interface + * Upper +----------+ + * Edge | tty-l26 | + * Implementation +----------+ + * + * + * Function +----------+ + * Descriptors | tty-if | <---- + * Registration +----------+ + * + * + * Function +----------+ + * I/O | acm | + * Implementation +----------+ + * + * + * Module +----------+ + * Loading | acm-l26 | + * +----------+ + * + * + * + * @ingroup ACMFunction + * @ingroup LINUXOS + */ + + +#include <otg/otg-compat.h> +#include <otg/otg-module.h> + +#include <linux/init.h> +#include <asm/uaccess.h> +#include <linux/ctype.h> +#include <linux/timer.h> +#include <linux/interrupt.h> +#include <asm/atomic.h> +#include <linux/smp_lock.h> +#include <linux/slab.h> + +#include <otg/usbp-chap9.h> +#include <otg/usbp-cdc.h> +#include <otg/usbp-func.h> +#include <otg/usbp-mcpc.h> + + +#include <otg/otg-trace.h> +#include "acm.h" + +/* + * CDC ACM Configuration + * + * Endpoint, Class, Interface, Configuration and Device descriptors/descriptions + */ +/*! Endpoint Index Lists + */ +static u8 tty_alt_endpoint_index[] = { BULK_IN, BULK_OUT, }; +static u8 tty_comm_endpoint_index[] = { INT_IN, }; + +/*! @name Class Descriptors + */ +/*@{*/ + +static struct usbd_class_header_function_descriptor tty_class_1 = { + bFunctionLength: 0x05, + bDescriptorType: USB_DT_CLASS_SPECIFIC, + bDescriptorSubtype: USB_ST_HEADER, + bcdCDC: __constant_cpu_to_le16(0x0110), +}; + +static struct usbd_call_management_functional_descriptor tty_class_2 = { + bFunctionLength: 0x05, + bDescriptorType: USB_DT_CLASS_SPECIFIC, + bDescriptorSubtype: USB_ST_CMF, + bmCapabilities: USB_CMFD_CALL_MANAGEMENT | USB_CMFD_DATA_CLASS, + bDataInterface: 1, /* offset to data interface within pipe group */ +}; + +static struct usbd_class_union_function_descriptor tty_class_3 = { + bFunctionLength: 0x05, + bDescriptorType: USB_DT_CLASS_SPECIFIC, + bDescriptorSubtype: USB_ST_UF, + bMasterInterface: 0x00, + bSlaveInterface: { 1 }, /* offset to slave interface within pipe group */ +}; + + +/*@}*/ + +/*! ACMF Class Descriptor + * ACMF - c.f. Table 28 + * currenty set to 0x2 - Support Set_Line_Coding etc, + * + * XXX Should we also set 0x4 - Supports Network_Notification? + */ +static struct usbd_abstract_control_functional_descriptor tty_class_4 = { + bFunctionLength: 0x04, + bDescriptorType: USB_DT_CLASS_SPECIFIC, + bDescriptorSubtype: USB_ST_ACMF, + bmCapabilities: USB_ACMFD_CONFIG /*| USB_ACMFD_SEND_BREAK */, +}; + +static struct usbd_generic_class_descriptor *tty_comm_class_descriptors[] = + { (struct usbd_generic_class_descriptor *) &tty_class_1, + (struct usbd_generic_class_descriptor *) &tty_class_2, + (struct usbd_generic_class_descriptor *) &tty_class_3, + (struct usbd_generic_class_descriptor *) &tty_class_4, }; + + +/*! Comm alternate descriptions + */ +static struct usbd_alternate_description tty_comm_alternate_descriptions[] = { + { + .iInterface = "Belcarra CDC/ACM", + .bInterfaceClass = COMMUNICATIONS_INTERFACE_CLASS, + .bInterfaceSubClass = COMMUNICATIONS_ACM_SUBCLASS, + .bInterfaceProtocol = 0x01, + .classes = sizeof (tty_comm_class_descriptors) / sizeof (struct usbd_generic_class_descriptor *), + .class_list = tty_comm_class_descriptors, + .endpoints = sizeof (tty_comm_endpoint_index) / sizeof(u8), + .endpoint_index = tty_comm_endpoint_index, + }, }; + +/*! Data alternate descriptions + */ +static struct usbd_alternate_description tty_data_alternate_descriptions[] = { + { + .iInterface = "CDC/ACM Data", + .bInterfaceClass = DATA_INTERFACE_CLASS, + .bInterfaceSubClass = COMMUNICATIONS_NO_SUBCLASS, + .bInterfaceProtocol = COMMUNICATIONS_NO_PROTOCOL, + .endpoints = sizeof (tty_alt_endpoint_index) / sizeof(u8), + .endpoint_index = tty_alt_endpoint_index, + }, }; + + +/*! Interface Descriptions List */ +static struct usbd_interface_description tty_interfaces[] = { + { + .alternates = sizeof (tty_comm_alternate_descriptions) / sizeof (struct usbd_alternate_description), + .alternate_list = tty_comm_alternate_descriptions, + }, + { + .alternates = sizeof (tty_data_alternate_descriptions) / sizeof (struct usbd_alternate_description), + .alternate_list = tty_data_alternate_descriptions, + }, +}; + +/*! Endpoint Request List + * XXX this should be in acm.c + */ +static struct usbd_endpoint_request tty_endpoint_requests[ENDPOINTS+1] = { + { INT_IN, 1, 0, 0, USB_DIR_IN | USB_ENDPOINT_INTERRUPT, 16, 16, 4, }, + { BULK_IN, 1, 1, 0, USB_DIR_IN | USB_ENDPOINT_BULK, 512 , 512, 0, }, + { BULK_OUT, 1, 1, 0, USB_DIR_OUT | USB_ENDPOINT_BULK, 512, 512, 0, }, + { 0, }, +}; + + +static struct usbd_function_operations tty_function_ops; + +/*! function_driver + */ +struct usbd_interface_driver tty_function_driver = { + .driver = { + .name = "tty-if", + .fops = &tty_function_ops, }, +// .driver.name = "tty-if", +// .driver.fops = &tty_function_ops, +// .driver.function_type = function_interface, + .interfaces = sizeof (tty_interfaces) / sizeof (struct usbd_interface_description), + .interface_list = tty_interfaces, + .endpointsRequested = ENDPOINTS, + .requestedEndpoints = tty_endpoint_requests, + + .bFunctionClass = DATA_INTERFACE_CLASS, + .bFunctionSubClass = COMMUNICATIONS_NO_SUBCLASS, + .bFunctionProtocol = COMMUNICATIONS_NO_PROTOCOL, + .iFunction = "Belcarra CDC/ACM", +}; + +/* ********************************************************************************************* */ +/* ********************************************************************************************* */ + +extern struct acm_os_ops tty_l26_os_ops; + +/*! + * @brief tty_if_function_enable - called by USB Device Core to enable the driver + * @param function_instance The function instance for this driver to use. + * @return non-zero if error. + */ +static int tty_if_function_enable (struct usbd_function_instance *function_instance) +{ + TRACE_MSG0(TTY, "ENABLED"); + return acm_function_enable(function_instance, tty_if, &tty_l26_os_ops, MAX_QUEUED_URBS, MAX_QUEUED_BYTES); +} + +/*! void tty_if_exit() + * @brief deregister tty_if interface function driver + * @return none + */ +void tty_if_exit(void) +{ + TRACE_MSG0(TTY, "EXIT"); + //acm_wait_task(acm->recv_tqueue); + usbd_deregister_interface_function (&tty_function_driver); +} + +/*! tty_function_ops + */ +static struct usbd_function_operations tty_function_ops = { + .function_enable = tty_if_function_enable, + .function_disable = acm_function_disable, + + .device_request = acm_device_request, + .endpoint_cleared = acm_endpoint_cleared, + .set_configuration = acm_set_configuration, + .set_interface = acm_set_interface, + .reset = acm_reset, + .suspended = acm_suspended, + .resumed = acm_resumed, +}; + + +/*!int tty_if_init(char*) + * @brief initialize and register tty_if interface driver + * @param usbd_module_info + * @return non-zero if error + */ +int tty_if_init(char *usbd_module_info) +{ + //printk(KERN_INFO"%s:\n", __FUNCTION__); + TRACE_MSG0(TTY, "INIT"); + return usbd_register_interface_function (&tty_function_driver, "tty-if", NULL); +} diff --git a/drivers/otg/functions/acm/tty-l26-os.c b/drivers/otg/functions/acm/tty-l26-os.c new file mode 100644 index 000000000000..737ec9d921f2 --- /dev/null +++ b/drivers/otg/functions/acm/tty-l26-os.c @@ -0,0 +1,2017 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/functions/acm/tty-l24-os.c + * @(#) balden@belcarra.com/seth2.rillanon.org|otg/functions/acm/tty-l26-os.c|20070921192720|19575 + * + * Copyright (c) 2003-2005 Belcarra Technologies Corp + * Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + * + * By: + * Stuart Lynne <sl@belcarra.com>, + * Bruce Balden <balden@belcarra.com> + * + * + */ + + +/*! + * @file otg/functions/acm/tty-l26-os.c + * @brief ACM Function Driver private defines + * + * This is the OS portion of the TTY version of the + * ACM driver. + * + * TTY + * Interface + * Upper +----------+ + * Edge | tty-l26 | <-------- + * Implementation +----------+ + * + * + * Function +----------+ + * Descriptors | tty-if | + * Registration +----------+ + * + * + * Function +----------+ + * I/O | acm | + * Implementation +----------+ + * + * + * Module +----------+ + * Loading | acm-l26 | + * +----------+ + * + * + * + * @ingroup ACMFunction + * @ingroup LINUXOS + */ + + +#include <otg/otg-compat.h> + +#include <linux/init.h> +#include <asm/uaccess.h> +#include <linux/ctype.h> +#include <linux/timer.h> +#include <linux/interrupt.h> +#include <asm/atomic.h> +#include <linux/tty.h> +#include <linux/tty_driver.h> +#include <linux/tty_flip.h> +#include <linux/smp_lock.h> +#include <linux/slab.h> +#include <linux/serial.h> + +#if defined(CONFIG_DEVFS_FS) +#include <linux/devfs_fs_kernel.h> +#endif /* defined(CONFIG_DEVFS_FS) */ + +/* XXX + * Set this to first kernel version that does not have flip bufs + */ +#define LINUX_KERNEL_VERSION_NO_FLIP_BUF KERNEL_VERSION(2,6,17) + +#include <otg/usbp-chap9.h> +#include <otg/usbp-cdc.h> +#include <otg/usbp-func.h> + +#include <linux/capability.h> +#include <otg/otg-trace.h> +#include "acm.h" +#include "tty.h" + +/* ACM ***************************************************************************************** */ + +#define ACM_TTY_MAJOR 166 +#define ACM_TTY_MINOR 0 +#define ACM_TTY_MINORS 4 + +/* + * All functions take one of struct tty or struct usbd_function_instance to + * identify the port and/or function. + * + * Each of the following tables provides the linkage of the various + * components for each minor device entry: + * + * tty_table - the tty struct, provide by tty layer above us, managed by tty_l26_open/tty_l26_close + * function_table - the acm interface instance managed by tty_fd_enable/tty_fd_disable + * + * The tty struct driver_data field points at the private data structure for + * the os layer and tty layer: + * + * os_private + * + * The function instance privdata filed points at the private data structure + * for the function instance: + * + * acm_private + * + * + * + * N.B. + * + * 1. There are several combinations + * + * enabled reset configured host closed host opened + * + * local closed + * + * local opened + * + * + */ + +static struct tty_struct **tty_table; +static struct usbd_function_instance **function_table; +static u8 tty_minor_count; + +#if defined(LINUX26) + +//static struct tty_struct **tty_table; +//static struct usbd_function_instance **function_table; +//static u8 tty_minor_count; +#define TTYINDEX(tty) tty->index + +#else /* defined(LINUX26) */ + +//static struct tty_struct ** tty_table ; +//static struct usbd_function_instance ** function_table; + + +#define TTYINDEX(tty) minor(tty->device) + +#endif /* defined(LINUX26) */ + + +/* ******************************************************************************************* */ + +void * tty_l26_wakeup_writers(void *data); +void tty_l26_recv_flip(void *data); +void * tty_l26_call_hangup(void *data); +int tty_l26_block_until_ready(struct tty_struct *tty, struct file *filp); +int tty_l26_loop_xmit_chars(struct usbd_function_instance *function_instance, minor_chan_t chan, + int count, int from_user, const unsigned char *buf); +void tty_l26_overflow_send(struct usbd_function_instance *function_instance, minor_chan_t, BOOL force); +int tty_l26_os_recv_space_available(struct usbd_function_instance *function_instance, minor_chan_t); +int tty_l26_os_recv_chars(struct usbd_function_instance *function_instance, u8 *cp, int n); +unsigned int tty_l26_tiocm(struct usbd_function_instance *function_instance, minor_chan_t); +int tty_l26_overflow_xmit_chars(struct usbd_function_instance *function_instance, minor_chan_t chan, + int count, int from_user, const unsigned char *buf); + +void * tty_l26_recv_start(void *data); +void tty_l26_recv_start_bh(struct usbd_function_instance *function_instance); +void tty_l26_os_line_coding(struct usbd_function_instance *function_instance); + +/* ******************************************************************************************* */ +/* ******************************************************************************************* */ +/* Linux TTY Functions + */ +/*! + * @brief tty_l26_open + * + * Called by TTY layer to open device. + * + * @param tty + * @param filp + * @returns non-zero for error + */ +STATIC int tty_l26_open(struct tty_struct *tty, struct file *filp) +{ + + int index = TTYINDEX(tty); + minor_chan_t chan = CHAN(index); + struct os_private *os_private = tty->driver_data; + struct usbd_function_instance *function_instance = function_table[index]; + struct acm_private *acm = function_instance ? function_instance->privdata : NULL; + + int used; + BOOL nonblocking; + int rc = 0; + + TRACE_MSG1(TTY,"acm: %x", acm); + +//static struct tty_struct *tty_table; + + tty_table[index] = tty; + + switch(chan) { + case data_chan: + break; + case command_chan: + default: + break; + } + + otg_pthread_mutex_lock(&acm->mutex); + UNLESS(tty->driver_data) { + THROW_UNLESS((os_private = CKMALLOC(sizeof(struct os_private))), error); + os_private->index = index; + tty->driver_data = os_private; + init_waitqueue_head(&os_private->open_wait); + init_waitqueue_head(&os_private->tiocm_wait); + init_waitqueue_head(&os_private->send_wait); + + os_private->wakeup_workitem = otg_workitem_init("wakeup", tty_l26_wakeup_writers, tty, TTY); + os_private->hangup_workitem = otg_workitem_init("hangup", tty_l26_call_hangup, tty, TTY); + os_private->recv_workitem = otg_workitem_init("receive", tty_l26_recv_start, tty, TTY); + } + otg_pthread_mutex_unlock(&acm->mutex); + + + nonblocking = BOOLEAN(filp->f_flags & O_NONBLOCK); /* O_NONBLOCK is same as O_NDELAY */ + + TRACE_MSG2(TTY,"f_flags: %08x NONBLOCK: %x", filp->f_flags, nonblocking); + + /* Lock and increment used counter, save current value. + * Check for exclusive open at the same time. + */ + otg_pthread_mutex_lock(&acm->mutex); + + /* The value of os_private->used controls MOD_{INC/DEC}_USE_COUNT, so + * it has to be incremented unconditionally, and no early return + * made until after USE_COUNT has been adjusted to match. + */ + used = atomic_post_inc(&os_private->used); +#ifdef MCEL + filp->f_flags |= O_EXCL; // QQQ Can we persuade MCEL to add this to their app? + if (nonblocking && acm && !(acm->connected)) { + // QQQ Is MCEL actually using this "feature"? (See below for printk) + rc = -EINVAL; + } + else +#endif +#if 0 + if (filp->f_flags & O_EXCL) { + /* This is intended to be an exclusive open, so + * make sure no one has the device open already, and + * set the exclusive flag so no one can open it later. + */ + if (used > 0) { + // Someone already has it. + rc = -EBUSY; + } + else { + os_private->flags |= TTYFD_EXCLUSIVE; + set_bit(TTY_EXCLUSIVE, &tty->flags); + } + } + if ((os_private->flags & TTYFD_EXCLUSIVE) && !capable(CAP_SYS_TTY_CONFIG)) + { + // Only the superuser can do a normal open of an O_EXCL tty + rc = -EBUSY; + } +#endif + otg_pthread_mutex_unlock(&acm->mutex); + + // OK, now it's safe to make an early return if we are failing. + if (rc) { +#ifdef MCEL + // This can dissappear when the "feature" above does. + if (-EINVAL == rc) + //printk(KERN_INFO "\nusb cable not connected!\n"); +#endif + return rc; + } + + + /* To truly emulate the old dual-device approach of having a non-blocking + * device (e.g cu0) and a blocking device (e.g. tty0) we would need to + * track blocking and non-blocking opens separately. We don't. This + * may lead to funny behavior in the multiple open case. + */ + UNLESS (used) { + + // First open. + TRACE_MSG2(TTY, "FIRST OPEN nonblocking: %x exclusive: %x", nonblocking, os_private->flags & TTYFD_EXCLUSIVE); + tty->low_latency = 1; + if (function_instance) { + acm_open(function_instance, chan); + acm_set_local(function_instance, 1); + } + os_private->flags |= TTYFD_OPENED; + } + + + /* All done if configured + */ + //RETURN_ZERO_UNLESS(acm_ready(function_instance)); + + /* All done if non blocking open + */ + RETURN_ZERO_IF(nonblocking); + + /* Block open - wait until ready + */ + TRACE_MSG0(TTY, "BLOCKING - wait until ready"); + return tty_l26_block_until_ready(tty, filp); + + /* The tty layer calls tty_l26_close() even if this open fails, + * so any cleanup (rc != 0) will be done there. + */ + //TRACE_MSG3(acm->trace_tag,"used: %d MOD_IN_USE: %d configured: %d", + // atomic_read(&os_private->used), MOD_IN_USE, acm->flags & ACM_CONFIGURED); + + + CATCH(error) { + return -ENOMEM; + } + +} + +/*! + * @brief tty_l26_close + * + * Called by TTY layer to close device. + * + * @param tty + * @param filp + * @returns non-zero for error + */ +STATIC void tty_l26_close(struct tty_struct *tty, struct file *filp) +{ + int index = TTYINDEX(tty); + minor_chan_t chan = CHAN(index); + struct os_private *os_private = tty->driver_data; + struct usbd_function_instance *function_instance = function_table[index]; + struct acm_private *acm = function_instance ? function_instance->privdata : NULL; + + int used; + + TRACE_MSG1(TTY,"acm: %x", (int)acm); + TRACE_MSG0(TTY, "entered"); + + switch(chan) { + case data_chan: + break; + case command_chan: + default: + break; + } + + + // lock and decrement used counter, save result + used = atomic_pre_dec(&os_private->used); + + // finished unless this is the last close + + RETURN_IF (used > 0); + + TRACE_MSG0(TTY,"FINAL CLOSE"); + + /* reset so no further operations can be started. + */ + //tty->driver_data = NULL; + + if (function_instance) { + + /* send any overflow data if neccessary + */ + tty_l26_overflow_send(function_instance, chan, TRUE); + + /* tell acm layer to close + */ + acm_close(function_instance, chan); + } + + //tty->driver_data = function_instance[index]; + + /* This should never happen if this is the last close, + * but it can't hurt to check. + */ + //if (os_private->open_wait_count) + wake_up_interruptible(&os_private->open_wait); + + if (os_private->flags & TTYFD_EXCLUSIVE) { + os_private->flags &= ~TTYFD_EXCLUSIVE; + if (tty) + clear_bit(TTYFD_EXCLUSIVE, &tty->flags); + } + + // XXX wait + + otg_workitem_exit(os_private->wakeup_workitem); + otg_workitem_exit(os_private->hangup_workitem); + otg_workitem_exit(os_private->recv_workitem); + + if (os_private) LKFREE(os_private); + // XXX moved up tty->driver_data = NULL; + tty->driver_data = NULL; + + TRACE_MSG0(TTY,"FINISHED"); + +} + +/* ******************************************************************************************* */ + + +/*! + * @brief tty_l26_block_until_ready + * + * Called from tty_l26_open to implement blocking open. Wait for DTR indication + * from acm-fd. + * + * Called by TTY layer to open device. + * + * @param tty + * @param filp + * @returns non-zero for error + */ +int tty_l26_block_until_ready(struct tty_struct *tty, struct file *filp) +{ + int index = TTYINDEX(tty); + struct os_private *os_private = tty->driver_data; + struct usbd_function_instance *function_instance = function_table[index]; + struct acm_private *acm = function_instance ? function_instance->privdata : NULL; + + int rc = 0; + + TRACE_MSG1(TTY,"acm: %x", (int)acm); + + for (;;) { + /* check if the file has been closed */ + if (tty_hung_up_p(filp)) { + TRACE_MSG0(TTY,"tty_hung_up_p()"); + return -ERESTARTSYS; + } + /* check for pending signals */ + if (signal_pending(current)) { + TRACE_MSG0(TTY,"signal_pending()"); + return -ERESTARTSYS; + } + /* check if the module is unloading */ + if (os_private->exiting) { + TRACE_MSG0(TTY,"module exiting()"); + return -ENODEV; + } + /* check for what we want, do we have DCD (RTS from host POV)? + */ + if (acm_get_d1_rts(function_instance)) { + // OK, there's somebody on the other end, let's go... + TRACE_MSG0(TTY,"found CAR"); + return 0; + } + /* sleep */ + interruptible_sleep_on(&os_private->open_wait); + } + /* NOT REACHED */ +} + +/* Transmit Function - called by tty layer ****************************************************** */ + +/*! + * @brief tty_l26_chars_in_buffer + * + * Called by TTY layer to determine amount of pending write data. + * + * @param tty - pointer to tty data structure + * @return amount of pending write data + */ +STATIC int tty_l26_chars_in_buffer(struct tty_struct *tty) +{ + int index = TTYINDEX(tty); + minor_chan_t chan = CHAN(index); + struct os_private *os_private = tty->driver_data; + struct usbd_function_instance *function_instance = function_table[index]; + int rc; + + TRACE_MSG1(TTY,"tty: %x", (int)tty); + + rc = acm_chars_in_buffer(function_instance, chan) + atomic_read(&os_private->tty_overflow_used); + + TRACE_MSG1(TTY, "chars in buffer: %d", rc); + + return rc; +} + +#if !defined(LINUX26) +/*! tty_l24_write + * + * Called by TTY layer to write data. + * @param tty - pointer to tty data structure + * @param from_user - true if from user memory space + * @param buf - pointer to data to send + * @param count - number of bytes want to send. + * @return count - number of bytes actually sent. + */ +int tty_l24_write(struct tty_struct *tty, int from_user, const unsigned char *buf, int count) +{ + int index = TTYINDEX(tty); + struct os_private *os_private = tty->driver_data; + struct usbd_function_instance *function_instance = function_table[index]; + struct acm_private *acm = function_instance ? function_instance->privdata : NULL; + minor_chan_t chan = CHAN(index); + + TRACE_MSG4(TTY,"tty: %x -> count: %d loopback: %d from_usr: %d", tty, count, os_private->tiocm & TIOCM_LOOP, from_user); + + RETURN_ZERO_UNLESS(acm); + + /* loopback mode + */ + if (os_private->tiocm & TIOCM_LOOP) + return tty_l26_loop_xmit_chars(function_instance, chan, count, from_user, buf); + + /* overflow mode + */ + if (atomic_read(&os_private->tty_overflow_used) || (tty_l26_chars_in_buffer(tty) && (count < 64))) + return tty_l26_overflow_xmit_chars(function_instance, chan, count, from_user, buf); + + /* straight through mode + */ + return acm_xmit_chars(function_instance, chan, count, from_user, buf); +} +#endif /* !defined(LINUX26) */ + +#if defined(LINUX26) +/*! tty_l26_write + * + * Called by TTY layer to write data. + * @param tty - pointer to tty data structure + * @param from_user - true if from user memory space + * @param buf - pointer to data to send + * @param count - number of bytes want to send. + * @return count - number of bytes actually sent. + */ +int tty_l26_write(struct tty_struct *tty, const unsigned char *buf, int count) +{ + int index = TTYINDEX(tty); + struct os_private *os_private = tty->driver_data; + struct usbd_function_instance *function_instance = function_table[index]; + struct acm_private *acm = function_instance ? function_instance->privdata : NULL; + minor_chan_t chan = CHAN(index); + int total_sent_chars= 0; + int sent_chars; + + + TRACE_MSG3(TTY,"sos909 tty: %x -> count: %d loopback: %d from_usr: %d", tty, count, os_private->tiocm & TIOCM_LOOP); + + #if 0 + { + int i; + const char *cp = buf; + for (i = 0; i < count; i += 8) { + TRACE_MSG8(TTY, " SEND [%02x %02x %02x %02x %02x %02x %02x %02x]", + cp[i + 0], cp[i + 1], cp[i + 2], cp[i + 3], + cp[i + 4], cp[i + 5], cp[i + 6], cp[i + 7] + ); + + } + + } + #else + + //if (count>0) printk(KERN_INFO "808 tty =>USB: Num:%d\n",count); + #endif + RETURN_ZERO_UNLESS(acm); + + /* loopback mode + */ + if (os_private->tiocm & TIOCM_LOOP) + return tty_l26_loop_xmit_chars(function_instance, chan, count, 0, buf); + + /* overflow mode + */ + if (atomic_read(&os_private->tty_overflow_used) || (tty_l26_chars_in_buffer(tty) && (count < 64))) + return tty_l26_overflow_xmit_chars(function_instance, chan, count, 0, buf); + + /* straight through mode + */ + + while ((sent_chars = acm_xmit_chars(function_instance, chan, count - total_sent_chars, 0, buf + total_sent_chars))) { + total_sent_chars += sent_chars; + } + return total_sent_chars; +} + +#endif /* defined(LINUX26) */ + +/*! tty_l26_write_room + * + * Called by TTY layer get amount of write room available. + * + * @param tty - pointer to tty data structure + * @return amount of write room available + */ +STATIC int tty_l26_write_room(struct tty_struct *tty) +{ + int index = TTYINDEX(tty); + struct os_private *os_private = tty->driver_data; + struct usbd_function_instance *function_instance = function_table[index]; + struct acm_private *acm = function_instance ? function_instance->privdata : NULL; + minor_chan_t chan = CHAN(index); + int rc; + + TRACE_MSG1(TTY,"tty: %x", (int)tty); + /* loopback mode + */ + if (os_private->tiocm & TIOCM_LOOP) + return tty_l26_os_recv_space_available(function_instance, chan); + + RETURN_ZERO_UNLESS(acm); +#if 0 + rc = acm_write_room(function_instance, chan) + + (MIN(TTY_OVERFLOW_SIZE, acm->writesize) - os_private->tty_overflow_used, chan); + + TRACE_MSG1(TTY, "write room: %d", rc); + + return rc; +#else + + rc = acm_write_room(function_instance, chan); + if(rc) rc = (MIN(TTY_OVERFLOW_SIZE, acm->writesize) - atomic_read(&os_private->tty_overflow_used)); + + TRACE_MSG1(TTY, "write room: %d", rc); + + return rc; +#endif + + +} + +static int throttle_count = 0; +static int unthrottle_count = 0; + +/*! tty_l26_throttle + * + * Called by TTY layer to throttle (do not allow received data.) + * + * @return amount of write room available + */ +STATIC void tty_l26_throttle(struct tty_struct *tty) +{ + int index = TTYINDEX(tty); + struct os_private *os_private = tty->driver_data; + struct usbd_function_instance *function_instance = function_table[index]; + minor_chan_t chan = CHAN(index); + + //printk(KERN_INFO"%s:\n", __FUNCTION__); + TRACE_MSG1(TTY,"tty: %x", (int)tty); + os_private->flags |= TTYFD_THROTTLED; + acm_throttle(function_instance, chan); + +} + +/*! tty_l26_unthrottle + * + * Called by TTY layer to unthrottle (allow received data.) + * + * @return amount of write room available + */ +STATIC void tty_l26_unthrottle(struct tty_struct *tty) +{ + int index = TTYINDEX(tty); + struct os_private *os_private = tty->driver_data; + struct usbd_function_instance *function_instance = function_table[index]; + minor_chan_t chan = CHAN(index); + + //printk(KERN_INFO"%s:\n", __FUNCTION__); + TRACE_MSG1(TTY,"tty: %x", (int)tty); + os_private->flags &= ~TTYFD_THROTTLED; + acm_unthrottle(function_instance, chan); + + /* possible recv was waiting + */ + //acm_restart_recv(function_instance); + //tty_l26_recv_start_bh(function_instance); + + + /* This function is called while the TTY_DONT_FLIP flag is still + * set, so there is no point trying to push the flip buffer. Just + * try to queue some recv urbs, and keep trying until we do manage + * to get some queued. + */ + //tty_schedule_flip(tty); + +} + + +unsigned int tty_l26_tiocm(struct usbd_function_instance *function_instance, minor_chan_t chan); + +static int tty_l26_tiocmset(struct tty_struct *tty, struct file *file, + unsigned int set, unsigned int clear); +/*! tty_l26_fd_ioctl + * + * Used by TTY layer to execute IOCTL command. + * + * Called by TTY layer to open device. + * + * Unhandled commands must return -ENOIOCTLCMD for correct operation. + * + * @param tty + * @param file + * @param cmd + * @param arg + * @returns non-zero for error + */ +STATIC int tty_l26_fd_ioctl(struct tty_struct *tty, struct file *file, unsigned int cmd, unsigned long arg) +{ + int index = TTYINDEX(tty); + minor_chan_t chan = CHAN(index); + struct os_private *os_private = tty->driver_data; + struct usbd_function_instance *function_instance = function_table[index]; + + unsigned int mask; + unsigned int newctrl; + int rc; + + //printk(KERN_INFO"%s:\n", __FUNCTION__); + //TRACE_MSG3(TTY,"tty: %x cmd: %08x arg: %08x", (int)tty, cmd, arg); + TRACE_MSG3(TTY,"entered: %8x dir: %x size: %x", cmd, _IOC_DIR(cmd), _IOC_SIZE(cmd)); + + switch(chan) { + + case data_chan: + break; + case command_chan: + default: + break; + + } + + switch(cmd) { +#if !defined(LINUX26) + case TIOCMGET: + //TRACE_MSG1(TTY, "TIOCMGET: tiocm: %08x", tiocm); + os_private->tiocm = tty_l26_tiocm(function_instance, chan); + RETURN_EINVAL_IF (copy_to_user((void *)arg, &os_private->tiocm, sizeof(int))); + return 0; + + case TIOCMBIS: + case TIOCMBIC: + case TIOCMSET: { + u32 tiocm = 0, set = 0, clear = 0; + + TRACE_MSG0(TTY, "TIOCMXXX: copying tiocm arguement"); + + TRACE_MSG1(TTY, "access: %d", access_ok(VERIFY_READ, (void *)arg, sizeof(int))); + RETURN_EINVAL_UNLESS(access_ok(VERIFY_READ, (void *)arg, sizeof(int))); + + + //RETURN_EINVAL_IF (copy_from_user(&tiocm, (void *)arg, sizeof(int))); + rc = copy_from_user((void *)&tiocm, (void *)arg, sizeof(int)); + TRACE_MSG2(TTY, "copy_from_user: %d tiocm: %08x", rc, tiocm); + + switch (cmd) { + case TIOCMBIS: + set = tiocm; + break; + case TIOCMBIC: + clear = tiocm; + break; + case TIOCMSET: + set = tiocm; + clear = ~set; + break; + } + + return tty_l26_tiocmset(tty, NULL, set, clear); + } + +#endif /* !defined(LINUX26) */ + + case TIOCGSERIAL: + TRACE_MSG0(TTY, "TIOCGSERIAL"); + RETURN_EINVAL_IF (copy_to_user((void *)arg, &os_private->serial_struct, sizeof(struct serial_struct))); + return 0; + + case TIOCSSERIAL: + TRACE_MSG0(TTY, "TIOCSSERIAL"); + //RETURN_EFAULT_IF (copy_from_user(&os_private->serial_struct, (void *)arg, sizeof(struct serial_struct))); + return -EINVAL; + + case TIOCSERCONFIG: + case TIOCSERGETLSR: /* Get line status register */ + case TIOCSERGSTRUCT: + TRACE_MSG0(TTY, "TIOCSER*"); + return -EINVAL; + + /* + * Wait for any of the 4 modem inputs (DCD,RI,DSR,CTS) to change + * - mask passed in arg for lines of interest + * (use |'ed TIOCM_RNG/DSR/CD/CTS for masking) + * Caller should use TIOCGICOUNT to see which one it was + * + * Note: CTS is always true, RI is always false. + * DCD and DSR always follow DTR signal from host. + */ + case TIOCMIWAIT: + TRACE_MSG0(TTY, "TIOCGMIWAIT"); + while (1) { + + u32 saved_tiocm = os_private->tiocm; + u32 changed_tiocm; + + interruptible_sleep_on(&os_private->tiocm_wait); + + /* see if a signal did it */ + if (signal_pending(current)) + return -ERESTARTSYS; + + os_private->tiocm = tty_l26_tiocm(function_instance, chan); + changed_tiocm = saved_tiocm ^ os_private->tiocm; + RETURN_ZERO_IF ( (changed_tiocm | TIOCM_CAR) | (changed_tiocm | TIOCM_DSR) ); + return -EIO; + /* loop */ + } + /* NOTREACHED */ + + /* + * Get counter of input serial line interrupts (DCD,RI,DSR,CTS) + * Return: write counters to the user passed counter struct + * NB: both 1->0 and 0->1 transitions are counted except for + * RI where only 0->1 is counted. + */ + case TIOCGICOUNT: + TRACE_MSG0(TTY, "TIOCGICOUNT"); + if (copy_to_user((void *)arg, &os_private->tiocgicount, sizeof(int))) + return -EFAULT; + return 0; + + case TCSETS: + TRACE_MSG0(TTY, "TCSETS: "); + return -ENOIOCTLCMD; + case TCFLSH: + TRACE_MSG0(TTY, "TCFLSH: x"); + return -ENOIOCTLCMD; + + case TCGETS: + TRACE_MSG0(TTY, "TCGETS: "); + return -ENOIOCTLCMD; + + default: + TRACE_MSG1(TTY, "unknown cmd: %08x", cmd); + return -ENOIOCTLCMD; + } + return -ENOIOCTLCMD; + + +} + +#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,20) +#define _TERMIOS_ ktermios +#else +#define _TERMIOS_ termios +#endif + +/*! tty_l26_set_termios + * + * Used by TTY layer to set termios structure according to current status. + * + * @param tty - pointer to acm private data structure + * @param termios_old - termios structure + */ +STATIC void tty_l26_set_termios(struct tty_struct *tty, struct _TERMIOS_ *termios_old) +{ + int index = TTYINDEX(tty); + minor_chan_t chan = CHAN(index); + struct os_private *os_private = tty->driver_data; + struct usbd_function_instance *function_instance = function_table[index]; + + unsigned int c_cflag = tty->termios->c_cflag; + + TRACE_MSG1(TTY,"tty: %x", (int)tty); + + switch(chan) { + case data_chan: + break; + case command_chan: + default: + break; + + } + + /* see if CLOCAL has changed */ + if ((termios_old->c_cflag ^ tty->termios->c_cflag ) & CLOCAL) + acm_set_local(function_instance, tty->termios->c_cflag & CLOCAL); + + /* save cflags */ + //os_private->c_cflag = c_cflag; + + /* send break? */ + if ((termios_old->c_cflag & CBAUD) && !(c_cflag & CBAUD)) + acm_bBreak(function_instance); +} + + +/*! tty_l26_wait_until_sent + * + * Used by TTY layer to wait until pending data drains (is sent.) + * + * @param tty - pointer to acm private data structure + * @param timeout - wait this amount of time, or forever if zero + */ +static void tty_l26_wait_until_sent(struct tty_struct *tty, int timeout) +{ + int index = TTYINDEX(tty); + struct os_private *os_private = tty->driver_data; + struct usbd_function_instance *function_instance = function_table[index]; + minor_chan_t chan = CHAN(index); + + TRACE_MSG0(TTY, "--"); + + RETURN_UNLESS(acm_send_queued(function_instance)); + + /* XXX This will require a wait structure and flag */ + interruptible_sleep_on(&os_private->send_wait); +} + +/*! tty_l26_flush_chars + * + * Used by TTY layer to flush pending data to hardware. + * + * @param tty - pointer to acm private data structure + */ +static void tty_l26_flush_chars(struct tty_struct *tty) +{ + int index = TTYINDEX(tty); + struct os_private *os_private = tty->driver_data; + struct usbd_function_instance *function_instance = function_table[index]; + minor_chan_t chan = CHAN(index); + + TRACE_MSG0(TTY, "--"); + + /* queue data in overflow buffer */ + tty_l26_overflow_send(function_instance, chan, TRUE); +} + +/*! tty_l26_flush_buffer + * + * Used by TTY layer to flush pending data to /dev/null (cancel.) + * + * @param tty - pointer to acm private data structure + */ +static void tty_l26_flush_buffer(struct tty_struct *tty) +{ + int index = TTYINDEX(tty); + struct os_private *os_private = tty->driver_data; + struct usbd_function_instance *function_instance = function_table[index]; + minor_chan_t chan = CHAN(index); + + TRACE_MSG0(TTY, "--"); + + /* clear overflow buffer */ + + atomic_set(&os_private->tty_overflow_used, 0); +} + + +#if defined(LINUX26) +/*! tty_operations + */ +static struct tty_operations tty_ops = { + + .open = tty_l26_open, + .close = tty_l26_close, + .write = tty_l26_write, + .write_room = tty_l26_write_room, + .ioctl = tty_l26_fd_ioctl, + .throttle = tty_l26_throttle, + .unthrottle = tty_l26_unthrottle, + .chars_in_buffer = tty_l26_chars_in_buffer, + .set_termios = tty_l26_set_termios, + #if 0 + .wait_until_sent = tty_l26_wait_until_sent, + .flush_chars = tty_l26_flush_chars, + .flush_buffer = tty_l26_flush_buffer, + #endif +}; + +/*! tty_driver + */ +static struct tty_driver *tty_driver; + +#else /* defined(LINUX26) */ + +static int tty_refcount; +static struct tty_struct *tty_table1[ACM_TTY_MINORS]; +static struct termios *tty_termios[ACM_TTY_MINORS]; +static struct termios *tty_termios_locked[ACM_TTY_MINORS]; + +static struct tty_driver tty_driver = { + + + .open = tty_l26_open, + .close = tty_l26_close, + .write = tty_l24_write, + .write_room = tty_l26_write_room, + .ioctl = tty_l26_fd_ioctl, + .throttle = tty_l26_throttle, + .unthrottle = tty_l26_unthrottle, + .chars_in_buffer = tty_l26_chars_in_buffer, + .set_termios = tty_l26_set_termios, + + .refcount = &tty_refcount, + .table = tty_table1, + .termios = tty_termios, + .termios_locked = tty_termios_locked, +}; + +#endif /* defined(LINUX26) */ + +/* Transmit Function - called by tty layer ****************************************************** */ + +/* ******************************************************************************************* */ +/* ******************************************************************************************* */ +/* TTY OS Functions + */ + +#define LOOP_BUF 16 +/*! + * @brief tty_l26_loop_xmit_chars + * + * Implement data loop back, send xmit data back as received data. + * + * @param function_instance + * @param chan + * @param count - number of bytes to send + * @param from_user - true if from user memory space + * @param buf - pointer to data to send + * @return count - number of bytes actually sent. + */ +int tty_l26_loop_xmit_chars(struct usbd_function_instance *function_instance, minor_chan_t chan, + int count, int from_user, const unsigned char *buf) +{ + + struct acm_private *acm = function_instance ? function_instance->privdata : NULL; + int index = acm->index; + struct tty_struct *tty = tty_table[index]; + struct os_private *os_private = tty->driver_data; + int received = 0; + int rc=0; + + TRACE_MSG2(TTY,"acm[%x] %x", chan, acm); + + while (count > 0) { + u8 copybuf[LOOP_BUF]; + int i; + int space = tty_l26_os_recv_space_available(function_instance, chan); + int recv = MIN (space, LOOP_BUF); + recv = MIN(recv, count); + + //TRACE_MSG7(TTY, "buf: %8x buf+received: %8x received: %d from_user: %d count: %d space: %d recv: %d\n", + // buf, buf + received, received, from_user, count, space, recv); + + BREAK_UNLESS(recv); + + if (from_user) + rc=copy_from_user ((void *)copybuf, (void*)(buf + received), recv); + else + memcpy ((void *)copybuf, (void*)(buf + received), recv); + + count -= recv; + received += recv; + TRACE_MSG3(TTY, "count: %d recv: %d received: %d", count, recv, received); + tty_l26_os_recv_chars(function_instance, copybuf, recv); + } + return received; +} + +/*! + * @brief tty_l26_overflow_send + * + * Check if there is data in overflow buffer and send if neccessary. + * + * @param function_instance - pointer to acm private data structure + * @param chan + * @param force - force the send + * @return none + */ +void tty_l26_overflow_send(struct usbd_function_instance *function_instance, minor_chan_t chan, int force) +{ + struct acm_private *acm = function_instance ? function_instance->privdata : NULL; + int index = acm->index; + struct tty_struct *tty = tty_table[index]; + struct os_private *os_private = tty->driver_data; + int chars_in_buffer; + int count; + + TRACE_MSG2(TTY,"acm[%x] %x", chan, acm); + + otg_pthread_mutex_lock(&acm->mutex); + + chars_in_buffer = acm_chars_in_buffer(function_instance, chan); + + TRACE_MSG3(TTY, "overflow_used: %d chars_in_buffer: %d force: %d", + atomic_read(&os_private->tty_overflow_used), chars_in_buffer, force); + + if (TRUE || !chars_in_buffer || (atomic_read(&os_private->tty_overflow_used) > 200)) { + + count = acm_xmit_chars(function_instance, chan, atomic_read(&os_private->tty_overflow_used), + 0, os_private->tty_overflow_buffer); + + TRACE_MSG2(TTY, "overflow_used: %d count: %d", atomic_read(&os_private->tty_overflow_used), count); + + if((count==0) && (atomic_read(&os_private->tty_overflow_used)>0)) + { + }else{ + atomic_set(&os_private->tty_overflow_used, 0); + } + + } + otg_pthread_mutex_unlock(&acm->mutex); +} + + +/*! + * @brief tty_l26_overflow_xmit_chars + * + * Implement overflow buffer. The TTY layer implements echo with single + * character writes which can quickly exhaust the urbs allowed for sending. + * This results in data being lost. + * + * This function is called to accumulate small amounts of data when there is + * already an bulk in urb in the queue. + * + * The urb sent function calls wakeup writers which will call + * tty_l26_overflow_send which will send the data. + * + * @param function_instance - pointer to acm private data structure + * @param chan + * @param count - number of bytes to send + * @param from_user - true if from user memory space + * @param buf - pointer to data to send + * @return count - number of bytes actually sent. + */ +int tty_l26_overflow_xmit_chars(struct usbd_function_instance *function_instance, minor_chan_t chan, + int count, int from_user, const unsigned char *buf) +{ + struct acm_private *acm = function_instance ? function_instance->privdata : NULL; + int index = acm->index; + struct tty_struct *tty = tty_table[index]; + struct os_private *os_private = tty->driver_data; + int overflow_room; + int write_room = acm_write_room(function_instance, chan); + int sent = 0; + int rc=0; + + TRACE_MSG4(TTY,"acm[%x] %x count: %d from_user:%d", chan, acm, count, from_user); + + RETURN_ZERO_UNLESS(acm); + + do { + otg_pthread_mutex_lock(&acm->mutex); + + if ((overflow_room = MIN(TTY_OVERFLOW_SIZE, acm->writesize) - atomic_read(&os_private->tty_overflow_used)) > 0) { + + int tosend = MIN(overflow_room, count); + if (from_user) + rc=copy_from_user ((void *)os_private->tty_overflow_buffer + atomic_read(&os_private->tty_overflow_used), + (void*)buf, tosend); + else + memcpy ((void *)os_private->tty_overflow_buffer + atomic_read(&os_private->tty_overflow_used), + (void*)buf, tosend); + + atomic_add(tosend, &os_private->tty_overflow_used); + count -= tosend; + buf += tosend; + sent += tosend; + } + else + { + count = 0; + } + otg_pthread_mutex_unlock(&acm->mutex); + + /* start sending from overflow if neccessary + */ + tty_l26_overflow_send(function_instance, chan, count ? TRUE : FALSE); + } while (count); + + return sent; +} +/*! tty_l26_setbit - set specific bit + * @brief set specific bit and output message + * + * @param result - new value after setbit + * @param mask - bit to set + * @param value - bool to decide whether to set + * @param msg - message to output + * @return new value after set bit + */ + +static int tty_l26_setbit(u32 result, u32 mask, BOOL value, char *msg) +{ + UNLESS (value) + return result; + + result |= mask; + + TRACE_MSG3(TTY, "result: %04x mask: %04x %s", result, mask, msg); + return result; +} + +/*! + * @brief tty_l26_tiocm + * @param function_instance + * @param chan + * @return - computed tiocm value + */ +unsigned int tty_l26_tiocm(struct usbd_function_instance *function_instance, minor_chan_t chan) +{ + u32 tiocm = 0; + TRACE_MSG2(TTY,"acm[%x] %x", chan, (int)function_instance ? function_instance->privdata : NULL ); + switch(chan) { + case data_chan: + tiocm = tty_l26_setbit(tiocm, TIOCM_DTR, acm_get_bRxCarrier(function_instance), "TIOCM_DTR (bRxCarrier)"); + tiocm = tty_l26_setbit(tiocm, TIOCM_RTS, !acm_get_throttled(function_instance), "TIOCM_RTS (TRUE)"); + tiocm = tty_l26_setbit(tiocm, TIOCM_CTS, acm_get_d0_dtr(function_instance), "TIOCM_CTS (TRUE)"); + tiocm = tty_l26_setbit(tiocm, TIOCM_CAR, acm_get_d1_rts(function_instance), "TIOCM_CAR (D1 RTS)"); + tiocm = tty_l26_setbit(tiocm, TIOCM_RI, FALSE, "TIOCM_RING"); + tiocm = tty_l26_setbit(tiocm, TIOCM_DSR, acm_get_d0_dtr(function_instance), "TIOCM_DSR (D0 DTR)"); + tiocm = tty_l26_setbit(tiocm, TIOCM_OUT1, FALSE, "TIOCM_OUT1 (FALSE)"); + tiocm = tty_l26_setbit(tiocm, TIOCM_OUT2, FALSE, "TIOCM_OUT2 (FALSE)"); + return tiocm; + + case command_chan: + default: + return 0; + } +} + +#if defined(LINUX26) || defined(LINUX24) + +/*! tty_l26_tiocmget + * + * Peripheral Host Internal + * TIOCM_DTR bRxCarrier + * TIOCM_RTS Always TRUE + * TIOCM_CTS Always TRUE + * TIOCM_CAR D1 + * TIOCM_RI Always FALSE + * TIOCM_DSR D0 + * TIOCM_OUT1 bRingSignal + * TIOCM_OUT2 bOverrrun + * TIOCM_LOOP Loopback + */ +static int tty_l26_tiocmget(struct tty_struct *tty, struct file *file) +{ + + int index = TTYINDEX(tty); + struct usbd_function_instance *function_instance = function_table[index]; + struct acm_private *acm = function_instance ? function_instance->privdata : NULL; + unsigned int result = 0; + unsigned int msr = 0; + unsigned int mcr = 0; + + TRACE_MSG1(TTY, "acm: %x", (int)acm); + RETURN_ZERO_UNLESS(acm); + + return tty_l26_tiocm(function_instance, data_chan); + +} + +/*! tty_l26_tiocsget + * Peripheral Host Internal + * TIOCM_DTR bRxCarrier + * TIOCM_RTS Always TRUE + * TIOCM_OUT1 bRingSignal + * TIOCM_OUT2 bOverrun + * TIOCM_LOOP Loopback + * + */ +static int tty_l26_tiocmset(struct tty_struct *tty, struct file *file, + unsigned int set, unsigned int clear) +{ + int index = TTYINDEX(tty); + struct usbd_function_instance *function_instance = function_table[index]; + struct acm_private *acm = function_instance ? function_instance->privdata : NULL; + unsigned int result = 0; + unsigned int msr = 0; + unsigned int mcr = 0; + + TRACE_MSG3(TTY,"acm: %x set: %04x clear: %04x", (int)acm, set, clear); + RETURN_ZERO_UNLESS(acm); + + + /* Set DTR -> DTR */ + if (set & TIOCM_DTR) + acm_set_bRxCarrier(function_instance, 1); + if (clear & TIOCM_DTR) + acm_set_bRxCarrier(function_instance, 0); + + /* Set Loop back */ + if (set & TIOCM_LOOP) + acm_set_loopback(function_instance, 1); + if (clear & TIOCM_LOOP) + acm_set_loopback(function_instance, 0); + + /* OUT1->Ring */ + if ((set & TIOCM_OUT1)) + acm_bRingSignal(function_instance); + + /* OUT2->Overrun */ + if ((set & TIOCM_OUT2)) + acm_bOverrun(function_instance); + + /* !RTS->Break */ + if (set & TIOCM_RTS) + acm_set_throttle(function_instance, 0); + if (clear & TIOCM_RTS) + acm_set_throttle(function_instance, 1); + + return 0; +} + +/*! tty_l26_break_ctl + */ +static int tty_l26_break_ctl(struct tty_struct *tty, int flag) +{ + int index = TTYINDEX(tty); + struct usbd_function_instance *function_instance = function_table[index]; + struct acm_private *acm = function_instance ? function_instance->privdata : NULL; + RETURN_ZERO_UNLESS(acm); + + if (flag) + acm_bBreak(function_instance); + return 0; +} + +#endif /* defined(LINUX26) */ +/* ********************************************************************************************** */ +/* ********************************************************************************************* */ + +/*! + * @brief tty_l26_call_hangup + * + * Bottom half handler to safely send hangup signal to process group. + * + * @param data + * @return none + */ +void * tty_l26_call_hangup(void *data) +{ + struct tty_struct *tty = data; + struct os_private *os_private = tty ? tty->driver_data : NULL; + + /* XXX Do we need to protect this + */ + RETURN_NULL_UNLESS(tty && os_private); + + TRACE_MSG3(TTY,"tty: %x c_cflag: %02x CLOCAL: %d", tty, + tty->termios->c_cflag, + BOOLEAN(tty->termios->c_cflag & CLOCAL) + ); + + + //if (tty && !(os_private->c_cflag & CLOCAL)) + tty_hangup(tty); + + wake_up_interruptible(&os_private->open_wait); + + TRACE_MSG0(TTY,"exited"); + return NULL; +} + +/*! + * @brief tty_l26_os_schedule_hangup + * + * Schedule hangup bottom half handler. + * + * @param function_instance + * @return none + + */ +void tty_l26_os_schedule_hangup(struct usbd_function_instance *function_instance) +{ + struct acm_private *acm = function_instance ? function_instance->privdata : NULL; + int index = acm->index; + struct tty_struct *tty = tty_table[index]; + struct os_private *os_private = tty ? tty->driver_data : tty; + + TRACE_MSG1(TTY,"acm: %x", (int)acm); + + RETURN_UNLESS(os_private); + + TRACE_MSG0(TTY, "entered"); + //tty_l26_schedule(tty, &os_private->hqueue); + otg_workitem_start(os_private->hangup_workitem); +} + + +/*! tty_l26_wakeup_writers + * + * Bottom half handler to wakeup pending writers. + * + * @param data - pointer to acm private data structure + */ +void * tty_l26_wakeup_writers(void *data) +{ + struct tty_struct *tty = data; + struct os_private *os_private = tty ? tty->driver_data : NULL; + int index = os_private->index; + minor_chan_t chan = CHAN(index); + struct usbd_function_instance *function_instance = function_table[index]; + + + unsigned long flags; + + TRACE_MSG1(TTY,"tty: %x", tty); + + /* XXX Do we need to protect this + */ + RETURN_NULL_UNLESS(os_private); + + //TRACE_MSG2(TTY,"used: %d MOD_IN_USE: %d", atomic_read(&os_private->used), MOD_IN_USE); + + RETURN_NULL_UNLESS(acm_ready(function_instance)); + RETURN_NULL_UNLESS(atomic_read(&os_private->used)); + RETURN_NULL_UNLESS(tty); + + /* start sending from overflow buffer if necessary + */ + tty_l26_overflow_send(function_instance, chan, FALSE); + + if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) && tty->ldisc.write_wakeup) + (tty->ldisc.write_wakeup)(tty); + + wake_up_interruptible(&tty->write_wait); + return NULL; +} + +/*! + * @brief tty_l26_os_wakeup_opens + * + * Called by acm_fd to wakeup processes blocked in open waiting for DTR. + * + * @param function_instance + * @return none + */ +void tty_l26_os_wakeup_opens(struct usbd_function_instance *function_instance) +{ + struct acm_private *acm = function_instance ? function_instance->privdata : NULL; + int index = acm->index; + struct tty_struct *tty = tty_table[index]; + struct os_private *os_private = tty ? tty->driver_data : tty; + + TRACE_MSG1(TTY,"acm: %x", acm); + + /* XXX Do we need to protect this + */ + RETURN_UNLESS(os_private); + + //if (os_private->open_wait_count > 0) + wake_up_interruptible(&os_private->open_wait); +} + +/*! + * @brief tty_l26_os_schedule_wakeup_writers + * + * Called by acm-fd to schedule a wakeup writes bottom half handler. + * + * @param function_instance - pointer to acm private data structure + * @return none + */ +void tty_l26_os_schedule_wakeup_writers(struct usbd_function_instance *function_instance) +{ + struct acm_private *acm = function_instance ? function_instance->privdata : NULL; + int index = acm->index; + struct tty_struct *tty = tty_table[index]; + struct os_private *os_private = tty ? tty->driver_data : tty; + + TRACE_MSG1(TTY,"acm: %x", (int)acm); + + /* XXX Do we need to protect this + */ + RETURN_UNLESS(os_private); + + //tty_l26_schedule(tty, &os_private->wqueue); + otg_workitem_start(os_private->wakeup_workitem); + // verify ok + wake_up_interruptible(&os_private->send_wait); +} + +/*! + * @brief tty_l26_os_wakeup_state + * + * Called by acm_fd to wakeup processes blocked waiting for state change + * + * @param function_instance - pointer to acm private data structure + * @return none + */ +void tty_l26_os_wakeup_state(struct usbd_function_instance *function_instance) +{ + struct acm_private *acm = function_instance ? function_instance->privdata : NULL; + int index = acm->index; + struct tty_struct *tty = tty_table[index]; + struct os_private *os_private = tty ? tty->driver_data : tty; + + TRACE_MSG1(TTY,"acm: %x", (int)acm); + + /* XXX Do we need to protect this + */ + RETURN_UNLESS(os_private); + + TRACE_MSG0(TTY, "entered"); + wake_up_interruptible(&os_private->tiocm_wait); +} + + +/* ********************************************************************************************* */ + +/*! + * @brief tty_l26_os_recv_space_available + * + * Used by acm-fd to determine receive space available. This will determine + * how many receive urbs can be queued as there are never more receive urbs + * pending than there is currently room for data to be received. + * + * @param function_instance + * @param chan + * @return count - number of bytes available + */ +int tty_l26_os_recv_space_available(struct usbd_function_instance *function_instance, minor_chan_t chan) +{ + struct acm_private *acm = function_instance ? function_instance->privdata : NULL; + int index = acm->index; + struct tty_struct *tty = tty_table[index]; + struct os_private *os_private = tty ? tty->driver_data : NULL; + int room_len = 0; + int rc; + + //TRACE_MSG1(TTY,"acm: %x", (int)acm); + + RETURN_ZERO_UNLESS(tty); +#if 1 + //room_len=tty_buffer_request_room(tty, TTY_FLIPBUF_SIZE); + TRACE_MSG1(TTY,"OS space room:%d",room_len); + printk(KERN_INFO"%s: space room:%d, TTY_FLIPBUF_SIZE:%d\n", __FUNCTION__,room_len,TTY_FLIPBUF_SIZE); + return room_len; + +#else + +#if LINUX_VERSION_CODE < LINUX_KERNEL_VERSION_NO_FLIP_BUF + UNLESS (!tty->flip.count || test_bit(TTY_THROTTLED, &tty->flags) || test_bit(TTY_DONT_FLIP, &tty->flags)) { + TRACE_MSG2(TTY, "FLIPPING count: %d available: %d", tty->flip.count, TTY_FLIPBUF_SIZE - tty->flip.count); + tty_flip_buffer_push(tty); + } + TRACE_MSG7(TTY, "FLIP count: %d available: %d read_cnt: %d minimum_to_wake: %d %s %s %s", + tty->flip.count, + TTY_FLIPBUF_SIZE - tty->flip.count, + tty->read_cnt, tty->minimum_to_wake, + tty->flags & TTY_THROTTLED ? "THROTTLED" : "", + tty->flags & TTY_DONT_FLIP ? "DONT_FLIP" : "", + tty->real_raw ? "REAL RAW" : "" + ); + /* not only do we have to test the local flip buffer for space, but if + * we are in raw mode check the n_tty buffer availability. + * + * In n_tty_receive_buf(), when in raw mode, the data is saved into + * a 4kb wrap around ring buffer. So we must ensure that we never + * attempt push more data to that layer than will fit. + */ + return MIN( (tty->real_raw ? (N_TTY_BUF_SIZE - acm->readsize - tty->read_cnt) : TTY_FLIPBUF_SIZE ), + (TTY_FLIPBUF_SIZE - acm->readsize) - tty->flip.count); +#else /* LINUX_VERSION_CODE < LINUX_KERNEL_VERSION_NO_FLIP_BUF */ + UNLESS((TTY_FLIPBUF_SIZE != tty_buffer_request_room(tty, TTY_FLIPBUF_SIZE)) + || test_bit(TTY_THROTTLED, &tty->flags) + ) { + tty_flip_buffer_push(tty); + } + return MIN((tty->real_raw ? (N_TTY_BUF_SIZE - acm->readsize - tty->read_cnt) : TTY_FLIPBUF_SIZE), + (tty_buffer_request_room(tty, TTY_FLIPBUF_SIZE) - acm->readsize)); + +#endif /* LINUX_VERSION_CODE < LINUX_KERNEL_VERSION_NO_FLIP_BUF */ +#endif +} + + +/*! + * @brief tty_l26_os_recv_chars + * + * Called by acm-fd when data has been received. This will + * receive n bytes starting at cp. This will never be + * more than the last call to tty_l26_os_recv_space_available(). + * This will be called from interrupt context. + * + * @param function_instance + * @param n - number of bytes to send + * @param cp - pointer to data to send + * @return non-zero if error + */ +int tty_l26_os_recv_chars(struct usbd_function_instance *function_instance, u8 *cp, int n) +{ + struct acm_private *acm = function_instance ? function_instance->privdata : NULL; + int index = acm->index; + struct tty_struct *tty = tty_table[index]; + struct os_private *os_private = tty ? tty->driver_data : tty; + + int buf_size; + RETURN_EINVAL_UNLESS(tty); +#if 1 + otg_pthread_mutex_lock(&acm->mutex); + buf_size=tty_buffer_request_room(tty,n); + //printk(KERN_INFO"%s: USB got data and push to tty:%d, buf_size:%d\n", __FUNCTION__,n,buf_size); + tty_insert_flip_string(tty, cp, buf_size); + tty_flip_buffer_push(tty); + otg_pthread_mutex_unlock(&acm->mutex); + return 0; +#else + +#if LINUX_VERSION_CODE < LINUX_KERNEL_VERSION_NO_FLIP_BUF + if (TTY_FLIPBUF_SIZE < (tty->flip.count + n)) { + TRACE_MSG3(TTY, "TTY_FLIP_BUF_SIZE: %d count: %d n: %d", TTY_FLIPBUF_SIZE, tty->flip.count, n); + printk(KERN_INFO"%s: FLIP BUFFER OVERFLOW ERROR\n", __FUNCTION__); + return -EINVAL; + } +#endif /* LINUX_VERSION_CODE < LINUX_KERNEL_VERSION_NO_FLIP_BUF */ + +#if 1 + if (tty->real_raw) { + if ((N_TTY_BUF_SIZE - 1) < (tty->read_cnt + n)) { + TRACE_MSG3(TTY, "N_TTY_BUF_SIZE::%d, count: %d n: %d", N_TTY_BUF_SIZE, tty->read_cnt, n); + printk(KERN_INFO"%s: N_TTY_BUF_SIZE:%d, tty->read_cnt: %d just rcv n: %d\n", __FUNCTION__, N_TTY_BUF_SIZE, tty->read_cnt, n); + printk(KERN_INFO"%s: N_TTY BUFFER OVERFLOW ERROR\n", __FUNCTION__); + return -EINVAL; + } + + } +#endif + + otg_pthread_mutex_lock(&acm->mutex); + +#if LINUX_VERSION_CODE < LINUX_KERNEL_VERSION_NO_FLIP_BUF + TRACE_MSG5(TTY, "tty: %x cb: %x fb: %x count: %d buf: %d", + tty, tty->flip.char_buf_ptr, tty->flip.flag_buf_ptr, tty->flip.count, tty->flip.buf_num); +#endif /* LINUX_VERSION_CODE < LINUX_KERNEL_VERSION_NO_FLIP_BUF */ + + + TRACE_MSG2(TTY, " sos909 FLIP: %d buffer: %x", n, cp); + #if 1 + + { + + int i; + + for (i = 0; i < n; i += 8) { + TRACE_MSG8(TTY, "sos909 %02x %02x %02x %02x %02x %02x %02x %02x", + cp[i + 0], cp[i + 1], cp[i + 2], cp[i + 3], + cp[i + 4], cp[i + 5], cp[i + 6], cp[i + 7] + ); + } + + } + #else + + + int i; + char prompt[1028]; + for (i = 0; i < n; i++) sprintf((prompt+3*i),"%02X ",cp[i]); + //TRACE_MSG2(TTY,"808 Hardware to tty: num%d, %s\n",n,prompt); + + if (n>0) printk(KERN_INFO "808 tty<=USB: Num: %d|| %s\n\n",n,prompt); + #endif + +#if LINUX_VERSION_CODE < LINUX_KERNEL_VERSION_NO_FLIP_BUF + memcpy(tty->flip.char_buf_ptr, cp, n); + memset(tty->flip.flag_buf_ptr, TTY_NORMAL, n); + tty->flip.count += n; + tty->flip.char_buf_ptr += n; + tty->flip.flag_buf_ptr += n; + atomic_add(n, &acm->bytes_forwarded); + TRACE_MSG5(TTY, "tty: %x cb: %x fb: %x count: %d buf: %d", + tty, tty->flip.char_buf_ptr, tty->flip.flag_buf_ptr, tty->flip.count, tty->flip.buf_num); + +#else /* LINUX_VERSION_CODE < LINUX_KERNEL_VERSION_NO_FLIP_BUF */ + n = tty_insert_flip_string(tty, cp, n); +#endif /* LINUX_VERSION_CODE < LINUX_KERNEL_VERSION_NO_FLIP_BUF */ + tty_flip_buffer_push(tty); + //otg_led(LED1, FALSE); + otg_pthread_mutex_unlock(&acm->mutex); + return 0; +#endif + +} + + +/* ********************************************************************************************* */ + +/*! + * @brief tty_l26_recv_start + * + * Bottom half handler to restart acm recv queue... + * + * This is implemented here because the background scheduling may b3 OS + * dependant.. but it must call the common acm_start_recv_urbs() function to + * do the work. + * + * @param data + * @return none + */ +void * tty_l26_recv_start(void *data) +{ + struct tty_struct *tty = data; + int index = TTYINDEX(tty); + struct usbd_function_instance *function_instance = function_table[index]; + struct os_private *os_private = tty ? tty->driver_data : NULL; + + RETURN_NULL_UNLESS(function_instance); + + TRACE_MSG3(TTY,"tty: %x c_cflag: %02x CLOCAL: %d", tty, + tty->termios->c_cflag, + BOOLEAN(tty->termios->c_cflag & CLOCAL) + ); + + /* keep trying to start urbs until we have enough or -1 (not ready) + */ + while (!acm_start_recv_urbs(function_instance)) { + struct acm_private *acm = function_instance ? function_instance->privdata : NULL; + TRACE_MSG0(TTY, "SLEEP START"); + interruptible_sleep_on_timeout(&acm->recv_wait, 1); + TRACE_MSG0(TTY, "SLEEP FINISH"); + } + + TRACE_MSG0(TTY,"exited"); + return NULL; +} + +/*! + * @brief tty_l26_recv_start_bh + * + * Schedule hangup bottom half handler. + * + * @param function_instance + * @return none + + */ +void tty_l26_recv_start_bh(struct usbd_function_instance *function_instance) +{ + struct acm_private *acm = function_instance ? function_instance->privdata : NULL; + int index = acm->index; + struct tty_struct *tty = tty_table[index]; + struct os_private *os_private = tty ? tty->driver_data : tty; + + TRACE_MSG1(TTY,"acm: %x", (int)acm); + + RETURN_UNLESS(os_private); + + TRACE_MSG0(TTY, "entered"); + + otg_workitem_start(os_private->recv_workitem); +} + +void tty_l26_os_line_coding(struct usbd_function_instance *function_instance) +{ + struct acm_private *acm = function_instance ? function_instance->privdata : NULL; + int index = acm->index; + struct tty_struct *tty = tty_table[index]; + struct os_private *os_private = tty ? tty->driver_data : tty; + struct cdc_acm_line_coding *line_coding = &acm->line_coding; + + TRACE_MSG1(TTY,"acm: %x", (int)acm); + + RETURN_UNLESS(os_private); + + TRACE_MSG0(TTY, "entered"); + + os_private->serial_struct.custom_divisor = 1; + os_private->serial_struct.baud_base = line_coding->dwDTERate; + os_private->serial_struct.flags = line_coding->bCharFormat << 16 | + line_coding->bParityType << 8 | line_coding->bDataBits; +} + +/* ********************************************************************************************* */ +/*! + * @brief tty_l26_os_enable + * + * Called by acm-fd when the function driver is enabled. + * @param function_instance + * @return int + */ +int tty_l26_os_enable(struct usbd_function_instance *function_instance) +{ + struct acm_private *acm = function_instance ? function_instance->privdata : NULL; + int index; + + TRACE_MSG1(TTY,"acm: %x", acm); + // XXX MODULE LOCK HERE + + // XXX should do something based on wIndex... + for (index = 0; index < acm->minors; index++) { + struct tty_struct *tty; + struct os_private *os_private; + CONTINUE_IF (function_table[index]); + function_table[index] = function_instance; + acm->index = index; + tty = tty_table[index]; + os_private = tty ? tty->driver_data : NULL; + + TRACE_MSG1(TTY, "OS Flags: %x", os_private ? os_private->flags : 0); + + RETURN_ZERO_UNLESS(os_private && (os_private->flags & TTYFD_OPENED)); + acm->flags |= ACM_OPENED; + tty_l26_os_schedule_wakeup_writers(function_instance); + + return 0; + } + // XXX MODULE LOCK HERE + return -EINVAL; +} + +/*! + * @brief tty_l26_os_disable + * @param function_instance + * @return none + * + * Called by acm-fd when the function driver is disabled. + */ +void tty_l26_os_disable(struct usbd_function_instance *function_instance) +{ + struct acm_private *acm = function_instance ? function_instance->privdata : NULL; + int index = acm->index; + TRACE_MSG1(TTY,"acm: %x", acm); + function_table[index] = NULL; + // XXX MODULE UNLOCK HERE +} + +/*! \var struct acm_os_ops tty_l26_os_ops + * \brief acm OS operations table + * + */ + + +struct acm_os_ops tty_l26_os_ops = { + .enable = tty_l26_os_enable, + .disable = tty_l26_os_disable, + .wakeup_opens = tty_l26_os_wakeup_opens, + .wakeup_state = tty_l26_os_wakeup_state, + .schedule_wakeup_writers = tty_l26_os_schedule_wakeup_writers, + .recv_space_available = tty_l26_os_recv_space_available, + .recv_chars = tty_l26_os_recv_chars, + .schedule_hangup = tty_l26_os_schedule_hangup, + .recv_start_bh = tty_l26_recv_start_bh, + //.comm_feature = tty_l26_os_comm_feature, + .line_coding = tty_l26_os_line_coding, + //.control_state = tty_l26_os_control_state, +}; + +/* ************************************************************************** */ +/* ************************************************************************** */ + +/* USB Module init/exit ************************************************************************ */ + +/*! + * @brief tty_l26_init - module init + * + * This is called immediately after the module is loaded or during + * the kernel driver initialization if linked into the kernel. + * @param name + * @param minor_num + * @return int + */ +int tty_l26_init (char *name, int minor_num) +{ + int i; + + TRACE_MSG2(TTY, "name: %s minor_num: %d", name, minor_num); + + + THROW_UNLESS (tty_table = (struct tty_struct **) + CKMALLOC(sizeof(struct tty_struct *)* minor_num ), error) ; + + THROW_UNLESS (function_table = (struct usbd_function_instance **) + CKMALLOC(sizeof(struct usbd_function_instance *) * minor_num), error) ; + + tty_minor_count = minor_num; + +#if defined(LINUX26) + + THROW_UNLESS((tty_driver = alloc_tty_driver(minor_num)), error); + + /* update init_termios and register as tty driver + */ + tty_driver->owner = THIS_MODULE; + tty_driver->driver_name = name; + tty_driver->name = "acm"; + tty_driver->major = ACM_TTY_MAJOR; + tty_driver->minor_start = 0; + tty_driver->minor_num = minor_num; + tty_driver->type = TTY_DRIVER_TYPE_SERIAL; + tty_driver->subtype = SERIAL_TYPE_NORMAL; + + tty_driver->flags = TTY_DRIVER_REAL_RAW; + + #if defined(TTY_DRIVER_NO_DEVFS) + tty_driver->devfs_name = "tts/acm%d"; + tty_driver->flags |= TTY_DRIVER_NO_DEVFS; +// #warning "TTY_DRIVER_NO_DEVFS------------" + #endif /* defined(TTY_DRIVER_NO_DEVFS) */ + // XXX Probably not correct config name + #if defined(TTY_DRIVER_DYNAMIC_DEV) + tty_driver->flags |= TTY_DRIVER_DYNAMIC_DEV; +// #warning "TTY_DRIVER_DYNAMIC_DEV------------" + #endif /* defined(TTY_DRIVER_DYNAMIC_DEV) */ + tty_driver->init_termios = tty_std_termios; + tty_driver->init_termios.c_cflag = B115200 | CS8 | CREAD | HUPCL | CLOCAL; + + tty_set_operations(tty_driver, &tty_ops); + tty_driver->tiocmget= tty_l26_tiocmget; + tty_driver->tiocmset= tty_l26_tiocmset; + THROW_IF(tty_register_driver(tty_driver), error); + + for (i = 0; i < tty_minor_count; i++) + tty_register_device(tty_driver, i, NULL); + + + /* for (i = 0; i < minor_num; i++) + devfs_mk_cdev(MKDEV(ACM_TTY_MAJOR, i), S_IFCHR|S_IRUGO, (i & 1) ? "atcom/%d" : "acm/%d", i);*/ + +#else /* defined(LINUX26) */ + + /* update init_termios and register as tty driver + */ + tty_driver.magic = TTY_DRIVER_MAGIC; + tty_driver.driver_name = name; + tty_driver.name = "acm0"; + + tty_driver.major = ACM_TTY_MAJOR; + tty_driver.num = ACM_TTY_MINORS; + tty_driver.minor_start = 0; + tty_driver.type = TTY_DRIVER_TYPE_SERIAL; + tty_driver.subtype = SERIAL_TYPE_NORMAL; + tty_driver.flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_NO_DEVFS; + tty_driver.init_termios = tty_std_termios; + tty_driver.init_termios.c_cflag = B115200 | CS8 | CREAD | HUPCL | CLOCAL; + + THROW_IF(tty_register_driver(&tty_driver), error); + + + for (i = 0; i < minor_num; i++) + tty_register_devfs(&tty_driver, 0, i); + + TRACE_MSG0(TTY,"Register ok"); + +#endif /* defined(LINUX26) */ + + CATCH(error) { + printk(KERN_ERR"%s: ERROR\n", __FUNCTION__); + +#if defined(LINUX26) + put_tty_driver(tty_driver); +#endif /* defined(LINUX26) */ + + if (tty_table) LKFREE(tty_table); + if (function_table) LKFREE(function_table); + return -EINVAL; + } + return 0; +} + + +/*! + *@brief tty_l26_exit - module cleanup + * + * This is called prior to the module being unloaded. + */ +void tty_l26_exit (void) +{ + unsigned long flags; + int i; + //struct usbd_urb *urb; + + /* Wake up any pending opens after setting the exiting flag. */ + //tty_l26_private.exiting = 1; + //if (tty_l26_private.open_wait_count > 0) + //wake_up_interruptible(&tty_l26_private.open_wait); + + /* verify no tasks are running */ + //acm_wait_task(acm->function_instance, &acm->recv_tqueue); + //acm_wait_task(acm->function_instance, &tty_l26_private.wqueue); + //acm_wait_task(acm->function_instance, &tty_l26_private.hqueue); + + /* de-register as tty and usb drivers */ +#if defined(LINUX26) + + printk(KERN_INFO "tty_l26_exit!\n"); + for (i = 0; i < tty_minor_count; i++) + + tty_unregister_device(tty_driver, i); + + // XXX blk_run_queues(); + tty_unregister_driver(tty_driver); + + put_tty_driver(tty_driver); + + printk(KERN_INFO "after tty_unregister_driver!\n"); +#else + run_task_queue(&tq_timer); + tty_unregister_driver(&tty_driver); +#endif + + if (tty_table) LKFREE(tty_table); + if (function_table) LKFREE(function_table); + printk(KERN_INFO "tty_l26_exit!\n"); + + +} diff --git a/drivers/otg/functions/acm/tty.h b/drivers/otg/functions/acm/tty.h new file mode 100644 index 000000000000..936e597b9bf8 --- /dev/null +++ b/drivers/otg/functions/acm/tty.h @@ -0,0 +1,162 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/functions/acm/tty.h + * @(#) tt/root@belcarra.com/debian286.bbb|otg/functions/acm/tty.h|20070911235625|36999 + * + * Copyright (c) 2003-2005 Belcarra + * Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + * + * By: + * Stuart Lynne <sl@belcarra.com>, + * Bruce Balden <balden@belcarra.com> + * + */ +/*! + * @file otg/functions/acm/tty.h + * @brief ACM Function Driver private defines + * + * + * This is an ACM Function Driver. The upper edge is exposed + * to the hosting OS as a Posix type character device. The lower + * edge implements the USB Device Stack API. + * + * These are emulated and set by the tty driver as appropriate + * to model a virutal serial port. The + * + * TIOCM_RNG RNG (Ring) not used + * TIOCM_LE DSR (Data Set Ready / Line Enable) + * TIOCM_DSR DSR (Data Set Ready) + * TIOCM_CAR DCD (Data Carrier Detect) + * TIOCM_CTS CTS (Clear to Send) + * TIOCM_CD TIOCM_CAR + * TIOCM_RI TIOCM_RNG + * + * These are set by the application: + * + * TIOCM_DTR DTR (Data Terminal Ready) + * TIOCM_RTS RTS (Request to Send) + * + * TIOCM_LOOP Set into loopback mode + * TIOCM_OUT1 Not used. + * TIOCM_OUT2 Not used. + * + * + * The following File and termio c_cflags are used: + * + * O_NONBLOCK don't block in tty_open() + * O_EXCL don't allow more than one open + * + * CLOCAL ignore DTR status + * CBAUD send break if set to B0 + * + * Virtual NULL Modem + * + * Peripheral to Host via Serial State Notification (write ioctls) + * + * Application TIOCM Null Modem ACM (DCE) Notificaiton Host (DTE) + * ------------------------------------------------------------------------------------------------------------- + * Request to send TIOCM_RTS RTS -> CTS CTS Not Available Clear to Send (N/A) + * Data Terminal Ready TIOCM_DTR DTR -> DSR DSR bTxCarrier Data Set Ready + * DTR -> DCD DCD bRXCarrier Carrier Detect + * Ring Indicator TIOCM_OUT1 OUT1 -> RI RI bRingSignal Ring Indicator + * Overrun TIOCM_OUT2 OUT2 -> Overrun Overrun bOverrun Overrun + * Send Break B0 B0 -> Break Break bBreak Break Received + * ------------------------------------------------------------------------------------------------------------- + * + * + * Host to Peripheral via Set Control Line State + * + * Host (DTE) Line State ACM (DCE) Null Modem TIOCM Peripheral (DTE) + * ------------------------------------------------------------------------------------------------------------- + * Data Terminal Ready D0 DTR DTR -> DSR TIOCM_DSR Data Set Ready + * DTR -> DCD TIOCM_CAR Carrier Detect + * Request To Send D1 RTS RTS -> CTS TIOCM_CTS Clear to Send + * ------------------------------------------------------------------------------------------------------------- + * + * + * @ingroup TTYFunction + * @ingroup LINUXAPI + */ + +extern struct usbd_function_driver tty_function_driver; + + +#define TTYFD_OPENED (1 << 0) /*! OPENED flag */ +#define TTYFD_CLOCAL (1 << 1) /*! CLOCAL flag */ +//#define TTYFD_LOOPBACK (1 << 2) /*! LOOPBACK flag */ +#define TTYFD_EXCLUSIVE (1 << 3) /*! EXCLUSIVE flag */ +#define TTYFD_THROTTLED (1 << 4) /*! THROTTLED flag */ + +#define TTY_OVERFLOW_SIZE 4096 + +/*! @struct os_private tty.h "otg/functions/acm/tty.h" + */ +struct os_private { + + //struct tty_driver *tty_driver; /*!< tty structure */ + int tty_driver_registered; /*!< non-zero if tty_driver registered */ + int usb_driver_registered; /*!< non-zero if usb function registered */ + + int index; + + struct otg_workitem *wakeup_workitem; + struct otg_workitem *hangup_workitem; + u32 flags; /*!< flags */ + + u32 tiocm; /*!< tiocm settings */ + + struct serial_struct serial_struct; /*!< serial structure used for TIOCSSERIAL and TIOCGSERIAL */ + + wait_queue_head_t tiocm_wait; + u32 tiocgicount; + + //u32 c_cflag; + + wait_queue_head_t open_wait; /*! wait queue for blocking open*/ + int exiting; /*! True if module exiting */ + + wait_queue_head_t send_wait; /*! wait queue for blocking open*/ + int sending; + + struct otg_workitem *recv_workitem; + atomic_t used; + + u8 tty_overflow_buffer[TTY_OVERFLOW_SIZE]; + atomic_t tty_overflow_used; + int minor_num; + + //u8 tty_inflow_buffer[TTY_FLIPBUF_SIZE]; + //int tty_inflow_used; +}; + +/*! @name modules initialization and unload functions */ + +//@{ +// +int tty_l26_init(char *, int); +void tty_l26_exit(void); + +int tty_if_init(void); +void tty_if_exit(void); + +int dun_if_init(void); +void dun_if_exit(void); + +int atcom_if_init(void); +void atcom_if_exit(void); + +int obex_if_init(void); +void obex_if_exit(void); + +//@} diff --git a/drivers/otg/functions/generic/Kconfig b/drivers/otg/functions/generic/Kconfig new file mode 100644 index 000000000000..f554be45e056 --- /dev/null +++ b/drivers/otg/functions/generic/Kconfig @@ -0,0 +1,347 @@ +# @(#) sp@belcarra.com|otg/functions/generic/Kconfig|20070417200305|49656 +menu "OTG Generic function" + depends on OTG + +config OTG_GENERIC + tristate "Generic Function" + depends on OTG + default OTG + ---help--- + This acts as a generic function, allowing selection of class + and interface function drivers to create a specific configuration. + +menu "OTG Generic Composite function options" + depends on OTG && OTG_GENERIC + +config OTG_GENERIC_VENDORID + hex "VendorID (hex value)" + depends on OTG && OTG_GENERIC + default "0x15ec" + +config OTG_GENERIC_PRODUCTID + hex "ProductID (hex value)" + depends on OTG && OTG_GENERIC + default "0xf010" + +config OTG_GENERIC_BCDDEVICE + hex "bcdDevice (binary-coded decimal)" + depends on OTG && OTG_GENERIC + default "0x0100" + +config OTG_GENERIC_MANUFACTURER + string "iManufacturer (string)" + depends on OTG && OTG_GENERIC + default "Belcarra" + +config OTG_GENERIC_PRODUCT_NAME + string "iProduct (string)" + depends on OTG && OTG_GENERIC + default "Generic Composite" + +choice + depends on OTG && OTG_GENERIC + prompt "Default Composite Driver Configuration" + + config OTG_GENERIC_CONFIG_NONE + bool 'none' + help + No pre-defined selection, use the OTG_GENERIC_CONFIG_NAME instead. + + config OTG_GENERIC_CONFIG_MOUSE + bool 'mouse' + depends on OTG_MOUSE + help + Configure the mouse driver in a single function non-composite configuration. + + config OTG_GENERIC_CONFIG_NET_EEM + bool 'cdc-eem' + depends on OTG_NETWORK_EEM + help + Configure the network driver as a single function CDC EEM non-composite device + + config OTG_GENERIC_CONFIG_NET_BLAN + bool 'net-blan' + depends on OTG_NETWORK_BLAN + help + Configure the network driver in a single function non-composite BLAN configuration. + + + config OTG_GENERIC_CONFIG_NET_CDC + bool 'net-cdc' + depends on OTG_NETWORK_ECM + help + Configure the network driver in a single function non-composite CDC configuration. + + config OTG_GENERIC_CONFIG_NET_SAFE + bool 'net-safe' + depends on OTG_NETWORK_SAFE + help + Configure the network driver in a single function non-composite SAFE configuration. + + + config OTG_GENERIC_CONFIG_ACM_TTY + bool 'acm-tty' + depends on OTG_ACM + help + Configure the acm driver in a single function non-composite TTY configuration. + + + config OTG_GENERIC_CONFIG_MSC + bool 'msc' + depends on OTG_MSC + help + Configure the msc driver in a single function non-composite configuration. + + config OTG_GENERIC_CONFIG_HID2_NON_IAD + bool 'hid2' + depends on OTG_MOUSE + help + Configure a non-IAD composite device with two HID interfaces. + + config OTG_GENERIC_CONFIG_HID2 + bool 'hid2-iad' + depends on OTG_MOUSE + help + Configure an IAD composite device with two HID interfaces. + + config OTG_GENERIC_CONFIG_HID_MSC + bool 'msc-hid' + depends on OTG_MSC && OTG_MOUSE + help + Configure a non-IAD composite device with mass storage and HID interface. + + +endchoice + + #config OTG_GENERIC_CONFIG_NAME + # string "Composite Configuration (string)" + # depends on OTG && OTG_GENERIC && OTG_GENERIC_CONFIG_NONE + # default "" + # ---help--- + # Name of predefined configuration to be enabled, note that + # if this is not defined (empty string) then the first available + # configuration will be used (assuming that the required interface + # function drivers are available. + + #config OTG_NETWORK_VENDORID + # hex "Network VendorID (hex value)" + # depends on OTG && OTG_NETWORK + # default "0x15ec" + # ---help--- + # The USB Peripheral Vendor ID. This should be set to your assigned + # USB Vendor ID (www.usb.org). The USB Host will use this and the + # Product ID to find and load an appropriate device driver. + + #comment -- + #comment "USBOTG Network Device Product ID" + + ######################################################################################################3 + # + # Product ID's for generic cf, non-composite, these are assigned to f00N + # + + #config OTG_NETWORK_PRODUCT_NAME + # string "iProduct (string)" + # depends on OTG && OTG_NETWORK + # default "Belcarra Network Device" + # ---help--- + # This will be used as the iProduct string descriptor. + + config OTG_GENERIC_PRODUCTID_CDCEEM + hex "Generic CDC-EEM ProductID (hex value)" + depends on OTG && OTG_NETWORK + default "0xf001" + ---help--- + The USB Peripheral Product ID. This should be set to your a + unique value for this product. The USB Host will use this and the + Vendor ID to find and load an appropriate device driver. + + config OTG_GENERIC_PRODUCTID_SERIAL + hex "Generic Serial Port CDC-ACM ProductID (hex value)" + depends on OTG && OTG_ACM + default "0xf002" + ---help--- + The USB Peripheral Product ID. This should be set to your a + unique value for this product. The USB Host will use this and the + Vendor ID to find and load an appropriate device driver. + + config OTG_GENERIC_PRODUCTID_HID + hex "Generic HID ProductID (hex value)" + depends on OTG && OTG_MOUSE + default "0xf003" + ---help--- + The USB Peripheral Product ID. This should be set to your a + unique value for this product. The USB Host will use this and the + Vendor ID to find and load an appropriate device driver. + + config OTG_GENERIC_PRODUCTID_MODEM + hex "Generic Modem CDC-ACM ProductID (hex value)" + depends on OTG && OTG_ACM + default "0xf004" + ---help--- + The USB Peripheral Product ID. This should be set to your a + unique value for this product. The USB Host will use this and the + Vendor ID to find and load an appropriate device driver. + + config OTG_GENERIC_PRODUCTID_CDCECM + hex "Generic CDC-ECM ProductID (hex value)" + depends on OTG && OTG_NETWORK + default "0xf005" + ---help--- + The USB Peripheral Product ID. This should be set to your a + unique value for this product. The USB Host will use this and the + Vendor ID to find and load an appropriate device driver. + + config OTG_GENERIC_PRODUCTID_MASS + hex "Generic MASS ProductID (hex value)" + depends on OTG && OTG_MSC + default "0xf006" + ---help--- + The USB Peripheral Product ID. This should be set to your a + unique value for this product. The USB Host will use this and the + Vendor ID to find and load an appropriate device driver. + #config OTG_GENERIC_PRODUCTID_COMPOSITE + # hex "Composite ProductID (hex value)" + # depends on OTG + # default "0xf008" + # ---help--- + # The USB Peripheral Product ID. This should be set to your a + # unique value for this product. The USB Host will use this and the + # Vendor ID to find and load an appropriate device driver. + + #####################################################################################################3 + # + # Composite ID's are assigned to + # + # c00N + # + + # c000 + config OTG_GENERIC_PRODUCTID_HID2_NON_IAD + hex "HID2 Composite ProductID (hex value)" + depends on OTG && OTG_MOUSE + default "0xc000" + ---help--- + + hid2_non_iad implements a two interface composite device using + with two mouse interfaces. + + This configuration does not use IAD (Interface Association Descriptors. + It will work with Win2k and newer versions of Windows. But + has been deprecated by the introduction of IAD. IAD support + is available in WinXP and newer versions of Windows. + + The USB Peripheral Product ID for the generic hid2 configuration. + This should be set to your a unique value for this product. + The USB Host will use this and the Vendor ID to find and + load an appropriate device driver. + + config OTG_GENERIC_PRODUCTID_HID2 + hex "HID2-IAD Composite ProductID (hex value)" + depends on OTG && OTG_MOUSE + default "0xc001" + ---help--- + hid2 implements a two interface composite device using + with two mouse interfaces. + + This configuration uses IAD (Interface Association Descriptors.) + It will work with WinXP and newer verions of Windows. + + The USB Peripheral Product ID for the hid2 iad + configuration. This should be set to your a unique value + for this product. The USB Host will use this and the Vendor + ID to find and load an appropriate device driver. + + # c002 + config OTG_GENERIC_PRODUCTID_HID_MSC + hex "MSC-HID Composite ProductID (hex value)" + depends on OTG && OTG_MOUSE && OTG_MSC + default "0xc002" + ---help--- + hid_msc implements a two interface composite device with + the mass storage and mouse interfaces. + + This configuration uses IAD (Interface Association Descriptors.) + It will work with WinXP and newer verions of Windows. + + The USB Peripheral Product ID for the generic msc-hid + configuration. This should be set to your a unique value for + this product. The USB Host will use this and the Vendor ID + to find and load an appropriate device driver. + + + # c003 + config OTG_GENERIC_PRODUCTID_HID_BLAN + hex "HID-BLAN Composite ProductID (hex value)" + depends on OTG && OTG_MOUSE && OTG_NETWORK_BLAN + default "0xc003" + ---help--- + hid_blan implements a two interface composite device with + a mouse and blan network interface. + + This configuration uses IAD (Interface Association Descriptors.) + + The USB Peripheral Product ID for the hid-blan + configuration. This should be set to your a unique value for + this product. The USB Host will use this and the Vendor ID + to find and load an appropriate device driver. + + + # c004 + config OTG_GENERIC_PRODUCTID_HID_ECM + hex "HID-ECM Composite ProductID (hex value)" + depends on OTG && OTG_MOUSE && OTG_NETWORK_ECM + default "0xc004" + ---help--- + hid_ecm implements a two interface composite device with + a mouse and cdc-ecm network interface. + + This configuration uses IAD (Interface Association Descriptors.) + + The USB Peripheral Product ID for the hid-ecm + configuration. This should be set to your a unique value for + this product. The USB Host will use this and the Vendor ID + to find and load an appropriate device driver. + + + # c005/c006 serial/modem + config OTG_GENERIC_PRODUCTID_HID_SERIAL + hex "HID-SERIAL Composite ProductID (hex value)" + depends on OTG && OTG_MOUSE && OTG_ACM + default "0xc005" + ---help--- + hid_serial implements a two interface composite device with + a mouse and cdc-acm serial interface. + + This configuration uses IAD (Interface Association Descriptors.) + + The USB Peripheral Product ID for the hid-serial + configuration. This should be set to your a unique value for + this product. The USB Host will use this and the Vendor ID + to find and load an appropriate device driver. + + config OTG_GENERIC_PRODUCTID_HID_MODEM + hex "HID-MODEM Composite ProductID (hex value)" + depends on OTG && OTG_MOUSE && OTG_ACM + default "0xc006" + ---help--- + hid_serial implements a two interface composite device with + a mouse and cdc-acm modem interface. + + This configuration uses IAD (Interface Association Descriptors.) + + The USB Peripheral Product ID for the hid-modem + configuration. This should be set to your a unique value for + this product. The USB Host will use this and the Vendor ID + to find and load an appropriate device driver. + + + #config OTG_NETWORK_BCDDEVICE + # hex "Network bcdDevice (binary-coded decimal)" + # depends on OTG && OTG_NETWORK + # default "0x0100" + + +endmenu + +endmenu diff --git a/drivers/otg/functions/generic/Makefile b/drivers/otg/functions/generic/Makefile new file mode 100644 index 000000000000..417ac2653689 --- /dev/null +++ b/drivers/otg/functions/generic/Makefile @@ -0,0 +1,26 @@ +# Generic Composite Driver +# +# @(#) balden@belcarra.com|otg/functions/generic/Makefile-l26|20060419204257|43174 +# Copyright (c) 2004-2005 Belcarra Technologies Corp +# Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + +ifeq ($(CONFIG_OTG_GENERIC),m) +generic_cf-objs := generic-cf.o generic.o +else +generic_cf-objs := generic-cf.o +obj-y += generic.o +endif + +extra-y := generic.o + + +generic_cl-objs := generic-cl.o + +obj-$(CONFIG_OTG_GENERIC) += generic_cf.o +obj-$(CONFIG_OTG_GENERIC) += generic_cl.o + + +OTG=$(TOPDIR)/drivers/otg +OTGCORE_DIR=$(OTG)/otgcore +EXTRA_CFLAGS += -I$(OTG) -Wno-unused -Wno-format -I$(OTGCORE_DIR) +EXTRA_CFLAGS_nostdinc += -I$(OTG) -Wno-unused -Wno-format -I$(OTGCORE_DIR) diff --git a/drivers/otg/functions/generic/generic-cf.c b/drivers/otg/functions/generic/generic-cf.c new file mode 100644 index 000000000000..e168d011120b --- /dev/null +++ b/drivers/otg/functions/generic/generic-cf.c @@ -0,0 +1,601 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/functions/generic/generic-cf.c + * @(#) sl@belcarra.com/whiskey.enposte.net|otg/functions/generic/generic-cf.c|20070425221028|63290 + * + * Copyright (c) 2003-2005 Belcarra + * Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + * + * By: + * Stuart Lynne <sl@belcarra.com>, + * Bruce Balden <balden@belcarra.com> + * + */ + +/*! + * @file otg/functions/generic/generic-cf.c + * @brief Generic Configuration Function Driver + * + * This implements a generic composite function + * + * - idVendor 16 bit word + * - idProduct 16 bit word + * - bcd_device 16 bit word + * + * - bDeviceClass byte + * - device_sub-class byte + * - bcdDevice byte + * + * - vendor string + * - iManufacturer string + * - product string + * - serial string + * + * - power byte + * - remote_wakeup bit + * + * - functions string + * + * + * The functions string would contain a list of names, the first would + * be the composite function driver, the rest would be the interface + * functions. For example: + * + * + * "cmouse-cf mouse-if" + * "mcpc-cf dun-if dial-if obex-if dlog-if" + * + * There are also a set of pre-defined configurations that + * can be loaded singly or in toto. + * + * @ingroup GenericFunction + */ + +#include <otg/otg-compat.h> +#include <otg/otg-module.h> + +#include <otg/usbp-chap9.h> +#include <otg/usbp-func.h> +#include <otg/usbp-cdc.h> +#include <otg/otg-trace.h> +#include <otg/otg-api.h> +#include <otg/otg-utils.h> + +#include "generic.h" +//#include "generic.c" + +/* Module Parameters ******************************************************** */ +/* ! + * @name XXXXX MODULE Parameters + */ +/* ! @{ */ + + +MOD_AUTHOR ("sl@belcarra.com"); +EMBED_LICENSE(); +MOD_DESCRIPTION ("Generic Composite Function"); + + +MOD_PARM_STR (driver_name, "Driver Name", NULL); + +MOD_PARM_INT(idVendor, "Device Descriptor idVendor field", 0); +MOD_PARM_INT(idProduct, "Device Descriptor idProduct field", 0); +MOD_PARM_INT(bcddevice, "Device Descriptor bcdDevice field", 0); + +MOD_PARM_INT (bDeviceClass, "Device Descriptor bDeviceClass field", 0); +MOD_PARM_INT (bDeviceSubClass, "Device Descriptor bDeviceSubClass field", 0); +MOD_PARM_INT (bDeviceProtocol, "Device Descriptor bDeviceProtocol field", 0); +MOD_PARM_INT (bcdDevice, "Device Descriptor bcdDevice field", 0); + +MOD_PARM_STR (iConfiguration, "Configuration Descriptor iConfiguration field", NULL); +MOD_PARM_STR (iManufacturer, "Device Descriptor iManufacturer field", NULL); +MOD_PARM_STR (iProduct, "Device Descriptor iProduct field", NULL); +MOD_PARM_STR (iSerialNumber, "Device Descriptor iSerialNumber field", NULL); + +MOD_PARM_STR (class_name, "Class Function Name", NULL); +MOD_PARM_STR (interface_names, "Interface Function Names List", NULL); + +MOD_PARM_STR (config_name, "Pre-defined Configuration", NULL); +MOD_PARM_BOOL (load_all, "Load all pre-defined Configurations", 1); +MOD_PARM_BOOL (override, "Override idVendor and idProduct", 0); + +/* Setup the pre-defined configuration name from configuration + * option if available. + * + * N.B. This list needs to be synchronized with both pre-defined + * configurations (see below) and the Kconfig list + */ +char *generic_predefined_configuration = ""; + + +/* ! *} */ + + +/* Setup the pre-defined configuration name from configuration + * option if available. + * + * N.B. This list needs to be synchronized with both pre-defined + * configurations (see below) and the Kconfig list + */ +#ifdef CONFIG_OTG_GENERIC_CONFIG_MOUSE +static char default_predefined_configuration[] = "hid"; +#elif defined(CONFIG_OTG_GENERIC_CONFIG_NET_BLAN) +static char default_predefined_configuration[] = "mdlm-blan"; +#elif defined(CONFIG_OTG_GENERIC_CONFIG_NET_SAFE) +static char default_predefined_configuration[] = "mdlm-safe"; +#elif defined(CONFIG_OTG_GENERIC_CONFIG_NET_CDC) +static char default_predefined_configuration[] = "cdc-ecm"; +#elif defined(CONFIG_OTG_GENERIC_CONFIG_NET_EEM) +static char default_predefined_configuration[] = "cdc-eem"; +#elif defined(CONFIG_OTG_GENERIC_CONFIG_ACM_TTY) +static char default_predefined_configuration[] = "serial"; +#elif defined(CONFIG_OTG_GENERIC_CONFIG_MSC) +static char default_predefined_configuration[] = "mass"; +#elif defined(CONFIG_OTG_GENERIC_CONFIG_HID2) +static char default_predefined_configuration[] = "hid2"; +#elif defined(CONFIG_OTG_GENERIC_CONFIG_HID2_NON_IAD) +static char default_predefined_configuration[] = "hid2-non_iad"; +#elif defined(CONFIG_OTG_GENERIC_CONFIG_MSC_HID) +static char default_predefined_configuration[] = "msc-hid"; +#else +static char default_predefined_configuration[] = ""; +#endif + +static char *generic_config_name(void){ + if(MODPARM(config_name) && strlen(MODPARM(config_name))) return MODPARM(config_name); + printk(KERN_INFO"%s: %s\n", __FUNCTION__, default_predefined_configuration); + return default_predefined_configuration; +} + +static int preset_config_name(void){ + return (strlen(generic_config_name()) > 0); +} + + +/* Pre-defined configurations *********************************************** */ + +static struct generic_config generic_cf_configs[] = { + #if defined(CONFIG_OTG_ACM) || defined(CONFIG_OTG_ACM_MODULE) || defined(_OTG_DOXYGEN) + { + .composite_driver = { .driver = { .name = "serial", }, }, + .interface_names = "tty-if", + .configuration_description = { + .iConfiguration = "acm-tty", + }, + .device_description = { + .bDeviceClass = COMMUNICATIONS_DEVICE_CLASS, + .bDeviceSubClass = 2, + .bDeviceProtocol = 0, + .idVendor = __constant_cpu_to_le16(CONFIG_OTG_GENERIC_VENDORID), + .idProduct = __constant_cpu_to_le16(CONFIG_OTG_GENERIC_PRODUCTID_SERIAL), + .bcdDevice = __constant_cpu_to_le16(CONFIG_OTG_GENERIC_BCDDEVICE), + .iManufacturer = CONFIG_OTG_GENERIC_MANUFACTURER, + .iProduct = CONFIG_OTG_GENERIC_PRODUCT_NAME " Serial", }, + + }, + { + .composite_driver = { .driver = { .name = "modem", }, }, + .interface_names = "tty-if", + .configuration_description = { + .iConfiguration = "acm-tty", + }, + .device_description = { + .bDeviceClass = COMMUNICATIONS_DEVICE_CLASS, + .bDeviceSubClass = 2, + .bDeviceProtocol = 0, + .idVendor = __constant_cpu_to_le16(CONFIG_OTG_GENERIC_VENDORID), + .idProduct = __constant_cpu_to_le16(CONFIG_OTG_GENERIC_PRODUCTID_MODEM), + .bcdDevice = __constant_cpu_to_le16(CONFIG_OTG_GENERIC_BCDDEVICE), + .iManufacturer = CONFIG_OTG_GENERIC_MANUFACTURER, + .iProduct = CONFIG_OTG_GENERIC_PRODUCT_NAME " Serial", }, + + }, + #endif /* defined(CONFIG_OTG_ACM) */ + #if defined(CONFIG_OTG_NETWORK_EEM) || defined(_OTG_DOXYGEN) + { + .composite_driver = { .driver = { .name = "cdc-eem", }, }, + .interface_names = "cdc-eem-if", + .configuration_description = { + .iConfiguration = "cdc-eem", + }, + .device_description = { + .bDeviceClass = COMMUNICATIONS_DEVICE_CLASS, + .bDeviceSubClass = COMMUNICATIONS_EEM_SUBCLASS, + .bDeviceProtocol = COMMUNICATIONS_EEM_PROTOCOL, + .idVendor = __constant_cpu_to_le16(CONFIG_OTG_GENERIC_VENDORID), + .idProduct = __constant_cpu_to_le16(CONFIG_OTG_GENERIC_PRODUCTID_CDCEEM), + .bcdDevice = __constant_cpu_to_le16(CONFIG_OTG_GENERIC_BCDDEVICE), + .iManufacturer = CONFIG_OTG_GENERIC_MANUFACTURER, + .iProduct = CONFIG_OTG_GENERIC_PRODUCT_NAME " CDC-EEM", } + }, + #endif /* defined(CONFIG_OTG_NETWORK_EEM) */ + #if defined(CONFIG_OTG_NETWORK_ECM) || defined(_OTG_DOXYGEN) + { + .composite_driver = { .driver = { .name = "cdc-ecm", }, }, + .interface_names = "cdc-ecm-if", + .configuration_description = { + .iConfiguration = "cdc-ecm", + }, + .device_description = { + .bDeviceClass = COMMUNICATIONS_DEVICE_CLASS, + .bDeviceSubClass = 2, + .bDeviceProtocol = 0, + .idVendor = __constant_cpu_to_le16(CONFIG_OTG_GENERIC_VENDORID), + .idProduct = __constant_cpu_to_le16(CONFIG_OTG_GENERIC_PRODUCTID_CDCECM), + .bcdDevice = __constant_cpu_to_le16(CONFIG_OTG_GENERIC_BCDDEVICE), + .iManufacturer = CONFIG_OTG_GENERIC_MANUFACTURER, + .iProduct = CONFIG_OTG_GENERIC_PRODUCT_NAME " CDC-ECM", }, + }, + #endif /* defined(CONFIG_OTG_NETWORK_CDC) */ + #if defined(CONFIG_OTG_MOUSE) || defined(CONFIG_OTG_MOUSE_MODULE) || defined(_OTG_DOXYGEN) + { + .composite_driver = { .driver = { .name = "hid", }, }, + .interface_names = "mouse-if", + .configuration_description = { + .iConfiguration = "mouse", + }, + .device_description = { + .bDeviceClass = 0, + .bDeviceSubClass = 0, + .bDeviceProtocol = 0, + .idVendor = __constant_cpu_to_le16(CONFIG_OTG_GENERIC_VENDORID), + .idProduct = __constant_cpu_to_le16(CONFIG_OTG_GENERIC_PRODUCTID_HID), + .bcdDevice = __constant_cpu_to_le16(CONFIG_OTG_GENERIC_BCDDEVICE), + .iManufacturer = CONFIG_OTG_GENERIC_MANUFACTURER, + .iProduct = CONFIG_OTG_GENERIC_PRODUCT_NAME " Mouse", }, + }, + #endif /* defined(CONFIG_OTG_MOUSE) */ + #if defined(CONFIG_OTG_MSC) || defined(CONFIG_OTG_MSC_MODULE) || defined(_OTG_DOXYGEN) + { + .composite_driver = { .driver = { .name = "mass", }, }, + .interface_names = "msc-if", + .configuration_description = { + .iConfiguration = "msc", + }, + .device_description = { + .bDeviceClass = 0, + .bDeviceSubClass = 0, + .bDeviceProtocol = 0, + .idVendor = __constant_cpu_to_le16(CONFIG_OTG_GENERIC_VENDORID), + .idProduct = __constant_cpu_to_le16(CONFIG_OTG_GENERIC_PRODUCTID_MASS), + .bcdDevice = __constant_cpu_to_le16(CONFIG_OTG_GENERIC_BCDDEVICE), + .iManufacturer = CONFIG_OTG_GENERIC_MANUFACTURER, + .iProduct = CONFIG_OTG_GENERIC_PRODUCT_NAME " Mass Storage", }, + }, + #endif /* defined(CONFIG_OTG_MSC) */ + + /* deprecated - use cdc-eem */ + #if defined(CONFIG_OTG_NETWORK_BLAN) || defined(_OTG_DOXYGEN) + { + .composite_driver = { .driver = { .name = "network", }, }, + .interface_names = "net-blan-if", + .configuration_description = { + .iConfiguration = "net-blan", + }, + .device_description = { + .bDeviceClass = COMMUNICATIONS_DEVICE_CLASS, + .bDeviceSubClass = 2, + .bDeviceProtocol = 0, + .idVendor = __constant_cpu_to_le16(CONFIG_OTG_GENERIC_VENDORID), + .idProduct = __constant_cpu_to_le16(CONFIG_OTG_GENERIC_PRODUCTID_CDCEEM), + .bcdDevice = __constant_cpu_to_le16(CONFIG_OTG_GENERIC_BCDDEVICE), + .iManufacturer = CONFIG_OTG_GENERIC_MANUFACTURER, + .iProduct = CONFIG_OTG_GENERIC_PRODUCT_NAME " MDLM-BLAN", }, + }, + { + .composite_driver = { .driver = { .name = "mdlm-blan", }, }, + .interface_names = "net-blan-if", + .configuration_description = { + .iConfiguration = "net-blan", + }, + .device_description = { + .bDeviceClass = COMMUNICATIONS_DEVICE_CLASS, + .bDeviceSubClass = 2, + .bDeviceProtocol = 0, + .idVendor = __constant_cpu_to_le16(CONFIG_OTG_GENERIC_VENDORID), + .idProduct = __constant_cpu_to_le16(CONFIG_OTG_GENERIC_PRODUCTID_CDCEEM), + .bcdDevice = __constant_cpu_to_le16(CONFIG_OTG_GENERIC_BCDDEVICE), + .iManufacturer = CONFIG_OTG_GENERIC_MANUFACTURER, + .iProduct = CONFIG_OTG_GENERIC_PRODUCT_NAME " MDLM-BLAN", }, + }, + #endif /* defined(CONFIG_OTG_NETWORK_BLAN) */ + + #if defined(CONFIG_OTG_NETWORK_SAFE) || defined(_OTG_DOXYGEN) + { + .composite_driver = { .driver = { .name = "mdlm-safe", }, }, + .interface_names = "net-safe-if", + .configuration_description = { + .iConfiguration = "net-safe", + }, + .device_description = { + .bDeviceClass = COMMUNICATIONS_DEVICE_CLASS, + .bDeviceSubClass = 2, + .bDeviceProtocol = 0, + .idVendor = __constant_cpu_to_le16(CONFIG_OTG_GENERIC_VENDORID), + .idProduct = __constant_cpu_to_le16(CONFIG_OTG_GENERIC_PRODUCTID_CDCEEM), + .bcdDevice = __constant_cpu_to_le16(CONFIG_OTG_GENERIC_BCDDEVICE), + .iManufacturer = CONFIG_OTG_GENERIC_MANUFACTURER, + .iProduct = CONFIG_OTG_GENERIC_PRODUCT_NAME " MDLM-SAFE", }, + }, + #endif /* defined(CONFIG_OTG_NETWORK_SAFE) */ + + #if defined(CONFIG_OTG_NETWORK_BASIC) || defined(_OTG_DOXYGEN) + { + .composite_driver = { .driver = { .name = "net-basic", }, }, + .interface_names = "net-basic-if", + .configuration_description = { + .iConfiguration = "net-basic", + }, + .device_description = { + .bDeviceClass = COMMUNICATIONS_DEVICE_CLASS, + .bDeviceSubClass = 2, + .bDeviceProtocol = 0, + .idVendor = __constant_cpu_to_le16(CONFIG_OTG_GENERIC_VENDORID), + .idProduct = __constant_cpu_to_le16(CONFIG_OTG_GENERIC_PRODUCTID_CDCEEM), + .bcdDevice = __constant_cpu_to_le16(CONFIG_OTG_GENERIC_BCDDEVICE), + .iManufacturer = CONFIG_OTG_GENERIC_MANUFACTURER, + .iProduct = CONFIG_OTG_GENERIC_PRODUCT_NAME " NET-BASIC", }, + }, + #endif /* defined(CONFIG_OTG_NETWORK_BASIC) */ + + #if defined(CONFIG_OTG_MOUSE) || defined(CONFIG_OTG_MOUSE_MODULE) + { + .composite_driver.driver.name = "hid2-non-iad", + .interface_names = "mouse-if:mouse-if", + .configuration_description.iConfiguration = "mouse2", + .device_description.bDeviceClass = 0, + .device_description.bDeviceSubClass = 0, + .device_description.bDeviceProtocol = 0, + .device_description.idVendor = __constant_cpu_to_le16(CONFIG_OTG_GENERIC_VENDORID), + .device_description.idProduct = __constant_cpu_to_le16(CONFIG_OTG_GENERIC_PRODUCTID_HID2), + .device_description.bcdDevice = __constant_cpu_to_le16(CONFIG_OTG_GENERIC_BCDDEVICE), + .device_description.iManufacturer = CONFIG_OTG_GENERIC_MANUFACTURER, + .device_description.iProduct = CONFIG_OTG_GENERIC_MANUFACTURER " USB HID (Mouse2)", + }, + { + .composite_driver.driver.name = "hid2", + .interface_names = "mouse-if:mouse-if", + .configuration_description.iConfiguration = "mouse2", + .device_description.bDeviceClass = USB_CLASS_MISC, + .device_description.idVendor = __constant_cpu_to_le16(CONFIG_OTG_GENERIC_VENDORID), + .device_description.idProduct = __constant_cpu_to_le16(CONFIG_OTG_GENERIC_PRODUCTID_HID2), + .device_description.bcdDevice = __constant_cpu_to_le16(CONFIG_OTG_GENERIC_BCDDEVICE), + .device_description.iManufacturer = CONFIG_OTG_GENERIC_MANUFACTURER, + .device_description.iProduct = CONFIG_OTG_GENERIC_MANUFACTURER " USB HID (Mouse2) IAD", + }, + #endif /* defined(CONFIG_OTG_MOUSE) */ + + #if (defined(CONFIG_OTG_MSC_MODULE) || defined(CONFIG_OTG_MSC)) && \ + (defined(CONFIG_OTG_MOUSE_MODULE) || defined(CONFIG_OTG_MOUSE)) + { + .composite_driver.driver.name = "hid-msc", + .interface_names = "mouse-if:msc-if", + .configuration_description.iConfiguration = "mouse-msc", + .device_description.bDeviceClass = USB_CLASS_MISC, + .device_description.idVendor = __constant_cpu_to_le16(CONFIG_OTG_GENERIC_VENDORID), + .device_description.idProduct = __constant_cpu_to_le16(CONFIG_OTG_GENERIC_PRODUCTID_HID_MSC), + .device_description.bcdDevice = __constant_cpu_to_le16(CONFIG_OTG_GENERIC_BCDDEVICE), + .device_description.iManufacturer = CONFIG_OTG_GENERIC_MANUFACTURER, + .device_description.iProduct = CONFIG_OTG_GENERIC_MANUFACTURER " HID-MSC Composite IAD", + }, + #endif /* defined(CONFIG_OTG_MSC) && defined(CONFIG_OTG_MOUSE)) */ + + #if defined(CONFIG_OTG_NETWORK_BLAN) && (defined(CONFIG_OTG_MOUSE_MODULE) || defined(CONFIG_OTG_MOUSE)) + { + .composite_driver.driver.name = "hid-blan", + .interface_names = "mouse-if:net-blan-if", + .configuration_description.iConfiguration = "hid-blan", + .device_description.bDeviceClass = USB_CLASS_MISC, + .device_description.idVendor = __constant_cpu_to_le16(CONFIG_OTG_GENERIC_VENDORID), + .device_description.idProduct = __constant_cpu_to_le16(CONFIG_OTG_GENERIC_PRODUCTID_HID_BLAN), + .device_description.bcdDevice = __constant_cpu_to_le16(CONFIG_OTG_GENERIC_BCDDEVICE), + .device_description.iManufacturer = CONFIG_OTG_GENERIC_MANUFACTURER, + .device_description.iProduct = CONFIG_OTG_GENERIC_MANUFACTURER " HID-BLAN Composite IAD", + }, + #endif /* defined(CONFIG_OTG_MSC) && defined(CONFIG_OTG_MOUSE)) */ + #if defined(CONFIG_OTG_NETWORK_ECM) && (defined(CONFIG_OTG_MOUSE_MODULE) || defined(CONFIG_OTG_MOUSE)) + { + .composite_driver.driver.name = "hid-ecm", + .interface_names = "mouse-if:cdc-ecm-if", + .configuration_description.iConfiguration = "hid-ecm", + .device_description.bDeviceClass = USB_CLASS_MISC, + .device_description.idVendor = __constant_cpu_to_le16(CONFIG_OTG_GENERIC_VENDORID), + .device_description.idProduct = __constant_cpu_to_le16(CONFIG_OTG_GENERIC_PRODUCTID_HID_ECM), + .device_description.bcdDevice = __constant_cpu_to_le16(CONFIG_OTG_GENERIC_BCDDEVICE), + .device_description.iManufacturer = CONFIG_OTG_GENERIC_MANUFACTURER, + .device_description.iProduct = CONFIG_OTG_GENERIC_MANUFACTURER " HID-BLAN Composite IAD", + }, + #endif /* defined(CONFIG_OTG_MSC) && defined(CONFIG_OTG_MOUSE)) */ + + #if ( \ + (defined(CONFIG_OTG_MOUSE_MODULE) || defined(CONFIG_OTG_MOUSE)) && \ + (defined(CONFIG_OTG_ACM) || defined(CONFIG_OTG_ACM_MODULE))) \ + || defined(_OTG_DOXYGEN) + { + .composite_driver = { .driver = { .name = "hid-serial", }, }, + .interface_names = "hid:tty-if", + .configuration_description = { + .iConfiguration = "hid-acm-tty", + }, + .device_description = { + .bDeviceClass = COMMUNICATIONS_DEVICE_CLASS, + .bDeviceSubClass = 2, + .bDeviceProtocol = 0, + .idVendor = __constant_cpu_to_le16(CONFIG_OTG_GENERIC_VENDORID), + .idProduct = __constant_cpu_to_le16(CONFIG_OTG_GENERIC_PRODUCTID_HID_SERIAL), + .bcdDevice = __constant_cpu_to_le16(CONFIG_OTG_GENERIC_BCDDEVICE), + .iManufacturer = CONFIG_OTG_GENERIC_MANUFACTURER, + .iProduct = CONFIG_OTG_GENERIC_PRODUCT_NAME " HID-Serial", }, + + }, + { + .composite_driver = { .driver = { .name = "hid-modem", }, }, + .interface_names = "hid:tty-if", + .configuration_description = { + .iConfiguration = "hid-acm-tty", + }, + .device_description = { + .bDeviceClass = COMMUNICATIONS_DEVICE_CLASS, + .bDeviceSubClass = 2, + .bDeviceProtocol = 0, + .idVendor = __constant_cpu_to_le16(CONFIG_OTG_GENERIC_VENDORID), + .idProduct = __constant_cpu_to_le16(CONFIG_OTG_GENERIC_PRODUCTID_HID_MODEM), + .bcdDevice = __constant_cpu_to_le16(CONFIG_OTG_GENERIC_BCDDEVICE), + .iManufacturer = CONFIG_OTG_GENERIC_MANUFACTURER, + .iProduct = CONFIG_OTG_GENERIC_PRODUCT_NAME " HID-Serial", }, + + }, + #endif /* defined(CONFIG_OTG_ACM) && defined(CONFIG_OTG_MOUSE) */ + + + { }, +}; + +static struct generic_config generic_config = { + .composite_driver = { .driver = { .name = "generic", }, }, + .device_description = { + .idVendor = __constant_cpu_to_le16(CONFIG_OTG_GENERIC_VENDORID), + .idProduct = __constant_cpu_to_le16(CONFIG_OTG_GENERIC_PRODUCTID), + .bcdDevice = __constant_cpu_to_le16(CONFIG_OTG_GENERIC_BCDDEVICE), + .iManufacturer = CONFIG_OTG_GENERIC_MANUFACTURER, + .iProduct = CONFIG_OTG_GENERIC_PRODUCT_NAME, }, +}; + + + + +static otg_tag_t GENERIC; + +/*! + * generic_cf_modinit() - module init + * + * This is called by the Linux kernel; either when the module is loaded + * if compiled as a module, or during the system intialization if the + * driver is linked into the kernel. + * + * This function will parse module parameters if required and then register + * the generic driver with the USB Device software. + * + */ +int generic_cf_modinit (void) +{ + /* load config or configs + */ + + #if defined(CONFIG_OTG_TRACE) || defined(CONFIG_OTG_TRACE_MODULE) + GENERIC = otg_trace_obtain_tag(NULL, "generic-cf"); + #endif + + if (preset_config_name() || MODPARM(load_all)) { + TRACE_MSG0(GENERIC, "PRESET"); + return generic_modinit(generic_cf_configs, + generic_config_name(), + MODPARM(override), + MODPARM(idVendor), + MODPARM(idProduct), + MODPARM(iSerialNumber), + GENERIC); + } + else { + struct generic_config *config = &generic_config; + + TRACE_MSG0(GENERIC, "SEARCH"); + + printk (KERN_INFO "%s: idVendor: %04x idProduct: %04x\n", __FUNCTION__, MODPARM(idVendor), MODPARM(idProduct)); + printk (KERN_INFO "%s: class_name: \"%s\" _interface_names: \"%s\"\n", + __FUNCTION__, MODPARM(class_name), MODPARM(interface_names)); + + if (MODPARM(driver_name) && strlen(MODPARM(driver_name))) + config->composite_driver.driver.name = MODPARM(driver_name); + + if (MODPARM(class_name) && strlen(MODPARM(class_name))) + config->class_name = MODPARM(class_name); + + if (MODPARM(interface_names) && strlen(MODPARM(interface_names))) + config->interface_names = MODPARM(interface_names); + + if (MODPARM(iConfiguration) && strlen(MODPARM(iConfiguration))) + config->configuration_description.iConfiguration = MODPARM(iConfiguration); + + if (MODPARM(bDeviceClass)) + config->device_description.bDeviceClass = MODPARM(bDeviceClass); + + if (MODPARM(bDeviceSubClass)) + config->device_description.bDeviceSubClass = MODPARM(bDeviceSubClass); + + if (MODPARM(bDeviceProtocol)) + config->device_description.bDeviceProtocol = MODPARM(bDeviceProtocol); + + if (MODPARM(idVendor)) + config->device_description.idVendor = MODPARM(idVendor); + else + config->device_description.idVendor = CONFIG_OTG_GENERIC_VENDORID; + + if (MODPARM(idProduct)) + config->device_description.idProduct = MODPARM(idProduct); + else + config->device_description.idProduct = CONFIG_OTG_GENERIC_PRODUCTID; + + if (MODPARM(bcdDevice)) + config->device_description.bcdDevice = MODPARM(bcdDevice); + else + config->device_description.bcdDevice = CONFIG_OTG_GENERIC_BCDDEVICE; + + if (MODPARM(iManufacturer) && strlen(MODPARM(iManufacturer))) + config->device_description.iManufacturer = MODPARM(iManufacturer); + else + config->device_description.iManufacturer = CONFIG_OTG_GENERIC_MANUFACTURER; + + if (MODPARM(iProduct) && strlen(MODPARM(iProduct))) + config->device_description.iProduct = MODPARM(iProduct); + else + config->device_description.iProduct = CONFIG_OTG_GENERIC_PRODUCT_NAME; + + if (MODPARM(iSerialNumber) && strlen(MODPARM(iSerialNumber))){ + config->device_description.iSerialNumber = MODPARM(iSerialNumber); + } + if (MODPARM(interface_names)) + config->interface_names = MODPARM(interface_names); + + generic_register(config, NULL, FALSE, 0, 0, NULL, GENERIC); + return 0; + } +} + +module_init (generic_cf_modinit); + +/*! + * generic_cf_modexit() - module init + * + * This is called by the Linux kernel; when the module is being unloaded + * if compiled as a module. This function is never called if the + * driver is linked into the kernel. + * @return void + */ +void generic_cf_modexit (void) +{ + generic_modexit(generic_cf_configs, GENERIC); + + if (generic_config.registered) + usbd_deregister_composite_function (&generic_config.composite_driver); + + if (generic_config.interface_list) + LKFREE(generic_config.interface_list); + + otg_trace_invalidate_tag(GENERIC); + +} +#if OTG_EPILOGUE +module_exit (generic_cf_modexit); +#endif diff --git a/drivers/otg/functions/generic/generic-cl.c b/drivers/otg/functions/generic/generic-cl.c new file mode 100644 index 000000000000..15ff2fdb33e7 --- /dev/null +++ b/drivers/otg/functions/generic/generic-cl.c @@ -0,0 +1,169 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/functions/generic/generic-cl.c + * @(#) sl@belcarra.com/whiskey.enposte.net|otg/functions/generic/generic-cl.c|20070425221028|46643 + * + * Copyright (c) 2003-2005 Belcarra Technologies Corp + * Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + * + * By: + * Stuart Lynne <sl@belcarra.com>, + * Bruce Balden <balden@belcarra.com> + * + */ +/*! + * @defgroup GenericClass Generic Class Function + * @ingroup ClassFunctions + */ + +/*! + * @file otg/functions/generic/generic-cl.c + * @brief Generic Class Function Driver + * + * This implements a generic NULL class function driver. + * + * @ingroup GenericClass + */ + +#include <otg/otg-compat.h> + +#if defined(CONFIG_OTG_LNX) || defined(_OTG_DOXYGEN) + +#include <otg/otg-module.h> + +#include <otg/usbp-chap9.h> +#include <otg/usbp-func.h> +#include <otg/otg-trace.h> +#include <otg/otg-api.h> + +#include "generic.h" + +/* Module Parameters ******************************************************** */ +/* ! + * @name XXXXX MODULE Parameters + */ +/* ! @{ */ + +MOD_AUTHOR ("sl@belcarra.com"); +EMBED_LICENSE(); +MOD_DESCRIPTION ("Belcarra Generic NULL Class Function"); + +otg_tag_t GCLASS; + +/* ! *} */ + +/* ********************************************************************************************* */ +/*! + * generic_cl_device_request - called to process a request to endpoint or interface + * @param function + * @param request + * @return non-zero for failure, will cause endpoint zero stall + */ +static int generic_cl_device_request (struct usbd_function_instance *function, struct usbd_device_request *request) +{ + TRACE_MSG5(GCLASS, "bmRequestType: %02x bRequest: %02x wValue: %04x wIndex: %04x wLength: %04x", + request->bmRequestType, request->bRequest, request->wValue, + request->wIndex, request->wLength); + + return -EINVAL; +} +/* ********************************************************************************************* */ +#if !defined(OTG_C99) +/*! function_ops - operations table for the USB Device Core + */ +static struct usbd_function_operations generic_function_ops; + +/*! generic_class_driver - USB Device Core function driver definition + */ +struct usbd_class_driver generic_class_driver; + +#else /* defined(OTG_C99) */ +/*! function_ops - operations table for the USB Device Core + */ +static struct usbd_function_operations generic_function_ops = { + .device_request = generic_cl_device_request, /*!< called for each received device request */ +}; + +/*! generic_class_driver - USB Device Core function driver definition + */ +struct usbd_class_driver generic_class_driver = { + .driver.name = "generic-class", /*!< driver name */ + .driver.fops = &generic_function_ops, /*!< operations table */ +}; +#endif /* defined(OTG_C99) */ + + +/* USB Module init/exit ***************************************************** */ + +//#if OTG_EPILOGUE +/*! + * generic_cl_modexit() - module init + * + * This is called by the Linux kernel; when the module is being unloaded + * if compiled as a module. This function is never called if the + * driver is linked into the kernel. + * @return none + */ +static void generic_cl_modexit (void) +{ + usbd_deregister_class_function (&generic_class_driver); + otg_trace_invalidate_tag(GCLASS); +} + +module_exit (generic_cl_modexit); +//#endif + +/*! + * generic_cl_modinit() - module init + * + * This is called by the Linux kernel; either when the module is loaded + * if compiled as a module, or during the system intialization if the + * driver is linked into the kernel. + * + * This function will parse module parameters if required and then register + * the generic driver with the USB Device software. + * + */ +static int generic_cl_modinit (void) +{ + #if !defined(OTG_C99) + /*! function_ops - operations table for the USB Device Core + */ + ZERO(generic_function_ops); + generic_function_ops.device_request=generic_cl_device_request; /*! called for each received device request */ + + /*! class_driver - USB Device Core function driver definition + */ + ZERO(generic_class_driver); + generic_class_driver.driver.name = "generic-class"; /*! driver name */ + generic_class_driver.driver.fops = &generic_function_ops; /*! operations table */ + #endif /* defined(OTG_C99) */ + + GCLASS = otg_trace_obtain_tag(NULL, "generic-cf"); + + // register as usb function driver + TRACE_MSG0(GCLASS, "REGISTER CLASS"); + THROW_IF (usbd_register_class_function (&generic_class_driver, "generic-class", NULL), error); + + TRACE_MSG0(GCLASS, "REGISTER FINISHED"); + + CATCH(error) { + generic_cl_modexit(); + return -EINVAL; + } + return 0; +} + +module_init (generic_cl_modinit); +#endif /* defined(CONFIG_OTG_LNX) */ diff --git a/drivers/otg/functions/generic/generic.c b/drivers/otg/functions/generic/generic.c new file mode 100644 index 000000000000..d3eaa43433dc --- /dev/null +++ b/drivers/otg/functions/generic/generic.c @@ -0,0 +1,291 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/functions/generic/generic-cf.c + * @(#) sl@belcarra.com/whiskey.enposte.net|otg/functions/generic/generic.c|20070425221028|47281 + * + * Copyright (c) 2003-2005 Belcarra Technologies Corp + * Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + * + * By: + * Stuart Lynne <sl@belcarra.com>, + * Bruce Balden <balden@belcarra.com> + * + */ +/*! + * @file otg/functions/generic/generic.c + * @brief Generic Configuration Function Driver + * + * This implements a generic composite function + * + * - idVendor 16 bit word + * - idProduct 16 bit word + * - bcd_device 16 bit word + * + * - bDeviceClass byte + * - device_sub-class byte + * - bcdDevice byte + * + * - vendor string + * - iManufacturer string + * - product string + * - serial string + * + * - power byte + * - remote_wakeup bit + * + * - functions string + * + * + * The functions string would contain a list of names, the first would + * be the composite function driver, the rest would be the interface + * functions. For example: + * + * + * "cmouse-cf mouse-if" + * "mcpc-cf dun-if dial-if obex-if dlog-if" + * + * There are also a set of pre-defined configurations that + * can be loaded singly or in toto. + * + * @ingroup GenericComposite + */ + +#include <otg/otg-compat.h> +#include <otg/otg-module.h> + +#include <otg/usbp-chap9.h> +#include <otg/usbp-func.h> +#include <otg/usbp-cdc.h> +#include <otg/otg-trace.h> +#include <otg/otg-api.h> +#include <otg/otg-utils.h> + +#include "generic.h" + +//static otg_tag_t GENERIC; + + +/* USB Module init/exit ***************************************************** */ + +/*! generic_cf_function_enable - called by USB Device Core to enable the driver + * @param function The function instance for this driver to use. + * @return non-zero if error. + */ +static int generic_cf_function_enable (struct usbd_function_instance *function) +{ + //TRACE_MSG0(GENERIC,"--"); + return 0; +} + +/*! generic_cf_function_disable - called by the USB Device Core to disable the driver + * @param function The function instance for this driver + */ +/*!@name generic composite functions + * @{ + */ +static void generic_cf_function_disable (struct usbd_function_instance *function) +{ + //TRACE_MSG0(GENERIC,"--"); +} + + +/*! function_ops - operations table for the USB Device Core + * */ +static struct usbd_function_operations generic_function_ops = { + .function_enable = generic_cf_function_enable, + .function_disable = generic_cf_function_disable, +}; + + + +/*! @} */ + +/*! + * generic_register() + * + */ +void generic_register(struct generic_config *config, const char *match, BOOL override, u16 idVendor, u16 idProduct, + char *iSerialNumber, otg_tag_t tag) +{ + char *cp, *sp, *lp, *np; + const char **interface_list = NULL; + int interfaces = 0; + char interface_name_list[256]; + + TRACE_MSG5(tag, "Driver: \"%s\" idVendor: %04x idProduct: %04x interface_names: \"%s\" match: \"%s\"", + config->composite_driver.driver.name, idVendor, idProduct, config->interface_names, + match ? match : ""); + + printk(KERN_INFO"Driver: \"%s\" idVendor: %04x idProduct: %04x interface_names: \"%s\" match: \"%s\"\n", + config->composite_driver.driver.name, idVendor, idProduct, config->interface_names, + match ? match : ""); + + + RETURN_IF (match && strlen(match) && strcmp(match, config->composite_driver.driver.name)); + + TRACE_MSG2(tag, "loading %s interface_names: %s", match, config->interface_names); + + + /* decompose interface names to construct interface_list + */ + RETURN_UNLESS (config->interface_names && strlen(config->interface_names)); + + /* count interface names and allocate _interface_names array + */ + strncpy(interface_name_list,config->interface_names,sizeof(interface_name_list)); + interface_name_list[sizeof(interface_name_list)-1]=0; + for (cp = sp = interface_name_list, interfaces = 0; cp && *cp; ) { + for (; *cp && *cp == ':'; cp++); // skip white space + sp = cp; // save start of token + for (; *cp && *cp != ':'; cp++); // find end of token + BREAK_IF (sp == cp); + if (*cp) cp++; + interfaces++; + } + + THROW_UNLESS(interfaces, error); + + config->interfaces=interfaces; + TRACE_MSG1(tag, "interfaces: %d", interfaces); + + THROW_UNLESS((interface_list = (const char **) CKMALLOC (sizeof (char *) * (interfaces + 1))), error); + + for (cp = sp = interface_name_list, interfaces = 0; cp && *cp; interfaces++) { + for (; *cp && *cp == ':'; cp++); // skip white space + sp = cp; // save start of token + for (; *cp && *cp != ':'; cp++); // find end of token + BREAK_IF (sp == cp); + lp = cp; + if (*cp) cp++; + np = CKMALLOC(lp - sp + 2); + strncpy(np, sp, lp - sp); + interface_list[interfaces] = np; + + TRACE_MSG3(tag, "INTERFACE[%2d] %x \"%s\"", + interfaces, interface_list[interfaces], interface_list[interfaces]); + } + + config->composite_driver.device_description = &config->device_description; + config->composite_driver.configuration_description = &config->configuration_description; + config->composite_driver.driver.fops = &generic_function_ops; + config->interface_list = interface_list; + + if (override) { + TRACE_MSG2(tag, "Overide idVendor: %04x idProduct: %04x", idVendor, idProduct); + config->composite_driver.device_description->idVendor = idVendor; + config->composite_driver.device_description->idProduct = idProduct; + } + + if ((iSerialNumber) && strlen(iSerialNumber)){ + config->composite_driver.device_description->iSerialNumber = iSerialNumber; + // config->device_description.iSerialNumber = iSerialNumber; + } + + THROW_IF (usbd_register_composite_function ( + &config->composite_driver, + config->composite_driver.driver.name, + config->class_name, + config->interface_list, NULL), error); + + config->registered++; + + TRACE_MSG0(tag, "REGISTER FINISHED"); + + CATCH(error) { + TRACE_MSG0(tag, "REGISTER FAILED"); + // otg_trace_invalidate_tag(GENERIC); + } + +} + +/*! + * generic_modinit() - module init + * + * This is called by the Linux kernel; either when the module is loaded + * if compiled as a module, or during the system intialization if the + * driver is linked into the kernel. + * + * This function will parse module parameters if required and then register + * the generic driver with the USB Device software. + * + */ +int generic_modinit (struct generic_config *generic_configs, + char *generic_config_name, BOOL override, u16 idVendor, u16 idProduct, char *iSerialNumber, otg_tag_t tag) +{ + int i; + + + TRACE_MSG5(tag, "config_name: \"%s\" Serial: \"%s\" override: %d idVendor: %04x idProduct: %04x", + generic_config_name ? generic_config_name : "", + iSerialNumber ? iSerialNumber : "", + override, idVendor, idProduct); + + /* search for named config + */ + for (i = 0; ; i++) { + struct generic_config *config = generic_configs + i; + TRACE_MSG2(tag, "i: %d interface_names: %d", i, config->interface_names); + BREAK_UNLESS(config->interface_names); + generic_register(config, generic_config_name, override, idVendor, idProduct, iSerialNumber, tag); + } + return 0; +} + +/*! + * generic_modexit() - module init + * + * This is called by the Linux kernel; when the module is being unloaded + * if compiled as a module. This function is never called if the + * driver is linked into the kernel. + * + * @param generic_configs + * @param tag + * @return void + */ +void generic_modexit (struct generic_config *generic_configs, otg_tag_t tag) +{ + int i,j; + //otg_trace_invalidate_tag(GENERIC); + for (i = 0; ; i++) { + struct generic_config *config = generic_configs + i; + + /* finished? + */ + BREAK_UNLESS(config->interface_names); + + /* continue if not registered + */ + CONTINUE_UNLESS(config->registered); + + /* de-register this composite function + */ + usbd_deregister_composite_function (&config->composite_driver); + + for (j=0;j<config->interfaces;j++) LKFREE ((char *) config->interface_list[j]); + + if (config->interface_list) + LKFREE(config->interface_list); + } + + /* + if (generic_config.registered) + usbd_deregister_composite_function (&generic_config.composite_driver); + + if (generic_config.interface_list) + LKFREE(generic_config.interface_list); + */ +} + +#if OTG_EPILOGUE +#endif diff --git a/drivers/otg/functions/generic/generic.h b/drivers/otg/functions/generic/generic.h new file mode 100644 index 000000000000..4429dcd26876 --- /dev/null +++ b/drivers/otg/functions/generic/generic.h @@ -0,0 +1,95 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/functions/generic/generic-cf.h + * @(#) tt@belcarra.com|otg/functions/generic/generic.h|20070316204018|00274 + * + * Copyright (c) 2003-2005 Belcarra Technologies Corp + * Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + * + * By: + * Stuart Lynne <sl@belcarra.com>, + * Bruce Balden <balden@belcarra.com> + * + */ + +/*! + * @defgroup GenericComposite Generic Composite Function + * @ingroup CompositeFunctions + */ + +/*! + * @file otg/functions/generic/generic.h + * @brief Generic Configuration Function Driver + * + * This implements a generic composite function + * + * - idVendor 16 bit word + * - idProduct 16 bit word + * - bcd_device 16 bit word + * + * - bDeviceClass byte + * - device_sub-class byte + * - bcdDevice byte + * + * - vendor string + * - iManufacturer string + * - product string + * - serial string + * + * - power byte + * - remote_wakeup bit + * + * - functions string + * + * + * The functions string would contain a list of names, the first would + * be the composite function driver, the rest would be the interface + * functions. For example: + * + * + * "cmouse-cf mouse-if" + * "mcpc-cf dun-if dial-if obex-if dlog-if" + * + * There are also a set of pre-defined configurations that + * can be loaded singly or in toto. + * + * @ingroup GenericComposite + */ + + +/* + * N.B. This list needs to be synchronized with both the default pre-defined + * configuration conditional settings (see above) and the Kconfig list + */ +/*! @struct generic_config generic.h "otg/functions/generic/generic.h" + */ +struct generic_config { + struct usbd_composite_driver composite_driver; + const char *class_name; + const char *interface_names; + const char ** interface_list; + struct usbd_configuration_description configuration_description; + struct usbd_device_description device_description; + int registered; + int interfaces; +}; + +int generic_modinit (struct generic_config *generic_configs, + char *generic_config_name, BOOL override, u16 idVendor, u16 idProduct, char *iSerialNumber, + otg_tag_t tag); +void generic_modexit (struct generic_config *generic_configs, + otg_tag_t tag); +void generic_register(struct generic_config *config, const char *match, BOOL override, u16 idVendor, u16 idProduct, + char *iSerialNumber, + otg_tag_t tag); diff --git a/drivers/otg/functions/generic/inteltest-cl.c b/drivers/otg/functions/generic/inteltest-cl.c new file mode 100644 index 000000000000..1cdcd9e304e0 --- /dev/null +++ b/drivers/otg/functions/generic/inteltest-cl.c @@ -0,0 +1,199 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/functions/generic/inteltest-cl.c + * @(#) sl@belcarra.com/whiskey.enposte.net|otg/functions/generic/inteltest-cl.c|20070425221028|59534 + * + * Copyright (c) 2007 Belcarra Technologies 2005 Corp + * + * By: + * Stuart Lynne <sl@belcarra.com>, + * Bruce Balden <balden@belcarra.com> + * + */ +/*! + * @addgroup GenericClass Generic Class Function + * @ingroup ClassFunctions + */ + +/*! + * @file otg/functions/generic/inteltest-cl.c + * @brief Generic Class Function Driver + * + * This implements a inteltest NULL class function driver. + * + * @ingroup GenericClass + */ + +#include <otg/otg-compat.h> + +#if defined(CONFIG_OTG_LNX) || defined(_OTG_DOXYGEN) + +#include <otg/otg-module.h> + +#include <otg/usbp-chap9.h> +#include <otg/usbp-func.h> +#include <otg/otg-trace.h> +#include <otg/otg-api.h> + +//#include "inteltest.h" + +/* Module Parameters ******************************************************** */ +/* ! + * @name XXXXX MODULE Parameters + */ +/* ! @{ */ + +MOD_AUTHOR ("sl@belcarra.com"); +EMBED_LICENSE(); +MOD_DESCRIPTION ("Belcarra Generic NULL Class Function"); + +otg_tag_t GCLASS; + +/* ! *} */ + +/* ********************************************************************************************* */ +/*! + * inteltest_cl_device_request - called to process a request to endpoint or interface + * @param function + * @param request + * @return non-zero for failure, will cause endpoint zero stall + */ +static int inteltest_cl_device_request (struct usbd_function_instance *function, struct usbd_device_request *request) +{ + struct usbd_urb *urb; + u16 wLength = le16_to_cpu(request->wLength); + + + TRACE_MSG5(GCLASS, "bmRequestType: %02x bRequest: %02x wValue: %04x wIndex: %04x wLength: %04x", + request->bmRequestType, request->bRequest, request->wValue, + request->wIndex, request->wLength); + + /* XXX it should be this, need to verify + */ + RETURN_EINVAL_UNLESS(USB_REQ_RECIPIENT_DEVICE == (request->bmRequestType & USB_REQ_RECIPIENT_MASK)); + + switch (ctrl->bRequest) { + switch (request->bmRequestType & USB_REQ_DIRECTION_MASK) { + case USB_REQ_DEVICE2HOST: + + switch (request->bRequest) { + case 0x5c: /* read test */ + RETURN_EINVAL_UNLESS((urb = usbd_alloc_urb_ep0 (function, wLength, NULL))); + RETURN_ZERO_UNLESS(rc || usbd_start_in_urb(urb)); + break; + } + break; + + case USB_REQ_HOST2DEVICE: + + switch (request->bRequest) { + case 0x5b: /* write test */ + RETURN_EINVAL_UNLESS((urb = usbd_alloc_urb_ep0 (function, wLength, NULL))); + RETURN_ZERO_UNLESS(rc || usbd_start_out_urb(urb)); + break; + } + break; + } + + return -EINVAL; +} +/* ********************************************************************************************* */ +#if !defined(OTG_C99) +/*! function_ops - operations table for the USB Device Core + */ +static struct usbd_function_operations inteltest_function_ops; + +/*! inteltest_class_driver - USB Device Core function driver definition + */ +struct usbd_class_driver inteltest_class_driver; + +#else /* defined(OTG_C99) */ +/*! function_ops - operations table for the USB Device Core + */ +static struct usbd_function_operations inteltest_function_ops = { + .device_request = inteltest_cl_device_request, /*!< called for each received device request */ +}; + +/*! inteltest_class_driver - USB Device Core function driver definition + */ +struct usbd_class_driver inteltest_class_driver = { + .driver.name = "inteltest-class", /*!< driver name */ + .driver.fops = &inteltest_function_ops, /*!< operations table */ +}; +#endif /* defined(OTG_C99) */ + + +/* USB Module init/exit ***************************************************** */ + +//#if OTG_EPILOGUE +/*! + * inteltest_cl_modexit() - module init + * + * This is called by the Linux kernel; when the module is being unloaded + * if compiled as a module. This function is never called if the + * driver is linked into the kernel. + * @return none + */ +static void inteltest_cl_modexit (void) +{ + usbd_deregister_class_function (&inteltest_class_driver); + otg_trace_invalidate_tag(GCLASS); +} + +module_exit (inteltest_cl_modexit); +//#endif + +/*! + * inteltest_cl_modinit() - module init + * + * This is called by the Linux kernel; either when the module is loaded + * if compiled as a module, or during the system intialization if the + * driver is linked into the kernel. + * + * This function will parse module parameters if required and then register + * the inteltest driver with the USB Device software. + * + */ +static int inteltest_cl_modinit (void) +{ + #if !defined(OTG_C99) + /*! function_ops - operations table for the USB Device Core + */ + ZERO(inteltest_function_ops); + inteltest_function_ops.device_request=inteltest_cl_device_request; /*! called for each received device request */ + + /*! class_driver - USB Device Core function driver definition + */ + ZERO(inteltest_class_driver); + inteltest_class_driver.driver.name = "inteltest-class"; /*! driver name */ + inteltest_class_driver.driver.fops = &inteltest_function_ops; /*! operations table */ + #endif /* defined(OTG_C99) */ + + GCLASS = otg_trace_obtain_tag(NULL, "inteltest-cf"); + + // register as usb function driver + TRACE_MSG0(GCLASS, "REGISTER CLASS"); + THROW_IF (usbd_register_class_function (&inteltest_class_driver, "inteltest-class", NULL), error); + + TRACE_MSG0(GCLASS, "REGISTER FINISHED"); + + CATCH(error) { + inteltest_cl_modexit(); + return -EINVAL; + } + return 0; +} + +module_init (inteltest_cl_modinit); +#endif /* defined(CONFIG_OTG_LNX) */ diff --git a/drivers/otg/functions/generic/otg-config.h b/drivers/otg/functions/generic/otg-config.h new file mode 100644 index 000000000000..4b293c7d555c --- /dev/null +++ b/drivers/otg/functions/generic/otg-config.h @@ -0,0 +1,133 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * + * @(#) balden@belcarra.com|otg/functions/generic/otg-config.h|20060419204257|50674 + * + */ + +/* + * tristate " Generic Function" + * This acts as a generic function, allowing selection of class + * and interface function drivers to create a specific configuration. + */ +#define CONFIG_OTG_GENERIC + +/* + * hex "VendorID (hex value)" + * default "0x15ec" + */ +#define CONFIG_OTG_GENERIC_VENDORID + +/* + * hex "ProductID (hex value)" + * default "0xf010" + */ +#define CONFIG_OTG_GENERIC_PRODUCTID + +/* + * hex "bcdDevice (binary-coded decimal)" + * default "0x0100" + */ +#define CONFIG_OTG_GENERIC_BCDDEVICE + +/* + * string "iManufacturer (string)" + * default "Belcarra" + */ +#define CONFIG_OTG_GENERIC_MANUFACTURER + +/* + * string "iProduct (string)" + * default "Generic Composite" + */ +#define CONFIG_OTG_GENERIC_PRODUCT_NAME + +/* + * bool 'none' + * No pre-defined selection, use the OTG_GENERIC_CONFIG_NAME instead. + */ +#define CONFIG_OTG_GENERIC_CONFIG_NONE + +/* + * bool 'mouse' + * Configure the mouse driver in a single function non-composite configuration. + */ +#define CONFIG_OTG_GENERIC_CONFIG_MOUSE + +/* + * bool 'net-blan' + * Configure the network driver in a single function non-composite BLAN configuration. + */ +#define CONFIG_OTG_GENERIC_CONFIG_NET_BLAN + +/* + * bool 'net-cdc' + * Configure the network driver in a single function non-composite CDC configuration. + */ +#define CONFIG_OTG_GENERIC_CONFIG_NET_CDC + +/* + * bool 'net-safe' + * Configure the network driver in a single function non-composite SAFE configuration. + */ +#define CONFIG_OTG_GENERIC_CONFIG_NET_SAFE + +/* + * bool 'acm-tty' + * Configure the acm driver in a single function non-composite TTY configuration. + */ +#define CONFIG_OTG_GENERIC_CONFIG_ACM_TTY + +/* + * bool 'msc' + * Configure the msc driver in a single function non-composite configuration. + */ +#define CONFIG_OTG_GENERIC_CONFIG_MSC + +/* + * bool 'mouse2' + * Configure the mouse driver in a demostration, two function composite configuration. + */ +#define CONFIG_OTG_GENERIC_CONFIG_MOUSE2 + +/* + * bool 'msc-mouse' + * Configure the msc and mouse driver in a demostration, two function composite configuration. + */ +#define CONFIG_OTG_GENERIC_CONFIG_MSC_MOUSE + +/* + * bool 'msc-blan' + * Configure the msc and network-blan driver in a demostration, two function composite configuration. + */ +#define CONFIG_OTG_GENERIC_CONFIG_MSC_BLAN + +/* + * bool 'msc-cdc' + * Configure the msc and network-cdc driver in a demostration, two function composite configuration. + */ +#define CONFIG_OTG_GENERIC_CONFIG_MSC_CDC + + + + +/* + * string "Composite Configuration (string)" + * default "" + * Name of predefined configuration to be enabled, note that + * if this is not defined (empty string) then the first available + * configuration will be used (assuming that the required interface + * function drivers are available. + */ +#define CONFIG_OTG_GENERIC_CONFIG_NAME diff --git a/drivers/otg/functions/mouse/Kconfig b/drivers/otg/functions/mouse/Kconfig new file mode 100644 index 000000000000..14e27029ee18 --- /dev/null +++ b/drivers/otg/functions/mouse/Kconfig @@ -0,0 +1,141 @@ +# @(#) sp@belcarra.com|otg/functions/mouse/Kconfig|20070417200305|04741 +# Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + +menu "OTG Random Mouse function" + depends on OTG + +config OTG_MOUSE + tristate "Random Mouse Function" + depends on OTG + ---help--- + This implements a simple Mouse driver that sends HID packets with + small random movements. This is a good function for initial testing + of Peripheral Controller Drivers as it provides for a complicated + enumeration but a simple uni-directional (send Interrupt only) data + transport. + +menu "Mouse support modules:" + depends on OTG_MOUSE && OTG + + +config OTG_MOUSE_INTERFACE + bool "Build an interface module" + depends on OTG && OTG_MOUSE + default "y" + ---help--- + This module needs a composite function module, such as + mouse_cf or generic_cf to be activated + +config OTG_MOUSE_COMPOSITE + bool "Build a simple OTG 2.x style composite function with mouse" + depends on OTG && OTG_MOUSE && OTG_MOUSE_INTERFACE + default "n" + ---help--- + This module provides a very simple composite function to drive + the mouse_if module (see above). The generic_cf module provides + a much richer composite function supporting multiple functions. + +config OTG_MOUSE_TRADITIONAL + bool "Build an OTG 1.x style function module/driver" + depends on OTG && OTG_MOUSE + default "n" + ---help--- + This is the mouse function driver from previous releases. It + does not use the composite/interface mechanism. If this module + is used, neither of the other two mouse modules is required. + +endmenu + + +menu "OTG Random Mouse function options" + depends on OTG && OTG_MOUSE + +config OTG_MOUSE_VENDORID + hex "VendorID (hex value)" + depends on OTG && OTG_MOUSE + default "0x15ec" + ---help--- + This parameter is not used by the mouse_if module because the + composite layer is responsible for setting this parameter + +config OTG_MOUSE_PRODUCTID + hex "ProductID (hex value)" + depends on OTG && OTG_MOUSE + default "0xf003" + ---help--- + Parameters such as PRODUCTID, VENDORID are now the responsibility + of the composite driver rather than the interface driver. For this + reason, the module parameters vendor_id, etc are not available in + the interface versions. Therefore, appropriate values should be set + in the kernel configuration. These configuration parameters + are referenced in both mouse_cf and generic_cf. + +config OTG_MOUSE_BCDDEVICE + hex "bcdDevice (binary-coded decimal)" + depends on OTG && OTG_MOUSE + default "0x0100" + +config OTG_MOUSE_MANUFACTURER + string "iManufacturer (string)" + depends on OTG && OTG_MOUSE + default "Belcarra" + +config OTG_MOUSE_PRODUCT_NAME + string "iProduct (string)" + depends on OTG && OTG_MOUSE + default "Random Mouse Device" + +config OTG_MOUSE_COMM_INTF + string "MOUSE Bulk Only iInterface (string)" + depends on OTG && OTG_MOUSE + default "MOUSE Data Intf" + +config OTG_MOUSE_DESC + string "Data Interface iConfiguration (string)" + depends on OTG && OTG_MOUSE + default "MOUSE Configuration" + +config OTG_MOUSE_BH + bool "MOUSE BH Test" + depends on OTG && OTG_MOUSE + default n + ---help--- + Implement the Mouse send packet in a bottom half handler, + this will delay responses to test the PCD works correctly + when the host polls before a send data urb is queued. + +config OTG_MOUSE_PACKETS + int "Number of packets (zero is continous)" + depends on OTG && OTG_MOUSE + default 10 + ---help--- + Number of Mouse packets to send, will run + forever if set to zero. + +config OTG_MOUSE_STALL + bool "MOUSE Stall Test" + depends on OTG && OTG_MOUSE + default n + ---help--- + Periodically stall the INTERRUPT endpoint. + Used for testing. + +config OTG_MOUSE_STALL_COUNT + int "Number of Stalls" + depends on OTG && OTG_MOUSE_STALL + default 1 + ---help--- + Number of Stall tests to perform. + +config OTG_MOUSE_INTERVAL + int 'Polling Interval ' + depends on OTG && OTG_MOUSE + default 1 + ---help--- + Sets interrupt endpoint interval. + + +endmenu + + +endmenu diff --git a/drivers/otg/functions/mouse/Makefile b/drivers/otg/functions/mouse/Makefile new file mode 100644 index 000000000000..3bd66b674974 --- /dev/null +++ b/drivers/otg/functions/mouse/Makefile @@ -0,0 +1,26 @@ +# Function driver for a Random Mouse +# @(#) balden@belcarra.com|otg/functions/mouse/Makefile-l26|20060419204257|16121 +# +# Copyright (c) 2004 Belcarra +# Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + +mouse_fd-objs := mouse-fd.o +mouse_cf-objs := mouse-cf.o +mouse_if-objs := mouse-if.o + +ifeq ($(CONFIG_OTG_MOUSE_TRADITIONAL),y) +obj-$(CONFIG_OTG_MOUSE) += mouse_fd.o +endif +ifeq ($(CONFIG_OTG_MOUSE_INTERFACE), y) +obj-$(CONFIG_OTG_MOUSE) += mouse_if.o +endif +ifeq ($(CONFIG_OTG_MOUSE_COMPOSITE), y) +obj-$(CONFIG_OTG_MOUSE) += mouse_cf.o +endif + +OTG=$(TOPDIR)/drivers/otg +MOUSED=$(OTG)/functions/mouse +OTGCORE_DIR=$(OTG)/otgcore +#USBDCORE_DIR=$(OTG)/usbdcore +EXTRA_CFLAGS += -I$(MOUSED) -I$(OTG) -Wno-unused -Wno-format -I$(OTGCORE_DIR) +EXTRA_CFLAGS_nostdinc += -I$(MOUSED) -I$(OTG) -Wno-unused -Wno-format -I$(OTGCORE_DIR) diff --git a/drivers/otg/functions/mouse/mouse-cf.c b/drivers/otg/functions/mouse/mouse-cf.c new file mode 100644 index 000000000000..31093c667f7b --- /dev/null +++ b/drivers/otg/functions/mouse/mouse-cf.c @@ -0,0 +1,474 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/functions/mouse/mouse-cf.c + * @(#) sl@belcarra.com/whiskey.enposte.net|otg/functions/mouse/mouse-cf.c|20061218212925|53990 + * + * Copyright (c) 2003-2005 Belcarra + * Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + * + * By: + * Stuart Lynne <sl@belcarra.com>, + * Bruce Balden <balden@belcarra.com> + * + */ + +/*! + * @defgroup MouseComposite Mouse Composite Function + * @ingroup CompositeFunctions + */ + +/*! + * @file otg/functions/mouse/mouse-cf.c + * @brief Mouse Configuration Function Driver + * + * This implements the composite function portion of the composite + * random mouse driver. + * + * It defines the device descriptor charateristics, selects the + * single class function and a list of one or more interface functions. + * + * The primary purpose of this driver is to demonstrate how to + * implement a simple composite function driver and to provide a simple + * uni-directional driver for testing USB Device peripheral drivers. + * + * The mouse driver has several other characteristics to allow testing of + * other features of USB Device peripheral drivers: + * + * - ep0 ZLP handling + * + * To verify that ep0 ZLP processing is being handling correctly this + * driver has a hard coded Product name that is returned as a 32 byte + * string. This forces most any implementations that have an endpoint + * zero packetsize of 8, 16 or 32 to send a Zero Length Packet to + * terminate the string transfer when the Product name is requested + * by the host. + * + * There is no request or data handling required for mouse. So this + * only implements required descriptors. + * + * @ingroup MouseComposite + */ + +#include <otg/otg-compat.h> +#if defined(CONFIG_OTG_LNX) || defined(_OTG_DOXYGEN) +#include <otg/otg-module.h> + +#include <otg/usbp-chap9.h> +#include <otg/usbp-func.h> +#include <otg/otg-trace.h> +#include <otg/otg-api.h> + +/* + * ep0 testing.... ensure that this is exactly 16 bytes + */ +#undef CONFIG_OTG_MOUSE_PRODUCT_NAME +#define CONFIG_OTG_MOUSE_PRODUCT_NAME "Belcarra Mouse" + +static const char *mouse_usb_strings[4] = { + "Mouse-cf String Test 1", + "Mouse-cf String Test 3", + "Mouse-cf String Test 5", + NULL, +}; + +static u8 mouse_usb_string_indexes[4] = { + 1, 3, 5, 0, +}; + + +#if !defined(OTG_C99) + +/*! @name mouse descriptors + * @{ */ + +/*! Configuration description(s) + */ +static struct usbd_configuration_descriptor mouse_configuration_descriptor; + +/*! Mouse Configuration Description + */ +struct usbd_configuration_description mouse_cf_configuration_description[1]; + +/*! Device Description + */ +static struct usbd_device_descriptor mouse_cf_device_descriptor; + +#ifdef CONFIG_OTG_HIGH_SPEED +/*! High Speed Device Description + */ +static struct usbd_device_qualifier_descriptor mouse_device_qualifier_descriptor; +#endif /* CONFIG_OTG_HIGH_SPEED */ + +/*! OTG Descriptor + */ +static struct usbd_otg_descriptor mouse_otg_descriptor; + +/*! Device Description + */ +struct usbd_device_description mouse_cf_device_description; +/* @} */ + +/*! mouse_cf_glocal_init - initialize global mouse configuration instance + */ + +void mouse_cf_global_init(void) +{ + /*! Configuration description(s) + */ + ZERO(mouse_configuration_descriptor); + mouse_configuration_descriptor.bLength = 0x09; + mouse_configuration_descriptor.bDescriptorType = USB_DT_CONFIGURATION; + mouse_configuration_descriptor.wTotalLength = 0x00; + + /*! Mouse Configuration Description + */ + ZERO(mouse_cf_configuration_description); + //mouse_cf_configuration_description[0].configuration_descriptor = &mouse_configuration_descriptor; + mouse_cf_configuration_description[0].iConfiguration = "USB Random Mouse Configuration"; + + #if 0 + /*! Device Description + */ + ZERO(mouse_cf_device_descriptor); + mouse_cf_device_descriptor.bLength = sizeof(struct usbd_device_descriptor); + mouse_cf_device_descriptor.bDescriptorType = USB_DT_DEVICE; + mouse_cf_device_descriptor.bcdUSB = __constant_cpu_to_le16(USB_BCD_VERSION); + mouse_cf_device_descriptor.bDeviceClass = 0x00; + mouse_cf_device_descriptor.bDeviceSubClass = 0x00; + mouse_cf_device_descriptor.bDeviceProtocol = 0x00; + mouse_cf_device_descriptor.bMaxPacketSize0 = 0x00; + mouse_cf_device_descriptor.idVendor = __constant_cpu_to_le16(CONFIG_OTG_MOUSE_VENDORID); + mouse_cf_device_descriptor.idProduct = __constant_cpu_to_le16(CONFIG_OTG_MOUSE_PRODUCTID); + mouse_cf_device_descriptor.bcdDevice = __constant_cpu_to_le16(CONFIG_OTG_MOUSE_BCDDEVICE); + +#ifdef CONFIG_OTG_HIGH_SPEED + /*! High Speed Device Description + */ + ZERO(mouse_device_qualifier_descriptor); + mouse_device_qualifier_descriptor.bLength = sizeof(struct usbd_device_qualifier_descriptor); + mouse_device_qualifier_descriptor.bDescriptorType = USB_DT_DEVICE_QUALIFIER; + mouse_device_qualifier_descriptor.bcdUSB = __constant_cpu_to_le16(USB_BCD_VERSION); + mouse_device_qualifier_descriptor.bDeviceClass = 0x00; + mouse_device_qualifier_descriptor.bDeviceSubClass = 0x00; + mouse_device_qualifier_descriptor.bDeviceProtocol = 0x00; + mouse_device_qualifier_descriptor.bMaxPacketSize0 = 0x00; +#endif /* CONFIG_OTG_HIGH_SPEED */ + #endif + + /*! OTG Descriptor + */ + ZERO(mouse_otg_descriptor); + mouse_otg_descriptor.bLength = sizeof(struct usbd_otg_descriptor); + mouse_otg_descriptor.bDescriptorType = USB_DT_OTG; + mouse_otg_descriptor.bmAttributes = 0; + + /*! Device Description + */ + ZERO(mouse_cf_device_description); + //mouse_cf_device_description.device_descriptor = &mouse_cf_device_descriptor; +#ifdef CONFIG_OTG_HIGH_SPEED + mouse_cf_device_description.device_qualifier_descriptor = &mouse_device_qualifier_descriptor; +#endif /* CONFIG_OTG_HIGH_SPEED */ + mouse_cf_device_description.bDeviceClass = 0x00; + mouse_cf_device_description.bDeviceSubClass = 0x00; + mouse_cf_device_description.bDeviceProtocol = 0x00; + mouse_cf_device_description.bMaxPacketSize0 = 0x00; + mouse_cf_device_description.idVendor = __constant_cpu_to_le16(CONFIG_OTG_MOUSE_VENDORID); + mouse_cf_device_description.idProduct = __constant_cpu_to_le16(CONFIG_OTG_MOUSE_PRODUCTID); + mouse_cf_device_description.bcdDevice = __constant_cpu_to_le16(CONFIG_OTG_MOUSE_BCDDEVICE); + mouse_cf_device_description.otg_descriptor = &mouse_otg_descriptor; + mouse_cf_device_description.iManufacturer = CONFIG_OTG_MOUSE_MANUFACTURER; + mouse_cf_device_description.iProduct = CONFIG_OTG_MOUSE_PRODUCT_NAME; +#if !defined(CONFIG_OTG_NO_SERIAL_NUMBER) && defined(CONFIG_OTG_SERIAL_NUMBER_STR) + mouse_cf_device_description.iSerialNumber = CONFIG_OTG_SERIAL_NUMBER_STR; +#endif +} + +#else /* defined(OTG_C99) */ + + +/*! @var struct usbd_configuartion_descriptor mouse_configuration_description(s) + */ +static struct usbd_configuration_descriptor mouse_configuration_descriptor = { + .bLength = 0x09, + .bDescriptorType = USB_DT_CONFIGURATION, +}; + +/*! @var struct usbd_configuration_description mouse _configuration_description + */ +struct usbd_configuration_description mouse_cf_configuration_description[] = { + { //.configuration_descriptor = &mouse_configuration_descriptor, + .iConfiguration = "USB Random Mouse Configuration", + }, +}; + +#if 0 +/*! Device Description + */ +static struct usbd_device_descriptor mouse_cf_device_descriptor = { + .bLength = sizeof(struct usbd_device_descriptor), + .bDescriptorType = USB_DT_DEVICE, + .bcdUSB = __constant_cpu_to_le16(USB_BCD_VERSION), + .bDeviceClass = 0x00, + .bDeviceSubClass = 0x00, + .bDeviceProtocol = 0x00, + .bMaxPacketSize0 = 0x00, + .idVendor = __constant_cpu_to_le16(CONFIG_OTG_MOUSE_VENDORID), + .idProduct = __constant_cpu_to_le16(CONFIG_OTG_MOUSE_PRODUCTID), + .bcdDevice = __constant_cpu_to_le16(CONFIG_OTG_MOUSE_BCDDEVICE), +}; + +#ifdef CONFIG_OTG_HIGH_SPEED +/*! High Speed Device Description + */ +static struct usbd_device_qualifier_descriptor mouse_device_qualifier_descriptor = { + .bLength = sizeof(struct usbd_device_qualifier_descriptor), + .bDescriptorType = USB_DT_DEVICE_QUALIFIER, + .bcdUSB = __constant_cpu_to_le16(USB_BCD_VERSION), + .bDeviceClass = 0x00, + .bDeviceSubClass = 0x00, + .bDeviceProtocol = 0x00, + .bMaxPacketSize0 = 0x00, +}; +#endif /* CONFIG_OTG_HIGH_SPEED */ +#endif + +#if 0 +/*! OTG Descriptor + */ +static struct usbd_otg_descriptor mouse_otg_descriptor = { + .bLength = sizeof(struct usbd_otg_descriptor), + .bDescriptorType = USB_DT_OTG, + .bmAttributes = 0, +}; +#endif + +/*! @var struct usbd_device_description mouse_cf_device_description + */ +struct usbd_device_description mouse_cf_device_description = { + //.device_descriptor = &mouse_cf_device_descriptor, +#ifdef CONFIG_OTG_HIGH_SPEED + .device_qualifier_descriptor = &mouse_device_qualifier_descriptor, +#endif /* CONFIG_OTG_HIGH_SPEED */ + .bDeviceClass = 0x00, + .bDeviceSubClass = 0x00, + .bDeviceProtocol = 0x00, + .bMaxPacketSize0 = 0x00, + .idVendor = __constant_cpu_to_le16(CONFIG_OTG_MOUSE_VENDORID), + .idProduct = __constant_cpu_to_le16(CONFIG_OTG_MOUSE_PRODUCTID), + .bcdDevice = __constant_cpu_to_le16(CONFIG_OTG_MOUSE_BCDDEVICE), + //.otg_descriptor = &mouse_otg_descriptor, + .iManufacturer = CONFIG_OTG_MOUSE_MANUFACTURER, + .iProduct = CONFIG_OTG_MOUSE_PRODUCT_NAME, +#if !defined(CONFIG_OTG_NO_SERIAL_NUMBER) && defined(CONFIG_OTG_SERIAL_NUMBER_STR) + .iSerialNumber = CONFIG_OTG_SERIAL_NUMBER_STR, +#endif +}; +#endif /* defined(OTG_C99) */ + + +/*! @} */ + + +/* MOUSE ***************************************************************************************** */ + +/* USB Device Functions ************************************************************************ */ + +/*! mouse_cf_function_enable - called by USB Device Core to enable the driver + * @param function The function instance for this driver to use. + * @return non-zero if error. + */ +static int mouse_cf_function_enable (struct usbd_function_instance *function) +{ + // XXX MODULE LOCK HERE + return 0; +} + +/*! mouse_cf_function_disable - called by the USB Device Core to disable the driver + * @param function The function instance for this driver + */ +static void mouse_cf_function_disable (struct usbd_function_instance *function) +{ + // XXX MODULE UNLOCK HERE +} + + +/* ********************************************************************************************* */ +#if !defined(OTG_C99) +/*! struct usbd_functions mouse_function_ops - operations table for the USB Device Core + */ +static struct usbd_function_operations mouse_function_ops; + +/*! struct usbd_composite_driver mouse_composite_driver - USB Device Core function driver definition + */ +struct usbd_composite_driver mouse_composite_driver; + +/*! mouse_cf_ops_init - initialze operations table for mouse configuration called by usbd core + */ + +void mouse_cf_ops_init(void) +{ + /*! function_ops - operations table for the USB Device Core + */ + ZERO(mouse_function_ops); + mouse_function_ops.function_enable = mouse_cf_function_enable; + mouse_function_ops.function_disable = mouse_cf_function_disable; + + /*! composite_driver - USB Device Core function driver definition + */ + ZERO(mouse_composite_driver); + mouse_composite_driver.driver.name = "mouse-random-fd"; /*! driver name */ + mouse_composite_driver.driver.fops = mouse_function_ops; /*! operations table */ + mouse_composite_driver.driver.usb_strings = mouse_usb_strings; /*! operations table */ + mouse_composite_driver.driver.usb_string_indexes = &mouse_usb_string_indexes; /*! operations table */ + mouse_composite_driver.device_description = &mouse_cf_device_description; /*! mouse device description */ + mouse_composite_driver.bNumConfigurations = + sizeof (mouse_cf_configuration_description) / sizeof (struct usbd_configuration_description); + mouse_composite_driver.configuration_description= mouse_cf_configuration_description; /*! mouse configuration */ + //mouse_composite_driver.idVendor = __constant_cpu_to_le16(CONFIG_OTG_MOUSE_VENDORID); + //mouse_composite_driver.idProduct = __constant_cpu_to_le16(CONFIG_OTG_MOUSE_PRODUCTID); + //mouse_composite_driver.bcdDevice = __constant_cpu_to_le16(CONFIG_OTG_MOUSE_BCDDEVICE); + + //mouse_composite_driver.interfaces = 0; + //mouse_composite_driver.interface_list = NULL; + //mouse_composite_driver.endpointsRequested = 0; + //mouse_composite_driver.requestedEndpoints = NULL; +} +#else /* defined(OTG_C99) */ +/*! @var struct usbd_function_operation mouse_function_ops - operations table for the USB Device Core + */ +static struct usbd_function_operations mouse_function_ops = { + .function_enable = mouse_cf_function_enable, + .function_disable = mouse_cf_function_disable, +}; + +/*! mouse_composite_driver - USB Device Core function driver definition + */ +struct usbd_composite_driver mouse_composite_driver = { + .driver = { + .name = "mouse-random-cf", + .fops = &mouse_function_ops, + .usb_strings = mouse_usb_strings, + .usb_string_indexes = mouse_usb_string_indexes, + }, + .device_description = &mouse_cf_device_description, + .bNumConfigurations = sizeof (mouse_cf_configuration_description) / sizeof (struct usbd_configuration_description), + .configuration_description = mouse_cf_configuration_description, +}; +#endif /* defined(OTG_C99) */ + + +/* Module Parameters ******************************************************** */ +/* ! + * @name XXXXX MODULE Parameters + */ +/* ! @{ */ + +MOD_AUTHOR ("sl@belcarra.com"); +EMBED_LICENSE(); +MOD_DESCRIPTION ("Belcarra Random Walk MOUSE Function"); + +static u32 vendor_id; /*!< override built-in vendor ID */ +static u32 product_id; /*!< override built-in product ID */ + +MOD_PARM (vendor_id, "i"); +MOD_PARM (product_id, "i"); + +MOD_PARM_DESC (vendor_id, "Device Vendor ID"); +MOD_PARM_DESC (product_id, "Device Product ID"); + +otg_tag_t MOUSE; +/* ! *} */ + +/* USB Module init/exit ***************************************************** */ +/*! char *mouse_arg_list[] */ +char *mouse_arg_list[] = { + //"2=mouse-random-if", + "mouse-random-if", + //"mouse-random-if", + //"mouse-random-if", + //"mouse-random-if", + NULL, +}; + +/*! + * mouse_cf_modinit() - module init + * + * This is called by the Linux kernel; either when the module is loaded + * if compiled as a module, or during the system intialization if the + * driver is linked into the kernel. + * + * This function will parse module parameters if required and then register + * the mouse driver with the USB Device software. + * + * @return 0 on success + * + */ +static int mouse_cf_modinit (void) +{ + int i; + printk (KERN_INFO "%s: vendor_id: %04x product_id: %04x\n", __FUNCTION__, vendor_id, product_id); + + #if !defined(OTG_C99) + mouse_cf_global_init(); + mouse_cf_ops_init(); + #endif /* defined(OTG_C99) */ + + MOUSE = otg_trace_obtain_tag(NULL, "mouse-cf"); + TRACE_MSG2(MOUSE, "vendor_id: %04x product_id: %04x",vendor_id, product_id); + + //if (vendor_id) + // mouse_composite_driver.idVendor = cpu_to_le16(vendor_id); + //if (product_id) + // mouse_composite_driver.idProduct = cpu_to_le16(product_id); + + + // register as usb function driver + TRACE_MSG0(MOUSE, "REGISTER COMPOSITE"); + + THROW_IF (usbd_register_composite_function (&mouse_composite_driver, + "mouse-random-cf", NULL, mouse_arg_list, NULL), error); + + TRACE_MSG0(MOUSE, "REGISTER FINISHED"); + + CATCH(error) { + otg_trace_invalidate_tag(MOUSE); + return -EINVAL; + } + return 0; +} + +module_init (mouse_cf_modinit); + +#if OTG_EPILOGUE +/*! + * mouse_cf_modexit() - module init + * + * This is called by the Linux kernel; when the module is being unloaded + * if compiled as a module. This function is never called if the + * driver is linked into the kernel. + * + * @param void + * @return void + */ +static void mouse_cf_modexit (void) +{ + usbd_deregister_composite_function (&mouse_composite_driver); + otg_trace_invalidate_tag(MOUSE); +} + +module_exit (mouse_cf_modexit); +#endif + +#endif /* defined(CONFIG_OTG_LNX) */ diff --git a/drivers/otg/functions/mouse/mouse-fd.c b/drivers/otg/functions/mouse/mouse-fd.c new file mode 100644 index 000000000000..617aae1fac72 --- /dev/null +++ b/drivers/otg/functions/mouse/mouse-fd.c @@ -0,0 +1,901 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/functions/mouse/mouse-fd.c + * @(#) sl@belcarra.com/whiskey.enposte.net|otg/functions/mouse/mouse-fd.c|20061218212925|31740 + * + * Copyright (c) 2003-2004 Belcarra + * Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + * + * By: + * Stuart Lynne <sl@belcarra.com>, + * Bruce Balden <balden@belcarra.com> + * + */ + +/*! + * @defgroup MouseSimple Mouse Simple Function + * @ingroup SimpleFunctions + */ + +/*! + * @file otg/functions/mouse/mouse-fd.c + * @brief Mouse Function Driver protocol implementation. + * + * This file implements the Mouse HID protocols and will generate + * random mouse data on the INTERRUPT endpoint. + * + * The primary purpose of this driver is to demonstrate how to + * implement a simple function driver and to provide a simple + * uni-directional driver for testing USB Device peripheral drivers. + * + * The mouse driver has several other characteristics to allow testing of + * other features of USB Device peripheral drivers: + * + * - ep0 ZLP handling + * + * - ep0 delayed CONTROL READ + * + * To verify that ep0 ZLP processing is being handling correctly this + * driver has a hard coded Product name that is returned as a 32 byte + * string. This forces most any implementations that have an endpoint + * zero packetsize of 8, 16 or 32 to send a Zero Length Packet to + * terminate the string transfer when the Product name is requested + * by the host. + * + * The delayed CONTROL READ test verifies that that USB Device peripheral + * drivers will correctly NAK until data is provide for device requests. + * This is done by (optionally) delaying the HID report via a bottom half + * handler. The device request returns normally and the peripheral driver + * must properly recognize that while the device request had a non-zero + * wLength field, there is currently no queued urb. + * + * When the bottom half handler is scheduled the HID report urb will be + * queued on endpoint zero and then returned to the host. + * + * + * @ingroup MouseSimple + */ + +#include <otg/otg-compat.h> +#if defined(CONFIG_OTG_LNX) || defined(_OTG_DOXYGEN) + +#include <otg/otg-module.h> + +#include <otg/usbp-chap9.h> +#include <otg/usbp-hid.h> +#include <otg/usbp-func.h> +#include <otg/otg-trace.h> +#include <otg/otg-api.h> + +/*!@struct mouse_private mouse-fd.c otg/functions/mouse/mouse-fd.c + * @brief The mouse_private structure is used to collect all of the mouse driver + * global variables into one place. + */ +struct mouse_private { +#ifdef CONFIG_OTG_MOUSE_BH + struct WORK_STRUCT notification_bh; +#endif /* CONFIG_OTG_MOUSE_BH */ + int usb_driver_registered; /*!< non-zero if usb function registered */ + unsigned char connected; /*!< non-zero if connected to host (configured) */ + unsigned int writesize; /*!< packetsize * 4 */ + struct usbd_urb *tx_urb; /*!< saved copy of current tx urb */ + int wLength; + int x; + int y; + int last_x; + int last_y; + int n; +}; + +#ifndef OTG_C99 +extern void mouse_global_init(void); +#endif /* OTG_C99 */ + +#define MOUSE mouse_fd_trace_tag +extern otg_tag_t MOUSE; + +struct mouse_private mouse_private; + +/* + * ep0 testing.... ensure that this is exactly 16 bytes + */ +#undef CONFIG_OTG_MOUSE_PRODUCT_NAME +#define CONFIG_OTG_MOUSE_PRODUCT_NAME "Belcarra Mouse" + +/*! + * @name Mouse Descriptors + * + * MouseHIDReport + * MOUSE Configuration + * + * Endpoint, Class, Interface, Configuration and Device descriptors/descriptions + */ + +/*! @name Mouse Descriptors + */ +/* @{*/ + +#define BULK_INT 0x00 /*!< Interrupt endpoint number */ +#define ENDPOINTS 0x01 /*!< Number of endpoints required */ + +/*! List of required endpoint numbers + */ +static u8 mouse_index[] = { BULK_INT, }; + +/*! List of requested endpoints + */ +static struct usbd_endpoint_request mouse_endpoint_requests[ENDPOINTS+1] = { + { BULK_INT, 1, 0, 0, USB_DIR_IN | USB_ENDPOINT_INTERRUPT, 16, 64, 2, }, + { 0, }, +}; + +/*! This is the HID report returned for the HID Device Request. + * There is no pre-defined descriptor type for this. + */ +static char MouseHIDReport[52] = { + 0x05, 0x01, /*!< USAGE_PAGE (Generic Desktop) */ + 0x09, 0x02, /*!< USAGE (Mouse) */ + 0xa1, 0x01, /*!< COLLECTION (Application) */ + 0x09, 0x01, /*!< USAGE (Pointer) */ + 0xa1, 0x00, /*!< COLLECTION (Physical) */ + 0x05, 0x09, /*!< USAGE_PAGE (Button) */ + 0x19, 0x01, /*!< USAGE_MINIMUM (Button 1) */ + 0x29, 0x03, /*!< USAGE_MAXIMUM (Button 3) */ + 0x15, 0x00, /*!< LOGICAL_MINIMUM (0) */ + 0x25, 0x01, /*!< LOGICAL_MAXIMUM (1) */ + 0x95, 0x03, /*!< REPORT_COUNT (3) */ + 0x75, 0x01, /*!< REPORT_SIZE (1) */ + 0x81, 0x02, /*!< INPUT (Data,Var,Abs) */ + 0x95, 0x01, /*!< REPORT_COUNT (1) */ + 0x75, 0x05, /*!< REPORT_SIZE (5) */ + 0x81, 0x03, /*!< INPUT (Cnst,Var,Abs) */ + 0x05, 0x01, /*!< USAGE_PAGE (Generic Desktop) */ + 0x09, 0x30, /*!< USAGE (X) */ + 0x09, 0x31, /*!< USAGE (Y) */ + 0x09, 0x38, /*!< USAGE (WHEEL) */ + 0x15, 0x81, /*!< LOGICAL_MINIMUM (-127) */ + 0x25, 0x7f, /*!< LOGICAL_MAXIMUM (127) */ + 0x75, 0x08, /*!< REPORT_SIZE (8) */ + 0x95, 0x03, /*!< REPORT_COUNT (3) */ + 0x81, 0x06, /*!< INPUT (Data,Var,Rel) */ + 0xc0, /*!< END_COLLECTION */ + 0xc0 /*!< END_COLLECTION */ +}; + +#if !defined(OTG_C99) + +/*! struct hid_descriptor mouse_fd_hid - This is the HID desccriptor. + */ +static struct hid_descriptor mouse_fd_hid; + +/*! staruct usbd_geeric_class_descriptor * mouse_fd_hid_descriptor - List of class descriptors + */ +static struct usbd_generic_class_descriptor *mouse_fd_hid_descriptors[] = { + (struct usbd_generic_class_descriptor *)&mouse_fd_hid, }; + +/*! struct usbd_interface_descriptor mouse_date_alternate_descriptor - Data Interface Alternate description + */ +static struct usbd_interface_descriptor mouse_data_alternate_descriptor; + +/*! struct usbd_alternate_description mouse_date_alternate_descriptions - Interface Descriptions + */ +static struct usbd_alternate_description mouse_data_alternate_descriptions[1]; + +/*! struct usbd_interface_description mouse_interfaces - List of Interface description(s) + */ +static struct usbd_interface_description mouse_interfaces[1]; + +/*! struct usbd_configuration_descriptor mouse_configuration_descriptor - Configuration description(s) + */ +static struct usbd_configuration_descriptor mouse_configuration_descriptor; + +/*! struct usbd_configuration_description mouse_fd_configuration - Mouse Configuration Description + */ +struct usbd_configuration_description mouse_fd_configuration_description[1]; + +/*! struct usbd_device_descriptor mouse_fd_device_descriptor - Device Description + */ +static struct usbd_device_descriptor mouse_fd_device_descriptor; + +#ifdef CONFIG_OTG_HIGH_SPEED +/*! struct usbd_device_qualifier_descriptor mouse_device_qualifier_descriptor - High Speed Device Description + */ +static struct usbd_device_qualifier_descriptor mouse_device_qualifier_descriptor; +#endif /* CONFIG_OTG_HIGH_SPEED */ + +/*! struct usbd_otg_descriptor mouse_otg_descriptor - OTG Descriptor + */ +static struct usbd_otg_descriptor mouse_otg_descriptor; + +/*! Device Description + */ +struct usbd_device_description mouse_fd_device_description; +/* mouse_global_init - initialize global variables for mouse instance + */ +void mouse_global_init(void) +{ + /*! This is the HID desccriptor. + */ + ZERO(mouse_fd_hid); + mouse_fd_hid.bLength = 0x09; + mouse_fd_hid.bDescriptorType = 0x21; + mouse_fd_hid.bcdHID = __constant_cpu_to_le16(0x110); + mouse_fd_hid.bCountryCode = 0x00; + mouse_fd_hid.bNumDescriptors = 0x01; + mouse_fd_hid.bReportType = 0x22; + mouse_fd_hid.wItemLength = __constant_cpu_to_le16(0x34); + + + /*! Data Interface Alternate description + */ + ZERO(mouse_data_alternate_descriptor); + mouse_data_alternate_descriptor.bLength = 0x09; + mouse_data_alternate_descriptor.bDescriptorType = USB_DT_INTERFACE; + mouse_data_alternate_descriptor.bInterfaceNumber = 0x00; + mouse_data_alternate_descriptor.bAlternateSetting = 0x00; + mouse_data_alternate_descriptor.bNumEndpoints = 0x01; + mouse_data_alternate_descriptor.bInterfaceClass = 0x03; + mouse_data_alternate_descriptor.bInterfaceSubClass = 0x01; + mouse_data_alternate_descriptor.bInterfaceProtocol = 0x02; + mouse_data_alternate_descriptor.iInterface = 0x00; + + /*! Interface Descriptions + */ + ZERO(mouse_data_alternate_descriptions); + mouse_data_alternate_descriptions[0].iInterface = "Random Mouse Interface - Interrupt"; + //mouse_data_alternate_descriptions[0].interface_descriptor = &mouse_data_alternate_descriptor; + mouse_data_alternate_descriptions[0].classes = + sizeof (mouse_fd_hid_descriptors) / sizeof (struct usbd_generic_class_descriptor *); + mouse_data_alternate_descriptions[0].class_list = mouse_fd_hid_descriptors; + mouse_data_alternate_descriptions[0].endpoints = 0x01; + mouse_data_alternate_descriptions[0].endpoint_index = mouse_index, + + /*! List of Interface description(s) + */ + ZERO(mouse_interfaces); + mouse_interfaces[0].alternates = sizeof (mouse_data_alternate_descriptions) / sizeof (struct usbd_alternate_description); + mouse_interfaces[0].alternate_list = mouse_data_alternate_descriptions; + + + /*! Configuration description(s) + */ + ZERO(mouse_configuration_descriptor); + mouse_configuration_descriptor.bLength = 0x09; + mouse_configuration_descriptor.bDescriptorType = USB_DT_CONFIGURATION; + mouse_configuration_descriptor.wTotalLength = 0x00; + mouse_configuration_descriptor.bNumInterfaces = sizeof (mouse_interfaces) / sizeof (struct usbd_interface_description); + mouse_configuration_descriptor.bConfigurationValue = 0x01; + mouse_configuration_descriptor.iConfiguration = 0x00; + mouse_configuration_descriptor.bmAttributes = 0; + mouse_configuration_descriptor.bMaxPower = 0; + + /*! Mouse Configuration Description + */ + ZERO(mouse_fd_configuration_description); + //mouse_fd_configuration_description[0].configuration_descriptor = &mouse_configuration_descriptor; + mouse_fd_configuration_description[0].iConfiguration = "USB Random Mouse Configuration"; + //mouse_fd_configuration_description[0].interfaces = + // sizeof (mouse_interfaces) / sizeof (struct usbd_interface_description); + // mouse_fd_configuration_description[0].interface_list = mouse_interfaces; + + /*! Device Description + */ + ZERO(mouse_fd_device_description); + //mouse_fd_device_description.device_descriptor = &mouse_fd_device_descriptor; +#ifdef CONFIG_OTG_HIGH_SPEED + mouse_fd_device_description.device_qualifier_descriptor = &mouse_device_qualifier_descriptor; +#endif /* CONFIG_OTG_HIGH_SPEED */ + mouse_fd_device_description.bDeviceClass = 0x00; + mouse_fd_device_description.bDeviceSubClass = 0x00; + mouse_fd_device_description.bDeviceProtocol = 0x00; + mouse_fd_device_description.bMaxPacketSize0 = 0x00; + mouse_fd_device_description.idVendor = __constant_cpu_to_le16(CONFIG_OTG_MOUSE_VENDORID); + mouse_fd_device_description.idProduct = __constant_cpu_to_le16(CONFIG_OTG_MOUSE_PRODUCTID); + mouse_fd_device_description.bcdDevice = __constant_cpu_to_le16(CONFIG_OTG_MOUSE_BCDDEVICE); + mouse_fd_device_description.otg_descriptor = &mouse_otg_descriptor; + mouse_fd_device_description.iManufacturer = CONFIG_OTG_MOUSE_MANUFACTURER; + mouse_fd_device_description.iProduct = CONFIG_OTG_MOUSE_PRODUCT_NAME; +#if !defined(CONFIG_OTG_NO_SERIAL_NUMBER) && defined(CONFIG_OTG_SERIAL_NUMBER_STR) + mouse_fd_device_description.iSerialNumber = CONFIG_OTG_SERIAL_NUMBER_STR; +#endif +} + +#else /* defined(OTG_C99) */ + +/*! struct hid_descriptor mouse_fd_hid - This is the HID desccriptor. + */ +struct hid_descriptor mouse_fd_hid = { + .bLength = 0x09, + .bDescriptorType = 0x21, + .bcdHID = __constant_cpu_to_le16(0x110), + .bCountryCode = 0x00, + .bNumDescriptors = 0x01, + .bReportType = 0x22, + .wItemLength = __constant_cpu_to_le16(0x34), +}; + +/*! struct usbd_generic_class_descriptor mouse_fd_hid_descriptor - List of class descriptors + */ +static struct usbd_generic_class_descriptor *mouse_fd_hid_descriptors[] = { + (struct usbd_generic_class_descriptor *)&mouse_fd_hid, }; + +/*! struct usbd_alternate_descriptor mouse_date_alternate_descriptor - Interface Descriptions + */ +static struct usbd_alternate_description mouse_data_alternate_descriptions[] = { + { + .iInterface = "Random Mouse Interface - Interrupt", + .bInterfaceClass = 0x03, + .bInterfaceSubClass = 0x01, + .bInterfaceProtocol = 0x02, + .classes = sizeof (mouse_fd_hid_descriptors) / sizeof (struct usbd_generic_class_descriptor *), + .class_list = mouse_fd_hid_descriptors, + .endpoints = sizeof (mouse_index) / sizeof(u8), + .endpoint_index = mouse_index, + }, +}; + +/*! struct usbd_interfacd_description mouse_interfaces - List of Interface description(s) + */ +static struct usbd_interface_description mouse_interfaces[] = { + { .alternates = sizeof (mouse_data_alternate_descriptions) / sizeof (struct usbd_alternate_description), + .alternate_list = mouse_data_alternate_descriptions,}, +}; + + +/*! struct usbd_configuration_descriptor mouse_configuration_descriptor - Configuration description(s) + */ +static struct usbd_configuration_descriptor mouse_configuration_descriptor = { + .bLength = 0x09, + .bDescriptorType = USB_DT_CONFIGURATION, + .wTotalLength = 0x00, + .bNumInterfaces = sizeof (mouse_interfaces) / sizeof (struct usbd_interface_description), + .bConfigurationValue = 0x01, + .iConfiguration = 0x00, + .bmAttributes = 0, + .bMaxPower = 0, +}; + +/*! struct usbd_configuration_description mouse_fd_configuration_description[] - Mouse Configuration Description + */ +struct usbd_configuration_description mouse_fd_configuration_description[] = { + { //.configuration_descriptor = &mouse_configuration_descriptor, + .iConfiguration = "USB Random Mouse Configuration", + }, +}; + +#if 0 +/*! OTG Descriptor + */ +static struct usbd_otg_descriptor mouse_otg_descriptor = { + .bLength = sizeof(struct usbd_otg_descriptor), + .bDescriptorType = USB_DT_OTG, + .bmAttributes = 0, +}; +#endif + +/*! struct usbd_device_description mouse_fd_device_description - Device Description + */ +struct usbd_device_description mouse_fd_device_description = { + //.device_descriptor = &mouse_fd_device_descriptor, +#ifdef CONFIG_OTG_HIGH_SPEED + .device_qualifier_descriptor = &mouse_device_qualifier_descriptor, +#endif /* CONFIG_OTG_HIGH_SPEED */ + .bDeviceClass = 0x00, + .bDeviceSubClass = 0x00, + .bDeviceProtocol = 0x00, + .bMaxPacketSize0 = 0x00, + .idVendor = __constant_cpu_to_le16(CONFIG_OTG_MOUSE_VENDORID), + .idProduct = __constant_cpu_to_le16(CONFIG_OTG_MOUSE_PRODUCTID), + .bcdDevice = __constant_cpu_to_le16(CONFIG_OTG_MOUSE_BCDDEVICE), + //.otg_descriptor = &mouse_otg_descriptor, + .iManufacturer = CONFIG_OTG_MOUSE_MANUFACTURER, + .iProduct = CONFIG_OTG_MOUSE_PRODUCT_NAME, +#if !defined(CONFIG_OTG_NO_SERIAL_NUMBER) && defined(CONFIG_OTG_SERIAL_NUMBER_STR) + .iSerialNumber = CONFIG_OTG_SERIAL_NUMBER_STR, +#endif +}; +#endif /* defined(OTG_C99) */ + + +/*@}*/ + +static int mouse_count; +static int mouse_stalls; + +/* MOUSE ***************************************************************************************** */ +/* Transmit Function *************************************************************************** */ +/*! int get_xy[] */ +static int get_xy[8] = { + 0, 0, 1, 1, + 0, 0, -1, -1, +}; + +/*! + * mouse_fd_send() - send a mouse data urb with random data + * @param function + * @return void + */ +void mouse_fd_send(struct usbd_function_instance *function) +{ + struct mouse_private *mouse = &mouse_private; + int new_x = 0; + int new_y = 0; + u8 random; + + TRACE_MSG1(MOUSE, "mouse_count: %d", mouse_count); + + memset(mouse->tx_urb->buffer, 0, 4); + if (!mouse->n) { + + get_random_bytes(&random, 1); + + mouse->last_x = MAX(-4, MIN(4, mouse->last_x + get_xy[random & 0x7])); + mouse->last_y = MAX(-4, MIN(4, mouse->last_y + get_xy[(random >> 3) & 0x7])); + mouse->n = (random>>6) & 0x3; + + new_x = mouse->x + mouse->last_x; + new_y = mouse->y + mouse->last_y; + + mouse->tx_urb->buffer[1] = mouse->last_x; + mouse->x = new_x; + mouse->tx_urb->buffer[2] = mouse->last_y; + mouse->y = new_y; + + } + else if ((mouse->n)&1) { + mouse->n--; + } + else { + mouse->n--; + mouse->tx_urb->buffer[1] = mouse->last_x; + mouse->x = new_x; + mouse->tx_urb->buffer[2] = mouse->last_y; + mouse->y = new_y; + } + + mouse->tx_urb->actual_length = 4; + usbd_start_in_urb(mouse->tx_urb); +} + +/*! + * mouse_urb_sent() - called to indicate URB transmit finished + * This function is the callback function for sent urbs. + * It simply queues up another urb until the packets to be sent + * configuration parameter is reached (or forever if zero.) + * @param urb The urb to be sent. + * @param rc result + * @return int Return non-zero for failure. + */ +static int mouse_urb_sent (struct usbd_urb *urb, int rc) +{ + struct mouse_private *mouse = &mouse_private; + struct usbd_function_instance *function = urb->function_instance; + + RETURN_ZERO_IF(usbd_get_device_status(function) == USBD_CLOSING); + RETURN_ZERO_IF(usbd_get_device_status(function) != USBD_OK); + RETURN_ZERO_IF(usbd_get_device_state(function) != STATE_CONFIGURED); + + TRACE_MSG2(MOUSE, "mouse_count: %d urb status: %d", mouse_count, urb->status); + + #ifndef CONFIG_OTG_MOUSE_STALL + #ifdef CONFIG_OTG_MOUSE_PACKETS + TRACE_MSG0(MOUSE," Normal routine"); + RETURN_ZERO_IF (CONFIG_OTG_MOUSE_PACKETS && (mouse_count++ > CONFIG_OTG_MOUSE_PACKETS)); + mouse_fd_send(function); // re-send + return 0; + #endif /* CONFIG_OTG_MOUSE_PACKETS */ + #else /* CONFIG_OTG_MOUSE_STALL */ + + if (mouse_count++ < 7) { + mouse_fd_send(function); // re-send + } + else { + usbd_halt_endpoint(function, BULK_INT); + mouse_count = 0; + TRACE_MSG0(MOUSE," MOUSE STALL"); + } + #endif /* CONFIG_OTG_MOUSE_STALL */ + return 0; +} + +/* USB Device Functions ************************************************************************ */ + +typedef enum mesg { + mesg_unknown, + mesg_configured, + mesg_reset, +} mesg_t; +mesg_t mouse_last_mesg; + +char * mouse_messages[3] = { + "", + "Mouse Configured", + "Mouse Reset", +}; +/*! mouse_check_mesg - + * @param curr_mesg - + */ +void mouse_check_mesg(mesg_t curr_mesg) +{ + RETURN_UNLESS(mouse_last_mesg != curr_mesg); + mouse_last_mesg = curr_mesg; + otg_message(mouse_messages[curr_mesg]); +} + +/*! + * mouse_event_handler() - process a device event + * This function is called to process USB Device Events. + * + * The DEVICE_CONFIGURED event causes the mouse_fd_send() to be called + * to start the data flow. + * + * The DEVICE_RESET or DEVICE_DE_CONFIGURED events cause the outstanding + * transmit urb to be cancelled. + * + * @param function the function instance + * @param event the event + * @param data + * @return void + * + */ +void mouse_event_handler (struct usbd_function_instance *function, usbd_device_event_t event, int data) +{ + struct mouse_private *mouse = &mouse_private; + + switch (event) { + case DEVICE_CONFIGURED: + + TRACE_MSG0(MOUSE, "Mouse Configured"); + mouse_check_mesg(mesg_configured); + mouse_count = 0; + mouse->connected = 1; + if (!(mouse->tx_urb = usbd_alloc_urb (function, BULK_INT, 4, mouse_urb_sent))) + printk(KERN_INFO"%s: alloc failed\n", __FUNCTION__); + mouse_fd_send(function); // start sending + break; + + case DEVICE_RESET: + TRACE_MSG0(MOUSE, "Mouse Reset"); + case DEVICE_DE_CONFIGURED: + TRACE_MSG0(MOUSE, "Mouse De-Configured"); + mouse_check_mesg(mesg_reset); + BREAK_IF(!mouse->connected); + mouse->connected = 0; + if (mouse->tx_urb) { + usbd_free_urb (mouse->tx_urb); + mouse->tx_urb = NULL; + } + break; + default: + break; + } +} + +/*! copy_report() + * This function copies the Mouse HID report into the provided URB. + * @param urb Destination + * @param data Source + * @param size Amount of data to copy. + * @param max_buf Size of buffer + * @return Non-zero if error. + * + */ +static int copy_report (struct usbd_urb *urb, void *data, int size, int max_buf) +{ + int available; + int length; + + RETURN_EINVAL_IF (!urb); + RETURN_EINVAL_IF (!data); + RETURN_EINVAL_IF (!(length = size)); + RETURN_EINVAL_IF ((available = max_buf - urb->actual_length) <= 0); + + length = (length < available) ? length : available; + memcpy (urb->buffer + urb->actual_length, data, length); + urb->actual_length += length; + return 0; +} + +/*! + * mouse_fd_send_hid() - send an EP0 urb containing HID report + * This is called to send the Mouse HID report. + */ +static int mouse_fd_send_hid (struct usbd_function_instance *function) +{ + struct mouse_private *mouse = &mouse_private; + struct usbd_urb *urb = usbd_alloc_urb_ep0(function, mouse->wLength, NULL); + int wMaxPacketSize = usbd_endpoint_zero_wMaxPacketSize(urb->function_instance, 0); + + TRACE_MSG1(MOUSE, "Send Hid wLength: %d", mouse->wLength); + RETURN_EINVAL_IF (copy_report(urb, MouseHIDReport, sizeof(MouseHIDReport), mouse->wLength)); + RETURN_EINVAL_UNLESS (wMaxPacketSize); + + if (!(urb->actual_length % wMaxPacketSize) && (urb->actual_length < mouse->wLength)) + urb->flags |= USBD_URB_SENDZLP; + + RETURN_ZERO_IF(!usbd_start_in_urb(urb)); + usbd_free_urb(urb); + return -EINVAL; +} + +#ifdef CONFIG_OTG_MOUSE_BH +/*! + * mouse_fd_hid_bh() - Bottom half handler to send a HID report + * @param data + */ +static void mouse_fd_hid_bh (void *data) +{ + struct usbd_function_instance *function = mouse_private.function; + mouse_fd_send_hid(function); +} + +/*! mouse_schedule_bh - schedule a call for mouse_fd_hid_bh + * @param void + */ +static int mouse_schedule_bh (struct usbd_function_instance *function) +{ + TRACE_MSG0(MOUSE, "Scheduling"); + SET_WORK_ARG(&mouse_private.notification_bh, (void)function) + return (!schedule_task (&mouse_private.notification_bh)) ? EINVAL : 0; +} +#endif /* CONFIG_OTG_MOUSE_BH */ + +/*! + * mouse_device_request - called to indicate urb has been received + * @param function + * @param request + */ +int mouse_device_request (struct usbd_function_instance *function, struct usbd_device_request *request) +{ + /* verify that this is a usb class request per cdc-mouse specification or a vendor request. + * determine the request direction and process accordingly + */ + + switch (request->bmRequestType & (USB_REQ_DIRECTION_MASK | USB_REQ_TYPE_MASK)) { + + case USB_REQ_HOST2DEVICE: + case USB_REQ_HOST2DEVICE | USB_REQ_TYPE_CLASS: + case USB_REQ_HOST2DEVICE | USB_REQ_TYPE_VENDOR: + return 0; + + case USB_REQ_DEVICE2HOST : + case USB_REQ_DEVICE2HOST | USB_REQ_TYPE_CLASS: + case USB_REQ_DEVICE2HOST | USB_REQ_TYPE_VENDOR: + + switch (request->bRequest) { + case USB_REQ_GET_DESCRIPTOR: + switch (le16_to_cpu(request->wValue)>>8) { + case HID_REPORT: + mouse_private.wLength = request->wLength; + #ifdef CONFIG_OTG_MOUSE_BH + return mouse_schedule_bh(function); + #else + return mouse_fd_send_hid(function); + #endif + } + default: break; + } + break; + + default: + break; + } + return -EINVAL; +} + + +/*! mouse_function_enable - called by USB Device Core to enable the driver + * @param function The function instance for this driver to use. + * @return non-zero if error. + */ +static int mouse_function_enable (struct usbd_function_instance *function) +{ + struct mouse_private *mouse = &mouse_private; + + // XXX MODULE LOCK HERE + mouse->n = 0; + mouse->x = 0; + mouse->y = 0; + mouse->last_x = 0; + mouse->last_y = 0; + mouse->writesize = usbd_endpoint_wMaxPacketSize(function, BULK_INT, 0); + return 0; +} + +/*! mouse_function_disable - called by the USB Device Core to disable the driver + * @param function The function instance for this driver + */ +static void mouse_function_disable (struct usbd_function_instance *function) +{ + struct mouse_private *mouse = &mouse_private; + mouse->writesize = 0; + // XXX MODULE UNLOCK HERE +} + +static void mouse_endpoint_cleared (struct usbd_function_instance *function, int bEndpointAddress) +{ + + TRACE_MSG1(MOUSE,"CLEARED bEndpointAddress: %02x", bEndpointAddress); + +} + + +/* ********************************************************************************************* */ +#if !defined(OTG_C99) +/*! function_ops - operations table for the USB Device Core + */ +static struct usbd_function_operations mouse_function_ops; + +/*! mouse_simple_driver - USB Device Core function driver definition + */ +struct usbd_simple_driver mouse_simple_driver; +/*! mouse_ops_init - initialize mouse ops + */ +void mouse_ops_init(void) +{ + /*! function_ops - operations table for the USB Device Core + */ + ZERO(mouse_function_ops); + mouse_function_ops.event_handler = mouse_event_handler; /*! called for each USB Device Event */ + mouse_function_ops.device_request = mouse_device_request; /*! called for each received device request */ + mouse_function_ops.function_enable = mouse_function_enable; /*! called to enable the function driver */ + mouse_function_ops.function_disable = mouse_function_disable; /*! called to disable the function driver */ + mouse_function_ops.endpoint_cleared = mouse_endpoint_cleared; + + /*! simple_driver - USB Device Core function driver definition + */ + ZERO(mouse_simple_driver); + mouse_simple_driver.driver.name = "mouse-random"; /*! driver name */ + mouse_simple_driver.driver.fops = &mouse_function_ops; /*! operations table */ + mouse_simple_driver.device_description = &mouse_fd_device_description; /*! mouse device description */ + mouse_simple_driver.bNumConfigurations = + sizeof (mouse_fd_configuration_description) / sizeof (struct usbd_configuration_description); + mouse_simple_driver.configuration_description = + mouse_fd_configuration_description; /*! mouse configuration description */ + //mouse_simple_driver.idVendor = __constant_cpu_to_le16(CONFIG_OTG_MOUSE_VENDORID); + //mouse_simple_driver.idProduct = __constant_cpu_to_le16(CONFIG_OTG_MOUSE_PRODUCTID); + //mouse_simple_driver.bcdDevice = __constant_cpu_to_le16(CONFIG_OTG_MOUSE_BCDDEVICE); + + + mouse_simple_driver.interfaces = sizeof (mouse_interfaces) / sizeof (struct usbd_interface_description); + mouse_simple_driver.interface_list = mouse_interfaces; + mouse_simple_driver.endpointsRequested = ENDPOINTS; + mouse_simple_driver.requestedEndpoints = mouse_endpoint_requests; +} +#else /* defined(OTG_C99) */ +/*! struct usbd_function_operations mouse_function_ops - operations table for the USB Device Core + */ +static struct usbd_function_operations mouse_function_ops = { + .event_handler = mouse_event_handler, /*!< called for each USB Device Event */ + .device_request = mouse_device_request, /*!< called for each received device request */ + .function_enable = mouse_function_enable, /*!< called to enable the function driver */ + .function_disable = mouse_function_disable, /*!< called to disable the function driver */ + .endpoint_cleared = mouse_endpoint_cleared, +}; + +/*! struct usbd_simple_driver mouse_simple_driver - USB Device Core function driver definition + */ +struct usbd_simple_driver mouse_simple_driver = { + .driver = { + .name = "mouse-random", + .fops = &mouse_function_ops, }, + .device_description = &mouse_fd_device_description, /*!< mouse device description */ + .bNumConfigurations = sizeof (mouse_fd_configuration_description) / sizeof (struct usbd_configuration_description), + .configuration_description = mouse_fd_configuration_description, /*!< mouse configuration description */ + .interfaces = sizeof (mouse_interfaces) / sizeof (struct usbd_interface_description), + .interface_list = mouse_interfaces, + .endpointsRequested = ENDPOINTS, + .requestedEndpoints = mouse_endpoint_requests, +}; +#endif /* defined(OTG_C99) */ + + + +/* Module Parameters ******************************************************** */ +/* ! + * @name XXXXX MODULE Parameters + */ +/* ! @{ */ + +MOD_AUTHOR ("sl@belcarra.com"); +EMBED_LICENSE(); +MOD_DESCRIPTION ("Belcarra Random Walk MOUSE Function"); + +static u32 vendor_id; /*!< override built-in vendor ID */ +static u32 product_id; /*!< override built-in product ID */ + +MOD_PARM (vendor_id, "i"); +MOD_PARM (product_id, "i"); + +MOD_PARM_DESC (vendor_id, "Device Vendor ID"); +MOD_PARM_DESC (product_id, "Device Product ID"); + +otg_tag_t MOUSE; +/* ! *} */ + +/* USB Module init/exit ***************************************************** */ + +/*! + * mouse_modinit() - module init + * + * This is called by the Linux kernel; either when the module is loaded + * if compiled as a module, or during the system intialization if the + * driver is linked into the kernel. + * + * This function will parse module parameters if required and then register + * the mouse driver with the USB Device software. + * + * If the CONFIG_OTG_MOUSE_BH option is enabled it will also setup the mouse + * bottom half handler. + * + */ +static int mouse_modinit (void) +{ + printk (KERN_INFO "%s: vendor_id: %04x product_id: %04x\n", __FUNCTION__, vendor_id, product_id); + + #if !defined(OTG_C99) + mouse_global_init(); + mouse_ops_init(); + #endif /* defined(OTG_C99) */ + + MOUSE = otg_trace_obtain_tag(NULL, "mouse-fd"); + TRACE_MSG2(MOUSE, "vendor_id: %04x product_id: %04x",vendor_id, product_id); + + mouse_fd_hid.wItemLength = cpu_to_le16(0x34); // XXX mips compiler bug..... + + // register as usb function driver + THROW_IF (usbd_register_simple_function (&mouse_simple_driver, "mouse", NULL), error); + TRACE_MSG0(MOUSE, "ok"); + #ifdef CONFIG_OTG_MOUSE_BH + mouse_private.notification_bh.routine = mouse_fd_hid_bh; + mouse_private.notification_bh.data = NULL; + #endif + CATCH(error) { + otg_trace_invalidate_tag(MOUSE); + return -EINVAL; + } + return 0; +} + +module_init (mouse_modinit); + +#if OTG_EPILOGUE +/*! + * mouse_modexit() - module init + * + * This is called by the Linux kernel; when the module is being unloaded + * if compiled as a module. This function is never called if the + * driver is linked into the kernel. + * + * @param void + * @return void + */ +static void mouse_modexit (void) +{ + #ifdef CONFIG_OTG_MOUSE_BH + while (PENDING_WORK_ITEM(mouse_private.notification_bh)) { + printk(KERN_ERR"%s: waiting for bh\n", __FUNCTION__); + schedule_timeout(10 * HZ); + } + #endif + usbd_deregister_simple_function (&mouse_simple_driver); + + otg_trace_invalidate_tag(MOUSE); +} + +module_exit (mouse_modexit); +#endif + + +#endif /* defined(CONFIG_OTG_LNX) */ diff --git a/drivers/otg/functions/mouse/mouse-if.c b/drivers/otg/functions/mouse/mouse-if.c new file mode 100644 index 000000000000..52b7a4684095 --- /dev/null +++ b/drivers/otg/functions/mouse/mouse-if.c @@ -0,0 +1,1032 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/functions/mouse/mouse-if.c + * @(#) sl@belcarra.com/whiskey.enposte.net|otg/functions/mouse/mouse-if.c|20070810225217|38889 + * + * Copyright (c) 2003-2005 Belcarra + * Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + * + * By: + * Stuart Lynne <sl@belcarra.com>, + * Bruce Balden <balden@belcarra.com> + * + */ + +/*! + * @defgroup MouseInterface Mouse Interface Function + * @ingroup InterfaceFunctions + */ +/*! + * @file otg/functions/mouse/mouse-if.c + * @brief Mouse Interface Function Driver protocol implementation. + * + * This file implements the Mouse HID protocols and will generate + * random mouse data on the INTERRUPT endpoint. + * + * The primary purpose of this driver is to demonstrate how to + * implement a simple function driver and to provide a simple + * uni-directional driver for testing USB Device peripheral drivers. + * + * The mouse driver has several other characteristics to allow testing of + * other features of USB Device peripheral drivers: + * + * - ep0 delayed CONTROL READ + * + * The delayed CONTROL READ test verifies that that USB Device peripheral + * drivers will correctly NAK until data is provide for device requests. + * This is done by (optionally) delaying the HID report via a bottom half + * handler. The device request returns normally and the peripheral driver + * must properly recognize that while the device request had a non-zero + * wLength field, there is currently no queued urb. + * + * When the bottom half handler is scheduled the HID report urb will be + * queued on endpoint zero and then returned to the host. + * + * The following configuration options are available: + * + * - CONFIG_OTG_MOUSE_PACKETS is used to set the number of mouse + * data reports to send. Set to zero for a continous stream of data. + * + * - CONFIG_OTG_MOUSE_INTERVAL sets the polling interval for the + * interrupt endpoint + * + * - CONFIG_OTG_BH configures a separate bottom half handler to + * respond to HID device requests + * + * + * @ingroup MouseInterface + */ + +#include <otg/otg-compat.h> +#include <otg/otg-module.h> + +#include <otg/usbp-chap9.h> +#include <otg/usbp-hid.h> +#include <otg/usbp-func.h> +#include <otg/otg-trace.h> +#include <otg/otg-api.h> + + +#define MOUSE mouse_if_trace_tag +extern otg_tag_t MOUSE; + +#ifdef CONFIG_OTG_MOUSE_BH +atomic_t mouse_if_bh_active; +#endif /* CONFIG_OTG_MOUSE_BH */ + +static int Mouse_Interfaces_Active = 0; + +/*!@struct mouse_if_private mouse-if.c otg/functions/mouse/mouse-if.c + * @brief The mouse_if_private structure is used to collect all of the mouse driver + * global variables into one place. + */ +struct mouse_if_private { + +#ifdef CONFIG_OTG_MOUSE_BH + struct WORK_STRUCT notification_bh; +#endif /* CONFIG_OTG_MOUSE_BH */ + + u8 interface_index; + u8 bEndpointAddress; + + unsigned int writesize; /*!< packetsize * 4 */ + int x; + int y; + int last_x; + int last_y; + int n; + int mouse_count; + + int wLength; + u16 pending_report; + u8 idle; + u8 duration; + u16 protocol; + + int mouse_interface_number; +}; + +/*! + * @name Mouse Descriptors + * + * MouseHIDReport + * MOUSE Configuration + * + * Endpoint, Class, Interface, Configuration and Device descriptors/descriptions + * + * @{ + */ + +/*! + * MouseHIDReport + * This is the HID report returned for the HID Device Request. + * There is no pre-defined descriptor type for this. + */ +static char MouseHIDReport[52] = { + 0x05, 0x01, /*!< USAGE_PAGE (Generic Desktop) */ + 0x09, 0x02, /*!< USAGE (Mouse) */ + 0xa1, 0x01, /*!< COLLECTION (Application) */ + 0x09, 0x01, /*!< USAGE (Pointer) */ + 0xa1, 0x00, /*!< COLLECTION (Physical) */ + 0x05, 0x09, /*!< USAGE_PAGE (Button) */ + 0x19, 0x01, /*!< USAGE_MINIMUM (Button 1) */ + 0x29, 0x03, /*!< USAGE_MAXIMUM (Button 3) */ + 0x15, 0x00, /*!< LOGICAL_MINIMUM (0) */ + 0x25, 0x01, /*!< LOGICAL_MAXIMUM (1) */ + 0x95, 0x03, /*!< REPORT_COUNT (3) */ + 0x75, 0x01, /*!< REPORT_SIZE (1) */ + 0x81, 0x02, /*!< INPUT (Data,Var,Abs) */ + 0x95, 0x01, /*!< REPORT_COUNT (1) */ + 0x75, 0x05, /*!< REPORT_SIZE (5) */ + 0x81, 0x03, /*!< INPUT (Cnst,Var,Abs) */ + 0x05, 0x01, /*!< USAGE_PAGE (Generic Desktop) */ + 0x09, 0x30, /*!< USAGE (X) */ + 0x09, 0x31, /*!< USAGE (Y) */ + 0x09, 0x38, /*!< USAGE (WHEEL) */ + 0x15, 0x81, /*!< LOGICAL_MINIMUM (-127) */ + 0x25, 0x7f, /*!< LOGICAL_MAXIMUM (127) */ + 0x75, 0x08, /*!< REPORT_SIZE (8) */ + 0x95, 0x03, /*!< REPORT_COUNT (3) */ + 0x81, 0x06, /*!< INPUT (Data,Var,Rel) */ + 0xc0, /*!< END_COLLECTION */ + 0xc0 /*!< END_COLLECTION */ +}; + + +#define BULK_INT 0x00 /*!< Interrupt endpoint number */ +#define ENDPOINTS 0x01 /*!< Number of endpoints required */ + +/*! List of required endpoint numbers + */ +static u8 mouse_if_index[] = { BULK_INT, }; + +/*! List of requested endpoints + */ +#ifndef CONFIG_OTG_MOUSE_INTERVAL +#define CONFIG_OTG_MOUSE_INTERVAL 1 +#endif /*CONFIG_OTG_MOUSE_INTERVAL*/ +static struct usbd_endpoint_request mouse_if_endpoint_requests[ENDPOINTS+1] = { + { BULK_INT, 1, 0, 0, USB_DIR_IN | USB_ENDPOINT_INTERRUPT, 16, 64, CONFIG_OTG_MOUSE_INTERVAL, }, + { 0, }, +}; + + +#if defined(CONFIG_OTG_NOC99) + +/*! This is the HID desccriptor. + */ +static struct hid_descriptor mouse_if_hid; + +/*! List of class descriptors + */ +static struct usbd_generic_class_descriptor *mouse_if_hid_descriptors[] = { + (struct usbd_generic_class_descriptor *)&mouse_if_hid, }; + + +/*! Interface Descriptions + */ +static struct usbd_alternate_description mouse_if_data_alternate_descriptions[1]; + +/*! List of Interface description(s) + */ +static struct usbd_interface_description mouse_interfaces[1]; + + + +/*! mouse_if_global_init - + */ +void mouse_if_global_init(void) +{ + /*! This is the HID desccriptor. + */ + ZERO(mouse_if_hid); + mouse_if_hid.bLength = 0x09; + mouse_if_hid.bDescriptorType = HID_DT_HID; + mouse_if_hid.bcdHID = __constant_cpu_to_le16(0x101); + mouse_if_hid.bCountryCode = 0x00; + mouse_if_hid.bNumDescriptors = 0x01; + mouse_if_hid.bReportType = HID_DT_REPORT; + mouse_if_hid.wItemLength = __constant_cpu_to_le16(sizeof(MouseHIDReport)); + + + /*! Interface Descriptions + */ + ZERO(mouse_if_data_alternate_descriptions); + + mouse_if_data_alternate_descriptions[0].iInterface = "Random Mouse Interface - Interrupt"; + //mouse_if_data_alternate_descriptions[0].interface_descriptor = &mouse_if_data_alternate_descriptor; + mouse_if_data_alternate_descriptions[0].classes = + sizeof (mouse_if_hid_descriptors) / sizeof (struct usbd_generic_class_descriptor *); + mouse_if_data_alternate_descriptions[0].class_list = mouse_if_hid_descriptors; + mouse_if_data_alternate_descriptions[0].endpoints = 0x1; + mouse_if_data_alternate_descriptions[0].endpoint_index = mouse_if_index; + + mouse_if_data_alternate_descriptions[0].bInterfaceClass = 0x03; + mouse_if_data_alternate_descriptions[0].bInterfaceSubClass = 0x01; + mouse_if_data_alternate_descriptions[0].bInterfaceProtocol = 0x02; + + /*! List of Interface description(s) + */ + ZERO(mouse_interfaces); + mouse_interfaces[0].alternates = sizeof (mouse_if_data_alternate_descriptions) / sizeof (struct usbd_alternate_description); + mouse_interfaces[0].alternate_list = mouse_if_data_alternate_descriptions; +} +#else /* defined(CONFIG_OTG_NOC99) */ +/*! + * mouse_if_hid + * This is the HID desccriptor. + */ +struct hid_descriptor mouse_if_hid = { + .bLength = 0x09, + .bDescriptorType = HID_DT_HID, + .bcdHID = __constant_cpu_to_le16(0x101), + + .bCountryCode = 0x00, + .bNumDescriptors = 0x01, + .bReportType = HID_DT_REPORT, + + .wItemLength = __constant_cpu_to_le16(sizeof(MouseHIDReport)), +}; + +/*! + * mouse_if_hid_descriptors + * List of class descriptors + */ +static struct usbd_generic_class_descriptor *mouse_if_hid_descriptors[] = { + (struct usbd_generic_class_descriptor *)&mouse_if_hid, }; + +/*! + * mouse_if_data_alternate_descriptions + * Interface Descriptions + */ +static struct usbd_alternate_description mouse_if_data_alternate_descriptions[] = { + { + /* This string is 31 bytes long which will result in 64 bytes string + * useful for testing zlp device requests + * + * 0123456789abcdef0123456789abcdef + * | | + */ + .iInterface = "Belcarra Random Mouse Interface", + .bInterfaceClass = 0x03, + .bInterfaceSubClass = 0x01, + .bInterfaceProtocol = 0x02, + .classes = sizeof (mouse_if_hid_descriptors) / sizeof (struct usbd_generic_class_descriptor *), + .class_list = mouse_if_hid_descriptors, + .endpoints = sizeof (mouse_if_index) / sizeof(u8), + .endpoint_index = mouse_if_index, + }, +}; + +/*! + * mouse_interfaces + * List of Interface description(s) + */ +static struct usbd_interface_description mouse_interfaces[] = { + { .alternates = sizeof (mouse_if_data_alternate_descriptions) / sizeof (struct usbd_alternate_description), + .alternate_list = mouse_if_data_alternate_descriptions,}, +}; + +#endif /* defined(CONFIG_OTG_NOC99) */ + +/*! @} */ + +/* MOUSE ***************************************************************************************** */ +/*! + * @name Transmit Function + * + * @{ + */ + +/*! + * get_xy + * Random walk array + */ +static int get_xy[8] = { + 0, 0, 1, 1, + 0, 0, -1, -1, +}; + + + +extern void mouse_if_send(struct usbd_function_instance *function); + +/*! + * @brief mouse_urb_sent() - called to indicate URB transmit finished + * This function is the callback function for sent urbs. + * + * It will continue to queue new urbs until either there is an error or the + * maximum count is exceeded. + * + * @param tx_urb The urb to be sent. + * @param rc result + * @return int Return non-zero for failure. + */ +static int mouse_urb_sent (struct usbd_urb *tx_urb, int rc) +{ + struct usbd_function_instance *function = tx_urb->function_instance; + struct usbd_interface_instance *interface_instance = (struct usbd_interface_instance *)function; + struct mouse_if_private *mouse = interface_instance->function.privdata; + + TRACE_MSG4(MOUSE, "mouse_count[%d]: count: %d status: %d state: %d", + interface_instance->wIndex, mouse->mouse_count, + usbd_get_device_status(function), usbd_get_device_state(function)); + // return non-zero to get tx_urb deallocated + RETURN_EINVAL_IF(usbd_get_device_status(function) == USBD_CLOSING); + RETURN_EINVAL_IF(usbd_get_device_status(function) != USBD_OK); + RETURN_EINVAL_IF(usbd_get_device_state(function) != STATE_CONFIGURED); + + //TRACE_MSG2(MOUSE, "mouse_count[%d]: %d", interface_instance->wIndex, mouse->mouse_count); + usbd_free_urb(tx_urb); + #ifdef CONFIG_OTG_MOUSE_PACKETS + RETURN_ZERO_IF (CONFIG_OTG_MOUSE_PACKETS && (mouse->mouse_count++ > CONFIG_OTG_MOUSE_PACKETS)); + #endif /* CONFIG_OTG_MOUSE_PACKETS */ + mouse_if_send(function); // re-start + return 0; +} +/*! + * mouse_if_send() - send a mouse data urb with random data + * + * Queue a new data urb to send small mouse movement to host. + * + * @param function + * @return void + */ +void mouse_if_send(struct usbd_function_instance *function) +{ + struct usbd_interface_instance *interface_instance = (struct usbd_interface_instance *)function; + struct mouse_if_private *mouse = interface_instance->function.privdata; + int new_x = 0; + int new_y = 0; + u8 random; + + struct usbd_urb *tx_urb; + + tx_urb = usbd_alloc_urb (function, BULK_INT, 4, mouse_urb_sent); + RETURN_UNLESS(tx_urb); + + memset(tx_urb->buffer, 0, 4); + + /* when n is zero, generate new directional movement, and new n, from random data */ + UNLESS (mouse->n) { + otg_get_random_bytes(&random, 1); + + mouse->last_x = MAX(-4, MIN(4, mouse->last_x + get_xy[random & 0x7])); + mouse->last_y = MAX(-4, MIN(4, mouse->last_y + get_xy[(random >> 3) & 0x7])); + mouse->n = (random>>6) & 0x3; + + new_x = mouse->x + mouse->last_x; + new_y = mouse->y + mouse->last_y; + + if (Mouse_Interfaces_Active > 1) { + if (mouse->mouse_interface_number & 0x1) + tx_urb->buffer[1] = mouse->last_x; + else + tx_urb->buffer[2] = mouse->last_y; + } + else { + tx_urb->buffer[1] = mouse->last_x; + tx_urb->buffer[2] = mouse->last_y; + + } + mouse->x = new_x; + mouse->y = new_y; + } + /* when n is odd, simply send zero's */ + else if (mouse->n & 0x1) { + mouse->n--; + } + /* when n is non-zero and not odd, send directional movement */ + else { + mouse->n--; + tx_urb->buffer[1] = mouse->last_x; + tx_urb->buffer[2] = mouse->last_y; + mouse->x = new_x; + mouse->y = new_y; + } + tx_urb->actual_length = 4; + TRACE_MSG0(MOUSE, "STARTING"); + RETURN_UNLESS(usbd_start_in_urb(tx_urb)); + TRACE_MSG0(MOUSE, "FAILED"); + usbd_free_urb(tx_urb); +} + +/*! @} */ + +/* USB Device Functions ************************************************************************ */ + + +/*! copy_report() + * This function copies the Mouse HID report into the provided URB. + * @param urb Destination + * @param data Source + * @param size Amount of data to copy. + * @param max_buf Size of buffer + * @return Non-zero if error. + * + */ +static int copy_report (struct usbd_urb *urb, void *data, int size, int max_buf) +{ + int available; + int length; + + RETURN_EINVAL_IF (!urb); + RETURN_EINVAL_IF (!data); + RETURN_EINVAL_IF (!(length = size)); + RETURN_EINVAL_IF ((available = max_buf - urb->actual_length) <= 0); + + TRACE_MSG3(MOUSE, "max_buf: %d actual: %d available: %d", max_buf, urb->actual_length, available); + length = (length < available) ? length : available; + memcpy (urb->buffer + urb->actual_length, data, length); + urb->actual_length += length; + return 0; +} + +/*! + * mouse_if_send_hid_descriptor() - send an EP0 urb containing HID descriptor + * This is called to send the Mouse HID report. + * + * This will satisfy a device request from the host. + * + * @param function - pointer to function instance + * @return int + */ +static int mouse_if_send_hid_descriptor (struct usbd_function_instance *function) +{ + struct usbd_interface_instance *interface_instance = (struct usbd_interface_instance *)function; + struct mouse_if_private *mouse = interface_instance->function.privdata; + struct usbd_urb *urb = usbd_alloc_urb_ep0(function, mouse->wLength, NULL); + int hs = usbd_high_speed(function); + int wMaxPacketSize = usbd_endpoint_zero_wMaxPacketSize(function, hs); + + TRACE_MSG2(MOUSE, "Send Hid wLength: %d wMaxPacketSize: %d", mouse->wLength, wMaxPacketSize); + TRACE_MSG0(MOUSE, "AAAA"); + RETURN_EINVAL_IF (copy_report(urb, (u8 *)&mouse_if_hid, sizeof(mouse_if_hid), mouse->wLength)); + TRACE_MSG1(MOUSE, "BBBB actual: %d", urb->actual_length); + RETURN_EINVAL_UNLESS (wMaxPacketSize); + TRACE_MSG0(MOUSE, "BBBB"); + + if (!(urb->actual_length % wMaxPacketSize) && (urb->actual_length < mouse->wLength)) + urb->flags |= USBD_URB_SENDZLP; + + RETURN_ZERO_IF(!usbd_start_in_urb(urb)); + TRACE_MSG0(MOUSE, "CCCC"); + usbd_free_urb(urb); + return -EINVAL; +} + +/*! + * mouse_if_send_hid_report() - send an EP0 urb containing HID report + * This is called to send the Mouse HID report. + * + * This will satisfy a device request from the host. + * + * @param function - pointer to function instance + * @return int + */ +static int mouse_if_send_hid_report (struct usbd_function_instance *function) +{ + struct usbd_interface_instance *interface_instance = (struct usbd_interface_instance *)function; + struct mouse_if_private *mouse = interface_instance->function.privdata; + struct usbd_urb *urb = usbd_alloc_urb_ep0(function, mouse->wLength, NULL); + int hs = usbd_high_speed(function); + int wMaxPacketSize = usbd_endpoint_zero_wMaxPacketSize(function, hs); + + TRACE_MSG1(MOUSE, "Send Hid wLength: %d", mouse->wLength); + TRACE_MSG0(MOUSE, "AAAA"); + RETURN_EINVAL_IF (copy_report(urb, MouseHIDReport, sizeof(MouseHIDReport), mouse->wLength)); + TRACE_MSG0(MOUSE, "BBBB"); + RETURN_EINVAL_UNLESS (wMaxPacketSize); + TRACE_MSG0(MOUSE, "BBBB"); + + if (!(urb->actual_length % wMaxPacketSize) && (urb->actual_length < mouse->wLength)) + urb->flags |= USBD_URB_SENDZLP; + + RETURN_ZERO_IF(!usbd_start_in_urb(urb)); + TRACE_MSG0(MOUSE, "CCCC"); + usbd_free_urb(urb); + return -EINVAL; +} + +#ifdef CONFIG_OTG_MOUSE_BH +/*! + * @brief mouse_if_hid_bh() - Bottom half handler to send a HID report + * Bottom half handler. + * + * Ssne device request response from bottom half. This tests + * delayed device request IN responses. + * + * @param data + * @return none + */ +static void mouse_if_hid_bh (void *data) +{ + struct usbd_function_instance *function = (struct usbd_function_instance *)data; + struct usbd_interface_instance *interface_instance = (struct usbd_interface_instance *)function; + mouse_if_send_hid_report(function); + atomic_inc(&mouse_if_bh_active); +} + +/*! mouse_schedule_bh - schedule a call for mouse_if_hid_bh + * This will schedule the mouse bottom half handler. + * + * @param void + * @return 0 on success + */ +static int mouse_schedule_bh (struct usbd_function_instance *function) +{ + struct usbd_interface_instance *interface_instance = (struct usbd_interface_instance *)function; + struct mouse_if_private *mouse = interface_instance->function.privdata; + + TRACE_MSG0(MOUSE, "Scheduling"); + TRACE_MSG0(MOUSE, "AAAA"); + PREPARE_WORK_ITEM(mouse->notification_bh, mouse_if_hid_bh, (void *)function); + atomic_set(&mouse_if_bh_active, 1); + SCHEDULE_WORK(mouse->notfication_bh); + TRACE_MSG0(MOUSE, "BBBB"); + return 0; +} +#endif /* CONFIG_OTG_MOUSE_BH */ + +/*! + * @brief mouse_report_received() - called to indicate URB transmit finished + * This function is the callback function for sent urbs. + * It simply queues up another urb until the packets to be sent + * configuration parameter is reached (or forever if zero.) + * @param rx_urb The urb to be sent. + * @param rc result + * @return int Return non-zero for failure. + */ +int mouse_report_received (struct usbd_urb *rx_urb, int rc) +{ + struct usbd_function_instance *function; + struct usbd_interface_instance *interface_instance; + struct mouse_if_private *mouse; + function = rx_urb->function_instance; + interface_instance = (struct usbd_interface_instance *)function; + mouse = interface_instance->function.privdata; + TRACE_MSG3(MOUSE, "REPORT[%02x]: len: %d buf[0]: %x", + mouse->pending_report, rx_urb->actual_length, rx_urb->buffer[0]); + usbd_free_urb(rx_urb); + mouse->pending_report = 0; + return 0; +} + + +/*! + * mouse_if_device_request - called to process a request to endpoint or interface + * Process a device request. + * + * The HID host sends various device requests. We only support a limited + * set. + * + * @param function + * @param request + * @return non-zero for failure, will cause endpoint zero stall + */ +static int mouse_if_device_request (struct usbd_function_instance *function, struct usbd_device_request *request) +{ + struct usbd_interface_instance *interface_instance = (struct usbd_interface_instance *)function; + struct mouse_if_private *mouse = interface_instance->function.privdata; + struct usbd_urb *urb = NULL; + + TRACE_MSG6(MOUSE, "Interface[%02d] bmRequestType: %02x bRequest: %02x wValue: %04x wIndex: %04x wLength: %04x", + interface_instance->wIndex, request->bmRequestType, request->bRequest, request->wValue, + request->wIndex, request->wLength); + + /* HID only sends requests to interface. + */ + TRACE_MSG0(MOUSE, "CHECK RECIP"); + RETURN_EINVAL_UNLESS(USB_REQ_RECIPIENT_INTERFACE == (request->bmRequestType & USB_REQ_RECIPIENT_MASK)); + + + TRACE_MSG0(MOUSE, "RECIP OK"); + + switch (request->bmRequestType & USB_REQ_DIRECTION_MASK) { + case USB_REQ_DEVICE2HOST: + TRACE_MSG0(MOUSE, "DEVICE2HOST"); + switch (request->bRequest) { + case USB_REQ_GET_DESCRIPTOR: + TRACE_MSG0(MOUSE, "GET DESCRIPTOR"); + switch (le16_to_cpu(request->wValue)>>8) { + case HID_DT_HID: + mouse->wLength = request->wLength; + return mouse_if_send_hid_descriptor(function); + + case HID_DT_REPORT: + TRACE_MSG0(MOUSE, "GET DESCRIPTOR HID REPORT"); + mouse->wLength = le16_to_cpu(request->wLength); + #ifdef CONFIG_OTG_MOUSE_BH + return mouse_schedule_bh(function); + #else + return mouse_if_send_hid_report(function); + #endif + default: + TRACE_MSG0(MOUSE, "GET DESCRIPTOR HID UNKNOWN"); + break; + } + return -EINVAL; + case USB_REQ_GET_REPORT: + TRACE_MSG0(MOUSE, "GET REPORT"); break; + case USB_REQ_GET_IDLE: + TRACE_MSG0(MOUSE, "GET IDLE"); break; + case USB_REQ_GET_PROTOCOL: + TRACE_MSG0(MOUSE, "GET PROTOCOL"); break; + break; + default: + TRACE_MSG0(MOUSE, "GET DEFAULT"); break; + return -EINVAL; + } + + RETURN_EINVAL_UNLESS(request->wLength && + ((urb = usbd_alloc_urb_ep0 (function, request->wLength, NULL)))); + + switch (request->bRequest) { + + case USB_REQ_GET_REPORT: + + switch (le16_to_cpu(request->wValue) >> 8) { + case HID_INPUT: + TRACE_MSG0(MOUSE, "GET REPORT HID_INPUT"); break; + case HID_OUTPUT: + TRACE_MSG0(MOUSE, "GET REPORT HID_OUTPUT"); break; + case HID_FEATURE: + TRACE_MSG0(MOUSE, "GET REPORT HID_FEATURE"); break; + break; + default: + TRACE_MSG0(MOUSE, "GET REPORT default"); break; + break; + } + // XXX create urb and send? + urb->actual_length = 1; + break; + + case USB_REQ_GET_IDLE: + case USB_REQ_GET_PROTOCOL: + urb->actual_length = 1; + switch (request->bRequest) { + case USB_REQ_GET_IDLE: + urb->buffer[0] = cpu_to_le16(mouse->idle); + TRACE_MSG1(MOUSE, "GET IDLE: %x", urb->buffer[0]); + break; + case USB_REQ_GET_PROTOCOL: + urb->buffer[0] = cpu_to_le16(mouse->protocol); + TRACE_MSG1(MOUSE, "GET PROTOCOL: %x", urb->buffer[0]); + break; + } + } + + RETURN_ZERO_UNLESS(usbd_start_in_urb(urb)); + + /* only get here if error */ + usbd_free_urb(urb); + TRACE_MSG0(MOUSE, "get failed"); + break; + + case USB_REQ_HOST2DEVICE: + TRACE_MSG0(MOUSE, "HOST2DEVICE"); + switch (request->bRequest) { + case USB_REQ_SET_REPORT: + TRACE_MSG0(MOUSE, "SET REPORT"); + // Sample: 21 09 00 02 00 00 01 00 + mouse->pending_report = le16_to_cpu(request->wValue); + RETURN_EINVAL_UNLESS(request->wLength && + (urb = usbd_alloc_urb_ep0(function, request->wLength, mouse_report_received))); + + RETURN_ZERO_UNLESS (usbd_start_out_urb (urb)); + /* failure */ + usbd_free_urb (urb); + mouse->pending_report = 0; + return -EINVAL; + + case USB_REQ_SET_IDLE: + TRACE_MSG0(MOUSE, "SET IDLE"); + mouse->idle = le16_to_cpu(request->wValue) >> 8; + mouse->duration = le16_to_cpu(request->wValue) & 0xff; + TRACE_MSG2(MOUSE, "SET IDLE: idle: %02x duration: %02x", mouse->idle, mouse->duration); + return 0; + + case USB_REQ_SET_PROTOCOL: + TRACE_MSG0(MOUSE, "SET PROTOCOL"); + mouse->protocol = le16_to_cpu(request->wValue); + TRACE_MSG1(MOUSE, "SET PROTOCOL: %x", request->wValue); + return 0; + default: + TRACE_MSG0(MOUSE, "SET UNKNOWN"); + break; + } + break; + } + return -EINVAL; +} + +/*! + * @brief mouse_if_set_configuration - device configured + * Called when the host performs a set configuration. + * + * This will start sending data. + * + * @param function + * @param configuration + * @return int + */ +static int mouse_if_set_configuration (struct usbd_function_instance *function, int configuration) +{ + struct usbd_interface_instance *interface_instance = (struct usbd_interface_instance *)function; + struct mouse_if_private *mouse = interface_instance->function.privdata; + int hs = usbd_high_speed(function); + + + TRACE_MSG3(MOUSE, "SET_CONFIGURATION MOUSE_IF[%d] %x cfg: %d ", interface_instance->wIndex, function, configuration); + + // XXX Need to differentiate between non-zero, zero and non-zero done twice + + function->privdata = mouse;; + + mouse->n = mouse->x = mouse->y = mouse->last_x = mouse->last_y = 0; + + mouse->bEndpointAddress = usbd_endpoint_bEndpointAddress(function, BULK_INT, hs); + mouse->writesize = usbd_endpoint_wMaxPacketSize(function, BULK_INT, hs); + + mouse->mouse_count = 0; + + TRACE_MSG4(MOUSE, "MOUSE_IF[%2d] Configured: %d bEndpointAddress: %02x size: %02x", + interface_instance->wIndex, configuration, mouse->bEndpointAddress, mouse->writesize); + + mouse_if_send(function); // start sending + + return 0; +} + +/*! + * @brief mouse_if_reset - called to indicate bus has been reset + * @param function_instance + * @return int + */ +int mouse_if_reset (struct usbd_function_instance *function_instance) +{ + TRACE_MSG0(MOUSE,"RESET"); + return 0; +} + + +/*! + * @brief mouse_if_suspended - called to indicate bus has been suspended + * @param function_instance + * @return int + */ +int mouse_if_suspended (struct usbd_function_instance *function_instance) +{ + TRACE_MSG0(MOUSE,"SUSPENDED"); + return 0; +} + + + +/*! + * @brief mouse_if_endpoint_cleared - called by the USB Device Core when endpoint cleared + * Clear an endpoint. + * + * @param function The function instance for this driver + * @param bEndpointAddress + * @return none + */ +static void mouse_if_endpoint_cleared (struct usbd_function_instance *function, int bEndpointAddress) +{ + //struct usbd_interface_instance *interface_instance = (struct usbd_interface_instance *)function; + //struct mouse_if_private *mouse = interface_instance->function.privdata; + TRACE_MSG1(MOUSE,"CLEARED bEndpointAddress: %02x", bEndpointAddress); + mouse_if_send(function); // re-start sending +} + + + +/* ********************************************************************************************* */ +/*! mouse_if_function_enable - called by USB Device Core to enable the driver + * Called by the USBD core layer to enable this driver. + * + * @param function The function instance for this driver to use. + * @return non-zero if error. + */ +static int mouse_if_function_enable (struct usbd_function_instance *function) +{ + struct usbd_interface_instance *interface_instance = (struct usbd_interface_instance *)function; + struct mouse_if_private *mouse = NULL; + int hs = usbd_high_speed(function); + + RETURN_EINVAL_UNLESS((mouse = CKMALLOC(sizeof(struct mouse_if_private)))); + // XXX MODULE LOCK HERE + interface_instance->function.privdata = (void *)mouse; + mouse->writesize = usbd_endpoint_wMaxPacketSize(function, BULK_INT, hs); + mouse->mouse_interface_number = Mouse_Interfaces_Active++; + return 0; +} + +/*! mouse_if_function_disable - called by the USB Device Core to disable the driver + * Called by the USBD core layer to disable this driver. + * + * @param function The function instance for this driver + */ +static void mouse_if_function_disable (struct usbd_function_instance *function) +{ + struct usbd_interface_instance *interface_instance = (struct usbd_interface_instance *)function; + struct mouse_if_private *mouse = interface_instance->function.privdata; + interface_instance->function.privdata = NULL; + Mouse_Interfaces_Active--; + LKFREE(mouse); + // XXX MODULE UNLOCK HERE +} + +/* ********************************************************************************************* */ +const char *mouse_usb_strings[4] = { + "Mouse String Test 2", + "Mouse String Test 4", + "Mouse String Test 6", + NULL, +}; +u8 mouse_usb_string_indexes[4] = { + 2, 4, 6, 0, +}; + +#if defined(CONFIG_OTG_NOC99) +/*! function_ops - operations table for the USB Device Core + */ +static struct usbd_function_operations mouse_function_ops; + +/*! mouse_interface_driver - USB Device Core function driver definition + */ +struct usbd_interface_driver mouse_interface_driver; + +/*! mouse_if_ops_init - initialize mouse interface operations + * @param void + * @return none + */ +void mouse_if_ops_init(void) +{ + /*! function_ops - operations table for the USB Device Core + */ + + ZERO(mouse_function_ops); + mouse_function_ops.set_configuration = mouse_if_set_configuration; + mouse_function_ops.device_request = mouse_if_device_request; /*! called for each received device request */ + mouse_function_ops.endpoint_cleared = mouse_if_endpoint_cleared; + + mouse_function_ops.function_enable = mouse_if_function_enable; + mouse_function_ops.function_disable = mouse_if_function_disable; + + /*! interface_driver - USB Device Core function driver definition + */ + ZERO(mouse_interface_driver); + mouse_interface_driver.driver.name = "mouse-random-if"; /*! driver name */ + mouse_interface_driver.driver.fops = &mouse_function_ops; /*! operations table */ + mouse_interface_driver.driver.usb_strings = mouse_usb_strings; /*! operations table */ + mouse_interface_driver.driver.usb_string_indexes = mouse_usb_string_indexes; /*! operations table */ + + mouse_interface_driver.interfaces = sizeof (mouse_interfaces) / sizeof (struct usbd_interface_description); + mouse_interface_driver.interface_list = mouse_interfaces; + mouse_interface_driver.endpointsRequested = ENDPOINTS; + mouse_interface_driver.requestedEndpoints = mouse_if_endpoint_requests; + mouse_interface_driver.iFunction = "Belcarra Random Mouse - 1.0 HID"; + +} + +#else /* defined(CONFIG_OTG_NOC99) */ +/*! function_ops - operations table for the USB Device Core + * The set of interface function operations supported by this driver. + */ +static struct usbd_function_operations mouse_function_ops = { + .set_configuration = mouse_if_set_configuration, + .device_request = mouse_if_device_request, /*!< called for each received device request */ + .endpoint_cleared = mouse_if_endpoint_cleared, + .function_enable = mouse_if_function_enable, + .function_disable = mouse_if_function_disable, +}; + +/*! mouse_interface_driver - USB Device Core function driver definition + * The interface function configuration for this driver. + */ +struct usbd_interface_driver mouse_interface_driver = { + .driver = { + .name = "mouse-random-if", + .fops = &mouse_function_ops, + .usb_strings = mouse_usb_strings, + .usb_string_indexes = mouse_usb_string_indexes, + }, + .interfaces = sizeof (mouse_interfaces) / sizeof (struct usbd_interface_description), + .interface_list = mouse_interfaces, + .endpointsRequested = ENDPOINTS, + .requestedEndpoints = mouse_if_endpoint_requests, + + .bFunctionClass = 0x03, + .bFunctionSubClass = 0x01, + .bFunctionProtocol = 0x02, + .iFunction = "Belcarra Random Mouse - 1.0 HID", +}; +#endif /* defined(CONFIG_OTG_NOC99) */ + + +/*! Module Parameters ******************************************************** */ + +/*! @name XXXXX MODULE Parameters + */ +/*@{*/ + +MOD_AUTHOR ("sl@belcarra.com"); +EMBED_LICENSE(); +MOD_DESCRIPTION ("Belcarra Random Walk MOUSE Function"); + +MOD_PARM_INT (vendor_id, "Device Vendor ID", 0); +MOD_PARM_INT (product_id, "Device Product ID", 0); + +otg_tag_t MOUSE; + +/*! @} */ + +/*! @name USB Module init/exit ***************************************************** */ + + /*! @{ + */ + +/*! + * mouse_if_modinit() - module init + * + * This is called by the Linux kernel; either when the module is loaded + * if compiled as a module, or during the system intialization if the + * driver is linked into the kernel. + * + * This function will parse module parameters if required and then register + * the mouse driver with the USB Device software. + * + * If the CONFIG_OTG_MOUSE_BH option is enabled it will also setup the mouse + * bottom half handler. + */ +int mouse_if_modinit (void) +{ + #if defined(CONFIG_OTG_NOC99) + mouse_if_global_init(); + mouse_if_ops_init(); + #endif /* defined(CONFIG_OTG_NOC99) */ + + MOUSE = otg_trace_obtain_tag(NULL, "mouse-if"); + TRACE_MSG2(MOUSE, "vendor_id: %04x product_id: %04x", MODPARM(vendor_id), MODPARM(product_id)); + + mouse_if_hid.wItemLength = cpu_to_le16(0x34); // XXX mips compiler bug..... + + // register as usb function driver + TRACE_MSG0(MOUSE, "REGISTER INTERFACE"); + TRACE_MSG2(MOUSE, "%s %d", mouse_interface_driver.driver.name, mouse_interface_driver.endpointsRequested); + + THROW_IF (usbd_register_interface_function ( &mouse_interface_driver, "mouse-if", NULL), error); + + TRACE_MSG2(MOUSE, "%s %d", mouse_interface_driver.driver.name, mouse_interface_driver.endpointsRequested); + + TRACE_MSG0(MOUSE, "REGISTER FINISHED"); + #ifdef CONFIG_OTG_MOUSE_BH + mouse_if_private.notification_bh.routine = mouse_if_hid_bh; + mouse_if_private.notification_bh.data = NULL; + #endif + CATCH(error) { + otg_trace_invalidate_tag(MOUSE); + return -EINVAL; + } + return 0; +} + +module_init (mouse_if_modinit); + +/*! + * mouse_if_modexit() - module init + * + * This is called by the Linux kernel; when the module is being unloaded + * if compiled as a module. This function is never called if the + * driver is linked into the kernel. + * + * @return void + */ +void mouse_if_modexit (void) +{ + #ifdef CONFIG_OTG_MOUSE_BH + while (atomic_read(&mouse_if_bh_active)) { + printk(KERN_ERR"%s: waiting for bh\n", __FUNCTION__); + schedule_timeout(10 * HZ); + } + #endif + usbd_deregister_interface_function (&mouse_interface_driver); + otg_trace_invalidate_tag(MOUSE); +} + +#if OTG_EPILOGUE +module_exit (mouse_if_modexit); +#endif +/*! @} */ diff --git a/drivers/otg/functions/mouse/otg-config.h b/drivers/otg/functions/mouse/otg-config.h new file mode 100644 index 000000000000..33d24d40d4d3 --- /dev/null +++ b/drivers/otg/functions/mouse/otg-config.h @@ -0,0 +1,143 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* +# @(#) balden@belcarra.com|otg/functions/mouse/otg-config.h|20060419204257|26035 + * + * Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + */ + +/* + * tristate " Random Mouse Function" + * This implements a simple Mouse driver that sends HID packets with + * small random movements. This is a good function for initial testing + * of Peripheral Controller Drivers as it provides for a complicated + * enumeration but a simple uni-directional (send Interrupt only) data + * transport. + */ +/* #define CONFIG_OTG_MOUSE */ + +/* + * bool "Build an interface module" + * default "y" + * This module needs a composite function module, such as + * mouse_cf or generic_cf to be activated + */ +#define CONFIG_OTG_MOUSE_INTERFACE + +/* + * bool "Build a simple OTG 2.x style composite function with mouse" + * default "n" + * This module provides a very simple composite function to drive + * the mouse_if module (see above). The generic_cf module provides + * a much richer composite function supporting multiple functions. + */ +#define CONFIG_OTG_MOUSE_COMPOSITE + +/* + * bool "Build an OTG 1.x style function module/driver" + * depends on OTG && OTG_MOUSE + * default "n" + * This is the mouse function driver from previous releases. It + * does not use the composite/interface mechanism. If this module + * is used, neither of the other two mouse modules is required. + */ +#define CONFIG_OTG_MOUSE_TRADITIONAL + +/* + * hex "VendorID (hex value)" + * default "0x15ec" + * This parameter is not used by the mouse_if module because the + * composite layer is responsible for setting this parameter + */ +#define CONFIG_OTG_MOUSE_VENDORID 0x15ec + +/* + * hex "ProductID (hex value)" + * default "0xf003" + * Parameters such as PRODUCTID, VENDORID are now the responsibility + * of the composite driver rather than the interface driver. For this + * reason, the module parameters vendor_id, etc are not available in + * the interface versions. Therefore, appropriate values should be set + * in the kernel configuration. These configuration parameters + * are referenced in both mouse_cf and generic_cf. + */ +#define CONFIG_OTG_MOUSE_PRODUCTID 0xf003 + +/* + * hex "bcdDevice (binary-coded decimal)" + * default "0x0100" + */ +#define CONFIG_OTG_MOUSE_BCDDEVICE 0x0100 + +/* + * string "iManufacturer (string)" + * default "Belcarra" + */ +#define CONFIG_OTG_MOUSE_MANUFACTURER "Belcarra" + +/* + * string "iProduct (string)" + * default "Random Mouse Device" + */ +#define CONFIG_OTG_MOUSE_PRODUCT_NAME "Random Mouse Device" + +/* + * string "MOUSE Bulk Only iInterface (string)" + * default "MOUSE Data Intf" + */ +#define CONFIG_OTG_MOUSE_COMM_INTF "MOUSE Data Intf" + +/* + * string "Data Interface iConfiguration (string)" + * default "MOUSE Configuration" + */ +#define CONFIG_OTG_MOUSE_DESC "MOUSE Configuration" + +/* + * bool " MOUSE BH Test" + * default n + * Implement the Mouse send packet in a bottom half handler, + * this will delay responses to test the PCD works correctly + * when the host polls before a send data urb is queued. + */ +#define CONFIG_OTG_MOUSE_BH + +/* + * int "Number of packets (zero is continous)" + * default 10 + * Number of Mouse packets to send, will run + * forever if set to zero. + */ +#define CONFIG_OTG_MOUSE_PACKETS 10 + +/* + * bool " MOUSE Stall Test" + * default n + * Periodically stall the INTERRUPT endpoint. + * Used for testing. + */ +#define CONFIG_OTG_MOUSE_STALL + +/* + * int "Number of Stalls" + * default 1 + * Number of Stall tests to perform. + */ +#define CONFIG_OTG_MOUSE_STALL_COUNT + +/* + * int 'Polling Interval ' + * default 1 + * Sets interrupt endpoint interval. + */ +#define CONFIG_OTG_MOUSE_INTERVAL 1 diff --git a/drivers/otg/functions/msc/Kconfig b/drivers/otg/functions/msc/Kconfig new file mode 100644 index 000000000000..321d4bc30569 --- /dev/null +++ b/drivers/otg/functions/msc/Kconfig @@ -0,0 +1,63 @@ +# @(#) balden@belcarra.com/seth2.rillanon.org|otg/functions/msc/Kconfig|20070531215019|51512 +# Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp +menu "OTG Mass Storage function" + depends on OTG + +config OTG_MSC + tristate "Mass Storage Function" + depends on OTG + +config OTG_MSC_PIPES_TEST + bool "Pipes Test Only" + depends on OTG && OTG_MSC && OTG_INTERNAL_TESTING + default n + ---help--- + Setting this allows the pipes to be tested separately. + +menu "OTG Mass Storage function options" + depends on OTG && OTG_MSC + +#config OTG_MSC_VENDORID +# hex "VendorID (hex value)" +# depends on OTG && OTG_MSC +# default "0x15ec" + +#config OTG_MSC_PRODUCTID +# hex "ProductID (hex value)" +# depends on OTG && OTG_MSC +# default "0xf006" + +#config OTG_MSC_BCDDEVICE +# hex "bcdDevice (binary-coded decimal)" +# depends on OTG && OTG_MSC +# default "0x0100" + + + +config OTG_MSC_MANUFACTURER + string "iManufacturer (string)" + depends on OTG && OTG_MSC + default "Belcarra" + +config OTG_MSC_PRODUCT_NAME + string "iProduct (string)" + depends on OTG && OTG_MSC + default "Mass Storage Class - Bulk Only" + +config OTG_MSC_INTF + string "MSC Bulk Only iInterface (string)" + depends on OTG && OTG_MSC + default "MSC BO Data Intf" + +#config OTG_MSC_DESC +# string "Data Interface iConfiguration (string)" +# depends on OTG && OTG_MSC +# default "MSC BO Configuration" + +config OTG_MSC_REGISTER_TRACE + bool "MSC Tracing" + depends on OTG && OTG_MSC && OTG_INTERNAL_TESTING + default n +endmenu + +endmenu diff --git a/drivers/otg/functions/msc/Makefile b/drivers/otg/functions/msc/Makefile new file mode 100644 index 000000000000..f49791849738 --- /dev/null +++ b/drivers/otg/functions/msc/Makefile @@ -0,0 +1,17 @@ +# Function driver for a Random Mouse +# @(#) balden@belcarra.com|otg/functions/msc/Makefile-l26|20060419204258|01484 +# +# Copyright (c) 2004 Belcarra +# Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + +msc_if-objs := msc-fd.o crc.o msc-linux.o msc-bo.o msc-io-l24.o + + +obj-$(CONFIG_OTG_MSC) += msc_if.o + +OTG=$(TOPDIR)/drivers/otg +ACMD=$(OTG)/functions/msc +OTGCORE_DIR=$(OTG)/otgcore +USBDCORE_DIR=$(OTG)/usbdcore +EXTRA_CFLAGS += -I$(ACMD) -I$(OTG) -Wno-unused -Wno-format -I$(USBDCORE_DIR) -I$(OTGCORE_DIR) +EXTRA_CFLAGS_nostdinc += -I$(ACMD) -I$(OTG) -Wno-unused -Wno-format -I$(USBDCORE_DIR) -I$(OTGCORE_DIR) diff --git a/drivers/otg/functions/msc/crc.c b/drivers/otg/functions/msc/crc.c new file mode 100644 index 000000000000..47c101b4c6c5 --- /dev/null +++ b/drivers/otg/functions/msc/crc.c @@ -0,0 +1,95 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/msc_fd/crc.c - crc table + * @(#) sl@belcarra.com/whiskey.enposte.net|otg/functions/msc/crc.c|20070425221028|06858 + * + * Copyright (c) 2003-2005 Belcarra + * Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + * + * By: + * Chris Lynne <cl@belcarra.com> + * Stuart Lynne <sl@belcarra.com> + * Bruce Balden <balden@belcarra.com> + * + */ + + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/version.h> + + +#include <linux/init.h> +#include <asm/uaccess.h> +#include <linux/ctype.h> +#include <linux/timer.h> +#include <linux/interrupt.h> +#include <asm/atomic.h> +#include <linux/mm.h> +#include <linux/slab.h> + +#include <otg/otg-compat.h> + +//#include <usbd-chap9.h> +//#include <usbd-mem.h> +//#include <usbd.h> +//#include <usbd-func.h> + +#include "crc.h" + +unsigned int *msc_crc32_table; + +/** + * Generate the crc32 table + * + * return non-zero if malloc fails + */ +int make_crc_table(void) +{ + unsigned int n; + if (msc_crc32_table) return 0; + if (!(msc_crc32_table = (unsigned int *)ckmalloc(256*4))) return -EINVAL; + for (n = 0; n < 256; n++) { + int k; + unsigned int c = n; + for (k = 0; k < 8; k++) { + c = (c & 1) ? (CRC32_POLY ^ (c >> 1)) : (c >> 1); + } + msc_crc32_table[n] = c; + } + return 0; +} +/*! free_crc_table - free ckmalloced resource for crc-table + * @return void + */ +void free_crc_table(void) +{ + if (msc_crc32_table) { + lkfree(msc_crc32_table); + msc_crc32_table = NULL; + } +} + +/*! crc32_compute - compute frame check sequence number + * @param src source data + * @param len length of source data + * @param val FCS value + * @return FCS result + */ + +unsigned int crc32_compute(unsigned char *src, int len, unsigned int val) +{ + for (; len-- > 0; val = COMPUTE_FCS (val, *src++)); + return val; +} diff --git a/drivers/otg/functions/msc/crc.h b/drivers/otg/functions/msc/crc.h new file mode 100644 index 000000000000..2c7ed4a57569 --- /dev/null +++ b/drivers/otg/functions/msc/crc.h @@ -0,0 +1,40 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/msc_fd/crc.c - crc table + * @(#) balden@belcarra.com|otg/functions/msc/crc.h|20060419204258|03869 + * + * Copyright (c) 2003-2004 Belcarra + * Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + * + * By: + * Chris Lynne <cl@belcarra.com> + * Stuart Lynne <sl@belcarra.com> + * Bruce Balden <balden@belcarra.com> + * + * + */ + + +extern unsigned int *msc_crc32_table; + +#define CRC32_INIT 0xffffffff // Initial FCS value +#define CRC32_GOOD 0xdebb20e3 // Good final FCS value + +#define CRC32_POLY 0xedb88320 // Polynomial for table generation + +#define COMPUTE_FCS(val, c) (((val) >> 8) ^ msc_crc32_table[((val) ^ (c)) & 0xff]) + +int make_crc_table(void); +void free_crc_table(void); +unsigned int crc32_compute(unsigned char *src, int len, unsigned int val); diff --git a/drivers/otg/functions/msc/msc-bo.c b/drivers/otg/functions/msc/msc-bo.c new file mode 100644 index 000000000000..4d2f35fdd557 --- /dev/null +++ b/drivers/otg/functions/msc/msc-bo.c @@ -0,0 +1,177 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/function/msc/msc-bo.c + * @(#) sl@belcarra.com/whiskey.enposte.net|otg/functions/msc/msc-bo.c|20070425221028|52337 + * + * Copyright (c) 2003-2004 Belcarra Technologies Corp + * Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + * + * By: + * Stuart Lynne <sl@belcarra.com>, + * Bruce Balden <balden@belcarra.com> + * + */ +/*! + * @file otg/functions/msc/msc-bo.c + * @brief Mass Storage Driver private defines + * + * This is a Mass Storage Class Function that uses the Bulk Only protocol. + * + * + * @ingroup MSCFunction + */ + +#include <otg/otg-compat.h> +#include <otg/usbp-chap9.h> +#include <otg/usbp-func.h> +#include <otg/otg-trace.h> + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/version.h> + +#include <linux/init.h> +#include <asm/uaccess.h> +#include <linux/ctype.h> +#include <linux/timer.h> +#include <linux/interrupt.h> +#include <asm/atomic.h> +#include <linux/random.h> +#include <linux/slab.h> + +#if defined(LINUX26) +#include <linux/buffer_head.h> +#endif +#include <linux/kdev_t.h> +#include <linux/blkdev.h> + + +#include "msc-scsi.h" +#include "msc.h" +#include "msc-fd.h" +#include "crc.h" + +//#include "rbc.h" + +# define CONFIG_OTG_SERIAL_NUMBER_STR "69D38D2036248D3A983C5CDA63888" + +/*! + * Mass Storage Class - Bulk Only + * + * Endpoint, Class, Interface, Configuration and Device descriptors/descriptions + */ + +u8 msc_index[] = { BULK_OUT, BULK_IN, }; + +extern struct usbd_function_operations msc_function_ops; + + + +static struct usbd_endpoint_request msc_if_endpoint_requests[ENDPOINTS+1] = { + { BULK_OUT, 1, 0, 0, USB_DIR_OUT | USB_ENDPOINT_BULK, PAGE_SIZE, PAGE_SIZE, 0, }, + { BULK_IN, 1, 0, 0, USB_DIR_IN | USB_ENDPOINT_BULK, PAGE_SIZE, PAGE_SIZE, 0, }, + { 0, }, +}; + + +#ifndef OTG_C99 + +//#warning "C99" +static struct usbd_alternate_description msc_data_alternate_descriptions[1]; + +struct usbd_interface_description msc_interfaces[] = { + { alternates:sizeof (msc_data_alternate_descriptions) / sizeof (struct usbd_alternate_description), + alternate_list:msc_data_alternate_descriptions,}, +}; + +struct usbd_interface_driver msc_interface_driver; +/*! msc_global_init - initialize global variables for msc instance + * @return void + */ + +void msc_global_init(void){ + + ZERO(msc_data_alternate_descriptions[0]); + + msc_data_alternate_descriptions[0].iInterface = CONFIG_OTG_MSC_INTF; + msc_data_alternate_descriptions[0].bInterfaceClass = MASS_STORAGE_CLASS; + msc_data_alternate_descriptions[0].bInterfaceSubClass = MASS_STORAGE_SUBCLASS_SCSI; + msc_data_alternate_descriptions[0].bInterfaceProtocol = MASS_STORAGE_PROTO_BULK_ONLY; + msc_data_alternate_descriptions[0].endpoints = 0x02, + msc_data_alternate_descriptions[0].endpoint_index = msc_index; + + + + ZERO(msc_interface_driver); + + msc_interface_driver.driver.name = "msc-if"; + msc_interface_driver.driver.fops = &msc_function_ops; + msc_interface_driver.interfaces = 0x1; + msc_interface_driver.interface_list = msc_interfaces; + msc_interface_driver.endpointsRequested = ENDPOINTS; + msc_interface_driver.requestedEndpoints = msc_if_endpoint_requests; + +} + + + +#else /* OTG_C99 */ +//#warning "Not C99" + +static struct usbd_alternate_description msc_data_alternate_descriptions[] = { + { + .iInterface = "Belcarra Mass Storage - Bulk Only", + .bInterfaceClass = MASS_STORAGE_CLASS, + .bInterfaceSubClass = MASS_STORAGE_SUBCLASS_SCSI, + .bInterfaceProtocol = MASS_STORAGE_PROTO_BULK_ONLY, + .endpoints = 0x02, + .endpoint_index = msc_index, + }, +}; + + + +struct usbd_interface_description msc_interfaces[] = { + { alternates:sizeof (msc_data_alternate_descriptions) / sizeof (struct usbd_alternate_description), +alternate_list:msc_data_alternate_descriptions,}, +}; + + +/*! msc_interface_driver - USB Device Core function driver definition + */ +struct usbd_interface_driver msc_interface_driver; + +struct usbd_interface_driver msc_interface_driver = { + .driver = { + .name = "msc-if", + .fops = &msc_function_ops, }, +// .driver.name = "msc-if", /*! driver name */ +// .driver.fops = &msc_function_ops, /*!< operations table */ + .interfaces = 0x1, + .interface_list = msc_interfaces, + .endpointsRequested = ENDPOINTS, + .requestedEndpoints = msc_if_endpoint_requests, + + .bFunctionClass = MASS_STORAGE_CLASS, + .bFunctionSubClass = MASS_STORAGE_SUBCLASS_SCSI, + .bFunctionProtocol = MASS_STORAGE_PROTO_BULK_ONLY, + .iFunction = "Belcarra Mass Storage - Bulk Only", +}; + + + + + + +#endif /* OTG_C99 */ diff --git a/drivers/otg/functions/msc/msc-fd.c b/drivers/otg/functions/msc/msc-fd.c new file mode 100644 index 000000000000..b8e47d219c48 --- /dev/null +++ b/drivers/otg/functions/msc/msc-fd.c @@ -0,0 +1,1568 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/function/msc/msc.-fd.c + * @(#) sl@belcarra.com/whiskey.enposte.net|otg/functions/msc/msc-fd.c|20070711072703|01715 + * + * Copyright (c) 2003-2006 Belcarra Technologies Corp + * Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + * + * By: + * Stuart Lynne <sl@belcarra.com>, + * Bruce Balden <balden@belcarra.com> + * Tony Tang <tt@belcarra.com> + * + */ + + +/*! + * @file otg/functions/msc/msc-fd.c + * @brief Mass Storage Driver private defines + * + * This is a Mass Storage Class Function that uses the Bulk Only protocol. + * + * + * Notes: + * + * 1. Currently we only support the Bulk Only model. Microsoft states that + * further support for the mass storage driver will only be done for devices + * that conform to the Bulk Only model. + * + * 2. Multiple LUN's are not supported, but in theory they could be. + * + * 3. Error handling should be done with STALL but using ZLP seems to also + * work. ZLP is usually easier to implement (except possibly on the SA1100.) + * We may need to make STALL an option if we find devices (perhaps SA1100) + * that cannot reliaby send a ZLP on BULK-IN endpoint. + * + * + * 4. WinXP will match the following: + * + * USB: Class_08&SubClass_02&Prot_50 + * USB: Class_08&SubClass_05&Prot_50 + * USB: Class_08&SubClass_06&Prot_50 + * + * SubClass 02 is MMC or SFF8020I + * SubClass 05 is SFF or SFF8070I + * SubClass 06 is SCSI + * + * From the Windows USB Storage FAQ: + * + * RBC not supported + * + * SubClass = 6 (SCSI) + * CDBs SHOULD NOT be padded to 12 bytes + * ModeSense/ModeSelect SHOULD NOT be translated from 1ah/15h to 5ah/55h + * should be used for FLASH + * + * SubClass !=6 + * CDBs SHOULD be padded to 12 bytes + * ModeSense/ModeSelect SHOULD be translated from 1ah/15h to 5ah/55h + * + * We are using the former, SubClass = 6, and implement the required SCSI operations. + * + * + * TODO + * + * + * + * TODO FIXME Bus Interface Notes + * + * 1. The bus interface driver must correctly implement NAK'ing if not rcv_urb is + * present (see au1x00.c or wmmx.c for examples.) + * + * 2. The bus interface driver must implement USBD_URB_SENDZLP flag (see au1x00.c + * for example.) + * + * @ingroup MSCFunction + */ + + +#include <otg/otg-compat.h> +#include <otg/usbp-chap9.h> +#include <otg/usbp-func.h> +#include <otg/otg-trace.h> + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/version.h> + +#include <linux/init.h> +#include <asm/uaccess.h> +#include <linux/ctype.h> +#include <linux/timer.h> +#include <linux/interrupt.h> +#include <asm/atomic.h> +#include <linux/random.h> +#include <linux/slab.h> + +#if defined(LINUX26) +#include <linux/buffer_head.h> +#endif +#include <linux/kdev_t.h> +#include <linux/blkdev.h> + + +#include "msc-scsi.h" +#include "msc.h" +#include "msc-fd.h" +#include "crc.h" + + +/* Module Parameters ************************************************************************* */ + + +#define DEVICE_EJECTED 0x0001 // MEDIA_EJECTED +#define DEVICE_INSERTED 0x0002 // MEDIA_INSERT + +#define DEVICE_OPEN 0x0004 // WR_PROTECT_OFF +#define DEVICE_WRITE_PROTECTED 0x0008 // WR_PROTECT_ON + +#define DEVICE_CHANGE_ON 0x0010 // MEDIA_CHANGE_ON + +#define DEVICE_PREVENT_REMOVAL 0x0020 + + +#define DEF_NUMBER_OF_HEADS 0x10 +#define DEF_SECTORS_PER_TRACK 0x20 + + + +/* MSC ******************************************************************************************** */ + +int msc_urb_sent (struct usbd_urb *tx_urb, int rc); +static int msc_recv_urb (struct usbd_urb *urb, int rc); + +otg_tag_t msc_fd_trace_tag; + +/* Sense Key *********************************************************************************** */ + +/*! + * @brief set_sense_data - set sensedata in msc private struture + * + * This saves sense data. Sense data indicates what type of error + * has occurred and will be returned to the host when a request sense + * command is sent. + + * @param function_instance + * @param sensedata + * @param info + * @return none + */ +void set_sense_data(struct usbd_function_instance *function_instance, u32 sensedata, u32 info) +{ + struct msc_private *msc = function_instance->privdata; + TRACE_SENSE(sensedata, info); + msc->sensedata = sensedata; + msc->info = info; +} + +/* Check blockdev ****************************************************************************** */ + +/*! + * @brief msc_check_blockdev_name - check current status of the block device + * + * Check if the block device is operational, either generate a failed CSW + * or a ZLP if not ready. + * + * Returns non-zero if the block device is not available for I/O operations + * and a failed CSW has already been sent. + * @param function_instance + * @param zlp + * @param name + * @return int + */ +int msc_check_blockdev_name(struct usbd_function_instance *function_instance, int zlp, char *name) +{ + struct msc_private *msc = function_instance->privdata; + if (msc->block_dev_state & DEVICE_EJECTED) { + TRACE_MSG0(MSC,"CHECK BLOCKDEV DEVICE_EJECTED"); + ((SENDZLP == zlp) ? msc_dispatch_query_urb_zlp : msc_start_sending_csw_failed) + (function_instance, SCSI_SENSEKEY_MEDIA_NOT_PRESENT, msc->lba, USB_MSC_FAILED); + return -EINVAL; + } + + if (msc->block_dev_state & DEVICE_CHANGE_ON) { + msc->block_dev_state &= ~DEVICE_CHANGE_ON; + TRACE_MSG0(MSC,"CHECK BLOCKDEV DEVICE_CHANGE_ON"); + + ((SENDZLP == zlp) ? msc_dispatch_query_urb_zlp : msc_start_sending_csw_failed) + (function_instance, SCSI_SENSEKEY_NOT_READY_TO_READY_CHANGE, msc->lba, USB_MSC_FAILED); + return -EINVAL; + } + //TRACE_MSG0(MSC,"CHECK BLOCKDEV DEVICE_INSERTED"); + return 0; +} + +/* Generic start recv urb and send csw ********************************************************* */ + +/*! + * @brief msc_start_recv - queue a receive urb + * + * Ensure that size is a multiple of the endpoint packetsize. + * + * Returns non-zero if there is an error in the USB layer. + * @param function_instance + * @param size + * @return int + */ +int msc_start_recv_urb(struct usbd_function_instance *function_instance, int size) +{ + struct usbd_urb *rcv_urb = NULL; + struct msc_private *msc = function_instance->privdata; + int wMaxPacketSize = usbd_endpoint_wMaxPacketSize(function_instance, BULK_OUT, usbd_high_speed(function_instance)); + TRACE_MSG0(MSC,"enter msc_start_recv_urb"); + if ((size % wMaxPacketSize)) + size = ((size + wMaxPacketSize) / wMaxPacketSize) * wMaxPacketSize; + RETURN_EINVAL_UNLESS((rcv_urb = usbd_alloc_urb (function_instance, BULK_OUT, size, msc_recv_urb))); + rcv_urb->function_privdata = function_instance; + msc->rcv_urb_finished = NULL; + RETURN_ZERO_UNLESS(usbd_start_out_urb(rcv_urb)); + TRACE_MSG0(MSC,"START RECV URB ERROR"); + usbd_free_urb(rcv_urb); + return -EINVAL; +} + +/*! + * @brief msc_start_sending_csw - start sending a new data or csw urb + * + * Generate a CSW (Command Status Wrapper) to send to the the host. + * + * Returns non-zero if there is an error in the USB layer. + * @param function_instance + * @param status + * @return int + */ +int msc_start_sending_csw(struct usbd_function_instance *function_instance, u8 status) +{ + struct msc_private *msc = function_instance->privdata; + struct msc_command_status_wrapper *csw; + int length = sizeof(struct msc_command_status_wrapper); + struct usbd_urb *tx_urb; + + //TRACE_MSG1(MSC,"START SENDING CSW %08x", status); + + msc->command_state = MSC_STATUS; + + RETURN_EINVAL_UNLESS((tx_urb = usbd_alloc_urb (function_instance, BULK_IN, length, msc_urb_sent))); + + tx_urb->actual_length = length; + tx_urb->function_privdata = function_instance; + + // fill in CSW and queue the urb + csw = (struct msc_command_status_wrapper *) tx_urb->buffer; + csw->dCSWSignature = CSW_SIGNATURE; + csw->dCSWTag = msc->command.dCBWTag; + csw->dCSWDataResidue = abs(msc->command.dCBWDataTransferLength - msc->data_transferred_in_bytes); + csw->bCSWStatus = status; + + TRACE_MSG2(MSC,"START SENDING CSW status: %02x data residue: %d", status, csw->dCSWDataResidue); + + RETURN_ZERO_UNLESS(usbd_start_in_urb (tx_urb)); + TRACE_MSG0(MSC,"START SENDING CSW FAILED"); + usbd_free_urb (tx_urb); + return -EINVAL; +} + +/*! + * @brief msc_start_sending_csw_failed - starting sending a CSW showing failure + * + * Sets sensedata and generates a CSW with status set to USB_MSC_FAILED. + * + * Returns non-zero if there is an error in the USB layer. + * @param function_instance + * @param sensedata + * @param info + * @param status + * @return int + */ +int msc_start_sending_csw_failed(struct usbd_function_instance *function_instance, u32 sensedata, u32 info, int status) +{ + struct msc_private *msc = function_instance->privdata; + TRACE_MSG2(MSC, "sensedata: %x status: %x", sensedata, status); + set_sense_data(function_instance, sensedata, info); + + return msc_start_sending_csw(function_instance, status); +} + + +/* ********************************************************************************************* */ + +/*! + * @brief msc_alloc_urb - allocate an urb for returning a query + * @param function_instance + * @param length + * @return pointer to a urb, Returns NULL if there is an error in the USB layer. + + */ +struct usbd_urb * msc_alloc_urb(struct usbd_function_instance *function_instance, int length) +{ + struct msc_private *msc = function_instance->privdata; + struct usbd_urb *urb; + + THROW_IF(!(urb = usbd_alloc_urb (function_instance, BULK_IN, length, msc_urb_sent)), error); + return urb; + CATCH(error) { + msc->command_state = MSC_READY; + return NULL; + } +} + +/*! + * @brief msc_dispatch_query_urb - dispatch an urb containing query data + * + * @param function_instance + * @param urb + * @param length + * @param status + * @return int Returns non-zero if there is an error in the USB layer. + */ +int msc_dispatch_query_urb(struct usbd_function_instance *function_instance, struct usbd_urb *urb, int length, int status) +{ + int rc; + unsigned long flags; + struct msc_private *msc = function_instance->privdata; + + TRACE_MSG2(MSC,"DISPATCH URB len: %d flags: %x", length, urb->flags); + + // save information in msc and urb + // + urb->function_privdata = function_instance; + urb->actual_length = msc->TransferLength_in_bytes = length; + + // dispatch urb + local_irq_save(flags); + if ((rc = usbd_start_in_urb (urb))) { + + TRACE_MSG0(MSC,"DISPATCH URB FAILED"); + usbd_free_urb (urb); + + // stall? + msc->command_state = MSC_READY; + local_irq_restore(flags); + return -EINVAL; + } + msc->command_state = MSC_QUERY; + msc->status = status; + local_irq_restore(flags); + return 0; +} + +/*! + * @brief msc_dispatch_query_urb_zlp - send a ZLP + * + * @param function_instance + * @param sensedata + * @param info + * @param status + * @return int Returns non-zero if there is an error in the USB layer. + */ +int msc_dispatch_query_urb_zlp(struct usbd_function_instance *function_instance, u32 sensedata, u32 info, int status) +{ + struct msc_private *msc = function_instance->privdata; + struct usbd_urb *urb; + TRACE_MSG2(MSC, "sensedata: %x status: %x", sensedata, status); + RETURN_EINVAL_IF(!(urb = msc_alloc_urb(function_instance, 1))); + set_sense_data(function_instance, sensedata, info); + urb->flags |= USBD_URB_SENDZLP; + return msc_dispatch_query_urb(function_instance, urb, 0, status); +} + +/* READ 10 COMMAND - read and send data to the host ******************************************** */ +extern int msc_scsi_read_10(struct usbd_function_instance *function_instance, char *name, int op); +extern int msc_in_read_10_urb_sent(struct usbd_urb *tx_urb); + +/* WRITE 10 - receive data from host and write to block device ********************************* */ +extern int msc_scsi_write_10(struct usbd_function_instance *function_instance, char *name, int op); +extern void msc_recv_out_blocks(struct usbd_urb *rcv_urb); + +/* SCSI Commands ******************************************************************************* */ + +/*! + * @brief msc_scsi_inquiry - process an inquiry + * + * Used by: + * win2k + * + * @param function_instance + * @param name + * @param op + * @return int Returns non-zero if there is an error in the USB layer. + */ +int msc_scsi_inquiry(struct usbd_function_instance *function_instance, char *name, int op) +{ + struct msc_private *msc = function_instance->privdata; + struct msc_scsi_inquiry_command *command = (struct msc_scsi_inquiry_command *)&msc->command.CBWCB; + struct msc_scsi_inquiry_data *data; + struct usbd_urb *urb; + int length = sizeof(struct msc_scsi_inquiry_data); + u32 temmp; + + /* + * C.f. SPC2 7.3 INQUIRY command + * C.f. Table 46 - Standard INQUIRY data format + * + * C.f. Table 47 - Peripheral Qualifier + * + * 000b The specified peripheral device type is currently connected to this + * logical unit..... + * 001b The device server is capable of of supporting the peripheral device + * type on this logical unit. However, the physical device is not currently + * connected to this logical unit..... + * 010b Reserved + * 011b The device server is not capable of supporting a physical device on + * this logical unit.... + * + */ + + TRACE_MSG4(MSC,"INQUIRY EnableVPD: %02x LogicalUnitNumber: %02x PageCode: %02x AllocLen: %02x", + command->EnableVPD, command->LogicalUnitNumber, command->PageCode, command->AllocationLength); + TRACE_MSG1(MSC, "msc->command.dCBWDataTransferLength:%d", msc->command.dCBWDataTransferLength); + // XXX THROW_IF(msc->command_state != MSC_READY, error); + + RETURN_EINVAL_IF(!(urb = msc_alloc_urb(function_instance, length))); + data = (struct msc_scsi_inquiry_data *)urb->buffer; +#if 1 + data->PeripheralQaulifier=(msc->block_dev_state & DEVICE_EJECTED) + ~(msc->block_dev_state & DEVICE_CHANGE_ON); + +#else + data->PeripheralQaulifier = msc->block_dev_state & (DEVICE_EJECTED | DEVICE_CHANGE_ON) ? 0x1 : 0; +#endif + + TRACE_MSG1(MSC,"%d",data->PeripheralQaulifier); + data->PeripheralDeviceType = 0x00; + data->RMB = 0x1; + data->ResponseDataFormat = 0x1; + data->AdditionalLength = 0x1f; + + strncpy(data->VendorInformation, CONFIG_OTG_MSC_MANUFACTURER, strlen(CONFIG_OTG_MSC_MANUFACTURER)); + strncpy(data->ProductIdentification, CONFIG_OTG_MSC_PRODUCT_NAME, strlen(CONFIG_OTG_MSC_PRODUCT_NAME)); + + return msc_dispatch_query_urb(function_instance, urb, msc->command.dCBWDataTransferLength, USB_MSC_PASSED); +} + +/*! + * @brief msc_scsi_read_format_capacity - process a query + * + * Used by: + * win2k + * + * @param function_instance + * @param name + * @param op + * @return int Returns non-zero if there is an error in the USB layer. + + */ +int msc_scsi_read_format_capacity(struct usbd_function_instance *function_instance, char *name, int op) +{ + struct msc_private *msc = function_instance->privdata; + struct msc_scsi_read_format_capacity_data *data; + struct usbd_urb *urb; + int length = sizeof(struct msc_scsi_read_format_capacity_data); + u32 block_num = msc->capacity; + u32 block_len; + + RETURN_EINVAL_IF(!(urb = msc_alloc_urb(function_instance, length))); + + data = (struct msc_scsi_read_format_capacity_data *) urb->buffer; + + data->CapacityListHeader.CapacityListLength = sizeof(data->CurrentMaximumCapacityDescriptor); + + data->CurrentMaximumCapacityDescriptor.NumberofBlocks = block_num; + data->CurrentMaximumCapacityDescriptor.DescriptorCode = 0x03; + memcpy(data->CurrentMaximumCapacityDescriptor.BlockLength + 1, &block_len, sizeof(block_len)); + + return msc_dispatch_query_urb(function_instance, urb, length, USB_MSC_PASSED); +} + +/*! + * @brief msc_read_capacity - process a read_capacity command + * + * Used by: + * win2k + * +* @param function_instance + * @param name + * @param op + * @return int Returns non-zero if there is an error in the USB layer. + */ +int msc_scsi_read_capacity(struct usbd_function_instance *function_instance, char *name, int op) +{ + struct msc_private *msc = function_instance->privdata; + struct msc_scsi_read_capacity_command *command = (struct msc_scsi_read_capacity_command *)&msc->command.CBWCB; + struct msc_scsi_read_capacity_data *data; + struct usbd_urb *urb; + int length = 8; + u32 lba; + + /* + * C.f. RBC 5.3 + */ + lba = be32_to_cpu(command->LogicalBlockAddress); + + TRACE_MSG1(MSC,"READ CAPACITY LBA: %d", lba); + + if ((command->PMI > 1) || (!command->PMI && lba)) { + TRACE_MSG1(MSC,"READ CAPACITY PMI: %d", command->PMI); + return msc_start_sending_csw_failed (function_instance, SCSI_SENSEKEY_INVALID_FIELD_IN_CDB, lba, USB_MSC_FAILED); + } + + // alloc urb + RETURN_EINVAL_IF(!(urb = msc_alloc_urb(function_instance, length))); + + data = (struct msc_scsi_read_capacity_data *) urb->buffer; + + data->LastLogicalBlockAddress = cpu_to_be32(msc->capacity); + data->BlockLengthInBytes = cpu_to_be32(msc->block_size); + + TRACE_MSG2(MSC,"RECV READ CAPACITY lba: %x block_size: %x", + be32_to_cpu(data->LastLogicalBlockAddress), be32_to_cpu(data->BlockLengthInBytes)); + + return msc_dispatch_query_urb(function_instance, urb, length, USB_MSC_PASSED); +} + + +/*! + * @brief msc_scsi_request_sense - process a request_sense command + * + * @param function_instance + * @param name + * @param op + * @return int Returns non-zero if there is an error in the USB layer. + */ +int msc_scsi_request_sense(struct usbd_function_instance *function_instance, char *name, int op) +{ + struct msc_private *msc = function_instance->privdata; + struct msc_scsi_request_sense_command * command = (struct msc_scsi_request_sense_command *)&msc->command.CBWCB; + struct msc_scsi_request_sense_data *data; + + /* + * C.f. SPC2 7.20 REQUEST SENSE command + */ + + struct usbd_urb *urb; + int length = sizeof(struct msc_scsi_request_sense_data); + + // alloc urb + RETURN_EINVAL_IF(!(urb = msc_alloc_urb(function_instance, length))); + + data = (struct msc_scsi_request_sense_data *) urb->buffer; + memset(command, 0x0, length); + data->ErrorCode = SCSI_ERROR_CURRENT; + data->SenseKey = msc->sensedata >> 16; + data->AdditionalSenseLength = 0xa; /* XXX is this needed */ + data->AdditionalSenseCode = msc->sensedata >> 8; + data->AdditionalSenseCodeQualifier = msc->sensedata; + data->Valid = 1; + + set_sense_data(function_instance, SCSI_SENSEKEY_NO_SENSE, 0); + + return msc_dispatch_query_urb(function_instance, urb, msc->command.dCBWDataTransferLength, USB_MSC_PASSED); +} + +/*! + * @brief msc_scsi_mode_sense - process a request_sense command + * + * Used by: + * win2k + * + * @param function_instance + * @param name + * @param op + * @return int Returns non-zero if there is an error in the USB layer. + */ +int msc_scsi_mode_sense(struct usbd_function_instance *function_instance, char *name, int op) +{ + struct msc_private *msc = function_instance->privdata; + struct msc_scsi_mode_sense_command* command = (struct msc_scsi_mode_sense_command*)&msc->command.CBWCB; + struct msc_scsi_mode_sense_data *data; + int length = sizeof(struct msc_scsi_mode_sense_data); + + struct usbd_urb *urb; + u8 *cp; + + TRACE_MSG4(MSC,"MODE SENSE dbd: %02x PageControl: %02x PageCode: %02x Alloc: %02x", + command->DBD, command->PageControl, command->PageCode, 0); + length = 8; + + // alloc urb + RETURN_EINVAL_IF(!(urb = msc_alloc_urb(function_instance, length))); + + cp = (u8 *) urb->buffer; + memset(cp, 0x0, length); + + cp[0] = 0; + cp[1] = 0; + cp[2] = 0; // 0x80 is writeprotect + cp[3] = 0x08; + cp[4] = 0; + cp[5] = 0; + cp[6] = 0; + cp[7] = 0; + + return msc_dispatch_query_urb(function_instance, urb, msc->command.dCBWDataTransferLength, USB_MSC_PASSED); +} + +/*! msc_scsi_mode_sense - process a process mode sense command + * + * Used by: + * win2k + * + * XXX this doesn't work, need to re-implement and add these pages. + * + * Returns non-zero if there is an error in the USB layer. + */ +int new_msc_scsi_mode_sense(struct usbd_function_instance *function_instance, char *name, int op) +{ + struct msc_private *msc = function_instance->privdata; + + /* + * C.f. SPC2 7.8.1 MODE SENSE(6) command + */ + + static struct msc_read_write_error_recovery_page page_01 = { + PageCode:0x01, + PageLength:0x0A, + ReadRetryCount:0x03, + WriteRetryCount:0x80, + }; + static struct msc_flexible_disk_page page_05 = { + PageCode:0x05, + PageLength:0x1E, + TransferRate:__constant_cpu_to_be16(0xFA00), + NumberofHeads:0xA0, + SectorsperTrack:0x00, + DataBytesperSector:__constant_cpu_to_be16(0x0002), + NumberofCylinders:__constant_cpu_to_be16(0x0000), + MotorOnDelay:0x05, + MotorOffDelay:0x1E, + MediumRotationRate:__constant_cpu_to_be16(0x6801), + }; + static struct msc_removable_block_access_capabilities_page page_1b = { + PageCode:0x1B, + PageLength:0x0A, + TLUN:0x01, + }; + static struct msc_timer_and_protect_page page_1c = { + PageCode:0x1c, + PageLength:0x06, + InactivityTimeMultiplier:0x0A, + }; + + struct msc_scsi_mode_sense_command *command = (struct msc_scsi_mode_sense_command *)&msc->command.CBWCB; + struct msc_scsi_mode_sense_data *data; + struct usbd_urb *urb; + int length = sizeof(struct msc_scsi_mode_sense_data); + + + TRACE_MSG4(MSC,"MODE SENSE dbd: %02x PageControl: %02x PageCode: %02x Alloc: %02x", + command->DBD, command->PageControl, command->PageCode, 0); + + + if (msc->block_dev_state & DEVICE_EJECTED) { + u16 sector = htons((unsigned short)msc->block_size); + u16 cylinder = 0; + page_05.NumberofHeads = 0; + page_05.SectorsperTrack = 0; + memcpy(&page_05.DataBytesperSector, §or, sizeof(sector)); + memcpy(&page_05.NumberofCylinders, &cylinder, sizeof(cylinder)); + } + else { + u16 sector = htons((unsigned short)msc->block_size); + u32 size = DEF_NUMBER_OF_HEADS * DEF_SECTORS_PER_TRACK * sector; + u16 cylinder = htons(msc->capacity / size); + page_05.NumberofHeads = DEF_NUMBER_OF_HEADS; + page_05.SectorsperTrack = DEF_SECTORS_PER_TRACK; + memcpy(&page_05.DataBytesperSector, §or, sizeof(sector)); + memcpy(&page_05.NumberofCylinders, &cylinder, sizeof(cylinder)); + } + + if (SCSI_MODEPAGE_CONTROL_CURRENT != command->PageControl) { + TRACE_MSG2(MSC,"MODE SENSE - requeested other than CONTROL_CURRENT: %d %d", + command->PageControl, command->PageCode); + return msc_start_sending_csw_failed (function_instance, SCSI_SENSEKEY_INVALID_FIELD_IN_CDB, msc->lba, USB_MSC_FAILED); + } + + RETURN_EINVAL_IF(!(urb = msc_alloc_urb(function_instance, length))); + data = (struct msc_scsi_mode_sense_data *) urb->buffer; + + data->ModeParameterHeader.WriteProtect = msc->block_dev_state & DEVICE_WRITE_PROTECTED ? 1 : 0; + + switch (command->PageCode) { + case SCSI_MODEPAGE_ERROR_RECOVERY: + TRACE_MSG0(MSC, "MODEPAGE ERROR_RECOVERY"); + length = sizeof(struct msc_mode_parameter_header) + sizeof(page_01); + data->ModeParameterHeader.ModeDataLength = length - 1; + memcpy(&data->ModePages, &page_01, sizeof(page_01)); + break; + case SCSI_MODEPAGE_FLEXIBLE_DISK_PAGE: + TRACE_MSG0(MSC, "MODEPAGE struct msc_flexible_disk_page"); + length = sizeof(struct msc_mode_parameter_header) + sizeof(page_05); + data->ModeParameterHeader.ModeDataLength = length - 1; + memcpy(&data->ModePages, &page_05, sizeof(page_05)); + break; + case SCSI_MODEPAGE_REMOVABLE_BLOCK_ACCESS: + TRACE_MSG0(MSC, "MODEPAGE REMOVABLE_BLOCK_ACCESS"); + length = sizeof(struct msc_mode_parameter_header) + sizeof(page_1b); + data->ModeParameterHeader.ModeDataLength = length - 1; + memcpy(&data->ModePages, &page_1b, sizeof(page_1b)); + break; + case SCSI_MODEPAGE_INFORMATION_EXCEPTIONS: + TRACE_MSG0(MSC, "MODEPAGE INFORMATION_EXCEPTIONS"); + length = sizeof(struct msc_mode_parameter_header) + sizeof(page_1c); + data->ModeParameterHeader.ModeDataLength = length - 1; + memcpy(&data->ModePages, &page_1c, sizeof(page_1c)); + break; + case SCSI_MODEPAGE_ALL_SUPPORTED: + TRACE_MSG0(MSC, "MODEPAGE ALL_SUPPORTED"); + length = sizeof(struct msc_mode_parameter_header) + sizeof(struct msc_mode_all_pages); + data->ModeParameterHeader.ModeDataLength = length - 1; + memcpy(&data->ModePages.ModeAllPages.ReadWriteErrorRecoveryPage, &page_01, sizeof(page_01)); + memcpy(&data->ModePages.ModeAllPages.FlexibleDiskPage, &page_05, sizeof(page_05)); + memcpy(&data->ModePages.ModeAllPages.RemovableBlockAccessCapabilitiesPage, &page_1b, sizeof(page_1b)); + memcpy(&data->ModePages.ModeAllPages.TimerAndProtectPage, &page_1c, sizeof(page_1c)); + break; + case SCSI_MODEPAGE_CACHING: + // see file_storage.c for an example if we want to support this + TRACE_MSG0(MSC, "MODEPAGE CACHING (not supported)"); + break; + } + + TRACE_MSG2(MSC,"LENGTH: %d %d", msc->command.dCBWDataTransferLength, length); + + /* + * XXX verify that length is <= 256 bytes, return CHECK_CONDITION if it is + */ + length = MIN(msc->command.dCBWDataTransferLength, length); + + return msc_dispatch_query_urb(function_instance, urb, length, USB_MSC_PASSED); +} + + +/*! msc_scsi_test_unit_ready - process a test uint ready command + * + * Used by: + * win2k + * + * Returns non-zero if there is an error in the USB layer. + */ +int msc_scsi_test_unit_ready(struct usbd_function_instance *function_instance, char *name, int op) +{ + return msc_start_sending_csw(function_instance, USB_MSC_PASSED); +} + + +/*! msc_scsi_prevent_allow - process a prevent/allow command + * + * Used by: + * win2k + * + * Returns non-zero if there is an error in the USB layer. + */ +int msc_scsi_prevent_allow(struct usbd_function_instance *function_instance, char *name, int op) +{ + struct msc_private *msc = function_instance->privdata; + struct msc_scsi_prevent_allow_media_removal_command* command = + (struct msc_scsi_prevent_allow_media_removal_command*)&msc->command.CBWCB; + + /* + * C.f. SPC2 7.12 Table 78 PREVENT ALLOW MEDIA REMOVAL Prevent Field + * + * 00b Medium removal shall be allowed from both the data transport + * element and the attached medium changer (if any). + * 01b Medium removal shall be prohibited from the data transport + * element but allowed from the attached medium changer (if any). + * 10b Medium removal shall be allowed for the data transport element + * but prohibited for the attached medium changer. + * 11b Medium remval shall be prohibited from both the data transport + * element and the attached medium changer + * + * Prevention shall terminate after 00b or 10b, after a SYNC CACHE or hard reset. + */ + + // XXX TODO + // this is from storageproto.c, shouldn't we implement something? + if (command->Prevent) + return msc_start_sending_csw_failed (function_instance, SCSI_SENSEKEY_INVALID_FIELD_IN_CDB, msc->lba, USB_MSC_FAILED); + + return msc_start_sending_csw(function_instance, USB_MSC_PASSED); +} + + +/*! msc_scsi_start_stop - process a start/stop command + * + * C.f. RBC 5.4 and 5.4.2 + * + * Used by: + * win2k + * + * Returns non-zero if there is an error in the USB layer. + */ +int msc_scsi_start_stop(struct usbd_function_instance *function_instance, char *name, int op) +{ + struct msc_private *msc = function_instance->privdata; + struct msc_scsi_start_stop_command* command = (struct msc_scsi_start_stop_command*)&msc->command.CBWCB; + + TRACE_MSG4(MSC,"START STOP: Immed: %d Power: %x LoEj: %d Start: %d", + command->IMMED, command->PowerConditions, command->LoEj, command->Start); + /* + * C.f. 5.4 + * + * IMMED - if set return status immediately after command validation, otherwise + * return status as soon operation is completed. + * + * C.f. 5.4.1 Table 8 POWER CONDITIONS + * + * 0 - M - no change in power condition + * 1 - M - place device in active condition + * 2 - M - place device in idle condition + * 3 - M - place device in Standby condition + * 4 - - reserved + * 5 - M - place device in Sleep condition + * 6 - - reserved + * 7 - 0 - Device Control + * + * C.f. 5.4.2 Table 9 START STOP control bit definitions + * + * Power Load/Eject Start + * 1-7 x x LoEj and Start Ignored + * 0 0 0 Stop the medium + * 0 0 1 Make the medium ready + * 0 1 0 Unload the medium + * 0 1 1 Load the medium + * + */ + // XXX TODO + // this is from storageproto.c, shouldn't we implement something? + + if (command->Start && command->LoEj) + return msc_start_sending_csw_failed (function_instance, SCSI_SENSEKEY_INVALID_FIELD_IN_CDB, msc->lba, USB_MSC_FAILED); + + return msc_start_sending_csw(function_instance, USB_MSC_PASSED); +} + +/*! msc_scsi_verify - process a verify command + * + * Used by: + * win2k + * + * Returns non-zero if there is an error in the USB layer. + */ +int msc_scsi_verify(struct usbd_function_instance *function_instance, char *name, int op) +{ + struct msc_private *msc = function_instance->privdata; + struct msc_scsi_verify_command *command = (struct msc_scsi_verify_command *)&msc->command.CBWCB; + + /* + * C.f. RBC 5.7 VERIFY command + */ + // XXX This actually should use the read_10 function and when + // finished reading simply send the following + return msc_start_sending_csw(function_instance, USB_MSC_PASSED); +} + + +/*! msc_scsi_mode_select - process a select command + * + * Returns non-zero if there is an error in the USB layer. + */ +int msc_scsi_mode_select(struct usbd_function_instance *function_instance, char *name, int op) +{ + //SCSI_MODE_SELECT_COMMAND *command = (SCSI_MODE_SELECT_COMMAND *)&msc->command.CBWCB; + + /* + * C.f. SPC2 7.6 MODE SELECT(6) command + */ + + // if less than correct amount of data return USB_MSC_PHASE_ERROR - see MV + // + return msc_start_sending_csw(function_instance, USB_MSC_PASSED); +} + +/*! msc_private_pcs - process a private command + * + * Returns non-zero if there is an error in the USB layer. + */ +int msc_cmd_private_pcs(struct usbd_function_instance *function_instance, char *name, int op) +{ + struct msc_private *msc = function_instance->privdata; + return msc_start_sending_csw_failed (function_instance, SCSI_SENSEKEY_INVALID_COMMAND, msc->lba, USB_MSC_FAILED); +} + +/*! msc_cmd_unknown - process an unknown command + * + * Returns non-zero if there is an error in the USB layer. + */ +int msc_cmd_unknown(struct usbd_function_instance *function_instance, char *name, int op) +{ + struct msc_private *msc = function_instance->privdata; + return msc_start_sending_csw_failed (function_instance, SCSI_SENSEKEY_INVALID_COMMAND, msc->lba, USB_MSC_FAILED); +} + +/*! @struct rbc_dispatch msc-fd.c "otg/functions/msc/msc-fd.c" + * @brief encapsulation for rbc command + */ +struct rbc_dispatch { + u8 op; + char *name; + int (*rbc_command) (struct usbd_function_instance *, char *, int op); + int device_check; + u8 dataphase; +}; + +/*! Command cross reference + * + * This is the list of commands observed from each host OS. It is necessarily + * incomplete in that we not have reached some condition necessary to have + * other commands used. + * Win2k WinXP + * SCSI_TEST_UNIT_READY yes yes + * SCSI_READ_10 yes yes + * SCSI_WRITE_10 yes yes + * SCSI_READ_CAPACITY yes yes + * SCSI_VERIFY yes + * SCSI_INQUIRY yes yes + * SCSI_MODE_SENSE yes + * SCSI_READ_FORMAT_CAPACITY yes yes + * SCSI_REQUEST_SENSE + * SCSI_PREVENT_ALLOW_MEDIA_REMOVAL + * SCSI_START_STOP + * SCSI_MODE_SELECT + * SCSI_FORMAT_UNIT + * + */ + +struct rbc_dispatch rbc_dispatch_table[] = { + { SCSI_TEST_UNIT_READY, "SCSI_TEST_UNIT_READY", msc_scsi_test_unit_ready, NOZLP, NODATAPHASE }, + { SCSI_READ_10, "SCSI_READ_10", msc_scsi_read_10, SENDZLP, HASDATAPHASE }, + { SCSI_WRITE_10, "SCSI_WRITE_10", msc_scsi_write_10, NOZLP, HASDATAPHASE }, + { SCSI_READ_CAPACITY, "SCSI_READ_CAPACITY", msc_scsi_read_capacity, SENDZLP, HASDATAPHASE }, + { SCSI_VERIFY, "SCSI_VERIFY", msc_scsi_verify, NOCHK, NODATAPHASE }, + { SCSI_INQUIRY, "SCSI_INQUIRY", msc_scsi_inquiry, NOCHK, HASDATAPHASE }, + { SCSI_MODE_SENSE, "SCSI_MODE_SENSE", msc_scsi_mode_sense, NOCHK, HASDATAPHASE }, + { SCSI_READ_FORMAT_CAPACITY, "SCSI_READ_FORMAT_CAPACITY", msc_scsi_read_format_capacity, SENDZLP, HASDATAPHASE }, + { SCSI_REQUEST_SENSE, "SCSI_REQUEST_SENSE", msc_scsi_request_sense, NOCHK, HASDATAPHASE}, + { SCSI_PREVENT_ALLOW_MEDIA_REMOVAL, "SCSI_PREVENT_ALLOW_MEDIA_REMOVAL", msc_scsi_prevent_allow, NOZLP, NODATAPHASE }, + { SCSI_START_STOP, "SCSI_START_STOP", msc_scsi_start_stop, NOZLP, NODATAPHASE }, + { SCSI_MODE_SELECT, "SCSI_MODE_SELECT", msc_scsi_mode_select, NOCHK , NODATAPHASE }, + { SCSI_FORMAT_UNIT, "SCSI_FORMAT_UNIT", msc_cmd_unknown, NOCHK , NODATAPHASE}, + + { SCSI_READ_6, "SCSI_READ_6", msc_cmd_unknown, NOCHK, NODATAPHASE }, + { SCSI_WRITE_6, "SCSI_WRITE_6", msc_cmd_unknown, NOCHK, NODATAPHASE}, + { SCSI_RESERVE, "SCSI_RESERVE", msc_cmd_unknown, NOCHK, NODATAPHASE }, + { SCSI_RELEASE, "SCSI_RELEASE", msc_cmd_unknown, NOCHK, NODATAPHASE }, + { SCSI_SEND_DIAGNOSTIC, "SCSI_SEND_DIAGNOSTIC", msc_cmd_unknown, NOCHK, NODATAPHASE }, + { SCSI_SYNCHRONIZE_CACHE, "SCSI_SYNCHRONIZE_CACHE", msc_cmd_unknown, NOCHK, NODATAPHASE }, + { SCSI_MODE_SENSE_10, "SCSI_MODE_SENSE_10", msc_cmd_unknown, NOCHK, NODATAPHASE }, + { SCSI_REZERO_UNIT, "SCSI_REZERO_UNIT", msc_cmd_unknown, NOCHK, NODATAPHASE }, + { SCSI_SEEK_10, "SCSI_SEEK_10", msc_cmd_unknown, NOCHK, NODATAPHASE }, + { SCSI_WRITE_AND_VERIFY, "SCSI_WRITE_AND_VERIFY", msc_cmd_unknown, NOCHK, NODATAPHASE }, + { SCSI_WRITE_12, "SCSI_WRITE_12", msc_cmd_unknown, NOCHK, NODATAPHASE }, + { SCSI_READ_12, "SCSI_READ_12", msc_cmd_unknown, NOCHK, NODATAPHASE }, + { SCSI_MODE_SELECT_10, "SCSI_MODE_SELECT_10", msc_cmd_unknown, NOCHK, NODATAPHASE }, + { SCSI_PRIVATE_PCS, "SCSI_PRIVATE_PCS", msc_cmd_private_pcs, NOCHK, NODATAPHASE }, + + { 0xff, "SCSI_UNKNOWN", msc_cmd_unknown, NOCHK, NODATAPHASE }, +}; + + + +/*! msc_recv_command - process a new CBW + * + * Return non-zero if urb was not disposed of. + */ +void msc_recv_command(struct usbd_urb *urb, struct usbd_function_instance *function_instance) +{ + struct msc_private *msc = function_instance->privdata; + struct msc_command_block_wrapper *command = (struct msc_command_block_wrapper *)urb->buffer; + u8 op = command->CBWCB[0]; + struct rbc_dispatch *dispatch; + + /* + * c.f. section 6.2 - Valid and Meaningful CBW + * c.f. section 6.2.1 - Valid CBW + * + * The CBW was received after the device had sent a CSW or after a + * reset XXX check that we only set MSC_READY after reset or sending + * CSW. + * + * The CBW is 31 (1Fh) bytes in length and the bCBWSignature is + * equal to 43425355h. + */ + TRACE_MSG1(MSC,"urb->actual_length:%d", urb->actual_length); + THROW_IF(31 != urb->actual_length, error); + TRACE_MSG3(MSC,"le32_to_cpu(command->dCBWSignature):%08X , le32_to_cpu(command->dCBWTag):%08X, le32_to_cpu(command->dCBWDataTransferLength):%08X", le32_to_cpu(command->dCBWSignature), le32_to_cpu(command->dCBWTag), le32_to_cpu(command->dCBWDataTransferLength)); + + THROW_IF(CBW_SIGNATURE != le32_to_cpu(command->dCBWSignature), error); + + /* + * c.f. section 6.2.2 - Meaningful CBW + * + * no reserved bits are set + * the bCBWLUN contains a valid LUN supported by the device + * both bCBWCBlength and the content of the CBWCB are in accordance with bInterfaceSubClass + */ + + // XXX checklun etc + + /* + * Success + */ + memcpy(&msc->command, command, sizeof(struct msc_command_block_wrapper)); + msc->data_transferred_in_bytes = msc->TransferLength_in_blocks = msc->TransferLength_in_bytes = 0; + + TRACE_TAG(command->dCBWTag, urb->framenum); + usbd_free_urb(urb); + urb = NULL; + + /* + * Search using the opcode to find the dispatch function to use and + * call it. + */ + for (dispatch = rbc_dispatch_table; dispatch->op != 0xff; dispatch++) { + CONTINUE_UNLESS ((dispatch->op == op)); + + + //Case , Device no data + if(dispatch->dataphase == NODATAPHASE){ + /*Case 9 case 4 : If Host Expect data, while device has no data, then report phase error directly */ + + if (msc->command.dCBWDataTransferLength>0) { + if (msc->command.bmCBWFlags & 0x80) + usbd_halt_endpoint(function_instance, BULK_IN); + else + usbd_halt_endpoint(function_instance, BULK_OUT); + + msc->data_transferred_in_bytes = msc->command.dCBWDataTransferLength; + msc->command_state = MSC_CBW_PASSED; /* XXX always Passed???*/ + return; + } + + } + TRACE_CBW(dispatch->name, dispatch->op, dispatch->device_check); + TRACE_RECV(MSC, 8, &(command->CBWCB[1])); + + /* Depending on the command we may need to check if the device is available + * and either fail or send a ZLP if it is not + */ + if (dispatch->device_check) + RETURN_IF (msc_check_blockdev_name(function_instance, dispatch->device_check, dispatch->name)); + + /* Call the specific function that implements the specified command + */ + if (dispatch->rbc_command(function_instance, dispatch->name, op)) + TRACE_MSG0(MSC,"COMMAND ERROR"); + return; + } + + /* FALL THROUGH if no match is found */ + + CATCH(error) { + TRACE_MSG0(MSC,"RECV CBW ERROR"); + if (urb) + usbd_free_urb(urb); + + /* XXX which of these do we stall? + */ + + TRACE_MSG0(MSC,"sos88 halt endpoint"); + usbd_halt_endpoint(function_instance, BULK_IN); + usbd_halt_endpoint(function_instance, BULK_OUT); + msc->endpoint_state=3; + msc->command_state=MSC_WAITFOR_RESET; + } + //msc_cmd_unknown(msc, "CMD_UNKNOWN", op); +} + + +/* Sent Function - process a sent urb ********************************************************** */ + +/*! msc_urb_sent - called to indicate URB transmit finished + * @param tx_urb: pointer to struct usbd_urb + * @param rc: result + * + * This is called when an urb is sent. Depending on current state + * it may: + * + * - continue sending data + * - send a CSW + * - start a recv for a CBW + * + * This is called from BOTTOM HALF context. + * + * @return non-zero if urb was not disposed of. + */ +int msc_urb_sent (struct usbd_urb *tx_urb, int rc) +{ + struct usbd_function_instance *function_instance = tx_urb->function_privdata; + struct msc_private *msc = function_instance->privdata; + static unsigned char tempbuf=0; + + RETURN_EINVAL_IF(!(function_instance = tx_urb->function_instance)); + RETURN_EINVAL_IF(usbd_get_device_status(function_instance) == USBD_CLOSING); + RETURN_EINVAL_IF(usbd_get_device_state(function_instance) != STATE_CONFIGURED); +#if defined(CONFIG_OTG_MSC_PIPES_TEST) + memset(tx_urb->buffer,tempbuf++,512); + RETURN_ZERO_UNLESS(usbd_start_in_urb (tx_urb)); + usbd_free_urb (tx_urb); + return 0; +#else + + switch (msc->command_state) { + case MSC_DATA_IN_READ: + case MSC_DATA_IN_READ_FINISHED: + TRACE_MSG0(MSC,"URB SENT READ"); + return msc_in_read_10_urb_sent(tx_urb); + + case MSC_QUERY: + // finished, send CSW + msc->data_transferred_in_bytes=tx_urb->actual_length; + TRACE_MSG0(MSC,"URB SENT QUERY"); + msc_start_sending_csw(function_instance, USB_MSC_PASSED); + break; + + case MSC_STATUS: + default: + // sent a CSW need to receive the next CBW + TRACE_MSG0(MSC,"URB SENT STATUS"); + msc->command_state = MSC_READY; + msc_start_recv_urb(function_instance, sizeof(struct msc_command_block_wrapper)); + break; + } + usbd_free_urb (tx_urb); + return 0; +#endif +} + + +/* Receive Function - receiving an urb ********************************************************* */ + +/*! msc_recv_urb - process a received urb + * + * Return non-zero if urb was not disposed of. + */ +static int msc_recv_urb (struct usbd_urb *rcv_urb, int rc) +{ + struct usbd_function_instance *function_instance = rcv_urb->function_privdata; + struct msc_private *msc = function_instance->privdata; + + RETURN_EINVAL_IF(!msc->connected); + + TRACE_MSG4(MSC, "RECV URB pointer: %08x, length: %d state: %d, rc:%d", rcv_urb, rcv_urb->actual_length, msc->command_state,rc); + + if(rc!=0) return -EINVAL; //XXX if urb callback with error, now do nothing + +#if defined(CONFIG_OTG_MSC_PIPES_TEST) + printk(KERN_INFO "rcv_urb->actual_length:%d\n",rcv_urb->actual_length); + RETURN_ZERO_UNLESS(usbd_start_out_urb(rcv_urb)); + TRACE_MSG0(MSC,"START RECV URB ERROR"); + usbd_free_urb(rcv_urb); + return -EINVAL; +#else /*defined(CONFIG_OTG_MSC_PIPES_TEST) */ + + switch(msc->command_state) { + + // ready to start a new transaction + case MSC_READY: + msc_recv_command(rcv_urb, function_instance); + return 0; + + // we think we are receiving data + case MSC_DATA_OUT_WRITE: + case MSC_DATA_OUT_WRITE_FINISHED: + + //if(rcv_urb->actual_length) + //printk(KERN_INFO "sos00 rcv_urb->actual_length: %x\n", rcv_urb->actual_length); + //else + // printk(KERN_INFO "sos00 rcv_urb->actual_length = 0\n"); + + msc_recv_out_blocks(rcv_urb); + return 0; + + // we think we are sending data + case MSC_DATA_IN_READ: + case MSC_DATA_IN_READ_FINISHED: + msc_start_sending_csw_failed (function_instance, SCSI_SENSEKEY_INVALID_COMMAND, msc->lba, USB_MSC_FAILED); + break; + + // we think we are sending status + case MSC_STATUS: + msc_start_sending_csw_failed (function_instance, SCSI_SENSEKEY_INVALID_COMMAND, msc->lba, USB_MSC_FAILED); + break; + case MSC_WAITFOR_RESET: + usbd_halt_endpoint(function_instance, BULK_IN); + usbd_halt_endpoint(function_instance, BULK_OUT); + msc->endpoint_state=3; + break; + + // we don't think + case MSC_UNKNOWN: + default: + TRACE_MSG0(MSC,"RECV URB ERROR"); + usbd_halt_endpoint(function_instance, BULK_IN); + usbd_halt_endpoint(function_instance, BULK_OUT); + msc->endpoint_state=3; + msc->command_state=MSC_WAITFOR_RESET; + } + // let caller dispose of urb + + return -EINVAL; +#endif /*defined(CONFIG_OTG_MSC_PIPES_TEST)*/ + +} + +/* USB Device Functions ************************************************************************ */ + + +#if 0 +static void msc_device_request (struct usbd_function_instance *function, struct usbd_device_request *request) +{ + TRACE_MSG0(MSC,"--"); +} + +static int msc_set_configuration (struct usbd_function_instance *function, int wValue) +{ + TRACE_MSG1(MSC,"wValue: %02x", wValue); + return 0; +} + +static int msc_set_interface (struct usbd_function_instance *function, int wIndex, int wValue) +{ + TRACE_MSG2(MSC,"wIndex: %02x wValue: %02x", wIndex, wValue); + return 0; +} +#endif + +/* USB Device Functions ************************************************************************ */ + +/*! msc_device_request - called to indicate urb has been received + * + * This function is called when a SETUP packet has been received that + * should be handled by the function driver. It will not be called to + * process the standard chapter nine defined requests. + * + * Return non-zero for failure. + */ +int msc_device_request(struct usbd_function_instance *function_instance, struct usbd_device_request *request) +{ + struct msc_private *msc = function_instance->privdata; + struct usbd_urb *urb; + + u8 bRequest = request->bRequest; + u8 bmRequestType = request->bmRequestType; + u16 wValue = le16_to_cpu(request->wValue); + u16 wIndex = le16_to_cpu(request->wIndex); + u16 wLength = le16_to_cpu(request->wLength); + + TRACE_MSG5(MSC, "MSC RECV SETUP bmRequestType:%02x bRequest:%02x wValue:%04x wIndex:%04x wLength:%04x", + bmRequestType, bRequest, wValue, wIndex, wLength); + + // verify that this is a usb class request per cdc-acm specification or a vendor request. + RETURN_ZERO_IF (!(request->bmRequestType & (USB_REQ_TYPE_CLASS | USB_REQ_TYPE_VENDOR))); + + // determine the request direction and process accordingly + switch (request->bmRequestType & (USB_REQ_DIRECTION_MASK | USB_REQ_TYPE_MASK)) { + + case USB_REQ_HOST2DEVICE | USB_REQ_TYPE_CLASS: + switch (request->bRequest) { + case MSC_BULKONLY_RESET: + TRACE_MSG0(MSC, "MSC_BULKONLY_RESET"); + RETURN_EINVAL_IF((wValue !=0) | (wIndex !=0) | ( wLength !=0)); + // set msc back to reset state here + //..... + + msc->command_state = MSC_WAITFOR_CLEAR; + msc->endpoint_state=3; + ////////////////////////// + return 0; + default: + TRACE_MSG0(MSC, "UNKNOWN H2D"); + return -EINVAL; + + } + + case USB_REQ_DEVICE2HOST | USB_REQ_TYPE_CLASS: + switch (request->bRequest) { + case MSC_BULKONLY_GETMAXLUN: + RETURN_EINVAL_IF((wValue !=0) | (wIndex !=0) | ( wLength !=1)); + RETURN_EINVAL_IF(!(urb = usbd_alloc_urb_ep0(function_instance, 1, NULL))); + urb->buffer[0] = 0; + urb->actual_length = 1; + RETURN_ZERO_IF(!usbd_start_in_urb(urb)); + usbd_free_urb(urb); + return -EINVAL; + default: + TRACE_MSG0(MSC, "UNKNOWN D2H"); + return -EINVAL; + } + default: + TRACE_MSG0(MSC,"unknown MSC device request"); + return -EINVAL; + break; + } + return -EINVAL; +} + +struct usbd_function_instance *proc_function_instance = NULL; + +int msc_os_init_l24(struct usbd_function_instance *function_instance); +int msc_io_init_l24(struct usbd_function_instance *function_instance); +void msc_io_exit_l24(void); + +/*! msc_function_enable - this is called by the USBD core layer + * + * This is called to initialize the function when a bus interface driver + * is loaded. + */ +static int msc_function_enable (struct usbd_function_instance *function_instance) +{ + struct msc_private *msc = NULL; + + RETURN_EINVAL_UNLESS((msc = CKMALLOC(sizeof(struct msc_private)))); + + // XXX MODULE LOCK HERE + + function_instance->privdata = msc; + + // XXX TODO need to verify that serial number is minimum of 12 + + init_waitqueue_head(&msc->msc_wq); + init_waitqueue_head(&msc->ioctl_wq); + + msc->block_dev_state = DEVICE_EJECTED; + msc->command_state = MSC_READY; + msc->io_state = MSC_INACTIVE; + msc->command_state = MSC_READY; + + msc_os_init_l24(function_instance); + msc_io_init_l24(function_instance); + + return 0; +} + +extern void msc_close_blockdev (struct usbd_function_instance *function_instance); + +/*! msc_function_disable - this is called by the USBD core layer + * + * This is called to close the function when a bus interface driver + * is unloaded. + */ +static void msc_function_disable (struct usbd_function_instance *function_instance) +{ + struct msc_private *msc = function_instance->privdata; + + TRACE_MSG0(MSC,"FUNCTION EXIT"); + + + msc_close_blockdev(function_instance); + function_instance->privdata = NULL; + + LKFREE(msc); + msc_io_exit_l24(); + // XXX MODULE UNLOCK HERE +} + + +/*! msc_set_configuration + * @brief - called to set configuration according received urb + * @param function_instance + * @param configuration + * @return int + */ +int msc_set_configuration (struct usbd_function_instance *function_instance, int configuration) +{ + struct usbd_interface_instance *interface_instance = (struct usbd_interface_instance *)function_instance; + struct msc_private *msc = function_instance->privdata; + struct usbd_urb *tx_urb; + + TRACE_MSG0(MSC, "CONFIGURED"); + + // XXX Need to differentiate between non-zero, zero and non-zero done twice + usbd_flush_endpoint_index(function_instance,BULK_IN); + usbd_flush_endpoint_index(function_instance,BULK_OUT); + + TRACE_MSG0(MSC,"EVENT CONFIGURED"); + msc->endpoint_state=0; + msc->connected = 1; + msc->command_state = MSC_READY; +#if defined(CONFIG_OTG_MSC_PIPES_TEST) + + msc_start_recv_urb(function_instance, sizeof(struct msc_command_block_wrapper)); + + RETURN_EINVAL_UNLESS((tx_urb = usbd_alloc_urb (function_instance, BULK_IN, 512, msc_urb_sent))); + + tx_urb->actual_length = 512; + tx_urb->function_privdata = function_instance; + memset(tx_urb->buffer,0x66,512); + RETURN_ZERO_UNLESS(usbd_start_in_urb (tx_urb)); + usbd_free_urb (tx_urb); + return -EINVAL; +#else /*defined(CONFIG_OTG_MSC_PIPES_TEST) */ + msc_start_recv_urb(function_instance, 512); +#endif + #if 0 + local_irq_save(flags); + if (msc->io_state & MSC_IOCTL_WAITING) { + msc->io_state &= ~MSC_IOCTL_WAITING; + TRACE_MSG0(MSC, "WAKEUP"); + } + local_irq_restore(flags); + #endif + wake_up_interruptible(&msc->ioctl_wq); + + return 0; +} + +/*! int msc_reset ( struct usbd_function_instance * ) + * @brief - called to process reset (urb) command + * @param function_instance + * @return int + */ +int msc_reset (struct usbd_function_instance *function_instance) +{ + struct usbd_interface_instance *interface_instance = (struct usbd_interface_instance *)function_instance; + struct msc_private *msc = function_instance ? function_instance->privdata : NULL; + int connected = msc ? msc->connected : 0; + + TRACE_MSG1(MSC,"msc: %x", (int)msc); + + + TRACE_MSG2(MSC,"EVENT RESET: connected %d msc->io_state %d", msc->connected, msc->io_state); + + #if 0 + local_irq_save(flags); + if (msc->io_state & MSC_IOCTL_WAITING) { + msc->io_state &= ~MSC_IOCTL_WAITING; + TRACE_MSG0(MSC, "WAKEUP"); + wake_up_interruptible(&msc->ioctl_wq); + } + local_irq_restore(flags); + #endif + if (msc->connected ==1) wake_up_interruptible(&msc->ioctl_wq); + msc->connected = 0; + RETURN_ZERO_UNLESS(connected); + + // XXX we should have a semaphore to protect this + RETURN_ZERO_UNLESS (msc->rcv_urb_finished); + usbd_free_urb (msc->rcv_urb_finished); + msc->rcv_urb_finished = NULL; + msc->io_state=0; + return 0; +} + +/*! msc_suspended + * @brief- called to process msc suspended command + * @param function_instance + * @return int + */ +int msc_suspended (struct usbd_function_instance *function_instance) +{ + struct usbd_interface_instance *interface_instance = (struct usbd_interface_instance *)function_instance; + struct msc_private *msc = function_instance->privdata; + + TRACE_MSG1(MSC,"msc: %x", (int)msc); + if (msc->connected ==1) wake_up_interruptible(&msc->ioctl_wq); + msc->connected =0; + TRACE_MSG0(MSC, "SUSPENDED"); + return 0; +} + + +/*!int msc_resumed (struct usbd_function_instance * ) + * @brief msc_resumed - called to process msc resumed command + * @param function_instance + * @return int + */ +int msc_resumed (struct usbd_function_instance *function_instance) +{ + struct usbd_interface_instance *interface_instance = (struct usbd_interface_instance *)function_instance; + struct msc_private *msc = function_instance->privdata; + TRACE_MSG1(MSC,"msc: %x", (int)msc); + if (msc->connected ==0) wake_up_interruptible(&msc->ioctl_wq); + msc->connected =1; + TRACE_MSG0(MSC, "RESUMED"); + + return 0; +} + +/*! + * @brief msc_endpoint_cleared- called to indicate endpoint has been cleared + * @param function_instance + * @param bEndpointAddress + * @return none + */ +static void msc_endpoint_cleared (struct usbd_function_instance *function_instance, int bEndpointAddress) +{ + struct usbd_interface_instance *interface_instance = (struct usbd_interface_instance *)function_instance; + struct msc_private *msc = function_instance->privdata; + + TRACE_MSG1(MSC,"bEndpointAddress: %02x", bEndpointAddress); + + switch (msc->command_state) { + + case MSC_CBW_PHASE_ERROR: + msc_start_sending_csw(function_instance, USB_MSC_PHASE_ERROR); + + break; + + case MSC_CBW_PASSED: + msc_start_sending_csw(function_instance, USB_MSC_PASSED); + + break; + + case MSC_WAITFOR_RESET: + usbd_halt_endpoint(function_instance, BULK_IN); + usbd_halt_endpoint(function_instance, BULK_OUT); + msc->endpoint_state=BULK_IN_HALTED | BULK_OUT_HALTED; + break; + case MSC_WAITFOR_CLEAR: + if(bEndpointAddress&0x80){ + msc->endpoint_state &= ~BULK_IN_HALTED; + TRACE_MSG0(MSC,"Bulk IN cleared"); + } + else{ + msc->endpoint_state &= ~BULK_OUT_HALTED; + TRACE_MSG0(MSC,"Bulk OUT cleared"); + } + + if(!msc->endpoint_state){ + msc->command_state = MSC_READY; + msc_start_recv_urb(function_instance, sizeof(struct msc_command_block_wrapper)); + + + TRACE_MSG0(MSC,"Ready to receive a new CBW"); + } + break; + + default: + break; + } + +} + + +/* ********************************************************************************************* */ +/*! msc_function_ops usbd_function_option structure variable, initialized for all usbd core called operations + */ +struct usbd_function_operations msc_function_ops = { + device_request: msc_device_request, + function_enable: msc_function_enable, + function_disable: msc_function_disable, + set_configuration: msc_set_configuration, + reset: msc_reset, + suspended: msc_suspended, + resumed: msc_resumed, + endpoint_cleared: msc_endpoint_cleared, +}; + +/* ********************************************************************************************* */ diff --git a/drivers/otg/functions/msc/msc-fd.h b/drivers/otg/functions/msc/msc-fd.h new file mode 100644 index 000000000000..e31a17041024 --- /dev/null +++ b/drivers/otg/functions/msc/msc-fd.h @@ -0,0 +1,83 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/msc_fd/msc.h - Mass Storage Class + * @(#) sl@belcarra.com|otg/functions/msc/msc-fd.h|20060702220757|39666 + * + * Copyright (c) 2003-2004 Belcarra + * Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + * + * By: + * Stuart Lynne <sl@belcarra.com> + * Bruce Balden <balden@belcarra.com> + * + */ + +#ifndef MSC_FD_H +#define MSC_FD_H 1 + +extern int msc_dispatch_query_urb(struct usbd_function_instance *, struct usbd_urb *, int , int ); +extern int msc_start_sending_csw(struct usbd_function_instance *, u8 ); +extern int msc_dispatch_query_urb_zlp(struct usbd_function_instance *, u32 sensedata, u32 info, int status); +extern int msc_start_sending_csw_failed(struct usbd_function_instance *, u32 sensedata, u32 info, int status); +extern int msc_start_recv_urb(struct usbd_function_instance *, int size); + +#define NOCHK 0 +#define NOZLP 1 +#define SENDZLP 2 + +#if 1 +/*! @name MSC activity trace functions */ +/*! @{*/ + +static __inline__ void TRACE_SENSE(unsigned int sense, unsigned int info) +{ + TRACE_MSG2(MSC, "--> SENSE: %06x INFO: %08x", sense, info); +} +static __inline__ void TRACE_RLBA(unsigned int lba, unsigned int crc) +{ + TRACE_MSG2(MSC, "<-- rlba [%8x %08x]", lba, crc); +} +static __inline__ void TRACE_SLBA(unsigned int lba, unsigned int crc) +{ + TRACE_MSG2(MSC, "--> slba [%8x %08x]", lba, crc); +} +static __inline__ void TRACE_TLBA(unsigned int lba, unsigned int crc) +{ + TRACE_MSG2(MSC, "--> tlba [%8x %08x]", lba, crc); +} +static __inline__ void TRACE_TAG(unsigned int tag, unsigned int frame) +{ + TRACE_MSG2(MSC, "--> TAG: %8x FRAME: %03x", tag, frame); +} +static __inline__ void TRACE_CBW(char *msg, int val, int check) +{ + TRACE_MSG3(MSC, " --> %s %02x check: %d", msg, val, check); +} +//static __inline__ void TRACE_RECV(unsigned char *cp) +//{ +// TRACE_MSG8(MSC, "<-- recv [%02x %02x %02x %02x %02x %02x %02x %02x]", +// cp[0], cp[1], cp[2], cp[3], cp[4], cp[5], cp[6], cp[7]); +//} +//static __inline__ void TRACE_SENT(unsigned char *cp) +//{ +// TRACE_MSG8(MSC, "--> sent [%02x %02x %02x %02x %02x %02x %02x %02x]", +// cp[0], cp[1], cp[2], cp[3], cp[4], cp[5], cp[6], cp[7]); +//} + +/*! @} */ + +#endif + + +#endif /* MSC_H */ diff --git a/drivers/otg/functions/msc/msc-io-l24.c b/drivers/otg/functions/msc/msc-io-l24.c new file mode 100644 index 000000000000..cf995b1b7ca1 --- /dev/null +++ b/drivers/otg/functions/msc/msc-io-l24.c @@ -0,0 +1,325 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/function/msc/msc-io-l24.c - MSC IO + * @(#) sl@belcarra.com/whiskey.enposte.net|otg/functions/msc/msc-io-l24.c|20070425221028|61154 + * + * Copyright (c) 2003-2005 Belcarra Technologies Corp + * Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + * + * By: + * Stuart Lynne <sl@lbelcarra.com>, + * Tony Tang <tt@belcarra.com>, + * Bruce Balden <balden@belcarra.com> + * + */ +/*! + * @file otg/functions/msc/msc-io-l24.c + * @brief + * + * NOTES + * + * TODO + * + * 1. implement prevent removal command. + * + * @ingroup MSCFunction + * @ingroup LINUXOS + */ + +#include <otg/otg-compat.h> +#include <otg/otg-module.h> + +#include <otg/usbp-chap9.h> +#include <otg/usbp-func.h> +#include <otg/otg-trace.h> +#include <otg/otg-api.h> + +#include <linux/poll.h> +#include <linux/sched.h> +//#include <linux/devfs_fs_kernel.h> + +#include <linux/kdev_t.h> +#include <linux/blkdev.h> + +#include "msc-scsi.h" +#include "msc.h" +#include "msc-fd.h" +#include "crc.h" +#include "msc-io.h" + +extern void msc_open_blockdev (struct usbd_function_instance *function_instance); +extern void msc_close_blockdev (struct usbd_function_instance *function_instance); +// XXX no use extern int msc_connection_blockdev (struct usbd_function_instance *function_instance); +//XXX No use extern struct msc_private msc_private; + +#define DEVICE_EJECTED 0x0001 //MEDIA_EJECTED +#define DEVICE_INSERTED 0x0002 //MEDIA_INSERT +#define DEVICE_MOUNTED 0x0001 //DEVICE_MOUNTED +#define DEVICE_UNMOUNTED 0x0002 //DEVICE_UNMOUNTED + +#if 0 +void msc_io_wait(struct usbd_function_instance *function_instance, u32 flag) +{ + unsigned long flags; + msc->io_state |= MSC_IOCTL_WAITING | flag; + TRACE_MSG1(MSC, "SLEEPING io_state: %x", msc->io_state); + interruptible_sleep_on(&msc->ioctl_wq); +} + +void msc_io_wakup(struct usbd_function_instance *function_instance) +{ + unsigned long flags; + local_irq_save(flags); + if (msc->io_state & MSC_IOCTL_WAITING) { + msc->io_state &= ~MSC_IOCTL_WAITING; + TRACE_MSG0(MSC, "WAKEUP"); + wake_up_interruptible(&msc->ioctl_wq); + } + local_irq_restore(flags); +} +#endif + +extern struct usbd_function_instance *proc_function_instance; + +/*! int msc_io_ioctl_internal (struct inode* , unsigned int, unsigned long) + * @brief process io control command + * + * @param inode + * @param cmd - ip command + * @param arg - command argument + * @return 0 for success + * + * + * XXX Can we get function_instance passed in as arg??? XXX + */ +int msc_io_ioctl_internal(struct inode *inode, unsigned int cmd, unsigned long arg) +{ + struct usbd_function_instance *function_instance = proc_function_instance; // XXX + struct msc_private *msc = function_instance ? function_instance->privdata : NULL; + int i; + int len; + int flag; + + static char func_buf[32]; + struct otgmsc_mount mount; + unsigned long flags; + + TRACE_MSG2(MSC, "cmd: %d connect: %d", cmd, msc->connected); + memset(&mount, 0, sizeof(mount)); + switch (cmd) { + + /* Mount - make the specified major/minof device available for use + * by the USB Host, this does not require an active connection. + */ + case OTGMSC_START: + TRACE_MSG0(MSC, "Mounting the device"); + RETURN_EINVAL_IF(copy_from_user(&mount, (void *)arg, _IOC_SIZE(cmd))); + + TRACE_MSG3(MSC, "major=%d minor=%d state=%d", mount.major, mount.minor, msc->block_dev_state); + msc->major = mount.major; + msc->minor = mount.minor; + + //msc->io_state = MSC_INACTIVE; + inode->i_rdev=MKDEV(msc->major, msc->minor); + msc_open_blockdev (function_instance); + RETURN_EAGAIN_UNLESS(msc->command_state == MSC_READY); + + mount.status = (msc->block_dev_state == DEVICE_INSERTED) ? DEVICE_MOUNTED : DEVICE_UNMOUNTED; + + TRACE_MSG1(MSC, "Device is mounted status: %d", mount.status); + + // XXX Need to copy result back to user space + RETURN_EINVAL_IF (copy_to_user((void *)arg, &mount, sizeof(mount))); + TRACE_MSG0(MSC, "Device mounted"); + return 0; + + /* Umount - make the currently mounted device unavailable to the USB Host, + * if there is pending block i/o block until it has finished. + * Note that if the driver is unloaded the waiting ioctl process + * must be woken up and informed with error code. + */ + case OTGMSC_STOP: + + TRACE_MSG0(MSC, "Unmounting the device"); + + RETURN_EINVAL_IF (copy_from_user (&mount, (void *)arg, _IOC_SIZE(cmd))); + + if (msc->command_state != MSC_READY) { + TRACE_MSG0(MSC, "SLEEPING"); + interruptible_sleep_on(&msc->ioctl_wq); + TRACE_MSG0(MSC, "AWAKE"); + } + + RETURN_EAGAIN_UNLESS(msc->command_state == MSC_INACTIVE); + + // XXX Need to copy result back to user space + msc->major = mount.major; + msc->minor = mount.minor; + msc_close_blockdev(function_instance); + mount.status = DEVICE_UNMOUNTED; + RETURN_EINVAL_IF (copy_to_user((void *)arg, &mount, sizeof(mount))); + TRACE_MSG0(MSC, "Device unmounted"); + return 0; + + + /* Status - return the current mount status. + */ + case OTGMSC_STATUS: + TRACE_MSG0(MSC, "Mount status"); + RETURN_EINVAL_IF (copy_from_user (&mount, (void *)arg, _IOC_SIZE(cmd))); + if (msc->block_dev_state == DEVICE_EJECTED) + mount.status = DEVICE_UNMOUNTED; + else + mount.status = DEVICE_MOUNTED; + + // XXX Need to copy result back to user space + RETURN_EINVAL_IF (copy_to_user((void *)arg, &mount, sizeof(mount))); + return 0; + + /* Wait_Connect - if not already connected wait until connected, + * Note that if the driver is unloaded the waiting ioctl process + * must be woken up and informed with error code. + */ + case OTGMSC_WAIT_CONNECT: + TRACE_MSG1(MSC, "Wait for connect: connected: %d", msc->connected); + // XXX local_irq_save(flags); + + if (msc->connected == 0) { + TRACE_MSG0(MSC, "SLEEPING"); + interruptible_sleep_on (&msc->ioctl_wq); + TRACE_MSG0(MSC, "AWAKE"); + } + RETURN_EAGAIN_UNLESS(msc->connected); + + RETURN_EINVAL_IF (copy_from_user (&mount, (void *)arg, _IOC_SIZE(cmd))); + RETURN_EINVAL_IF (copy_to_user((void *)arg, &mount, sizeof(mount))); + TRACE_MSG0(MSC, "Device connected"); + return 0; + + /* Wait_DisConnect - if not already disconnected wait until disconnected, + * Note that if the driver is unloaded the waiting ioctl process + * must be woken up and informed with error code. + */ + case OTGMSC_WAIT_DISCONNECT: + TRACE_MSG1(MSC, "Wait for disconnect: connected: %d", msc->connected); + + if (msc->connected == 1) { + TRACE_MSG0(MSC, "SLEEPING"); + interruptible_sleep_on (&msc->ioctl_wq); + TRACE_MSG0(MSC, "AWAKE"); + } + RETURN_EAGAIN_IF(msc->connected); + RETURN_EINVAL_IF (copy_from_user (&mount, (void *)arg, _IOC_SIZE(cmd))); + RETURN_EINVAL_IF (copy_to_user((void *)arg, &mount, sizeof(mount))); + TRACE_MSG0(MSC, "Device disconnected"); + return 0; + + default: + TRACE_MSG1(MSC, "Unknown command: %x", cmd); + TRACE_MSG1(MSC, "OTGMSC_START: %x", OTGMSC_START); + TRACE_MSG1(MSC, "OTGMSC_WRITEPROTECT: %x", OTGMSC_WRITEPROTECT); + TRACE_MSG1(MSC, "OTGMSC_STOP: %x", OTGMSC_STOP); + TRACE_MSG1(MSC, "OTGMSC_STATUS: %x", OTGMSC_STATUS); + TRACE_MSG1(MSC, "OTGMSC_WAIT_CONNECT: %x", OTGMSC_WAIT_CONNECT); + TRACE_MSG1(MSC, "OTGMSC_WAIT_DISCONNECT: %x", OTGMSC_WAIT_DISCONNECT); + return -EINVAL; + } + return 0; +} + + + +/*! int msc_io_ioctl(struct inode* inode, struct file*, unsigned int, unsigned long) + * @brief verify ioctl command and call msc_io_ioctl_internal to process ioctl command + * + * @param inode pointer to device node + * @param filp file pointer + * @param cmd command to process + * @param arg command argument + * @return non-zero for error + */ +int msc_io_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg) +{ + struct usbd_function_instance *function_instance = proc_function_instance; // XXX + struct msc_private *msc = function_instance ? function_instance->privdata : NULL; + int i; + int len; + int flag; + + TRACE_MSG6(MSC, "cmd: %08x arg: %08x type: %02d nr: %02d dir: %02d size: %02d", + cmd, arg, _IOC_TYPE(cmd), _IOC_NR(cmd), _IOC_DIR(cmd), _IOC_SIZE(cmd)); + + RETURN_EINVAL_UNLESS (_IOC_TYPE(cmd) == OTGMSC_MAGIC); + RETURN_EINVAL_UNLESS (_IOC_NR(cmd) <= OTGMSC_MAXNR); + + RETURN_EFAULT_IF((_IOC_DIR(cmd) == _IOC_READ) && !access_ok(VERIFY_WRITE, (void *)arg, _IOC_SIZE(cmd))); + RETURN_EFAULT_IF((_IOC_DIR(cmd) == _IOC_WRITE) && !access_ok(VERIFY_READ, (void *)arg, _IOC_SIZE(cmd))); + + return msc_io_ioctl_internal(inode, cmd, arg); +} + +/*! msc_io_proc_ioctl -process ioctl command + * + * @param inode - device node pointer + * @param filp - file pointer + * @param cmd - command to process + * @param arg - command argument + * @return non-zero for error + */ + +int msc_io_proc_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg) +{ + struct usbd_function_instance *function_instance = proc_function_instance; // XXX + struct msc_private *msc = function_instance ? function_instance->privdata : NULL; + return msc_io_ioctl(inode, filp, cmd, arg); +} + + +static struct file_operations msc_io_proc_switch_functions = { +ioctl:msc_io_proc_ioctl, +}; + + +/*! int msc_io_init_l24(struct usbd_function_instance* ) + * @brief - create proc dircetory entry: /proc/msc_io + * + * @param function_instance - pointer to this function driver instance + * @return 0 for success + */ +int msc_io_init_l24(struct usbd_function_instance *function_instance) +{ + struct proc_dir_entry *message = NULL; + + proc_function_instance = function_instance; + + THROW_IF (!(message = create_proc_entry ("msc_io", 0666, 0)), error); + message->proc_fops = &msc_io_proc_switch_functions; + CATCH(error) { + printk(KERN_ERR"%s: creating /proc/msc_io failed\n", __FUNCTION__); + if (message) + remove_proc_entry("msc_io", NULL); + return -EINVAL; + } + return 0; +} + +/*! void msc_io_exit_l24() + * @brief -remove and exit proc operation + * @return none + */ +void msc_io_exit_l24(void) +{ + remove_proc_entry("msc_io", NULL); + proc_function_instance = NULL; +} diff --git a/drivers/otg/functions/msc/msc-io.h b/drivers/otg/functions/msc/msc-io.h new file mode 100644 index 000000000000..2e1b7cae224d --- /dev/null +++ b/drivers/otg/functions/msc/msc-io.h @@ -0,0 +1,72 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/functions/msc/msc-io.h - Mass Storage Class + * @(#) balden@belcarra.com|otg/functions/msc/msc-io.h|20060510202833|21396 + * + * Copyright (c) 2003-2005 Belcarra Technologies Corp + * Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + * + * By: + * Stuart Lynne <sl@belcarra.com> + * Bruce Balden <balden@belcarra.com> + * + */ +/*! + * @file otg/functions/msc/msc-io.h + * @brief Mass Storage Driver private defines + * + * OTGMSC_START - make specified major/minor device available to USB Host + * OTGMSC_WRITEPROTECT - set or reset write protect flag + * + * OTGMSC_STOP - remove access to current device, block until pending I/O finished + * OTGMSC_STATUS - remove current USB connection status (connected or disconnected) + * OTGMSC_WAIT_CONNECT - wait until device is connected (may return immediately if already connected) + * OTGMSC_WAIT_DISCONNECT - wait until device is disconnected (may return immediately if already disconnected) + * + * @ingroup MSCFunction + */ + +//#ifndef MSC_H +//#define MSC_H 1 + +#define MSC_IO "/proc/msc_io" +/*! @struct otgmsc_mount msc-io.h otg/functions/msc/msc-io.h + * @brief struct for managing device mount/umount operation + */ +struct otgmsc_mount { + int major; + int minor; + int lun; + int writeprotect; + int result; + int status; +}; + +#define OTGMSC_MAGIC 'M' +#define OTGMSC_MAXNR 10 + +#define OTGMSC_START _IOWR(OTGMSC_MAGIC, 1, struct otgmsc_mount) +#define OTGMSC_WRITEPROTECT _IOWR(OTGMSC_MAGIC, 2, struct otgmsc_mount) + +#define OTGMSC_STOP _IOR(OTGMSC_MAGIC, 1, struct otgmsc_mount) +#define OTGMSC_STATUS _IOR(OTGMSC_MAGIC, 2, struct otgmsc_mount) +#define OTGMSC_WAIT_CONNECT _IOR(OTGMSC_MAGIC, 3, struct otgmsc_mount) +#define OTGMSC_WAIT_DISCONNECT _IOR(OTGMSC_MAGIC, 4, struct otgmsc_mount) + + +#define MSC_CONNECTED 0x01 +#define MSC_DISCONNECTED 0x02 +#define MSC_WRITEPROTECTED 0x04 + +//#endif /* MSC_H */ diff --git a/drivers/otg/functions/msc/msc-linux.c b/drivers/otg/functions/msc/msc-linux.c new file mode 100644 index 000000000000..f8b0170bf0a3 --- /dev/null +++ b/drivers/otg/functions/msc/msc-linux.c @@ -0,0 +1,1568 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/function/msc/msc-linux.c + * @(#) sl@belcarra.com/whiskey.enposte.net|otg/functions/msc/msc-linux.c|20070425221028|41479 + * + * Copyright (c) 2003-2006 Belcarra Technologies Corp + * Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + * + * By: + * Stuart Lynne <sl@belcarra.com>, + * Tony Tang <tt@belcarra.com>, + * Bruce Balden <balden@belcarra.com> + * + * + */ + +/*! + * @file otg/functions/msc/msc-linux.c + * @brief Mass Storage Driver private defines + * + * + * This is a Mass Storage Class Function that uses the Bulk Only protocol. + * + * To use simply load with something like: + * + * insmod msc_fd.o vendor_id=0xffff product_id=0xffff + * + * Notes: + * + * 1. Currently block I/O is done a page at a time. I have not determined if + * it is possible to dispatch a multiple page generic request. It should at + * least be possible to queue page requests. + * + * 2. Currently for READ operations we have a maximum of one outstanding + * read block I/O and send urb. These are allowed to overlap so that we can + * continue to read data while sending data to the host. + * + * 3. Currently for WRITE operations we have a maximum of one outstanding + * recv urb and one outstanding write block I/O. These are allowed to + * overlap so that we can continue to receive data from the host while + * waiting for writing to complete. + * + * 4. It would be possible to allow multiple writes to be pending, to the + * limit of the page cache, if desired. + * + * 5. It should be possible to allow multiple outstanding reads to be + * pending, to the limit of the page cache, but this potentially could + * require dealing with out of order completions of the reads. Essentially a + * list of completed buffer heads would be required to hold any completed + * buffer heads that cannot be sent prior to another, earlier request being + * completed. + * + * 6. Currently ioctl calls are available to start and stop device i/o. + * + * 7. The driver can optionally generate trace messages showing each sectors + * CRC as read or written with LBA. These can be compared by user programs to + * ensure that the correct data was read and/or written. + * + * + * TODO + * + * 1. error handling for block io, e.g. what if using with removable block + * device (like CF card) and it is removed. + * + * 2. Currently we memcpy() data from between the urb buffer and buffer + * head page. It should be possible to simply use the page buffer for the + * urb. + * + * 3. Should we offer using fileio as an option? This would allow direct access + * to a block device image stored in a normal file or direct access to (for example) + * ram disks. It would require implementing a separate file I/O kernel thread to + * do the actual I/O. + * + * 4. It may be interesting to support use of SCSI block device and pass the + * scsi commands directly to that. This would allow vendor commands for real + * devices to be passed through and executed with results being properly + * returned to the host. [This is the intended design for the mass storage + * specification.] + * + * + * TODO FIXME Bus Interface Notes + * + * + * @ingroup MSCFunction + * @ingroup LINUXOS + */ + + +#include <otg/otg-compat.h> +#include <otg/usbp-chap9.h> +#include <otg/usbp-func.h> +#include <otg/otg-trace.h> + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/version.h> + +#include <otg/otg-linux.h> +#include <linux/init.h> +#include <asm/uaccess.h> +#include <linux/ctype.h> +#include <linux/timer.h> +#include <linux/interrupt.h> +#include <asm/atomic.h> +#include <linux/random.h> +#include <linux/slab.h> + +#if defined(LINUX26) +#include <linux/bio.h> +#include <linux/buffer_head.h> +#endif +#define CONFIG_OTG_MSC_BLOCK_TRACE 1 +#include <linux/blkdev.h> +#include <linux/kdev_t.h> + +#include "msc-scsi.h" +#include "msc.h" +#include "msc-fd.h" +#ifdef CONFIG_OTG_MSC_BLOCK_TRACE +#include "crc.h" +#endif /* CONFIG_OTG_MSC_BLOCK_TRACE */ +#include "msc-io.h" + + +//#include "rbc.h" + + +/* Module Parameters ************************************************************************* */ +EMBED_LICENSE(); + +MOD_PARM_INT (vendor_id, "Device Vendor ID", 0); +MOD_PARM_INT (product_id, "Device Product ID", 0); +MOD_PARM_INT (major, "Device Major", 0); +MOD_PARM_INT (minor, "Device Minor", 0); + + +#define DEVICE_EJECTED 0x0001 // MEDIA_EJECTED +#define DEVICE_INSERTED 0x0002 // MEDIA_INSERT + +#define DEVICE_OPEN 0x0004 // WR_PROTECT_OFF +#define DEVICE_WRITE_PROTECTED 0x0008 // WR_PROTECT_ON + +#define DEVICE_CHANGE_ON 0x0010 // MEDIA_CHANGE_ON + +#define DEVICE_PREVENT_REMOVAL 0x0020 + + +#define DEF_NUMBER_OF_HEADS 0x10 +#define DEF_SECTORS_PER_TRACK 0x20 + + +DECLARE_MUTEX(msc_sem); + +spinlock_t msc_lock; + +/* MSC ********************************************************************** */ + +//XXX no use struct msc_private msc_private; + +int msc_urb_sent (struct usbd_urb *tx_urb, int rc); + + +/* Block Device ************************************************************* */ + +/*! msc_open_blockdev - open the block device specified in msc->major, msc->minor + * + * Sets appropriate fields to show current status of block device. + * + * XXX TODO - this needs to be tested against RO and absent devices. + * + * @param function_instance - pointer to this function instance + * @return none + * + */ +void msc_open_blockdev(struct usbd_function_instance *function_instance) +{ + struct msc_private *msc = function_instance->privdata; + int rc; + struct inode *inode; + request_queue_t *qqq; + + spin_lock_init(&msc_lock); + down(&msc_sem); + msc->block_dev_state = DEVICE_EJECTED; + + TRACE_MSG2(MSC, "OPEN BLOCKDEV: Major: %x Minor: %x", msc->major, msc->minor); + + /* + * Check device information and verify access to the block device. + */ + THROW_UNLESS(msc->major, ejected); + + msc->dev = MKDEV(msc->major, msc->minor); + +#if defined(LINUX26) + + THROW_UNLESS((msc->bdev = bdget(msc->dev)), ejected); + + + + if ((rc = blkdev_get(msc->bdev, FMODE_READ | FMODE_WRITE, 0))) { + + TRACE_MSG0(MSC,"OPEN BLOCKDEV: cannot open RW"); + THROW_IF ((rc = blkdev_get(msc->bdev, FMODE_READ, 0)), ejected); + msc->block_dev_state |= DEVICE_WRITE_PROTECTED; + } + + + printk(KERN_INFO"%s: bdev: %x\n", __FUNCTION__, msc->bdev); + printk(KERN_INFO"%s: bd_part: %x\n", __FUNCTION__, msc->bdev->bd_part); + printk(KERN_INFO"%s: bd_disk: %x\n", __FUNCTION__, msc->bdev->bd_disk); + printk(KERN_INFO"%s: capacity: %d\n", __FUNCTION__, msc->bdev->bd_disk->capacity); + + #else /* defined(LINUX26) */ /*the same as linux2.4*/ + THROW_IF (!(msc->bdev = bdget(kdev_t_to_nr(msc->dev))), ejected); + if ((rc = blkdev_get(msc->bdev, FMODE_READ | FMODE_WRITE, 0, BDEV_RAW))) { + + TRACE_MSG0(MSC,"OPEN BLOCKDEV: cannot open RW"); + THROW_IF ((rc = blkdev_get(msc->bdev, FMODE_READ, 0, BDEV_RAW)), ejected); + msc->block_dev_state |= DEVICE_WRITE_PROTECTED; + } + +#endif /* defined(LINUX26) */ + + msc->io_state = MSC_INACTIVE; + msc->block_dev_state &= ~DEVICE_EJECTED; + msc->block_dev_state |= DEVICE_INSERTED | DEVICE_CHANGE_ON; + + TRACE_MSG1(MSC,"OPEN BLOCKDEV: opened block_dev_state: %x", msc->block_dev_state); + + /* + * Note that capacity must return the LBA of the last addressable block + * c.f. RBC 4.4, RBC 5.3 and notes below RBC Table 6 + * + * The blk_size array contains the number of addressable blocks or N, + * capacity is therefore N-1. + */ + +#if defined(LINUX26) + TRACE_MSG0(MSC,"Linux 2.6"); + msc->capacity = msc->bdev->bd_disk->capacity; + msc->block_size = bdev_hardsect_size(msc->bdev); + msc->max_blocks = PAGE_SIZE / msc->block_size; + + +#else /* defined(LINUX26) */ + TRACE_MSG0(MSC,"Linux 2.4"); + msc->capacity = (blk_size[MAJOR(msc->dev)][MINOR(msc->dev)] << 1) - 1; + msc->block_size = get_hardsect_size(msc->dev); + msc->max_blocks = PAGE_SIZE / msc->block_size; + + TRACE_MSG2(MSC,"blk_size: %x %d", + blk_size[MAJOR(msc->dev)][MINOR(msc->dev)] << 1, + blk_size[MAJOR(msc->dev)][MINOR(msc->dev)] << 1); +#endif /* defined(LINUX26) */ + + TRACE_MSG2(MSC,"capacity: %x %d", msc->capacity, msc->capacity); + TRACE_MSG2(MSC,"block_size: %x %d", msc->block_size, msc->block_size); + TRACE_MSG2(MSC,"max_blocks: %x %d", msc->max_blocks, msc->max_blocks); + + /* setup generic buffer_head + * XXX do we need two pages? it should be possible to have a single page + * for both read and write, in fact do we need a read_bh and write_bh? + * XXX ensure the page (or pages) get deallocated + */ +#if defined(LINUX26) + printk(KERN_INFO "BIO,start\n"); + msc->write_pending = msc->read_pending = 0; + bio_init(&msc->write_bio); + bio_init(&msc->read_bio); + msc->write_bio.bi_private = msc->read_bio.bi_private = function_instance; + + msc->read_bio.bi_io_vec=&msc->rbio_vec; + msc->rbio_vec.bv_page = alloc_page(GFP_NOIO); // XXX ensure that this gets de-allocated + msc->rbio_vec.bv_len = 0; + msc->rbio_vec.bv_offset = 0; + msc->read_bio.bi_vcnt = 1; + msc->read_bio.bi_idx = 0; + msc->read_bio.bi_size = 0; + msc->read_bio.bi_bdev = msc->bdev; + msc->read_bio.bi_sector = 0; + + msc->write_bio.bi_io_vec=&msc->wbio_vec; + msc->wbio_vec.bv_page = alloc_page(GFP_NOIO); // XXX ensure that this gets de-allocated + msc->wbio_vec.bv_len = 0; + msc->wbio_vec.bv_offset = 0; + msc->write_bio.bi_vcnt = 1; + msc->write_bio.bi_idx = 0; + msc->write_bio.bi_size = 0; + msc->write_bio.bi_bdev = msc->bdev; + msc->write_bio.bi_sector = 0; + printk(KERN_INFO "BIO,end\n"); + +#else + msc->write_pending = msc->read_pending = 0; + msc->write_bh.b_private = msc->read_bh.b_private = function_instance; + msc->read_bh.b_page = alloc_page(GFP_NOIO); // XXX ensure that this gets de-allocated + msc->write_bh.b_page = alloc_page(GFP_NOIO); // XXX ensure that this gets de-allocated + msc->read_bh.b_data = page_address(msc->read_bh.b_page); + msc->write_bh.b_data = page_address(msc->write_bh.b_page); + + msc->write_bh.b_rdev = msc->read_bh.b_rdev = msc->dev; +#endif /* defined(LINUX26) */ + + qqq=bdev_get_queue(msc->bdev); + if(!(qqq->queue_lock)) qqq->queue_lock=&msc_lock; + + CATCH(ejected) { + TRACE_MSG1(MSC,"OPEN BLOCKDEV: EJECTED block_dev_state: %x", msc->block_dev_state); + printk(KERN_INFO"%s: Cannot get device %d %d\n", __FUNCTION__, msc->major, msc->minor); + } + up(&msc_sem); +} + +/*! msc_close_blockdev - close the device for host + * @param function_instance - pointer to this function instance + * @return none + */ + +void msc_close_blockdev(struct usbd_function_instance *function_instance) +{ + struct msc_private *msc = function_instance->privdata; + + RETURN_IF(msc->block_dev_state == DEVICE_EJECTED); + + // XXX this should be a wait for read_pending/write_pending to go to ZERO + +#if defined(LINUX26) + + { + static DECLARE_WAIT_QUEUE_HEAD(msc1_wq); + printk(KERN_INFO"%s: read_pending:%x,write_pending:%x\n", __FUNCTION__,msc->read_pending,msc->write_pending); + wait_event_interruptible(msc1_wq, ((msc->read_pending+msc->write_pending)==0)); + } + +#else /* defined(LINUX26) */ + + while (msc->read_pending || msc->write_pending) { + printk(KERN_INFO"%s: sleeping on read or write bh\n", __FUNCTION__); + sleep_on_timeout(&msc->msc_wq, 20); + } + +#endif /* defined(LINUX26) */ + + down(&msc_sem); + msc->block_dev_state = DEVICE_EJECTED; + +#if defined(LINUX26) + if (msc->bdev) + blkdev_put(msc->bdev); +#else /* defined(LINUX26) */ + if (msc->bdev) + blkdev_put(msc->bdev, BDEV_RAW); +#endif /* defined(LINUX26) */ + +#if defined(LINUX26) + __free_page(msc->rbio_vec.bv_page); + msc->rbio_vec.bv_page = NULL; + __free_page(msc->wbio_vec.bv_page); + msc->wbio_vec.bv_page = NULL; + +#else /* defined(LINUX26) */ + __free_page((void *)&msc->read_bh.b_page); + msc->read_bh.b_page = NULL; + __free_page((void *)&msc->write_bh.b_page); + msc->write_bh.b_page = NULL; +#endif /* defined(LINUX26) */ + up(&msc_sem); +} + + +/* READ 10 COMMAND - read and send data to the host ******************************************** */ + +int msc_start_reading_block_data(struct usbd_function_instance *function); + +#if defined(LINUX26) + +static int msc_block_read_finished(struct bio *bio,unsigned int bytes_done,int err); + +#else /* defined(LINUX26) */ + +void msc_block_read_finished(struct buffer_head *bh, int flag); + +#endif /* defined(LINUX26) */ + +/*! msc_scsi_read_10 - process a read(10) command + * + * We have received a READ(10) CBW, if transfer length is non-zero + * initiate a generic block i/o otherwise send a CSW. + * + * Returns non-zero if there is an error in the USB layer. + */ +int msc_scsi_read_10(struct usbd_function_instance *function_instance, char *name, int op) +{ + struct msc_private *msc = function_instance->privdata; + struct msc_scsi_read_10_command *command = (struct msc_scsi_read_10_command *)&msc->command.CBWCB; + + /* + * save the CBW information and setup for transmitting data read from the block device + */ + + msc->lba = be32_to_cpu(command->LogicalBlockAddress); + TRACE_MSG1(MSC,"sos msc->lba:%d",msc->lba); + msc->TransferLength_in_blocks = be16_to_cpu(command->TransferLength); + msc->TransferLength_in_bytes = msc->TransferLength_in_blocks * msc->block_size; + msc->command_state = MSC_DATA_IN_READ; + msc->io_state = MSC_INACTIVE; + + /*Case 1: If Host Expect no data, while device has data, then report phase error directly */ + + if((msc->command.dCBWDataTransferLength==0) && (msc->TransferLength_in_blocks>0)){ + usbd_halt_endpoint(function_instance, BULK_IN); + msc->data_transferred_in_bytes=msc->TransferLength_in_bytes; + msc->command_state = MSC_CBW_PHASE_ERROR; + return 0; + } + + /*Case 2:simply send the CSW if the host didn't actually ask for a non-zero length.*/ + + if((msc->command.dCBWDataTransferLength==0) && (msc->TransferLength_in_blocks==0)){ + return msc_start_sending_csw(function_instance, USB_MSC_PASSED); + } + + /*Case 3: If Host Expect data, while device has no data, then device stalls BulkIn and put to CBW_PASSED state */ + if((msc->command.dCBWDataTransferLength>0) && (msc->TransferLength_in_blocks==0)){ + + usbd_halt_endpoint(function_instance, BULK_IN); + msc->data_transferred_in_bytes=msc->TransferLength_in_bytes; + msc->command_state = MSC_CBW_PASSED; + return 0; + } + + /* Case 10 Host expects data OUT, but Device reads data: Stall BulkOut and CBW phase error */ + if ((msc->TransferLength_in_blocks) && (msc->command.bmCBWFlags & 0x80)==0){ + usbd_halt_endpoint(function_instance, BULK_OUT); + msc->data_transferred_in_bytes=msc->TransferLength_in_bytes; + msc->command_state = MSC_CBW_PHASE_ERROR; + return 0; + } + /* Host expects data and Device has to read data: Start reading blocks to send */ + + if ((msc->TransferLength_in_blocks) && (msc->command.bmCBWFlags & 0x80)){ + return msc_start_reading_block_data(function_instance); + + } + return 0; +/* + return (msc->TransferLength_in_blocks) ? msc_start_reading_block_data(function_instance) : + msc_start_sending_csw(function_instance, USB_MSC_PASSED); + +*/ +} + + +/*! msc_start_reading_block_data - start reading data + * + * Generate a generic block io request to read some data to transmit. + * + * This function is initially called by msc_scsi_read_10() but can also be called + * by msc_urb_sent() if the amount of data requested was too large. + * + * The function msc_block_read_finished() will be called to actually send the data + * to the host when the I/O request is complete. + * + */ +int msc_start_reading_block_data(struct usbd_function_instance *function_instance) +{ + struct msc_private *msc = function_instance->privdata; + int TransferLength_in_blocks = MIN(msc->max_blocks, msc->TransferLength_in_blocks); + unsigned long flags; + char *btemp_data; + request_queue_t *qqq; + /*TRACE_MSG2(MSC,"START READING BLOCK DATA lba: %x blocks: %d %d ", + msc->lba, msc->TransferLength_in_blocks); + */ + if(msc->TransferLength_in_blocks<=0) + printk(KERN_INFO "msc->TransferLength_in_blocks111=0\n"); + /*else + printk(KERN_INFO "START READING BLOCK DATA lba111: %x blocks: %d \n",msc->lba, msc->TransferLength_in_blocks); + */ + /* ensure that device state is ok + */ + if (msc->block_dev_state != DEVICE_INSERTED) { + TRACE_MSG0(MSC,"START READ MEDIA NOT PRESENT"); + return msc_start_sending_csw_failed (function_instance, SCSI_SENSEKEY_MEDIA_NOT_PRESENT, + msc->lba, USB_MSC_FAILED); + } + + // XXX an ioctl has requested that pending io be aborted + local_irq_save(flags); + if (msc->io_state & MSC_ABORT_IO) { + msc->io_state &= ~MSC_ABORT_IO; + TRACE_MSG0(MSC,"BLOCK READ ABORTED"); + local_irq_restore(flags); + return msc_start_sending_csw_failed (function_instance, SCSI_SENSEKEY_UNRECOVERED_READ_ERROR, + msc->lba, USB_MSC_FAILED); + } + local_irq_restore(flags); + + /* sanity check lba against capacity + */ + if (msc->lba >= msc->capacity) { + TRACE_MSG2(MSC, "START READ LBA out of range: lba: %d capacity: %d", msc->lba, msc->capacity); + return msc_start_sending_csw_failed (function_instance, SCSI_SENSEKEY_BLOCK_ADDRESS_OUT_OF_RANGE, msc->lba, USB_MSC_FAILED); + } + + /* setup buffer head - msc_block_read_finished() will be called when block i/o is finished + */ + +#if defined(LINUX26) + if(TransferLength_in_blocks<=0) + printk(KERN_INFO "TransferLength_in_blocks==0"); + + /*else + printk(KERN_INFO "Start reading blocks: %x\n", TransferLength_in_blocks);*/ + + + msc->rbio_vec.bv_len = TransferLength_in_blocks * msc->block_size;; + msc->rbio_vec.bv_offset = 0; + msc->read_bio.bi_vcnt = 1; + msc->read_bio.bi_idx = 0; + msc->read_bio.bi_size = TransferLength_in_blocks * msc->block_size; + msc->read_bio.bi_bdev = msc->bdev; + msc->read_bio.bi_sector = msc->lba; + msc->read_bio.bi_end_io = msc_block_read_finished; + msc->io_state |= MSC_BLOCKIO_PENDING; + msc->read_pending=1; + + + //btemp_data = page_address(msc->read_bio.bi_io_vec->bv_page); + + btemp_data=__bio_kmap_atomic( &msc->read_bio,0,KM_USER0); + + memset(btemp_data, 0x0, msc->read_bio.bi_size); + __bio_kunmap_atomic(btemp_data,KM_USER0); + + //generic_make_request(READ, &msc->read_bio); + if (msc->read_bio.bi_size){ + + /* printk(KERN_INFO "Start reading bio->size: %x\n", msc->read_bio.bi_size); */ + submit_bio(READ, &msc->read_bio); + generic_unplug_device(bdev_get_queue(msc->bdev)); + } + else + { + printk(KERN_INFO "msc->read_bio.bi_size=0\n"); + } + + +#else /* defined(LINUX26) */ + + msc->read_bh.b_end_io = msc_block_read_finished; + msc->read_bh.b_size = TransferLength_in_blocks * msc->block_size; + msc->read_bh.b_rsector = msc->lba; + msc->read_bh.b_state = (1UL << BH_Mapped) | (1UL << BH_Lock); + msc->io_state |= MSC_BLOCKIO_PENDING; + msc->read_pending=1; + memset(msc->read_bh.b_data, 0x0, msc->read_bh.b_size); + generic_make_request(READ, &msc->read_bh); + generic_unplug_device(blk_get_queue(msc->read_bh.b_rdev)); + +#endif /* defined(LINUX26) */ + return 0; +} + + +/*! msc_block_read_finished - called by generic request + * + * Called when block i/o read is complete, send the data to the host if possible. + * + * The function msc_urb_sent() will be called when the data is sent to + * either send additional data or the CSW as appropriate. + * + * If more data is required then call msc_start_reading_block_data() to + * issue another block i/o to get more data. + * + * These means that there can be two outstanding actions when this + * function completes: + * + * 1. a transmit urb may be pending, sending the most recently + * read data to the host + * 2. another block i/o may be pending to read additional data + * + * This leads to a race condition, if the block i/o finished before the urb + * transmit, then we must simply exit. The msc_in_read_10_urb_sent() + * function will ensure that this is restarted. + */ + +#if defined(LINUX26) + +static int msc_block_read_finished(struct bio *bio,unsigned int bytes_done,int err) +{ + struct usbd_function_instance *function_instance = bio->bi_private; + struct msc_private *msc = function_instance->privdata; + //int TransferLength_in_blocks = bh->b_size / msc->block_size; + + int TransferLength_in_blocks; + + struct usbd_urb *tx_urb; + unsigned long flags; + int rc; + int i; + int bytes_done1; + char *btemp_data; + +#ifdef CONFIG_OTG_MSC_BLOCK_TRACE + u32 crc; +#endif /* CONFIG_OTG_MSC_BLOCK_TRACE */ + + if(bio->bi_size) + return 1; + + //msc = bh->b_private; + TransferLength_in_blocks = bytes_done / msc->block_size; + + TRACE_MSG1(MSC," bio BLOCK READ FINISHED size: %x", bytes_done); + + wake_up_interruptible(&msc->ioctl_wq); + + /* + * Race condition here, if we have not finished sending the + * previous tx_urb then we want to simply remmber the parameters ,exit and let + * msc_in_read_10_urb_sent() call us again. + * + * Ensure that we do not reset BLOCKIO flags if SEND PENDING and + * that we do reset BLOCKIO if not SEND PENDING + */ + { + unsigned long flags; + local_irq_save(flags); + if (msc->io_state & MSC_SEND_PENDING) { + TRACE_MSG0(MSC,"BLOCK READ SEND PENDING"); + msc->io_state |= MSC_BLOCKIO_FINISHED; + msc->bytes_done = bytes_done; + msc->err = err; + local_irq_restore(flags); + return 0 ; + } + msc->io_state &= ~(MSC_BLOCKIO_PENDING | MSC_BLOCKIO_FINISHED); + local_irq_restore(flags); + } + + /* verify that the I/O completed + */ + if (err) { + TRACE_MSG0(MSC,"BLOCK READ FAILED"); + msc_start_sending_csw_failed (function_instance, SCSI_SENSEKEY_UNRECOVERED_READ_ERROR, msc->lba, USB_MSC_FAILED); + return 0; + } + + bytes_done1=bytes_done; + + /* allocate a tx_urb and copy into it + */ + //THROW_IF(!(function = msc->function), error); + + /* To see if enough data has been read. We only send as many as Host expects and don't read more*/ + + if ((msc->data_transferred_in_bytes+bytes_done)>msc->command.dCBWDataTransferLength){ + bytes_done1=msc->command.dCBWDataTransferLength - msc->data_transferred_in_bytes; + } + + THROW_IF(!(tx_urb = usbd_alloc_urb (function_instance, BULK_IN, bytes_done1, msc_urb_sent)), error); + TRACE_MSG2(MSC,"bio BLOCK READ FINISHED urb: %p bytes_done:%d ", (int)tx_urb, bytes_done1); + + tx_urb->function_privdata = function_instance; + tx_urb->actual_length = tx_urb->buffer_length = bytes_done1; + + + //btemp_data = page_address(msc->read_bio.bi_io_vec->bv_page); + btemp_data=__bio_kmap_atomic(&msc->read_bio,0,KM_USER0); + + +#ifdef CONFIG_OTG_MSC_BLOCK_TRACE + TRACE_MSG0(MSC,"sos CONFIG_OTG_MSC_BLOCK_TRACE"); + /* debug output trace - dump rlba and computed crc + */ + for (i = 0; i < bytes_done / msc->block_size; i++) { + crc = crc32_compute(btemp_data + (i * msc->block_size), msc->block_size, CRC32_INIT); + TRACE_RLBA(bio->bi_sector + i, crc); + } +#endif /* CONFIG_OTG_MSC_BLOCK_TRACE */ + + memcpy(tx_urb->buffer, btemp_data, bytes_done1); + + __bio_kunmap_atomic(btemp_data,KM_USER0); + + + + msc->read_pending=0; + + { + unsigned long flags; + local_irq_save(flags); + + msc->io_state |= MSC_SEND_PENDING; + msc->TransferLength_in_bytes -= bytes_done1; + msc->TransferLength_in_blocks -= TransferLength_in_blocks; + msc->lba += TransferLength_in_blocks; + + if ((!msc->TransferLength_in_blocks) || (bytes_done>bytes_done1)) { + TRACE_MSG0(MSC,"bio BLOCK READ FINISHED - IO FINISHED"); + // set flag so that CSW can be sent + msc->io_state |= MSC_DATA_IN_READ_FINISHED; + } + local_irq_restore(flags); + } + + /* dispatch urb - msc_urb_sent() will be called when urb is finished + */ + if ((rc = usbd_start_in_urb (tx_urb))) { + TRACE_MSG0(MSC,"BLOCK READ FINISHED FAILED"); + usbd_free_urb (tx_urb); + THROW(error); + } + + /* if more data is required then call msc_start_reading_block_data() to + * issue another block i/o to get more data. + */ + if (!(msc->io_state & MSC_DATA_IN_READ_FINISHED)) { + TRACE_MSG0(MSC,"BLOCK READ SEND RESTARTING"); + msc_start_reading_block_data(function_instance); + } + + CATCH(error) { + TRACE_MSG0(MSC,"BLOCK READ FINISHED ERROR"); + // stall? + } + return 0; +} + + + + +#else /* defined(LINUX26) */ +void msc_block_read_finished(struct buffer_head *bh, int uptodate) +{ + struct usbd_function_instance *function_instance = bh->b_private; + struct msc_private *msc = function_instance->privdata; + //int TransferLength_in_blocks = bh->b_size / msc->block_size; + + int TransferLength_in_blocks; + + struct usbd_urb *tx_urb; + unsigned long flags; + int rc; +#ifdef CONFIG_OTG_MSC_BLOCK_TRACE + u32 crc; +#endif /* CONFIG_OTG_MSC_BLOCK_TRACE */ + int i; + + //msc = bh->b_private; + TransferLength_in_blocks = bh->b_size / msc->block_size; + + TRACE_MSG1(MSC,"BLOCK READ FINISHED size: %x", bh->b_size); + + wake_up_interruptible(&msc->ioctl_wq); + + /* + * Race condition here, if we have not finished sending the + * previous tx_urb then we want to simply exit and let + * msc_in_read_10_urb_sent() call us again. + * + * Ensure that we do not reset BLOCKIO flags if SEND PENDING and + * that we do reset BLOCKIO if not SEND PENDING + */ + { + unsigned long flags; + local_irq_save(flags); + if (msc->io_state & MSC_SEND_PENDING) { + TRACE_MSG0(MSC,"BLOCK READ SEND PENDING"); + msc->io_state |= MSC_BLOCKIO_FINISHED; + msc->uptodate = uptodate; + local_irq_restore(flags); + return; + } + msc->io_state &= ~(MSC_BLOCKIO_PENDING | MSC_BLOCKIO_FINISHED); + local_irq_restore(flags); + } + + /* verify that the I/O completed + */ + if (1 != uptodate) { + TRACE_MSG0(MSC,"BLOCK READ FAILED"); + msc_start_sending_csw_failed (function_instance, SCSI_SENSEKEY_UNRECOVERED_READ_ERROR, msc->lba, USB_MSC_FAILED); + return; + } + +#ifdef CONFIG_OTG_MSC_BLOCK_TRACE + /* debug output trace - dump rlba and computed crc + */ + for (i = 0; i < bh->b_size / msc->block_size; i++) { + crc = crc32_compute(bh->b_data + (i * msc->block_size), msc->block_size, CRC32_INIT); + TRACE_RLBA(bh->b_rsector + i, crc); + } +#endif /* CONFIG_OTG_MSC_BLOCK_TRACE */ + /* allocate a tx_urb and copy into it + */ + //THROW_IF(!(function = msc->function), error); + THROW_IF(!(tx_urb = usbd_alloc_urb (function_instance, BULK_IN, bh->b_size, msc_urb_sent)), error); + TRACE_MSG1(MSC,"BLOCK READ FINISHED urb: %p", (int)tx_urb); + + tx_urb->function_privdata = function_instance; + tx_urb->actual_length = tx_urb->buffer_length = bh->b_size; + memcpy(tx_urb->buffer, bh->b_data, bh->b_size); + + msc->read_pending=0; + + { + unsigned long flags; + local_irq_save(flags); + + msc->io_state |= MSC_SEND_PENDING; + msc->TransferLength_in_bytes -= bh->b_size; + msc->TransferLength_in_blocks -= TransferLength_in_blocks; + msc->lba += TransferLength_in_blocks; + + if (!msc->TransferLength_in_blocks) { + TRACE_MSG0(MSC,"BLOCK READ FINISHED - IO FINISHED"); + // set flag so that CSW can be sent + msc->io_state |= MSC_DATA_IN_READ_FINISHED; + } + local_irq_restore(flags); + } + + /* dispatch urb - msc_urb_sent() will be called when urb is finished + */ + if ((rc = usbd_start_in_urb (tx_urb))) { + TRACE_MSG0(MSC,"BLOCK READ FINISHED FAILED"); + usbd_free_urb (tx_urb); + THROW(error); + } + + /* if more data is required then call msc_start_reading_block_data() to + * issue another block i/o to get more data. + */ + if (!(msc->io_state & MSC_DATA_IN_READ_FINISHED)) { + TRACE_MSG0(MSC,"BLOCK READ SEND RESTARTING"); + msc_start_reading_block_data(function_instance); + } + + CATCH(error) { + TRACE_MSG0(MSC,"BLOCK READ FINISHED ERROR"); + // stall? + } +} + +#endif /* defined(LINUX26) */ + + +/*! msc_in_read_10_urb_sent - called by msc_urb_sent when state is MSC_DATA_IN_READ + * + * This will process a read_10 urb that has been sent. Specifically if any previous + * read_10 block I/O has finished we recall the msc_block_read_finished() function + * to transmit another read_10 urb. + * + * If there is no other pending read_10 to do we create and send a CSW. + * + * If there is more I/O to do or there is already an outstanding I/O we simply + * return after freeing the URB. + * + * Return non-zero if urb was not disposed of. + */ +int msc_in_read_10_urb_sent(struct usbd_urb *tx_urb) +{ + struct usbd_function_instance *function_instance = tx_urb->function_privdata; + struct msc_private *msc = function_instance->privdata; + unsigned long flags; + + TRACE_MSG0(MSC,"URB SENT DATA IN"); + + /* + * Potential race condition here, we may need to restart blockio. + */ + local_irq_save(flags); + + msc->io_state &= ~MSC_SEND_PENDING; + msc->data_transferred_in_bytes += tx_urb->actual_length; + + TRACE_MSG1(MSC,"URB SENT DATA IN data transferred: %d", msc->data_transferred_in_bytes); + + if (!tx_urb->actual_length) + msc->TransferLength_in_blocks = 0; + + /* XXX We should be checking urb status + */ + + usbd_free_urb (tx_urb); + + /* + * Check to see if we need to send CSW. + */ + if (MSC_DATA_IN_READ_FINISHED & msc->io_state) { + /* Case 5: Host expect more data Hi>Di Then stall BulkIn ,later send CSW*/ + if (msc->data_transferred_in_bytes < msc->command.dCBWDataTransferLength){ + usbd_halt_endpoint(function_instance, BULK_IN); + msc->data_transferred_in_bytes=msc->TransferLength_in_bytes; + msc->command_state = MSC_CBW_PASSED; + local_irq_restore(flags); + return 0; + } + + /* Case 6: Hi = Di */ + if (msc->TransferLength_in_bytes==0){ + TRACE_MSG0(MSC,"URB SENT DATA IN - FINISHED SENDING CSW"); + local_irq_restore(flags); + return msc_start_sending_csw(function_instance, USB_MSC_PASSED); + } + + /*Case 7 Host expects less data Hi < Di, Then Stall after transfer exected length */ + + if (msc->TransferLength_in_bytes>0){ + usbd_halt_endpoint(function_instance, BULK_IN); + msc->data_transferred_in_bytes=msc->TransferLength_in_bytes; + msc->command_state = MSC_CBW_PHASE_ERROR; + local_irq_restore(flags); + return 0; + } + } + /* + * Check to see if there is a block read needs to be finished. + */ + if (MSC_BLOCKIO_FINISHED & msc->io_state) { + TRACE_MSG0(MSC,"URB SENT DATA IN - RESTART"); + local_irq_restore(flags); + // XXX uptodate? +#if defined(LINUX26) + msc_block_read_finished(&msc->read_bio, msc->bytes_done, msc->err); +#else /* defined(LINUX26) */ + msc_block_read_finished(&msc->read_bh, msc->uptodate); +#endif /* defined(LINUX26) */ + return 0; + } + local_irq_restore(flags); + return 0; +} + + +/* WRITE 10 - receive data from host and write to block device ********************************* */ + +int msc_start_receiving_data(struct usbd_function_instance *function); + +#if defined(LINUX26) +static int msc_data_written(struct bio *bio, unsigned bytes_done,int err); +#else /* defined(LINUX26) */ +void msc_data_written(struct buffer_head *bh, int flag); +#endif /* defined(LINUX26) */ + +void msc_data_test(struct buffer_head *bh, int flag); + +/*! int msc_scsi_write_10(struct usbd_function_instance*, char* ,int ) + * @brief - process a write command + * + * Call either msc_start_receiving_data() or msc_start_sending_csw() as appropriate. + * Normally msc_start_receiving_data() is called and it will use msc_start_recv_urb() + * to setup a receive urb of the appropriate size. + * + * @param function_instance - pointer to this function instance + * @param name + * @param op + * @return non-zero if there is an error in the USB layer. + */ +int msc_scsi_write_10(struct usbd_function_instance *function_instance, char *name, int op) +{ + struct msc_private *msc = function_instance->privdata; + struct msc_scsi_write_10_command *command = (struct msc_scsi_write_10_command *)&msc->command.CBWCB; + + /* save the CBW and setup for receiving data to be written to the block device + */ + msc->lba = be32_to_cpu(command->LogicalBlockAddress); + msc->TransferLength_in_blocks = be16_to_cpu(command->TransferLength); + msc->TransferLength_in_bytes = msc->TransferLength_in_blocks * msc->block_size; + + msc->command_state = MSC_DATA_OUT_WRITE; + msc->io_state = MSC_INACTIVE; + + TRACE_MSG1(MSC,"RECV WRITE lba: %x", msc->lba); + TRACE_MSG1(MSC,"RECV WRITE blocks: %d", msc->TransferLength_in_blocks); + + + /*Case 3: If Host Expect no data, device writes data Hn < Do then report phase error directly */ + + if((msc->command.dCBWDataTransferLength==0) && (msc->TransferLength_in_blocks>0)){ + usbd_halt_endpoint(function_instance, BULK_IN); + msc->data_transferred_in_bytes=msc->TransferLength_in_bytes; + msc->command_state = MSC_CBW_PHASE_ERROR; + return 0; + } + + /*Case9: Host sends data, but Device doesn't expect to rcv data*/ + + if((msc->command.dCBWDataTransferLength>0) && (msc->TransferLength_in_blocks==0)){ + usbd_halt_endpoint(function_instance, BULK_OUT); + msc->data_transferred_in_bytes=msc->TransferLength_in_bytes; + msc->command_state = USB_MSC_PASSED; + return 0; + } + + /* Case 8 Host expect data IN, but Device writes data: Stall BulkIn and CBW phase error */ + if ((msc->TransferLength_in_blocks) && (msc->command.bmCBWFlags & 0x80)){ + usbd_halt_endpoint(function_instance, BULK_IN); + msc->data_transferred_in_bytes=msc->TransferLength_in_bytes; + msc->command_state = MSC_CBW_PHASE_ERROR; + return 0; + } + + /*Case 1: Hn = Dn */ + + if((msc->command.dCBWDataTransferLength==0) && (msc->TransferLength_in_blocks==0)){ + return msc_start_sending_csw(function_instance, USB_MSC_PASSED); + } + + return msc_start_receiving_data(function_instance); + +} + + +/*! msc_start_receiving_data(struct usbd_function_instance * ) + * @brief - called to initiate an urb to receive WRITE(10) data + * + * Initiate a receive urb to receive upto PAGE_SIZE WRITE(10) data. The + * msc_recv_urb() function will call msc_recv_out_blocks() to actually + * write the data. + * + * This is called from msc_scsi_write_10() to initiate the WRITE(10) and + * called from msc_data_written() to start the next page sized receive + * urb. + * + * @param function_instance + * @return non-zero for error + */ +int msc_start_receiving_data(struct usbd_function_instance *function_instance) +{ + struct msc_private *msc = function_instance->privdata; + u32 minmumbytes=0; + /* + * Calculating the length is most of the work we do :-) + */ + int TransferLength_in_blocks = MIN(msc->max_blocks, msc->TransferLength_in_blocks); + + TRACE_MSG1(MSC,"START RECEIVING DATA lba: %x", msc->lba); + TRACE_MSG1(MSC,"START RECEIVING DATA blocks: %d", msc->TransferLength_in_blocks); + TRACE_MSG1(MSC,"START RECEIVING DATA blocks: %d", TransferLength_in_blocks); + TRACE_MSG1(MSC,"START RECEIVING DATA bytes: %d", TransferLength_in_blocks * msc->block_size); + +/* Ensure Host will send the length data*/ + minmumbytes= +MIN((msc->command.dCBWDataTransferLength - msc->data_transferred_in_bytes),TransferLength_in_blocks * msc->block_size); + + THROW_IF(msc->command_state != MSC_DATA_OUT_WRITE, error); + THROW_IF(!msc->TransferLength_in_blocks, error); + + msc->io_state |= MSC_RECV_PENDING; + + //return msc_start_recv_urb(function_instance, TransferLength_in_blocks * msc->block_size); + return msc_start_recv_urb(function_instance, minmumbytes); + CATCH(error) { + TRACE_MSG0(MSC,"START RECEIVING DATA ERROR"); + return -EINVAL; + } +} + + +/*! void msc_recv_out_blocks(struct usbd_urb ) + * @brief - process received WRITE(10) data by writing to block device + * + * We get here indirectly from msc_recv_urb() when state is MSC_DATA_OUT_WRITE. + * + * Dealloc the urb and call Initiate the generic request to write the + * received data. The b_end_io function msc_data_written() will send the + * CSW. + * + * @param rcv_urb - pointer to recv urb + * @return none + * + */ +void msc_recv_out_blocks(struct usbd_urb *rcv_urb) +{ + struct usbd_function_instance *function_instance = rcv_urb->function_privdata; + struct msc_private *msc = function_instance->privdata; + int TransferLength_in_bytes = rcv_urb->actual_length; + int TransferLength_in_blocks = TransferLength_in_bytes / msc->block_size; +#ifdef CONFIG_OTG_MSC_BLOCK_TRACE + u32 crc; +#endif /* CONFIG_OTG_MSC_BLOCK_TRACE */ + int i; +#if defined(LINUX26) + char *btemp_data; +#endif + TRACE_MSG1(MSC,"RECV OUT bytes: %d", TransferLength_in_bytes); + TRACE_MSG1(MSC,"RECV OUT iostate: %x", msc->io_state); + TRACE_MSG1(MSC,"RECV OUT data transferred: %d", msc->data_transferred_in_bytes); + //if(TransferLength_in_bytes) + //printk(KERN_INFO "sos0 TransferLength_in_bytes: %x\n", TransferLength_in_bytes); + // else + //printk(KERN_INFO "sos0 write TransferLength_in_bytes = 0\n"); + + + /* + * Race condition here, we may get to here before the previous block + * write completed. If so just exit and the msc_data_written() + * function will recall us. + */ + { + unsigned long flags; + local_irq_save(flags); + if (msc->io_state & MSC_BLOCKIO_PENDING) { + TRACE_MSG0(MSC,"RECV OUT BLOCKIO PENDING"); + msc->io_state |= MSC_RECV_FINISHED; + msc->rcv_urb_finished = rcv_urb; + local_irq_restore(flags); + return; + } + msc->io_state &= ~(MSC_RECV_PENDING |MSC_RECV_FINISHED); + local_irq_restore(flags); + } + + msc->data_transferred_in_bytes += rcv_urb->actual_length; + +#ifdef CONFIG_OTG_MSC_BLOCK_TRACE + for (i = 0; i < TransferLength_in_blocks; i++) { + crc = crc32_compute(rcv_urb->buffer + (i * msc->block_size), msc->block_size, CRC32_INIT); + TRACE_SLBA(msc->lba + i, crc); + } +#endif /* CONFIG_OTG_MSC_BLOCK_TRACE */ + + msc->write_pending=1; +#if defined(LINUX26) + + btemp_data=__bio_kmap_atomic( &msc->write_bio,0,KM_USER0); + + //btemp_data = page_address(msc->write_bio.bi_io_vec->bv_page); + memcpy(btemp_data,rcv_urb->buffer, TransferLength_in_bytes); + + __bio_kunmap_atomic(btemp_data,KM_USER0); +#else + memcpy(msc->write_bh.b_data, rcv_urb->buffer, rcv_urb->actual_length); +#endif + usbd_free_urb(rcv_urb); + + /* ensure media state is ok + */ + if (msc->block_dev_state != DEVICE_INSERTED) { + TRACE_MSG0(MSC,"START READ MEDIA NOT PRESENT"); + msc_start_sending_csw_failed (function_instance, SCSI_SENSEKEY_MEDIA_NOT_PRESENT, msc->lba, USB_MSC_FAILED); + return; + } + + // XXX an ioctl has requested that pending io be aborted + if (msc->io_state & MSC_ABORT_IO) { + unsigned long flags; + local_irq_save(flags); + msc->io_state &= ~MSC_ABORT_IO; + TRACE_MSG0(MSC,"BLOCK READ ABORTED"); + local_irq_restore(flags); + msc_start_sending_csw_failed (function_instance, SCSI_SENSEKEY_UNRECOVERED_READ_ERROR, msc->lba, USB_MSC_FAILED); + return; + } + + /* sanity check lba against capacity + */ + if (msc->lba >= msc->capacity) { + TRACE_MSG2(MSC, "START READ LBA out of range: lba: %d capacity: %d", msc->lba, msc->capacity); + msc_start_sending_csw_failed (function_instance, SCSI_SENSEKEY_BLOCK_ADDRESS_OUT_OF_RANGE, msc->lba, USB_MSC_FAILED); + return; + } + + /* additional sanity check for lba - ensure non-zero + */ + if (!msc->TransferLength_in_blocks) { + TRACE_MSG0(MSC,"START READ LBA TransferLength_in_blocks zero:"); + msc_start_sending_csw_failed (function_instance, SCSI_SENSEKEY_BLOCK_ADDRESS_OUT_OF_RANGE, msc->lba, USB_MSC_FAILED); + return; + } + + /* XXX additional sanity check required here - verify that the transfer + * size agrees with what we where expecting, specifically I think + * we need to verify that we have received a multiple of the block + * size and either a full bulk transfer (so more will come) or + * the actual exact amount that the host said it would send. + * An error should generate a CSW with a USB_MSC_PHASE_ERROR + */ + + + TRACE_MSG1(MSC,"RECV OUT lba: %x", msc->lba); + TRACE_MSG1(MSC,"RECV OUT blocks left: %d", msc->TransferLength_in_blocks); + TRACE_MSG1(MSC,"RECV OUT blocks current: %d", TransferLength_in_blocks); + + /* Initiate writing the data - msc_data_written() will be called + * when finished. + */ + //if(TransferLength_in_bytes) + //printk(KERN_INFO "sos TransferLength_in_bytes: %x\n", TransferLength_in_bytes); + // else + //printk(KERN_INFO"sos write TransferLength_in_bytes = 0\n"); + +#if defined(LINUX26) + + msc->wbio_vec.bv_offset = 0; + msc->write_bio.bi_vcnt = 1; + msc->write_bio.bi_idx = 0; + msc->write_bio.bi_size = TransferLength_in_bytes; + msc->wbio_vec.bv_len = TransferLength_in_bytes; + msc->write_bio.bi_bdev = msc->bdev; + msc->write_bio.bi_sector = msc->lba; + msc->write_bio.bi_end_io = msc_data_written; + msc->io_state |= MSC_BLOCKIO_PENDING; + msc->write_pending=1; +#if 0 + /* decrement counters and increment address + */ + msc->TransferLength_in_bytes -= TransferLength_in_bytes; + msc->TransferLength_in_blocks -= TransferLength_in_blocks; + msc->lba += TransferLength_in_blocks; + + /* Check current status of TransferLength - if non-zero then we need + * to queue another urb to receive more data. + */ + //if (!msc->TransferLength_in_blocks) + if ( msc->data_transferred_in_bytes > msc->command.dCBWDataTransferLength + msc->command_state = MSC_DATA_OUT_WRITE_FINISHED; + else + msc_start_receiving_data(function_instance); +#else + /* Check current status of TransferLength - if non-zero then we need + * to queue another urb to receive more data. + */ + //if (!msc->TransferLength_in_blocks) + + /*Case 13 Ho < Do */ + if((msc->data_transferred_in_bytes >= msc->command.dCBWDataTransferLength) && + (msc->TransferLength_in_bytes>TransferLength_in_bytes)) { + msc->command_state = MSC_DATA_OUT_WRITE_FINISHED; + TRACE_MSG0( MSC, "Case 13"); + msc->caseflag=13; + } + + /*Case 11 Ho > Do */ + else if ( msc->TransferLength_in_blocks <=TransferLength_in_blocks){ + + msc->command_state = MSC_DATA_OUT_WRITE_FINISHED; + if(msc->command.dCBWDataTransferLength>msc->data_transferred_in_bytes) { + msc->write_bio.bi_size = msc->TransferLength_in_bytes; + msc->wbio_vec.bv_len = msc->TransferLength_in_bytes; + msc->caseflag=11; + TRACE_MSG0( MSC, "Case 11"); + }else{ + /* Case 12 Ho = Do */ + msc->caseflag=12; + TRACE_MSG0( MSC, "Case 12"); + } + } + + + else { + + + /* decrement counters and increment address + */ + msc->TransferLength_in_bytes -= TransferLength_in_bytes; + msc->TransferLength_in_blocks -= TransferLength_in_blocks; + msc->lba += TransferLength_in_blocks; + msc_start_receiving_data(function_instance); + } + + +#endif + + /* initiate the block i/o request + */ + if(msc->write_bio.bi_size){ + /*printk(KERN_INFO"write OUT bio->size: %x\n", msc->write_bio.bi_size);*/ + submit_bio(WRITE, &msc->write_bio); + generic_unplug_device(bdev_get_queue(msc->bdev)); + } + else + { + printk(KERN_INFO"write OUT bio->size=0\n"); + } + + + +#else /* defined(LINUX26) */ + + msc->write_bh.b_end_io = msc_data_written; + + /* set address in buffer head + */ + msc->write_bh.b_size = TransferLength_in_bytes; + msc->write_bh.b_rsector = msc->lba; + + /* decrement counters and increment address + */ + msc->TransferLength_in_bytes -= TransferLength_in_bytes; + msc->TransferLength_in_blocks -= TransferLength_in_blocks; + msc->lba += TransferLength_in_blocks; + + msc->write_bh.b_state = (1UL << BH_Mapped) | (1UL << BH_Lock); + + msc->io_state |= MSC_BLOCKIO_PENDING; + + + /* Check current status of TransferLength - if non-zero then we need + * to queue another urb to receive more data. + */ + if (!msc->TransferLength_in_blocks) + msc->command_state = MSC_DATA_OUT_WRITE_FINISHED; + else + msc_start_receiving_data(function_instance); + + /* initiate the block i/o request + */ + + generic_make_request(WRITE, &msc->write_bh); + generic_unplug_device(blk_get_queue(msc->write_bh.b_rdev)); +#endif /* defined(LINUX26) */ +} + + +/*! int msc_data_written(struct bio *, unsigned , int) + * @brief - called by generic request + * + * Called when current block i/o read is finished, restart block i/o or send the CSW. + * + * @param bh - + * @param uptodate - + * @return 0 + */ +#if defined(LINUX26) +static int msc_data_written(struct bio *bio, unsigned bytes_done,int err) +{ + struct usbd_function_instance *function_instance = bio->bi_private; + struct msc_private *msc = function_instance->privdata; + unsigned long flags; + u8 io_state; + if(bio->bi_size) + return 1; + /* printk(KERN_INFO "DATA WRITTEN bytes_done: %x\n", bytes_done);*/ + + TRACE_MSG4(MSC,"DATA WRITTEN bytes_done: %x err: %x state: %x io: %x", + bytes_done, err, msc->command_state, msc->io_state); + + TRACE_MSG1(MSC,"DATA WRITTEN lba: %x", msc->lba); + + local_irq_save(flags); + io_state = msc->io_state &= ~MSC_BLOCKIO_PENDING; + msc->write_pending=0; + local_irq_restore(flags); + + wake_up_interruptible(&msc->ioctl_wq); + + /* + * verify that the I/O completed + */ + if (err) { + TRACE_MSG0(MSC,"BLOCK READ FAILED"); + msc_start_sending_csw_failed (function_instance, SCSI_SENSEKEY_UNRECOVERED_READ_ERROR, msc->lba, USB_MSC_FAILED); + // XXX CHECKME + if (msc->rcv_urb_finished) { + usbd_free_urb(msc->rcv_urb_finished); + msc->rcv_urb_finished = NULL; + } + return 0; + } + + /* + * If there was a rcv_urb that was not processed then we need + * to process it now. This is done by simply restarting msc_recv_out() + */ + if ((io_state & MSC_RECV_FINISHED) && msc->rcv_urb_finished) { + struct usbd_urb *rcv_urb = msc->rcv_urb_finished; + msc->rcv_urb_finished = NULL; + TRACE_MSG0(MSC,"DATA WRITTEN RECV FINISHED"); + //printk(KERN_INFO "previuos rcv urb process\n"); + msc_recv_out_blocks(rcv_urb); + } + /* + * DATA_IN mode and no more data to write + */ + if (MSC_DATA_OUT_WRITE_FINISHED == msc->command_state) { + // finished, send CSW + + if (msc->caseflag==12){ + TRACE_MSG0(MSC,"DATA WRITTEN send CSW"); + msc_start_sending_csw(function_instance, USB_MSC_PASSED); + return 0; + } + + if (msc->caseflag==11){ + usbd_halt_endpoint(function_instance, BULK_OUT); + msc->data_transferred_in_bytes=msc->TransferLength_in_bytes; + msc->command_state = MSC_CBW_PASSED; + return 0; + } + if (msc->caseflag==13){ + usbd_halt_endpoint(function_instance, BULK_IN); + msc->data_transferred_in_bytes=msc->TransferLength_in_bytes; + msc->command_state = MSC_CBW_PHASE_ERROR; + return 0; + } + } + return 0; +} + +#else /* defined(LINUX26) */ +void msc_data_written(struct buffer_head *bh, int uptodate) +{ + struct usbd_function_instance *function_instance = bh->b_private; + struct msc_private *msc = function_instance->privdata; + unsigned long flags; + u8 io_state; + + TRACE_MSG4(MSC,"DATA WRITTEN uptodate: %x b_state: %x state: %x io: %x", + uptodate, bh->b_state, msc->command_state, msc->io_state); + + TRACE_MSG1(MSC,"DATA WRITTEN lba: %x", msc->lba); + + local_irq_save(flags); + io_state = msc->io_state &= ~MSC_BLOCKIO_PENDING; + msc->write_pending=0; + local_irq_restore(flags); + + wake_up_interruptible(&msc->ioctl_wq); + + /* + * verify that the I/O completed + */ + if (1 != uptodate) { + TRACE_MSG0(MSC,"BLOCK READ FAILED"); + msc_start_sending_csw_failed (function_instance, SCSI_SENSEKEY_UNRECOVERED_READ_ERROR, msc->lba, USB_MSC_FAILED); + // XXX CHECKME + if (msc->rcv_urb_finished) { + usbd_free_urb(msc->rcv_urb_finished); + msc->rcv_urb_finished = NULL; + } + return; + } + + /* + * If there was a rcv_urb that was not processed then we need + * to process it now. This is done by simply restarting msc_recv_out() + */ + if ((io_state & MSC_RECV_FINISHED) && msc->rcv_urb_finished) { + struct usbd_urb *rcv_urb = msc->rcv_urb_finished; + msc->rcv_urb_finished = NULL; + TRACE_MSG0(MSC,"DATA WRITTEN RECV FINISHED"); + msc_recv_out_blocks(rcv_urb); + } + /* + * DATA_IN mode and no more data to write + */ + if (MSC_DATA_OUT_WRITE_FINISHED == msc->command_state) { + // finished, send CSW + TRACE_MSG0(MSC,"DATA WRITTEN send CSW"); + msc_start_sending_csw(function_instance, USB_MSC_PASSED); + } +} +#endif /* defined(LINUX26) */ +extern struct usbd_interface_driver msc_interface_driver; + + +/* USB Module init/exit ***************************************************** */ + +void msc_io_exit_l24(void); + +/*! int msc_os_int_l24(struct usbd_function_intance * ) + * @brief - called to initialize msc intance major and minor device number + * + * @param function_instance - pointer to this function instance + * @return 0 + */ +int msc_os_init_l24(struct usbd_function_instance *function_instance) +{ + struct msc_private *msc = function_instance->privdata; + msc->major = MODPARM(major); + msc->minor = MODPARM(minor); + return 0; +} + +extern void msc_global_init(void); +/*! int msc_modinit ( ) + * @brief - initialize msc global variables, and register interface function driver + * + *@return non-zero for error + */ +static int msc_modinit (void) +{ + int rc; + + printk(KERN_INFO "Copyright (c) 2004 Belcarra Technologies; www.belcarra.com; sl@belcarra.com\n"); + printk (KERN_INFO "%s vendor_id: %04x product_id: %04x major: %d minor: %d\n", __FUNCTION__, + MODPARM(vendor_id), MODPARM(product_id), MODPARM(major), MODPARM(minor)); + +#ifndef OTG_C99 +#warning "C99" + msc_global_init(); +#endif + MSC = otg_trace_obtain_tag(NULL, "msc-if"); + + //if (vendor_id) + // msc_function_driver.idVendor = cpu_to_le16(vendor_id); + //if (product_id) + // msc_function_driver.idProduct = cpu_to_le16(product_id); + + #if 0 + init_waitqueue_head(&msc->msc_wq); + init_waitqueue_head(&msc->ioctl_wq); + + msc->block_dev_state = DEVICE_EJECTED; + + + msc->command_state = MSC_READY; + msc->io_state = MSC_INACTIVE; + #endif + +#ifdef CONFIG_OTG_MSC_BLOCK_TRACE + make_crc_table(); +#endif /* CONFIG_OTG_MSC_BLOCK_TRACE */ + + TRACE_MSG3(MSC,"PAGE_SHIFT: %x PAGE_SIZE: %x %d", PAGE_SHIFT, PAGE_SIZE, PAGE_SIZE); + + + /* register as usb function driver + */ + RETURN_EINVAL_IF(usbd_register_interface_function (&msc_interface_driver, "msc-if", NULL)); + + CATCH(error) { + otg_trace_invalidate_tag(MSC); + return -EINVAL; + } + return 0; +} + +/*! msc_modexit - module cleanup + */ +static void msc_modexit (void) +{ + #if 0 + /* destroy control io interface + */ + msc_io_exit_l24(); + + /* flush io and free page buffers + */ + msc_close_blockdev(msc); + #endif + usbd_deregister_interface_function (&msc_interface_driver); + +#ifdef CONFIG_OTG_MSC_BLOCK_TRACE + free_crc_table(); +#endif /* CONFIG_OTG_MSC_BLOCK_TRACE */ + otg_trace_invalidate_tag(MSC); +} + + +module_init (msc_modinit); +module_exit (msc_modexit); diff --git a/drivers/otg/functions/msc/msc-scsi.h b/drivers/otg/functions/msc/msc-scsi.h new file mode 100644 index 000000000000..a36264d1a01a --- /dev/null +++ b/drivers/otg/functions/msc/msc-scsi.h @@ -0,0 +1,837 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/msc/msc-scsi.h - mass storage protocol library header + * @(#) sl@belcarra.com/whiskey.enposte.net|otg/functions/msc/msc-scsi.h|20061218212925|54647 + * + * Copyright(c) 2004-2006, Belcarra + * Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + * + */ + +/* + * + * Documents + * Universal Serial Bus Mass Storage Class - Specification Overview + * Universal Serial Bus Mass Storage Class - Bulk-Only Transport + * T10/1240-D - Information technology - Reduced Block Commands + * + * Notes + * + * 1. Reduced Block Command set C.f. Table 2 RBC + * + * Command Support + * Name OpCode Fixed Removable Reference + * Format Unit 04h O O RBC + * Inquiry 12h M M SPC-2 + * Mode Select (6) 15h M M SPC-2 + * Mode Sense (6) 1Ah M M SPC-2 + * Perisstent Reserve In 5Eh O O SPC-2 + * Persistent Reserve Out 5Fh O O SPC-2 + * Prevent/Allow Medium Removal 1Eh N/A M SPC-2 + * Read (10) 28h M M RBC + * Read Capacity 25h M M RBC + * Reelase (6) 17h O O SPC-2 + * Request Sense 03h O O SPC-2 + * Reserve (6) 16h O O SPC-2 + * Start Stop Unit 1Bh M M RBC + * Synchronize Cache 35h O O RBC + * Test Unit Ready 00h M M SPC-2 + * Verify (10) 2Fh M M RBC + * Write (10) 2Ah M M RBC + * Write Buffer 3Bh M O SPC-2 + * + * 2. Other commands seen? + * Sh MV FS + * SCSI_REZERO_UNIT 0x01 no + * SCSI_READ_6 0x08 yes + * SCSI_WRITE_6 0x0a yes + * SCSI_SEND_DIAGNOSTIC 0x1d no yes + * SCSI_READ_FORMAT_CAPACITY 0x23 yes yes + * SCSI_WRITE_AND_VERIFY 0x2e no + * SCSI_SEEK_10 0x2b no + * SCSI_MODE_SELECT_10 0x55 no yes yes + * SCSI_READ_12 0xa8 no yes + * SCSI_WRITE_12 0xaa no yes + * + * + * 3. Status - C.f. Chapter 5 - RBC + * + * Check Condition 02h + * + * + * 4. Sense Keys - C.f. Chapter 5 - RBC + * + * Not Ready 02h + * Media Error 03h + * Illegal Request 05h + * Unit Attention 06h + * + * 5. ASC/ASCQ - C.f. Chapter 5 - RBC + * + * Logical Unit Not Ready 04h + * Logical Unit Not Ready, Format in Progress 04h,04h + * Invalid Command Operation Code 20h + * Invalide Field in CDB 24h + * Format Command Failed 31h,01h + * Status Notification / Power Management Class Event 38h,02h + * Status Notification / Media Class Event 38h,04h + * Status Notification / Device Busy Class Event 38h,06h + * Low Power Condition Active 5Eh,00 + * Power Condition Change to Active 5Eh,41h + * Power Condition Change to Idle 5Eh,42h + * Power Condition Change to Standby 5Eh,43h + * Power Condition Change to Sleep 5Eh,45h + * Power Condition Change to Device Control 5Eh,47h + * + * 6. ASCQ - C.f. Chapter 5 - RBC + * + * + * + * 7. Sense Keys C.f. Chapter 5 - RBC + * + * Command Status Sense Key ASC/ASCQ + * + * 5.1 Format Unit + * Format progress Check Condition Not Ready, Logical Unit Note Ready, Format in Progress + * Sueccsful completion Check Condition Unit Attention, Status Notification / Media Class Event + * Failure Check Condition Media error, Format Command Failed + * + * 5.2 Read (10) + * + * 5.3 Read Capacity + * No Media Check Condition Not Ready, Logical Unit Not Ready + * + * 5.4 Start Stop Unit + * Power Consumption Check Condition Illegal Request,Low Power Condition Active + * + * 5.5 Synchronize Cache Check Condition Illegal Request,Invalid Command Operation Code + * + * 5.6 Write (10 + * + * 5.7 Verify + * + * 5.8 Mode + * + * 6.1 Inquiry + * + * 6.2 Mode Select (6) + * Cannot Save Check Condition Illegal Request,Invalid Field in CDB + * + * 6.3 Mode Sense (6) + * + * 6.4 Prevent Allow Medium Removal + * + * 6.5 Request Sense + * + * 6.6 Test Unit Ready + * + * 6.7 Write Buffer + * + * 7.1 Unit Attention + * Power Contition change Check Condition Unit Attention, Power Condition Change Notification + * + * 7.4.1 Event Status Sense + * + * Power Management Event Check Condition Unit Attention, Event Status Notification / Power Managment + * Media Class Event Check Condition Unit Attention, Event Status Notification / Media Class + * Device Busy Event Check Condition Unit Attention, Event Status Notification / Device Busy Class + * + * 7.4.6 Removable Medium Device Initial Response + * Ready Check Condition Unit Attention, Event Status Notification / Media Class Event + * Power Check Condition Unit Attention, Event Status Notification / Power Management Class Event + */ + +#ifndef _MSCSCSIPROTO_H_ +#define _MSCSCSIPROTO_H_ + + +/* + * Class Specific Requests - C.f. MSC BO Chapter 3 + */ + +#define MSC_BULKONLY_RESET 0xff +#define MSC_BULKONLY_GETMAXLUN 0xfe + + +/* + * Class Code + */ + +#define MASS_STORAGE_CLASS 0x08 + +/* + * MSC - Specification Overview + * + * SubClass Codes - C.f MSC Table 2.1 + */ + +#define MASS_STORAGE_SUBCLASS_RBC 0x01 +#define MASS_STORAGE_SUBCLASS_SFF8020I 0x02 +#define MASS_STORAGE_SUBCLASS_QIC157 0x03 +#define MASS_STORAGE_SUBCLASS_UFI 0x04 +#define MASS_STORAGE_SUBCLASS_SFF8070I 0x05 +#define MASS_STORAGE_SUBCLASS_SCSI 0x06 + +/* + * Protocol - C.f MSC Table 3.1 + */ + +#define MASS_STORAGE_PROTO_CBI_WITH_COMP 0x00 +#define MASS_STORAGE_PROTO_CBI_NO_COMP 0x01 +#define MASS_STORAGE_PROTO_BULK_ONLY 0x50 + +/* + * SCSI Command +*/ + +#define SCSI_TEST_UNIT_READY 0x00 +#define SCSI_REQUEST_SENSE 0x03 +#define SCSI_FORMAT_UNIT 0x04 +#define SCSI_INQUIRY 0x12 +#define SCSI_MODE_SELECT 0x15 // aka MODE_SELECT_6 +#define SCSI_MODE_SENSE 0x1a // aka MODE_SENSE_6 +#define SCSI_START_STOP 0x1b +#define SCSI_PREVENT_ALLOW_MEDIA_REMOVAL 0x1e +#define SCSI_READ_FORMAT_CAPACITY 0x23 +#define SCSI_READ_CAPACITY 0x25 +#define SCSI_READ_10 0x28 +#define SCSI_WRITE_10 0x2a +#define SCSI_VERIFY 0x2f + +#define SCSI_READ_6 0x08 +#define SCSI_WRITE_6 0x0a +#define SCSI_RESERVE 0x16 +#define SCSI_RELEASE 0x17 +#define SCSI_SEND_DIAGNOSTIC 0x1d +#define SCSI_SYNCHRONIZE_CACHE 0x35 +#define SCSI_MODE_SENSE_10 0x5a + +#define SCSI_REZERO_UNIT 0x01 +#define SCSI_REASSIGN_BLOCKS 0x07 +#define SCSI_COPY 0x18 +#define SCSI_RECEIVE_DIAGNOSTIC_RESULTS 0x1c +#define SCSI_WRITE_AND_VERIFY 0x2e +#define SCSI_PREFETCH 0x34 +#define SCSI_READ_DEFECT_DATA 0x37 +#define SCSI_COMPARE 0x39 +#define SCSI_COPY_AND_VERIFY 0x3a +#define SCSI_WRITE_BUFFER 0x3b +#define SCSI_READ_BUFFER 0x3c +#define SCSI_READ_LONG 0x3e +#define SCSI_WRITE_LONG 0x3f +#define SCSI_CHANGE_DEFINITION 0x40 +#define SCSI_WRITE_SAME 0x41 +#define SCSI_LOG_SELECT 0x4c +#define SCSI_LOG_SENSE 0x4d +#define SCSI_XD_WRITE 0x50 +#define SCSI_XP_WRITE 0x51 +#define SCSI_XD_READ 0x52 +#define SCSI_MODE_SELECT_10 0x55 +#define SCSI_RESERVE_10 0x56 +#define SCSI_RELEASE_10 0x57 +#define SCSI_MODE_SELECT_10 0x55 +#define SCSI_XD_WRITE_EXTENDED 0x80 +#define SCSI_REBUILD 0x81 +#define SCSI_REGENERATE 0x82 + +#define SCSI_SEEK_10 0x2b +#define SCSI_WRITE_AND_VERIFY 0x2e +#define SCSI_WRITE_12 0xaa +#define SCSI_READ_12 0xa8 + +/* + * Private + */ +#define SCSI_PRIVATE_PCS 0xff + +/* + * SCSI Command Parameter + */ + +#define CBW_SIGNATURE 0x43425355 /* USBC */ +#define CSW_SIGNATURE 0x53425355 /* USBS */ + +#define PRODUCT_REVISION_LEVEL "1.00" + +/* + * Command Block Status Values - C.f MSC BO Table 5.3 + */ + +#define USB_MSC_PASSED 0x00 // good +#define USB_MSC_FAILED 0x01 // bad +#define USB_MSC_PHASE_ERROR 0x02 // we want to be reset + +#define NODATAPHASE 1 +#define HASDATAPHASE 0 + + + +/* + * SCSI Sense + * SenseKey + * AdditionalSenseCode + * SenseCodeQualifier + */ + +#define SCSI_ERROR_CURRENT 0x70 +#define SCSI_ERROR_DEFERRED 0x07 + +/* + * SCSI Sense Keys + */ + +#define SK_NO_SENSE 0x00 +#define SK_RECOVERED_ERROR 0x01 +#define SK_NOT_READY 0x02 +#define SK_MEDIA_ERROR 0x03 +#define SK_HARDWARE_ERROR 0x04 +#define SK_ILLEGAL_REQUEST 0x05 +#define SK_UNIT_ATTENTION 0x06 +#define SK_DATA_PROTECT 0x07 +#define SK_BLANK_CHECK 0x08 +#define SK_COPY_ABORTED 0x0a +#define SK_ABORTED_COMMAND 0x0b +#define SK_VOLUME_OVERFLOW 0x0d +#define SK_MISCOMPARE 0x0e + +/* + * 5. ASC/ASCQ - C.f. Chapter 5 - RBC + */ + +#define SK(SenseKey,ASC,ASCQ) ((SenseKey<<16) | (ASC << 8) | (ASCQ)) + +#define SCSI_SENSEKEY_NO_SENSE SK(SK_NO_SENSE, 0x00,0x00) // 0x000000 + +#define SCSI_FAILURE_PREDICTION_THRESHOLD_EXCEEDED SK(SK_RECOVERED_ERROR, 0x5d,0x00) // 0x015d00 + +#define SCSI_SENSEKEY_LOGICAL_UNIT_NOT_READY SK(SK_NOT_READY, 0x04,0x00) // 0x020400 +#define SCSI_SENSEKEY_FORMAT_IN_PROGRESS SK(SK_NOT_READY, 0x04,0x04) // 0x020404 +#define SCSI_SENSEKEY_MEDIA_NOT_PRESENT SK(SK_NOT_READY, 0x3a,0x00) // 0x023a00 + +#define SCSI_SENSEKEY_WRITE_ERROR SK(SK_MEDIA_ERROR, 0x0c,0x02) // 0x030c02 +#define SCSI_SENSEKEY_UNRECOVERED_READ_ERROR SK(SK_MEDIA_ERROR, 0x11,0x00) // 0x031100 +#define SCSI_FORMAT_COMMAND_FAILED SK(SK_MEDIA_ERROR, 0x31,0x01) // 0x033101 + +#define SCSI_SENSEKEY_COMMUNICATION_FAILURE SK(SK_HARDWARE_ERROR, 0x08,0x00) // 0x040800 + +#define SCSI_SENSEKEY_INVALID_COMMAND SK(SK_ILLEGAL_REQUEST, 0x20,0x00) // 0x052000 +#define SCSI_SENSEKEY_BLOCK_ADDRESS_OUT_OF_RANGE SK(SK_ILLEGAL_REQUEST, 0x21,0x00) // 0x052100 +#define SCSI_SENSEKEY_INVALID_FIELD_IN_CDB SK(SK_ILLEGAL_REQUEST, 0x24,0x00) // 0x052400 +#define SCSI_SENSEKEY_LOGICAL_UNIT_NOT_SUPPORTED SK(SK_ILLEGAL_REQUEST, 0x25,0x00) // 0x052500 +#define SCSI_SENSEKEY_SAVING_PARAMETERS_NOT_SUPPORTED SK(SK_ILLEGAL_REQUEST, 0x39,0x00) // 0x053900 +#define SCSI_MEDIA_REMOVAL_PREVENTED SK(SK_ILLEGAL_REQUEST, 0x53,0x02) // 0x055302 + +#define SCSI_SENSEKEY_NOT_READY_TO_READY_CHANGE SK(SK_UNIT_ATTENTION, 0x28,0x00) // 0x062800 +#define SCSI_SENSEKEY_RESET_OCCURRED SK(SK_UNIT_ATTENTION, 0x29,0x00) // 0x062900 + +#define SCSI_SENSEKEY_STATUS_NOTIFICATION_POWER_CLASS SK(SK_UNIT_ATTENTION, 0x38,0x02) // 0x063802 +#define SCSI_SENSEKEY_STATUS_NOTIFICATION_MEDIA_CLASS SK(SK_UNIT_ATTENTION, 0x38,0x04) // 0x063804 +#define SCSI_SENSEKEY_STATUS_NOTIFICATION_DEVICE_BUSY SK(SK_UNIT_ATTENTION, 0x38,0x06) // 0x063806 + +#define SCSI_SENSEKEY_LOW_POWER_CONDITION_ACTIVE SK(SK_UNIT_ATTENTION, 0x5e,0x00) // 0x065e00 +#define SCSI_SENSEKEY_POWER_CONDITION_CHANGE_TO_ACTIVE SK(SK_UNIT_ATTENTION, 0x5e,0x41) // 0x065e41 +#define SCSI_SENSEKEY_POWER_CONDITION_CHANGE_TO_IDLE SK(SK_UNIT_ATTENTION, 0x5e,0x42) // 0x065e42 +#define SCSI_SENSEKEY_POWER_CONDITION_CHANGE_TO_STANDBY SK(SK_UNIT_ATTENTION, 0x5e,0x43) // 0x065e43 +#define SCSI_SENSEKEY_POWER_CONDITION_CHANGE_TO_SLEEP SK(SK_UNIT_ATTENTION, 0x5e,0x45) // 0x065e45 +#define SCSI_SENSEKEY_POWER_CONDITION_CHANGE_TO_DEVICE SK(SK_UNIT_ATTENTION, 0x5e,0x47) // 0x065e47 + + +#define SCSI_SENSEKEY_WRITE_PROTECTED SK(SK_DATA_PROTECT, 0x27,0x00) // 0x072700 + + +/* + * Mode Page Code and Page Control + */ +#define SCSI_MODEPAGE_CONTROL_CURRENT 0x0 +#define SCSI_MODEPAGE_CONTROL_CHANGEABLE 0x1 +#define SCSI_MODEPAGE_CONTROL_DEFAULT 0x2 +#define SCSI_MODEPAGE_CONTROL_SAVED 0x3 + +#define SCSI_MODEPAGE_UNIT_ATTENTION 0x00 +#define SCSI_MODEPAGE_ERROR_RECOVERY 0x01 +#define SCSI_MODEPAGE_DISCONNNECT_RECONNECT 0x02 +#define SCSI_MODEPAGE_FORMAT 0x03 +#define SCSI_MODEPAGE_RIGID_DRIVE_GEOMETRY 0x04 +#define SCSI_MODEPAGE_FLEXIBLE_DISK_PAGE 0x05 // check +#define SCSI_MODEPAGE_VERIFY_ERROR_RECOVERY 0x07 +#define SCSI_MODEPAGE_CACHING 0x08 +#define SCSI_MODEPAGE_CONTROL_MODE 0x0a +#define SCSI_MODEPAGE_NOTCH_AND_PARTITION 0x0c +#define SCSI_MODEPAGE_POWER_CONDITION 0x0d +#define SCSI_MODEPAGE_XOR 0x10 +#define SCSI_MODEPAGE_CONTROL_MODE_ALIAS 0x1a +#define SCSI_MODEPAGE_REMOVABLE_BLOCK_ACCESS 0x1b// check +#define SCSI_MODEPAGE_INFORMATION_EXCEPTIONS 0x1c +#define SCSI_MODEPAGE_ALL_SUPPORTED 0x3f + + + +/* + * Command Block Wrapper / Command Status Wrapper + */ + +/*! @struct msc_command_block_wrapper msc-scsi.h "otg/functions/msc/msc-scsi.h" + * + * @brief Command Block Wrapper + */ + struct msc_command_block_wrapper { + u32 dCBWSignature; + u32 dCBWTag; + u32 dCBWDataTransferLength; + u8 bmCBWFlags; + u8 bCBWLUN:4, + Reserved:4; + u8 bCBWCBLength:5, + Reserved2:3; + u8 CBWCB[16]; +} __attribute__((packed)); + +/*! @struct msc_command_status_wrapper msc-scsi.h "otg/functions/msc/msc-scsi.h" + * + * @brief Command Status Wrapper + */ +struct msc_command_status_wrapper { + u32 dCSWSignature; + u32 dCSWTag; + u32 dCSWDataResidue; + u8 bCSWStatus; +} __attribute__((packed)); + +/*! @struct msc_scsi_inquiry_command msc-scsi.h "otg/functions/msc/msc-scsi.h" + * + * @brief SCSI INQUIRY command wrapper + */ +struct msc_scsi_inquiry_command { + u8 OperationCode; + u8 EnableVPD:1, + Reserved1:4, + LogicalUnitNumber:3; + u8 PageCode; + u8 Reserved2; + u8 AllocationLength; + u8 Reserved3; + u8 Reserved4; + u8 Reserved5; + u8 Reserved6; + u8 Reserved7; + u8 Reserved8; + u8 Reserved9; +} __attribute__((packed)); + +/* ! @struct msc_scsi_inquiry_data msc-scsi.h "otg/functions/msc/msc-scsi.h" + * + * @brief msc scsi inquiry data wrapper + */ +struct msc_scsi_inquiry_data { + u8 PeripheralDeviceType:5, + PeripheralQaulifier:3; + u8 Reserved2:7, + RMB:1; + u8 ANSIVersion:3, + ECMAVersion:3, + ISOVersion:2; + u8 ResponseDataFormat:4, + Reserved3:4; + u8 AdditionalLength; + u8 Reserved4; + u8 Reserved5; + u8 Reserved6; + u8 VendorInformation[8]; + u8 ProductIdentification[16]; + u8 ProductRevisionLevel[4]; +} __attribute__((packed)); + +/*! @struct msc_scsi_read_format_capacity_command msc-scsi.h "otg/functions/msc/msc-scsi.h" + * + * @brief msc scsi read_format_capacity command wrapper + * + */ +struct msc_scsi_read_format_capacity_command { + u8 OperationCode; + u8 Reserved1:5, + LogicalUnitNumber:3; + u8 Reserved2; + u8 Reserved3; + u8 Reserved4; + u8 Reserved5; + u8 Reserved6; + u16 AllocationLength; + u8 Reserved7; + u8 Reserved8; + u8 Reserved9; +} __attribute__((packed)); + +/*! @struct msc_scsi_read_format_capacity_data msc-scsi.h "otg/functions/msc/msc-scsi.h" + * + * @brief msc scsi read_format_capacity data warpper + * + */ + +struct msc_scsi_read_format_capacity_data { + struct{ + u8 Reserved1; + u8 Reserved2; + u8 Reserved3; + u8 CapacityListLength; + } __attribute__((packed)) CapacityListHeader; + + struct{ + u32 NumberofBlocks; + u8 DescriptorCode:2, + Reserved1:6; + u8 BlockLength[3]; + } __attribute__((packed)) CurrentMaximumCapacityDescriptor; + +} __attribute__((packed)); + +/*! @struct msc_scsi_read_capacity_command msc-scsi.h "otg/functions/msc/msc-scsi.h" + * + * @brief msc scsi read_capacity_command wrapper + * + */ +struct msc_scsi_read_capacity_command { + u8 OperationCode; + u8 RelAdr:1, + Reserved1:4, + LogicalUnitNumber:3; + u32 LogicalBlockAddress; + u8 Reserved2; + u8 Reserved3; + u8 PMI:1, + Reserved4:7; + u8 Reserved5; + u8 Reserved6; + u8 Reserved7; +} __attribute__((packed)); + +/*! @struct msc_scsi_read_capacity_data msc-scsi.h "otg/functions/msc/msc-scsi.h" + * + * @brief READ FORMAT CAPACITY DATA + */ +struct msc_scsi_read_capacity_data { + u32 LastLogicalBlockAddress; + u32 BlockLengthInBytes; +} __attribute__((packed)); + +/*! @struct msc_scsi_request_sense_command msc-scsi.h "otg/functions/msc/msc-scsi.h" + * + * @brief REQUEST SENSE + */ +struct msc_scsi_request_sense_command { + u8 OperationCode; + u8 Reserved1:5, + LogicalUnitNumber:3; + u8 Reserved2; + u8 Reserved3; + u8 AllocationLength; + u8 Reserved4; + u8 Reserved5; + u8 Reserved6; + u8 Reserved7; + u8 Reserved8; + u8 Reserved9; + u8 Reserved10; +} __attribute__((packed)); + +/*! @struct msc_scsi_request_sense_data msc-scsi.h "otg/functions/msc/msc-scsi.h" + * + * @brief REQUEST SENSE DATA + */ +struct msc_scsi_request_sense_data { + u8 ErrorCode:7, + Valid:1; + u8 Reserved1; + u8 SenseKey:4, + Reserved2:4; + u32 Information; + u8 AdditionalSenseLength; + u8 Reserved3[4]; + u8 AdditionalSenseCode; + u8 AdditionalSenseCodeQualifier; + u8 Reserved4; + u8 Reserved5[3]; +} __attribute__((packed)); + +/* @struct msc_scsi_read_10_command msc-scsi.h "otg/functions/msc/msc-scsi.h" + * + * @brief msc scsi read_10 command wrapper + */ +struct msc_scsi_read_10_command { + u8 OperationCode; + u8 RelAdr:1, + Reserved1:2, + FUA:1, + DPO:1, + LogicalUnitNumber:3; + u32 LogicalBlockAddress; + u8 Reserved2; + u16 TransferLength; + u8 Reserved3; + u8 Reserved4; + u8 Reserved5; +} __attribute__((packed)); + +/*! @struct msc_scsi_mode_sense_command msc-scsi.h "otg/functions/msc/msc-scsi.h" + * + * @brief msc_scsi_mode_sense_command wrapper + * MODE SENSE + */ +struct msc_scsi_mode_sense_command { + u8 OperationCode; + u8 Reserved1:3, + DBD:1, + Reserved2:1, + LogicalUnitNumber:3; + u8 PageCode:6, + PageControl:2; // PC + u8 Reserved3; + u8 Reserved4; + u8 Reserved5; + u8 Reserved6; + u16 ParameterListLength; + u8 Reserved7; + u8 Reserved8; + u8 Reserved9; +} __attribute__((packed)); + +/*! @struct msc_mode_parameter_header msc-scsi.h "otg/functions/msc/msc-scsi.h" + * + * @brief MODE PARAMETER HEADER + */ +struct msc_mode_parameter_header { + u8 ModeDataLength; + u8 MediumTypeCode; + u8 Reserved1:4, + DPOFUA:1, + Reserved2:2, + WriteProtect:1; + u8 Reserved3; +} __attribute__((packed)); + +/*! @struct msc_read_write_error_recovery_page msc-scsi.h "otg/functions/msc/msc-scsi.h" + * + * @brief MODE READ WRITE ERROR RECOVERY PAGE + */ +struct msc_read_write_error_recovery_page{ + u8 PageCode:6, + Reserved1:1, + PS:1; + u8 PageLength; + u8 DCR:1, + Reserved2:1, + PER:1, + Reserved3:1, + RC:1, + Reserved4:1, + Reserved5:1, + AWRE:1; + u8 ReadRetryCount; + u8 Reserved6[4]; + u8 WriteRetryCount; + u8 Reserved7[3]; +} __attribute__((packed)); + +/*! @struct msc_flexible_disk_page msc-scsi.h "otg/functions/msc/msc-scsi.h" + * + * @brief FLEXIBLE DISK PAGE + */ +struct msc_flexible_disk_page { + u8 PageCode:6, + Reserved1:1, + PS:1; + u8 PageLength; + u16 TransferRate; + u8 NumberofHeads; + u8 SectorsperTrack; + u16 DataBytesperSector; + u16 NumberofCylinders; + u8 Reserved2[9]; + u8 MotorOnDelay; + u8 MotorOffDelay; + u8 Reserved3[7]; + u16 MediumRotationRate; + u8 Reserved4; + u8 Reserved5; +} __attribute__((packed)); + +/*! @struct msc_removable_block_access_capabilities_page msc-scsi.h "otg/functions/msc/msc-scsi.h" + * + * @brief REMOVABLE BLOCK ACCESS CAPABILITIES PAGE + */ +struct msc_removable_block_access_capabilities_page { + u8 PageCode:6, + Reserved1:1, + PS:1; + u8 PageLength; + u8 Reserved2:6, + SRFP:1, + SFLP:1; + u8 TLUN:3, + Reserved3:3, + SML:1, + NCD:1; + u8 Reserved4[8]; +} __attribute__((packed)); + +/*! @struct msc_timer_and_protect_page msc-scsi.h "otg/functions/msc/msc-scsi.h" + * + * @brief TIMER AND PROTECT PAGE + */ + struct msc_timer_and_protect_page { + u8 PageCode:6, + Reserved1:1, + PS:1; + u8 PageLength; + u8 Reserved2; + u8 InactivityTimeMultiplier:4, + Reserved3:4; + u8 SWPP:1, + DISP:1, + Reserved4:6; + u8 Reserved5; + u8 Reserved6; + u8 Reserved7; +} __attribute__((packed)); + +/*! @struct msc_mode_all_pages msc-scsi.h "otg/functions/msc/msc-scsi.h" + * + * @brief MODE ALL PAGES + */ +struct msc_mode_all_pages { + struct msc_read_write_error_recovery_page ReadWriteErrorRecoveryPage; + struct msc_flexible_disk_page FlexibleDiskPage; + struct msc_removable_block_access_capabilities_page RemovableBlockAccessCapabilitiesPage; + struct msc_timer_and_protect_page TimerAndProtectPage; +} __attribute__((packed)); + +/*! @struct msc_scsi_mode_sense_date msc-scsi.h "otg/functions/msc/msc-scsi.h" + * + * @brief SCSI MODE SENSE DATA + */ +struct msc_scsi_mode_sense_data { + struct msc_mode_parameter_header ModeParameterHeader; + union{ + struct msc_read_write_error_recovery_page ReadWriteErrorRecoveryPage; + struct msc_flexible_disk_page FlexibleDiskPage; + struct msc_removable_block_access_capabilities_page RemovableBlockAccessCapabilitiesPage; + struct msc_timer_and_protect_page TimerAndProtectPage; + struct msc_mode_all_pages ModeAllPages; + } __attribute__((packed)) ModePages; +} __attribute__((packed)); + +/*! @struct msc_scsi_test_unit_ready_command msc-scsi.h "otg/functions/msc/msc-scsi.h" + * + * @brief TEST UNIT READY + */ +struct msc_scsi_test_unit_ready_command { + u8 OperationCode; + u8 Reserved1:5, + LogicalUnitNumber:3; + u8 Reserved2; + u8 Reserved3; + u8 Reserved4; + u8 Reserved5; + u8 Reserved6; + u8 Reserved7; + u8 Reserved8; + u8 Reserved9; + u8 Reserved10; + u8 Reserved11; +} __attribute__((packed)); + +/*! @struct msc_scsi_prevent_allow_media_removal_command msc-scsi.h "otg/functions/msc/msc-scsi.h" + * + * @brief PREVENT-ALLOW MEDIA REMOVAL + */ +struct msc_scsi_prevent_allow_media_removal_command { + u8 OperationCode; + u8 Reserved1:5, + LogicalUnitNumber:3; + u8 Reserved2; + u8 Reserved3; + u8 Prevent:2, + Reserved4:6; + u8 Reserved5; + u8 Reserved6; + u8 Reserved7; + u8 Reserved8; + u8 Reserved9; + u8 Reserved10; + u8 Reserved11; +} __attribute__((packed)); + +/*! @struct msc_scsi_start_stop_command msc-scsi.h "otg/functions/msc/msc-scsi.h" + * + * @brief START-STOP UNIT + */ +struct msc_scsi_start_stop_command { + u8 OperationCode; + u8 IMMED:1, + Reserved1:4, + LogicalUnitNumber:3; + u8 Reserved2; + u8 Reserved3; + u8 Start:1, + LoEj:1, + Reserved4:2, + PowerConditions:4; + u8 Reserved5; + u8 Reserved6; + u8 Reserved7; + u8 Reserved8; + u8 Reserved9; + u8 Reserved10; + u8 Reserved11; +} __attribute__((packed)); + +/*! @struct msc_scsi_write_10_command msc-scsi.h "otg/functions/msc/msc-scsi.h" + * + * @brief WRITE 10 command wrapper + */ +struct msc_scsi_write_10_command { + u8 OperationCode; + u8 RelAdr:1, + Reserved1:2, + FUA:1, + DPO:1, + LogicalUnitNumber:3; + u32 LogicalBlockAddress; + u8 Reserved2; + u16 TransferLength; + u8 Reserved3; + u8 Reserved4; + u8 Reserved5; +} __attribute__((packed)); + +/*! @struct msc_scsi_verify_command msc-scsi.h "otg/functions/msc/msc-scsi.h" + * + * @brief msc_scsi VERIFY command wrapper + */ +struct msc_scsi_verify_command { + u8 OperationCode; + u8 RelAdr:1, + ByteChk:1, + Reserved1:1, + Reserved2:1, + DPO:1, + LogicalUnitNumber:3; + u32 LogicalBlockAddress; + u8 Reserved3; + u16 VerificationLength; + u8 Reserved4; + u8 Reserved5; + u8 Reserved6; +} __attribute__((packed)); + +#endif /* _MSCSCSIPROTO_H_ */ diff --git a/drivers/otg/functions/msc/msc.h b/drivers/otg/functions/msc/msc.h new file mode 100644 index 000000000000..155a12f71fc0 --- /dev/null +++ b/drivers/otg/functions/msc/msc.h @@ -0,0 +1,182 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/function/msc/msc.h - Mass Storage Class + * @(#) sl@belcarra.com/whiskey.enposte.net|otg/functions/msc/msc.h|20061218212925|08467 + * + * Copyright (c) 2003-2006 Belcarra Technologies Corp + * Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + * + * By: + * Stuart Lynne <sl@belcarra.com> + * Bruce Balden <balden@belcarra.com> + * Tony Tang <tt@belcarra.com> + * + */ +/*! + * @defgroup MSCFunction Mass Storage Interface Function + * @ingroup InterfaceFunctions + */ +/*! + * @file otg/functions/msc/msc.h + * @brief Mass Storage Driver private defines + * + * + * @ingroup MSCFunction + */ + +#ifndef MSC_H +#define MSC_H 1 + +extern otg_tag_t msc_fd_trace_tag; +#define MSC msc_fd_trace_tag + +/* + * Command/Data/Status Flow + * C.f. 5 - Figure 1 + */ + +typedef enum msc_state { + MSC_READY, + MSC_DATA_OUT_WRITE, + MSC_DATA_OUT_WRITE_FINISHED, + MSC_DATA_IN_READ, + MSC_DATA_IN_READ_FINISHED, + MSC_STATUS, + MSC_QUERY, + MSC_WAITFOR_RESET, + MSC_CBW_PASSED, + MSC_CBW_PHASE_ERROR, + MSC_CBW_FAILED, + MSC_WAITFOR_CLEAR, + MSC_UNKNOWN, +} msc_state_t; + + +/* + * Device Transfer state + * C.F. Table 6.1 + */ + +typedef enum msc_device_state { + MSC_DEVICE_DN, // The device intends to transfer no data + MSC_DEVICE_DI, // The device intends to send data to the host + MSC_DEVICE_DO, // The device intents to received data from the host +} msc_device_state_t; + +#define MSC_INACTIVE 0x0000 +#define MSC_BLOCKIO_PENDING 0x0001 +#define MSC_BLOCKIO_FINISHED 0x0002 +#define MSC_RECV_PENDING 0x0010 +#define MSC_RECV_FINISHED 0x0020 +#define MSC_SEND_PENDING 0x0040 +#define MSC_SEND_FINISHED 0x0080 +#define MSC_IOCTL_WAITING 0x0100 // there is an ioctl call waiting for I/O completion +#define MSC_ABORT_IO 0x0200 // please abort current i/o + +#if 0 +struct SPC_inquiry_cdb { + u8 OperationCode; /* 12H */ + u8 EnableVPD:1; + u8 CmdSupportData:1; + u8 Reserved0:6; + u8 PageCode; + u8 Reserved1; + u8 AllocationLen; + u8 Control; +} __attribute__((packed)); +#endif +/*! @struct msc_private msc.h "otg/functions/msc/msc.h" + * + * @brief msc private data wrapper + */ +struct msc_private { + + unsigned char connected; // non-zero if connected to host (configured) + + struct usbd_urb *rcv_urb_finished; + +#if defined(LINUX26) + struct bio read_bio; + struct bio_vec rbio_vec; + struct bio write_bio; + struct bio_vec wbio_vec; + unsigned int bytes_done; + int err; + +#else /* defined(LINUX26) */ + struct buffer_head read_bh; + struct buffer_head write_bh; + int uptodate; +#endif /* defined(LINUX26) */ + + u8 read_pending; + u8 write_pending; + u8 caseflag; + + msc_device_state_t device_state; + msc_state_t command_state; // current command state + u16 io_state; // current IO state + u8 endpoint_state; // Bulk in and Bulk out state bit 0 IN, bit 1 OUT. The rest are reserved + + struct msc_command_block_wrapper command; + + u32 lba; // next lba to read/write from + u32 transfer_blocks; + u32 TransferLength_in_blocks; // amount of transfer remaining + u32 TransferLength_in_bytes; // amount of transfer remaining + u32 data_transferred_in_bytes; // amount of data actually transferred + + int major; + int minor; + //kdev_t dev; + dev_t dev; + struct gendisk *disk; + struct block_device *bdev; + u32 block_size; + u32 capacity; + u32 max_blocks; + + + + wait_queue_head_t msc_wq; + wait_queue_head_t ioctl_wq; + + u32 status; + u32 block_dev_state; + u32 sensedata; + u32 info; +}; + + +/* + * MSC Configuration + * + * Endpoint, Class, Interface, Configuration and Device descriptors/descriptions + */ + +#define BULK_OUT 0x00 +#define BULK_IN 0x01 +#define ENDPOINTS 0x02 + +/* endpoint state*/ + +#define BULK_IN_HALTED 0x01 +#define BULK_OUT_HALTED 0x02 + + +extern struct usbd_function_operations function_ops; +extern struct usbd_function_driver function_driver; + + +#endif diff --git a/drivers/otg/functions/msc/otg-config.h b/drivers/otg/functions/msc/otg-config.h new file mode 100644 index 000000000000..56dbcb0044b1 --- /dev/null +++ b/drivers/otg/functions/msc/otg-config.h @@ -0,0 +1,71 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * + * @(#) balden@belcarra.com|otg/functions/msc/otg-config.h|20060419204258|23307 + * + */ + + +/* + * tristate " Mass Storage Function" + */ +/* #define CONFIG_OTG_MSC */ + +/* + * hex "VendorID (hex value)" + * default "0x15ec" + */ +#define CONFIG_OTG_MSC_VENDORID 0x15ec + +/* + * hex "ProductID (hex value)" + * default "0xf006" + */ +#define CONFIG_OTG_MSC_PRODUCTID 0xf006 + +/* + * hex "bcdDevice (binary-coded decimal)" + * default "0x0100" + */ +#define CONFIG_OTG_MSC_BCDDEVICE 0x0100 + +/* + * string "iManufacturer (string)" + * default "Belcarra" + */ +#define CONFIG_OTG_MSC_MANUFACTURER "Belcarra" + +/* + * string "iProduct (string)" + * default "Mass Storage Class - Bulk Only" + */ +#define CONFIG_OTG_MSC_PRODUCT_NAME "Mass Storage Class - Bulk Only" + +/* + * string "MSC Bulk Only iInterface (string)" + * default "MSC BO Data Intf" + */ +#define CONFIG_OTG_MSC_INTF "MSC BO Data Intf" + +/* + * string "Data Interface iConfiguration (string)" + * default "MSC BO Configuration" + */ +#define CONFIG_OTG_MSC_DESC "MSC BO Configuratino" + +/* + * bool " MSC Tracing" + * default n + */ +#define CONFIG_OTG_MSC_REGISTER_TRACE diff --git a/drivers/otg/functions/msc/trace.c b/drivers/otg/functions/msc/trace.c new file mode 100644 index 000000000000..02251351c1b2 --- /dev/null +++ b/drivers/otg/functions/msc/trace.c @@ -0,0 +1,347 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/msc_fd/trace.c + * @(#) sl@belcarra.com/whiskey.enposte.net|otg/functions/msc/trace.c|20070425221028|00673 + * + * Copyright (c) 2003-2005 Belcarra + * Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + * + * By: + * Stuart Lynne <sl@belcarra.com>, + * + */ + +#include <linux/module.h> +#include <linux/version.h> + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/pci.h> +#include <linux/init.h> +#include <linux/delay.h> + +#include <linux/proc_fs.h> +#include <linux/vmalloc.h> + +#include <asm/atomic.h> +#include <asm/io.h> + +#include <linux/proc_fs.h> + +#include <linux/netdevice.h> +#include <linux/pci.h> +#include <linux/cache.h> + + + +#include <asm/uaccess.h> +#include <asm/io.h> +#include <asm/pgtable.h> +#include <asm/pgalloc.h> + +#include <usbp-chap9.h> +#include <usbp-mem.h> +#include <usbp-func.h> +#include "msc-scsi.h" +#include "msc.h" +#include "trace.h" + + +static struct msc_private *msc_private; +int msc_trace_first; +int msc_trace_next; +msc_trace_t *msc_traces; + +extern int msc_interrupts; + + +#if defined(CONFIG_OTG_MSC_REGISTER_TRACE) && defined(CONFIG_PROC_FS) || defined(_OTG_DOXYGEN) + +msc_trace_t *MSC_TRACE_NEXT(msc_trace_types_t msc_trace_type) +{ + msc_trace_t *p; + + p = msc_traces + msc_trace_next; + + if (msc_private) { + p->ticks = usbd_ticks(msc_private->function); + p->sofs = usbd_framenum(msc_private->function); + } + p->interrupts = msc_interrupts; + p->msc_trace_type = msc_trace_type; + + msc_trace_next++; + msc_trace_next = (msc_trace_next == TRACE_MAX) ? 0 : msc_trace_next; + + if (msc_trace_next == msc_trace_first) { + msc_trace_first++; + msc_trace_first = (msc_trace_first == TRACE_MAX) ? 0 : msc_trace_first; + } + + return p; +} + +/* Proc Filesystem *************************************************************************** */ + +/* * + * msc_trace_proc_read - implement proc file system read. + * @file + * @buf + * @count + * @pos + * + * Standard proc file system read function. + */ +static ssize_t msc_trace_proc_read (struct file *file, char *buf, size_t count, loff_t * pos) +{ + unsigned long page; + int len = 0; + int index; + int oindex; + int previous; + + MOD_INC_USE_COUNT; + // get a page, max 4095 bytes of data... + if (!(page = get_free_page (GFP_KERNEL))) { + MOD_DEC_USE_COUNT; + return -ENOMEM; + } + + len = 0; + oindex = index = (*pos)++; + + if (index == 0) + len += sprintf ((char *) page + len, " Index Ints Ticks\n"); + + + index += msc_trace_first; + if (index >= TRACE_MAX) + index -= TRACE_MAX; + + previous = (index) ? (index - 1) : (TRACE_MAX - 1); + + + if ( + ((msc_trace_first < msc_trace_next) && (index >= msc_trace_first) && (index < msc_trace_next)) || + ((msc_trace_first > msc_trace_next) && ((index < msc_trace_next) || (index >= msc_trace_first))) + ) + { + + u64 ticks = 0; + + msc_trace_t *p = msc_traces + index; + unsigned char *cp; + unsigned int *ip; + int skip = 0; + + if (oindex > 0) { + msc_trace_t *o = msc_traces + previous; + + if (o->ticks) + ticks = (p->ticks > o->ticks) ? (p->ticks - o->ticks) : (o->ticks - p->ticks) ; + + if (o->interrupts != p->interrupts) + skip++; + + } + + //printk(KERN_INFO"index: %d interrupts: %d\n", index, p->interrupts); + len += sprintf ((char *) page + len, "%s%6d %8d ", skip?"\n":"", index, p->interrupts); + + if (ticks > 1024*1024) + len += sprintf ((char *) page + len, "%8dM ", ticks>>20); + else + len += sprintf ((char *) page + len, "%8d ", ticks); + + len += sprintf ((char *) page + len, "%6d ", (int)p->sofs); + + switch (p->msc_trace_type) { + case msc_trace_msg: + len += sprintf ((char *) page + len, " -- "); + len += sprintf ((char *) page + len, p->trace.msg.msg); + break; + + case msc_trace_w: + len += sprintf ((char *) page + len, " --> "); + len += sprintf ((char *) page + len, "[%8x] W %s", p->trace.msg32.val, p->trace.msg32.msg); + break; + + case msc_trace_r: + len += sprintf ((char *) page + len, "<-- "); + len += sprintf ((char *) page + len, "[%8x] R %s", p->trace.msg32.val, p->trace.msg32.msg); + break; + + case msc_trace_msg32: + len += sprintf ((char *) page + len, " -- "); + len += sprintf ((char *) page + len, p->trace.msg32.msg, p->trace.msg32.val); + break; + + case msc_trace_msg16: + len += sprintf ((char *) page + len, " -- "); + len += sprintf ((char *) page + len, p->trace.msg16.msg, p->trace.msg16.val0, p->trace.msg16.val1); + break; + + case msc_trace_msg8: + len += sprintf ((char *) page + len, " -- "); + len += sprintf ((char *) page + len, p->trace.msg8.msg, + p->trace.msg8.val0, p->trace.msg8.val1, p->trace.msg8.val2, p->trace.msg8.val3); + break; + + case msc_trace_setup: + cp = (unsigned char *)&p->trace.setup; + len += sprintf ((char *) page + len, + " -- request [%02x %02x %02x %02x %02x %02x %02x %02x]", + cp[0], cp[1], cp[2], cp[3], cp[4], cp[5], cp[6], cp[7]); + break; + + case msc_trace_recv: + case msc_trace_sent: + cp = (unsigned char *)&p->trace.sent; + len += sprintf ((char *) page + len, + "%s %s [%02x %02x %02x %02x %02x %02x %02x %02x]", + ( p->msc_trace_type == msc_trace_recv)?"<-- ":" -->", + ( p->msc_trace_type == msc_trace_recv)?"recv":"sent", + cp[0], cp[1], cp[2], cp[3], cp[4], cp[5], cp[6], cp[7]); + break; + case msc_trace_rlba: + ip = (unsigned int *)&p->trace.ints; + len += sprintf ((char *) page + len, + "%s %s [%8x %08x]", + "<-- ", "rlba", ip[0], ip[1]); + break; + case msc_trace_slba: + case msc_trace_tlba: + ip = (unsigned int *)&p->trace.ints; + len += sprintf ((char *) page + len, + "%s %s [%8x %08x]", " -->", + ( p->msc_trace_type == msc_trace_tlba)?"tlba":"slba", + ip[0], ip[1]); + break; + + case msc_trace_tag: + ip = (unsigned int *)&p->trace.ints; + len += sprintf ((char *) page + len, + "%s TAG: %8x FRAME: %03x", " -->", + ip[0], ip[1]); + break; + + case msc_trace_sense: + ip = (unsigned int *)&p->trace.ints; + len += sprintf ((char *) page + len, + "%s SENSE: %06x INFO: %08x", " -->", + ip[0], ip[1]); + break; + + case msc_trace_cbw: + len += sprintf ((char *) page + len, " --> "); + len += sprintf ((char *) page + len, "%s %02x", p->trace.msg32.msg, p->trace.msg32.val); + break; + } + len += sprintf ((char *) page + len, "\n"); + } + + if ((len > count) || (len == 0)) + len = -EINVAL; + else if (len > 0 && copy_to_user (buf, (char *) page, len)) + len = -EFAULT; + + free_page (page); + MOD_DEC_USE_COUNT; + return len; +} + +/* * + * msc_trace_proc_write - implement proc file system write. + * @file + * @buf + * @count + * @pos + * + * Proc file system write function, used to signal monitor actions complete. + * (Hotplug script (or whatever) writes to the file to signal the completion + * of the script.) An ugly hack. + */ +static ssize_t msc_trace_proc_write (struct file *file, const char *buf, size_t count, loff_t * pos) +{ + return count; +} + +static struct file_operations msc_trace_proc_operations_functions = { + read:msc_trace_proc_read, + write:msc_trace_proc_write, +}; + + +/** + * msc_trace_init + * + * Return non-zero if not successful. + */ +int msc_trace_init (char *name, struct msc_private *msc) +{ + printk(KERN_INFO"%s:\n", __FUNCTION__); + if (!(msc_traces = vmalloc(sizeof(msc_trace_t) * TRACE_MAX))) { + printk(KERN_ERR"%s: malloc failed %p %d\n", __FUNCTION__, msc_traces, sizeof(msc_trace_t) * TRACE_MAX); + return -EINVAL; + } + memset(msc_traces, 0, sizeof(msc_trace_t) * TRACE_MAX); + + { + struct proc_dir_entry *p; + + // create proc filesystem entries + if ((p = create_proc_entry (name, 0, 0)) == NULL) { + printk(KERN_INFO"BITRACE PROC FS failed\n"); + } + else { + p->proc_fops = &msc_trace_proc_operations_functions; + } + } + printk(KERN_INFO"%s: OK\n", __FUNCTION__); + msc_private = msc; + return 0; +} + +/** + * udc_release_io - release UDC io region + */ +void msc_trace_exit (char *name) +{ + msc_private = NULL; + { + unsigned long flags; + local_irq_save (flags); + remove_proc_entry (name, NULL); + if (msc_traces) { + msc_trace_t *p = msc_traces; + msc_traces = NULL; + vfree(p); + } + local_irq_restore (flags); + } +} + + +#else +int msc_trace_init (void) +{ + return 0; +} + +void msc_trace_exit (char *) +{ + return; +} +#endif diff --git a/drivers/otg/functions/network/Kconfig b/drivers/otg/functions/network/Kconfig new file mode 100644 index 000000000000..f26cbd5996f2 --- /dev/null +++ b/drivers/otg/functions/network/Kconfig @@ -0,0 +1,261 @@ +# @(#) balden@belcarra.com/seth2.rillanon.org|otg/functions/network/Kconfig|20070531044942|29753 +# Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp +menu "OTG Network Function" + depends on OTG + + config OTG_NETWORK + + tristate "USBOTG Network Interface Function Driver" + depends on OTG + default OTG + + + menu "USBOTG Network Interface Function Driver options" + depends on OTG_NETWORK + + #comment -- + #comment "Network Device Product Information" + + #config OTG_NETWORK_VENDORID + # depends on OTG && OTG_NETWORK + # default "0x15ec" + # ---help--- + # The USB Peripheral Vendor ID. This should be set to your assigned + # USB Vendor ID (www.usb.org). The USB Host will use this and the + # Product ID to find and load an appropriate device driver. + + #config OTG_NETWORK_PRODUCTID + # hex "ProductID (hex value)" + # depends on OTG && OTG_NETWORK + # default "0xf001" + # ---help--- + # The USB Peripheral Product ID. This should be set to your a + # unique value for this product. The USB Host will use this and the + # Vendor ID to find and load an appropriate device driver. + + #config OTG_NETWORK_BCDDEVICE + # hex "bcdDevice (binary-coded decimal)" + # depends on OTG && OTG_NETWORK + # default "0x0100" + + #config OTG_NETWORK_MANUFACTURER + # string "iManufacturer (string)" + # depends on OTG && OTG_NETWORK + # default "Belcarra" + # ---help--- + # This will be used as the iManufacturer string descriptor. + + #config OTG_NETWORK_PRODUCT_NAME + # string "iProduct (string)" + # depends on OTG && OTG_NETWORK + # default "Belcarra Network Device" + # ---help--- + # This will be used as the iProduct string descriptor. + + comment -- + comment "Network Protocols" + + config OTG_NETWORK_EEM + bool 'Enable CDC/EEM' + default TRUE + ---help--- + Implement CDC/EEM (Ethernet Emulation Model) support. + This is the simplest and most efficent protocol available. + It requires only Bulk IN and Bulk Out endpoints and can + implement data streaming. It can be used for any type of + network device. + + config OTG_NETWORK_EEM_CRC + bool "Append 32bit CRC" + depends on OTG && OTG_NETWORK_EEM + default TRUE + ---help--- + Setting this allows the host and device to verify that + all network frames have been successfully transferred. + + config OTG_NETWORK_EEM_STREAM + bool "Streaming mode" + depends on OTG && OTG_NETWORK_EEM + default FALSE + ---help--- + Setting this will tell the EEM driver to stream data as much as + possible. This usually will result in higher throughput but possibly + slightly higher latencies. + + config OTG_NETWORK_EEM_ZLE + bool "Use ZLE not ZLP" + depends on OTG && OTG_NETWORK_EEM + default FALSE + ---help--- + Setting this will force the use of ZLE (Zero Length EEM packet) + instead of ZLP (Zero Length Packets) when a short packet is required + to terminate a transfer. + + config OTG_NETWORK_ECM + bool 'Enable CDC/ECM networking' + default FALSE + ---help--- + ECM implements the USB CDC ECM Class Specification Ethernet + Class Model to support infra-structure devices. This is the + older style of CDC networking over USB and requires 2 BULK + (IN and OUT) endpoints, an Interrupt endpoint and the device + must properly support SET INTERFACE. + + comment -- + comment "Older Network Protocols" + + config OTG_NETWORK_DEPRECATED + bool "Enable older network protocols" + default FALSE + ---help--- + Allow older protocols to be used. + + config OTG_NETWORK_BLAN + bool 'Enable MDLM-BLAN networking' + depends on OTG && OTG_NETWORK_DEPRECATED + default FALSE + ---help--- + BLAN supports non-infrastructure devices in virtual + bridged network environment. This protocol is deprecated. + + config OTG_NETWORK_BLAN_CRC + bool "Append 32bit CRC" + depends on OTG && OTG_NETWORK_BLAN + default TRUE + ---help--- + Setting this allows the host and device to verify that + all network frames have been successfully transferred. + + config OTG_NETWORK_BLAN_AUTO_CONFIG + bool "Support Vendor Requests to configure the network interface" + depends on OTG && OTG_NETWORK_BLAN + default TRUE + ---help--- + The driver will automatically configure the network interface + based on the IPADDR sent from the host to the device during + enumeration. This eliminates the need for hotplug. + + config OTG_NETWORK_BLAN_NONBRIDGED + bool "Act as infrastructure device" + depends on OTG && OTG_NETWORK_BLAN + default FALSE + ---help--- + Normally MDLM-BLAN is used to support smart devices in a virtual + network LAN implemented over USB with the USB Host acting as + a bridge between itself and all similiar devices. This option + tells the host to instead treat this as an infrastructure device + and not participate in the bridge. + + config OTG_NETWORK_SAFE + bool 'Enable MDLM-SAFE networking' + depends on OTG && OTG_NETWORK_DEPRECATED + default FALSE + ---help--- + SAFE supports infrastructure devices but does not + require support for SET INTERFACE or interrupt endpoints. + This protocol is deprecated. + + config OTG_NETWORK_SAFE_CRC + bool "Append 32bit CRC" + depends on OTG && OTG_NETWORK_SAFE + default TRUE + ---help--- + Setting this allows the host and device to verify that + all network frames have been successfully transferred. + + config OTG_NETWORK_BASIC + bool 'Enable Basic network' + depends on OTG && OTG_NETWORK_DEPRECATED + default FALSE + ---help--- + Implement a very simple network configuration + with a single data interface. This protocol is deprecated. + + config OTG_NETWORK_BASIC2 + bool 'Enable Basic2 network' + depends on OTG && OTG_NETWORK_DEPRECATED + default FALSE + ---help--- + Implement a very simple network configuration with + two interfaces. This protocol is deprecated. + + + #config OTG_NETWORK_START_SINGLE + # bool " Start Single Urb Test" + # depends on OTG && OTG_NETWORK + # default n + # ---help--- + # Used for testing, will not allow multiple receive urbs + # to be queued. This will generally slow transfer speeds + # and is used to test bus interface drivers operate properly + # when there is no receive urb queued. + + #config OTG_NETWORK_EP0TEST + # bool " EP0 Test" + # depends on OTG && OTG_NETWORK + # default n + # ---help--- + # Used for testing, this will change the product string to + # a string that is a multiple of the ep0 packetsize. This + # can be used to verify that the bus interface driver + # properly sends a ZLP after a string. + + comment -- + comment "Linux Network Options)" + config OTG_NETWORK_HOTPLUG + bool "Enable additional network hotplug support" + depends on OTG && HOTPLUG && OTG_NETWORK + default FALSE + ---help--- + Enable additional Hotplug support. The network_fd agent script + will be called with ACTION=attach or ACTION=detach when the + device is configured or de-configured. + + config OTG_NETWORK_HOTPLUG_PATH + string "Pathname of master hotplug program" + depends on OTG && HOTPLUG && OTG_NETWORK && OTG_NETWORK_HOTPLUG + default "/usr/sbin/hotplug" + + +if (OTG != 'y') + comment -- + comment "Network Device Type and Address configuration (testing)" + config OTG_NETWORK_TESTING + bool "Enable auto-config for testing" + depends on OTG && OTG_NETWORK_EEM + default FALSE + ---help--- + Allow older protocols to be used. + + + config OTG_NETWORK_INFRASTRUCTURE + bool 'Infrastructure Device' + depends on OTG_NETWORK_TESTING + default FALSE + ---help--- + The USB Peripheral is implementing networking as an + infrastructure device. More specifically a device + that may bridge network frames to another network. + This will enable a RARPD reply to requests from the + USB Host for a MAC address (for the host to use.) + + config OTG_NETWORK_RARPD_AUTO_CONFIG + bool "Automatically configure the network interface using RARPD" + depends on OTG_NETWORK_TESTING + default TRUE + ---help--- + The driver will automatically configure the network interface + using RARPD. RARPD is an extension to RARP that allows the + USB Host and USB Peripheral to determine if link level configuration + of MAC addresses. + + config OTG_NETWORK_RFC868_AUTO_CONFIG + bool "Set the USB Peripheral System time using RFC868 Time Protocol (UDP)" + depends on OTG_NETWORK_TESTING + default TRUE + ---help--- + The driver will automatically set the system time + using Time of Day (UDP) Service (RFC868.) +endif + endmenu +endmenu diff --git a/drivers/otg/functions/network/Makefile b/drivers/otg/functions/network/Makefile new file mode 100644 index 000000000000..ec14e417bfce --- /dev/null +++ b/drivers/otg/functions/network/Makefile @@ -0,0 +1,25 @@ +# +# Network Function Driver +# @(#) balden@belcarra.com/seth2.rillanon.org|otg/functions/network/Makefile-l26|20070509205304|55506 +# +# Copyright (c) 2002-2005 Belcarra Technologies Corp +# Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + +network_if-objs := net-l24-os.o net-fd.o blan-if.o basic-if.o basic2-if.o ecm-if.o eem-if.o safe-if.o fermat.o net-ip.o + +obj-$(CONFIG_OTG_NETWORK) += network_if.o + +obj-y += net-l24-fix.o + +OTG=$(TOPDIR)/drivers/otg +NETWORKD=$(OTG)/functions/network +OTGCORE_DIR=$(OTG)/otgcore +EXTRA_CFLAGS += -I$(NETWORKD) -I$(OTG) -Wno-unused -Wno-format -I$(OTGCORE_DIR) +EXTRA_CFLAGS_nostdinc += -I$(NETWORKD) -I$(OTG) -Wno-unused -Wno-format -I$(OTGCORE_DIR) + + + +# Link rules for multi-part drivers. + +#network_fd.o: $(network_fd-objs) +# $(LD) -r -o $@ $(network_fd-objs) diff --git a/drivers/otg/functions/network/basic-if.c b/drivers/otg/functions/network/basic-if.c new file mode 100644 index 000000000000..5b74c952aa2b --- /dev/null +++ b/drivers/otg/functions/network/basic-if.c @@ -0,0 +1,174 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/functions/network/basic.c - Network Function Driver + * @(#) sl@belcarra.com/whiskey.enposte.net|otg/functions/network/basic-if.c|20070220211521|39075 + * + * Copyright (c) 2002-2006 Belcarra Technologies Corp + * Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + * + * By: + * Chris Lynne <cl@belcarra.com> + * Stuart Lynne <sl@belcarra.com> + * Bruce Balden <balden@belcarra.com> + * + */ +/*! + * @file otg/functions/network/basic-if.c + * @brief This file implements the required descriptors to implement + * a basic network device with a single interface. + * + * The BASIC network driver implements a very simple descriptor set. + * A single interface with two BULK data endpoints and a optional + * INTERRUPT endpoint. + * + * @ingroup NetworkFunction + */ + + +#include <otg/otg-compat.h> +#include <otg/otg-module.h> + +#include <otg/usbp-chap9.h> +#include <otg/usbp-cdc.h> +#include <otg/usbp-func.h> + +#include "network.h" + +#define NTT network_fd_trace_tag + +#if defined(CONFIG_OTG_NETWORK_BASIC) || defined(_OTG_DOXYGEN) + +/* USB BASIC Configuration ******************************************************************** */ + +/*! Data Alternate Interface Description List + */ +static struct usbd_alternate_description basic_data_alternate_descriptions[] = { + { + .iInterface = "Belcarra USBLAN - MDLM/BLAN", + .bInterfaceClass = LINEO_CLASS, + .bInterfaceSubClass = LINEO_SUBCLASS_BASIC_NET, + .bInterfaceProtocol = LINEO_BASIC_NET_CRC, + .endpoints = ENDPOINTS, + .endpoint_index = net_fd_endpoint_index, + }, +}; + + +/* BASIC Interface descriptions and descriptors + */ +/*! Interface Description List + */ +struct usbd_interface_description basic_interfaces[] = { + { + .alternates = sizeof (basic_data_alternate_descriptions) / sizeof (struct usbd_alternate_description), + .alternate_list = basic_data_alternate_descriptions, + }, +}; + + +/* ********************************************************************************************* */ + +/*! int basic_fd_function_enable( struct usbd_function_instance) + * @brief -enable the function driver + * + * Called for usbd_function_enable() from usbd_register_device() + * + * @param function_instance - pointer to this function intance + * @return 0 for success + */ + +int basic_fd_function_enable (struct usbd_function_instance *function_instance) +{ + return net_fd_function_enable(function_instance, + network_blan, net_fd_recv_urb_mdlm, + net_fd_start_xmit_mdlm, + net_fd_start_recv_mdlm, 0 + ); +} + + +/* ********************************************************************************************* */ +/*! basic_fd_function_ops - operations table for network function driver + */ +struct usbd_function_operations basic_fd_function_ops = { + .function_enable = basic_fd_function_enable, + .function_disable = net_fd_function_disable, + + .device_request = net_fd_device_request, + .endpoint_cleared = net_fd_endpoint_cleared, + + .endpoint_cleared = net_fd_endpoint_cleared, + .set_configuration = net_fd_set_configuration, + .set_interface = NULL, + .reset = net_fd_reset, + .suspended = net_fd_suspended, + .resumed = net_fd_resumed, +}; + + +/*! function driver description + */ +struct usbd_interface_driver basic_interface_driver = { + .driver = { + .name = "net-basic-if", + .fops = &basic_fd_function_ops, }, + .interfaces = sizeof (basic_interfaces) / sizeof (struct usbd_interface_description), + .interface_list = basic_interfaces, + .endpointsRequested = ENDPOINTS, + .requestedEndpoints = net_fd_endpoint_requests, + + .bFunctionClass = USB_CLASS_COMM, + .bFunctionSubClass = COMMUNICATIONS_ENCM_SUBCLASS, + .bFunctionProtocol = COMMUNICATIONS_NO_PROTOCOL, + .iFunction = "Belcarra USBLAN - Basic", +}; + +/* ********************************************************************************************* */ + +/*!int basic_mod_init() + * @brief initialize interface module throught registering driver + * @return int + */ +int basic_mod_init (void) +{ + return usbd_register_interface_function (&basic_interface_driver, "net-basic-if", NULL); +} + +/*! +* @brief basic_mod_exit + * @return none + */ +void basic_mod_exit(void) +{ + usbd_deregister_interface_function (&basic_interface_driver); +} + +#else /* CONFIG_OTG_NETWORK_BASIC */ +/*! + * @brief basic_mod_init + * @return int + */ +int basic_mod_init (void) +{ + return 0; +} + +/*! + * @brief basic_mod_exit + * @return none + */ +void basic_mod_exit(void) +{ +} +#endif /* CONFIG_OTG_NETWORK_BASIC */ diff --git a/drivers/otg/functions/network/basic2-if.c b/drivers/otg/functions/network/basic2-if.c new file mode 100644 index 000000000000..053b84f793c7 --- /dev/null +++ b/drivers/otg/functions/network/basic2-if.c @@ -0,0 +1,185 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/functions/network/basic2.c - Network Function Driver + * @(#) sl@belcarra.com/whiskey.enposte.net|otg/functions/network/basic2-if.c|20070220211521|10696 + * + * Copyright (c) 2002-2006 Belcarra Technologies Corp + * Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + * + * By: + * Chris Lynne <cl@belcarra.com> + * Stuart Lynne <sl@belcarra.com> + * Bruce Balden <balden@belcarra.com> + * + */ +/*! + * @file otg/functions/network/basic2-if.c + * @brief This file implements the required descriptors to implement + * a basic network device with two interfaces. + * + * The BASIC2 network driver implements a very simple descriptor set. + * Two interfaces with two BULK data endpoints and a optional + * INTERRUPT endpoint. + * + * @ingroup NetworkFunction + */ + + +#include <otg/otg-compat.h> +#include <otg/otg-module.h> + +#include <otg/usbp-chap9.h> +#include <otg/usbp-cdc.h> +#include <otg/usbp-func.h> + +#include "network.h" + +#define NTT network_fd_trace_tag + +#if defined(CONFIG_OTG_NETWORK_BASIC) || defined(_OTG_DOXYGEN) + +/* USB BASIC Configuration ******************************************************************** */ + +/*! Data Alternate Interface Description List + */ +static struct usbd_alternate_description basic2_nodata_alternate_descriptions[] = { + { + .iInterface = "Belcarra USBLAN - Basic2", + .bInterfaceClass = LINEO_CLASS, + .bInterfaceSubClass = LINEO_SUBCLASS_BASIC_NET, + .bInterfaceProtocol = 0, + .endpoints = 1, + .endpoint_index = cdc_int_endpoint_index, + }, +}; +/*! @var struct usbd_alternate_description basic2_data_alternate_descriptions + * @brief an array of alternate descriptions + * + */ + static struct usbd_alternate_description basic2_data_alternate_descriptions[] = { + { + .iInterface = "Belcarra USBLAN - Basic2 Data", + .bInterfaceClass = LINEO_CLASS, + .bInterfaceSubClass = LINEO_SUBCLASS_BASIC_NET, + .bInterfaceProtocol = LINEO_BASIC_NET_CRC, + .endpoints = 2, + .endpoint_index = cdc_data_endpoint_index, + }, +}; + + +/* BASIC Interface descriptions and descriptors + */ +/*! Interface Description List + */ +struct usbd_interface_description basic2_interfaces[] = { + { + .alternates = sizeof (basic2_nodata_alternate_descriptions) / sizeof (struct usbd_alternate_description), + .alternate_list = basic2_data_alternate_descriptions, + }, + { + .alternates = sizeof (basic2_data_alternate_descriptions) / sizeof (struct usbd_alternate_description), + .alternate_list = basic2_data_alternate_descriptions, + }, +}; + + +/* ********************************************************************************************* */ + +/*! basic2_fd_function_enable - enable the function driver + * + * Called for usbd_function_enable() from usbd_register_device() + */ + +int basic2_fd_function_enable (struct usbd_function_instance *function_instance) +{ + return net_fd_function_enable(function_instance, + network_blan, net_fd_recv_urb_mdlm, + net_fd_start_xmit_mdlm, + net_fd_start_recv_mdlm, 0 + ); +} + + +/* ********************************************************************************************* */ +/*! basic2_fd_function_ops - operations table for network function driver + */ +struct usbd_function_operations basic2_fd_function_ops = { + .function_enable = basic2_fd_function_enable, + .function_disable = net_fd_function_disable, + + .device_request = net_fd_device_request, + .endpoint_cleared = net_fd_endpoint_cleared, + + .endpoint_cleared = net_fd_endpoint_cleared, + .set_configuration = net_fd_set_configuration, + .set_interface = NULL, + .reset = net_fd_reset, + .suspended = net_fd_suspended, + .resumed = net_fd_resumed, +}; + + +/*! function driver description + */ +struct usbd_interface_driver basic2_interface_driver = { + .driver = { + .name = "net-basic2-if", + .fops = &basic2_fd_function_ops, }, + .interfaces = sizeof (basic2_interfaces) / sizeof (struct usbd_interface_description), + .interface_list = basic2_interfaces, + .endpointsRequested = ENDPOINTS, + .requestedEndpoints = net_fd_endpoint_requests, + + .bFunctionClass = USB_CLASS_COMM, + .bFunctionSubClass = COMMUNICATIONS_ENCM_SUBCLASS, + .bFunctionProtocol = COMMUNICATIONS_NO_PROTOCOL, + .iFunction = "Belcarra USBLAN - Basic2", +}; + +/* ********************************************************************************************* */ + +/*! + * @brief basic2_mod_init + * @return none + */ +int basic2_mod_init (void) +{ + return usbd_register_interface_function (&basic2_interface_driver, "net-basic2-if", NULL); +} + +/*! + * basic2_mod_exit + * @return void + */ + +void basic2_mod_exit(void) +{ + usbd_deregister_interface_function (&basic2_interface_driver); +} + +#else /* CONFIG_OTG_NETWORK_BASIC */ +/*! + * @brief basic2_mod_init + * @return none + */ +int basic2_mod_init (void) +{ + return 0; +} + +void basic2_mod_exit(void) +{ +} +#endif /* CONFIG_OTG_NETWORK_BASIC */ diff --git a/drivers/otg/functions/network/blan-if.c b/drivers/otg/functions/network/blan-if.c new file mode 100644 index 000000000000..54788c62fb4e --- /dev/null +++ b/drivers/otg/functions/network/blan-if.c @@ -0,0 +1,1098 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/functions/network/blan.c - Network Function Driver + * @(#) sl@belcarra.com/whiskey.enposte.net|otg/functions/network/blan-if.c|20070814215823|43564 + * + * Copyright (c) 2002-2006 Belcarra Technologies Corp + * Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + * + * By: + * Chris Lynne <cl@belcarra.com> + * Stuart Lynne <sl@belcarra.com> + * Bruce Balden <balden@belcarra.com> + * + */ +/*! + * @file otg/functions/network/blan-if.c + * @brief This file implements the required descriptors to implement + * a BLAN network device with a single interface. + * + * The BLAN network driver implements the BLAN protocol descriptors. + * + * The BLAN protocol is designed to support smart devices that want + * to create a virtual network between them host and other similiar + * devices. + * + * @ingroup NetworkFunction + */ + + +#include <otg/otg-compat.h> +#include <otg/otg-module.h> + +#include <otg/usbp-chap9.h> +#include <otg/usbp-cdc.h> +#include <otg/usbp-func.h> + +#include "network.h" +#include "net-os.h" + +#define NTT network_fd_trace_tag + +#define TRACE_VERBOSE 0 +#define TRACE_VERY_VERBOSE 0 +#undef DEBUG_CRC_SEEN + +#if defined(CONFIG_OTG_NETWORK_BLAN) || defined(_OTG_DOXYGEN) + +/* USB BLAN Configuration ******************************************************************** */ + +/* + * BLAN Ethernet Configuration + */ + +/*! @name Descriptors - Communication Interface Class descriptors + * + * @{ + */ +static struct usbd_class_header_function_descriptor blan_class_1 = { + .bFunctionLength = 0x05, /* Length */ + .bDescriptorType = USB_DT_CLASS_SPECIFIC, + .bDescriptorSubtype = USB_ST_HEADER, + .bcdCDC = __constant_cpu_to_le16(0x0110) /*Version */ +}; +static struct usbd_class_mdlm_descriptor blan_class_2 = { + .bFunctionLength = 0x15, + .bDescriptorType = USB_DT_CLASS_SPECIFIC, + .bDescriptorSubtype = USB_ST_MDLM, + .bcdVersion = __constant_cpu_to_le16(0x0100), + .bGUID = { + 0x74, 0xf0, 0x3d, 0xbd, 0x1e, 0xc1, 0x44, 0x70, /* bGUID */ + 0xa3, 0x67, 0x71, 0x34, 0xc9, 0xf5, 0x54, 0x37, /* bGUID */ }, +}; +static struct usbd_class_blan_descriptor blan_class_3 = { + .bFunctionLength = 0x07, + .bDescriptorType = USB_DT_CLASS_SPECIFIC, + .bDescriptorSubtype = USB_ST_MDLMD, + .bGuidDescriptorType = 0x01, + .bmNetworkCapabilities = 0x00, + .bmDataCapabilities = 0x00, + .bPad = 0x00, +}; +static struct usbd_class_ethernet_networking_descriptor blan_class_4 = { + .bFunctionLength = 0x0d, + .bDescriptorType = USB_DT_CLASS_SPECIFIC, + .bDescriptorSubtype = USB_ST_ENF, + .iMACAddress = 0x00, + .bmEthernetStatistics = 0x00, + .wMaxSegmentSize = 0x05ea, /* 1514 maximum frame size */ + .wNumberMCFilters = 0x00, + .bNumberPowerFilters = 0x00 , +}; +static struct usbd_class_network_channel_descriptor blan_class_5 = { + .bFunctionLength = 0x07, + .bDescriptorType = USB_DT_CLASS_SPECIFIC, + .bDescriptorSubtype = USB_ST_NCT, + .bEntityId = 0, + .iName = 0, + .bChannelIndex = 0, + .bPhysicalInterface = 0, +}; + + +static struct usbd_generic_class_descriptor *blan_comm_class_descriptors[] = { + (struct usbd_generic_class_descriptor *) &blan_class_1, + (struct usbd_generic_class_descriptor *) &blan_class_2, + (struct usbd_generic_class_descriptor *) &blan_class_3, + (struct usbd_generic_class_descriptor *) &blan_class_4, + (struct usbd_generic_class_descriptor *) &blan_class_5, }; + +/*! Data Alternate Interface Description List + */ +static struct usbd_alternate_description blan_alternate_descriptions[] = { + { + .iInterface = "Belcarra USBLAN - MDLM/BLAN", + .bInterfaceClass = COMMUNICATIONS_INTERFACE_CLASS, + .bInterfaceSubClass = COMMUNICATIONS_MDLM_SUBCLASS, + .bInterfaceProtocol = COMMUNICATIONS_NO_PROTOCOL, + .classes = sizeof (blan_comm_class_descriptors) / sizeof (struct usbd_generic_class_descriptor *), + .class_list = blan_comm_class_descriptors, + .endpoints = ENDPOINTS, + .endpoint_index = net_fd_endpoint_index, + }, +}; + +/*! @} */ +/* Interface descriptions and descriptors + */ +/*! Interface Description List + */ +static struct usbd_interface_description blan_interfaces[] = { + { + .alternates = sizeof (blan_alternate_descriptions) / sizeof (struct usbd_alternate_description), + .alternate_list = blan_alternate_descriptions, + }, +}; + +/* ********************************************************************************************* */ + +/*! net_fd_start_xmit_mdlm + * @brief - start sending a buffer + * + * @param function_instance - function instance pointer + * @param buffer + * @param len + * @param data + * + * @return: 0 if all OK + * -EINVAL, -EUNATCH, -ENOMEM + * rc from usbd_start_in_urb() if that fails (is != 0, may be one of err values above) + * Note: -ECOMM is interpreted by calling routine as signal to leave IF stopped. + */ +int net_fd_start_xmit_mdlm (struct usbd_function_instance *function_instance, u8 *buffer, int len, void *data) +{ + struct usb_network_private *npd = function_instance->privdata; + struct usbd_urb *urb = NULL; + int rc; + u32 crc; + + #ifndef CONFIG_OTG_NETWORK_DOUBLE_IN + int xmit_index = 0; + int endpoint_index = BULK_IN_A; + #else /* CONFIG_OTG_NETWORK_DOUBLE_IN */ + int xmit_index = (otg_atomic_read(&npd->xmit_urbs_started[0]) <= otg_atomic_read(&npd->xmit_urbs_started[1])) + ? 0 : 1; + int endpoint_index = (xmit_index) ? BULK_IN_B : BULK_IN_A; + #endif /* CONFIG_OTG_NETWORK_DOUBLE_IN */ + + int in_pkt_sz = usbd_endpoint_wMaxPacketSize(function_instance, xmit_index, usbd_high_speed(function_instance)); + + if (TRACE_VERBOSE) + TRACE_MSG8(NTT,"npd: %p flags: %04x len: %d endpoint_index: %d " + "xmit_index: %d xmit_started: %d %d in_pkt_sz: %d", + npd, npd->flags, len, endpoint_index, xmit_index, otg_atomic_read(&npd->xmit_urbs_started[0]), + otg_atomic_read(&npd->xmit_urbs_started[1]), in_pkt_sz); + + #if defined(CONFIG_OTG_NETWORK_BLAN_CRC) || defined(CONFIG_OTG_NETWORK_SAFE_CRC) + /* allocate urb 5 bytes larger than required */ + if (!(urb = usbd_alloc_urb (function_instance, endpoint_index, len + 5 + 4 + in_pkt_sz, net_fd_urb_sent_bulk ))) { + u8 epa = usbd_endpoint_bEndpointAddress(function_instance, endpoint_index, usbd_high_speed(function_instance)); + TRACE_MSG2(NTT,"urb alloc failed len: %d endpoint: %02x", len, epa); + return -ENOMEM; + } + + urb->actual_length = len; + + /* copy and crc len bytes */ + crc = crc32_copy(urb->buffer, buffer, len, CRC32_INIT); + + if ((urb->actual_length % in_pkt_sz) == (in_pkt_sz - 4)) { + /* no longer in Kconfig - change undef to define to active padbyte */ + #undef CONFIG_OTG_NETWORK_PADBYTE + #ifdef CONFIG_OTG_NETWORK_PADBYTE + // add a pad byte if required to ensure a short packet, usbdnet driver + // will correctly handle pad byte before or after CRC, but the MCCI driver + // wants it before the CRC. + crc = crc32_pad(urb->buffer + urb->actual_length, 1, crc); + urb->actual_length++; + #else /* CONFIG_OTG_NETWORK_PADBYTE */ + urb->flags |= USBD_URB_SENDZLP; + TRACE_MSG2(NTT,"setting ZLP: urb: %p flags: %x", urb, urb->flags); + #endif /* CONFIG_OTG_NETWORK_PADBYTE */ + } + crc = ~crc; + urb->buffer[urb->actual_length++] = crc & 0xff; + urb->buffer[urb->actual_length++] = (crc >> 8) & 0xff; + urb->buffer[urb->actual_length++] = (crc >> 16) & 0xff; + urb->buffer[urb->actual_length++] = (crc >> 24) & 0xff; + + #if defined(CONFIG_OTG_NETWORK_BLAN_FERMAT) + if (npd->fermat) fermat_encode(urb->buffer, urb->actual_length); + #endif + + #else /* defined(CONFIG_OTG_NETWORK_BLAN_CRC) ...*/ + + /* allocate urb with no buffer */ + #ifdef CONFIG_OTG_NETWORK_XMIT_OS + if (!(urb = usbd_alloc_urb (function_instance, endpoint_index, 0, net_fd_urb_sent_bulk ))) { + u8 epa = usbd_endpoint_bEndpointAddress(function_instance, endpoint_index, usbd_high_speed(function_instance)); + TRACE_MSG2(NTT,"urb alloc failed len: %d endpoint: %02x", len, epa); + return -ENOMEM; + } + urb->actual_length = len; + urb->buffer = buffer; + #else /* CONFIG_OTG_NETWORK_XMIT_OS */ + if (!(urb = usbd_alloc_urb (function_instance, endpoint_index, len + 5 + 4 + in_pkt_sz, net_fd_urb_sent_bulk ))) { + u8 epa = usbd_endpoint_bEndpointAddress(function_instance, endpoint_index, usbd_high_speed(function_instance)); + TRACE_MSG2(NTT,"urb alloc failed len: %d endpoint: %02x", len, epa); + printk(KERN_ERR"%s: urb alloc failed len: %d endpoint: %02x\n", __FUNCTION__, len, epa); + return -ENOMEM; + } + urb->actual_length = len; + memcpy (urb->buffer, buffer, len); + #endif /* CONFIG_OTG_NETWORK_XMIT_OS */ + + urb->flags |= ((urb->actual_length % in_pkt_sz) == 0) ? USBD_URB_SENDZLP : 0; + + #endif /* defined(CONFIG_OTG_NETWORK_BLAN_CRC) ...*/ + + if (TRACE_VERBOSE) + TRACE_MSG3(NTT,"urb: %p buf: %p priv: %p", urb, data, urb->function_privdata); + urb->function_privdata = data; + + otg_atomic_add(urb->actual_length, &npd->queued_bytes); + otg_atomic_inc(&npd->xmit_urbs_started[xmit_index]); + if ((rc = usbd_start_in_urb (urb))) { + + TRACE_MSG1(NTT,"FAILED: %d", rc); + printk(KERN_ERR"%s: FAILED: %d\n", __FUNCTION__, rc); + urb->function_privdata = NULL; + otg_atomic_sub(urb->actual_length, &npd->queued_bytes); + otg_atomic_dec(&npd->xmit_urbs_started[xmit_index]); + usbd_free_urb (urb); + + return rc; + } + return 0; +} + +/* ********************************************************************************************* */ + +/*! net_fd_recv_urb_mdlm + * @brief callback to process a received URB + * + * @param urb - pointer to received urb + * @param rc - dummy parameter + * @return non-zero for failure. + */ +int net_fd_recv_urb_mdlm(struct usbd_urb *urb, int rc) +{ + struct usbd_function_instance *function_instance = urb->function_instance; + struct usb_network_private *npd = function_instance->privdata; + void *os_data = NULL; + void *os_buffer = NULL; + int crc_bad = 0; + int trim = 0; + int len; + u32 crc; + u32 temmp; + len = urb->actual_length; + trim = 0; + + //TRACE_MSG2(NTT, "status: %d actual_length: %d", urb->status, urb->actual_length); + //RETURN_EINVAL_IF (urb->status == USBD_URB_OK); + + os_data = urb->function_privdata; + + //TRACE_MSG2(NTT, "os_data: %x os_buffer: %x", os_data, os_buffer); + + + #if defined(CONFIG_OTG_NETWORK_BLAN_PADAFTER) + { + /* This version simply checks for a correct CRC along the + * entire packet. Some UDC's have trouble with some packet + * sizes, this allows us to add pad bytes after the CRC. + */ + + u8 *src = urb->buffer; + int copied; + + // XXX this should work, but the MIPS optimizer seems to get it wrong.... + //copied = (len < urb->wMaxPacketSize) ? 0 : ((len / urb->wMaxPacketSize) - 1) * urb->wMaxPacketSize; + + if (len < urb->wMaxPacketSize*2) + copied = 0; + else { + int pkts = ((len - urb->wMaxPacketSize) / urb->wMaxPacketSize); + copied = (pkts - 1) * urb->wMaxPacketSize; + } + + len -= copied; + crc = CRC32_INIT; + for (; copied-- > 0 ; crc = COMPUTE_FCS (crc, *os_buffer++ = *src++)); + + for (; (len-- > 0) && (CRC32_GOOD != crc); crc = COMPUTE_FCS (crc, *os_buffer++ = *src++)); + + trim = len + 4; + + if (CRC32_GOOD != crc) { + TRACE_MSG1(NTT,"AAA frame: %03x", urb->framenum); + THROW_IF(npd->seen_crc, crc_error); + } + else + npd->seen_crc = 1; + } + //#else /* defined(CONFIG_OTG_NETWORK_BLAN_PADAFTER) */ + #elif defined(CONFIG_OTG_NETWORK_BLAN_CRC) || defined(CONFIG_OTG_NETWORK_SAFE_CRC) + /* + * The CRC can be sent in two ways when the size of the transfer + * ends up being a multiple of the packetsize: + * + * | + * <data> <CRC><CRC><CRC><CRC>|<???> case 1 + * <data> <NUL><CRC><CRC><CRC>|<CRC> case 2 + * <data> <NUL><CRC><CRC><CRC><CRC>| case 3 + * <data> <NUL><CRC><CRC><CRC>|<CRC> | case 4 + * | + * + * This complicates CRC checking, there are four scenarios: + * + * 1. length is 1 more than multiple of packetsize with a trailing byte + * 2. length is 1 more than multiple of packetsize + * 3. length is multiple of packetsize + * 4. none of the above + * + * Finally, even though we always compute CRC, we do not actually throw + * things away until and unless we have previously seen a good CRC. + * This allows backwards compatibility with hosts that do not support + * adding a CRC to the frame. + * + */ + + // test if 1 more than packetsize multiple + if (1 == (len % urb->wMaxPacketSize)) { + u8 *cp = urb->buffer + len - 1 - 4; + // copy and CRC up to the packetsize boundary + crc = crc32_nocopy(urb->buffer, len - 1, CRC32_INIT); + + if (TRACE_VERBOSE) + TRACE_MSG7(NTT,"A CRC nocopy: %08x %08x len: %d CRC: %02x %02x %02x %02x", + CRC32_GOOD, crc, len, cp[0], cp[1], cp[2], cp[3]); + + // if the CRC is good then this is case 1 + if (CRC32_GOOD != crc) { + + crc = crc32_nocopy(urb->buffer + len - 1, 1, crc); + + if (CRC32_GOOD != crc) { + //crc_errors[len%64]++; + TRACE_MSG3(NTT,"A CRC error %08x %08x %03x", CRC32_GOOD, crc, urb->framenum); + printk(KERN_INFO"%s: A CRC\n", __FUNCTION__); + npd->seen_crc_error = 1; + THROW_IF(npd->seen_crc, crc_error); + } + else + npd->seen_crc = 1; + } + else + npd->seen_crc = 1; + + } + else { + u8 *cp = urb->buffer + len - 4; + crc = crc32_nocopy(urb->buffer, len, CRC32_INIT); + if (TRACE_VERBOSE) + TRACE_MSG7(NTT,"B CRC nocopy: %08x %08x len: %d CRC: %02x %02x %02x %02x", + CRC32_GOOD, crc, len, cp[0], cp[1], cp[2], cp[3]); + + if (CRC32_GOOD != crc) { + //crc_errors[len%64]++; + TRACE_MSG3(NTT,"B CRC error %08x %08x %03x", CRC32_GOOD, crc, urb->framenum); + if (TRACE_VERBOSE) { + + TRACE_MSG2(NTT, "status: %d actual_length: %d", urb->status, urb->actual_length); + + TRACE_NRECV(NTT, 32, urb->buffer); + TRACE_MSG0(NTT, "--"); + TRACE_RECV(NTT, urb->actual_length, urb->buffer); + } + printk(KERN_INFO"%s: B CRC\n", __FUNCTION__); + npd->seen_crc_error = 1; + THROW_IF(npd->seen_crc, crc_error); + } + else + npd->seen_crc = 1; + + // XXX shorten by 4 bytes? + + } + // trim IFF we are paying attention to crc + if (npd->seen_crc) + trim = 4; + #endif /* defined(CONFIG_OTG_NETWORK_BLAN_CRC) ...*/ + + if (net_fd_recv_buffer(function_instance, urb->buffer, len, os_data, crc_bad, trim)) { + TRACE_MSG0(NTT, "FAILED"); + net_os_dealloc_buffer(function_instance, os_data, os_buffer); + } + + // catch a simple error, just increment missed error and general error + CATCH(error) { + //TRACE_MSG4(NTT,"CATCH(error) urb: %p status: %d len: %d function: %p", + // urb, urb->status, urb->actual_length, function_instance); + // catch a CRC error + CATCH(crc_error) { + crc_bad = 1; + npd->seen_crc_error = 1; + } + } + return 0; +} + + + +/* ********************************************************************************************* */ +int blan_start_recv(struct usbd_function_instance *function_instance); + +/*! net_fd_recv_urb2 - callback to process a received URB + * + * @param urb - pointer to copy of received urb, + * @param rc - receiving urb result code + * + * @return non-zero for failure. + */ +int net_fd_recv_urb2(struct usbd_urb *urb, int rc) +{ + struct usbd_function_instance *function_instance = urb->function_instance; + struct usb_network_private *npd = function_instance->privdata; + int hs = usbd_high_speed(function_instance); + int endpoint_index = urb->endpoint_index; + + #ifndef CONFIG_OTG_NETWORK_DOUBLE_OUT + int recv_index = 0; + #else /* CONFIG_OTG_NETWORK_DOUBLE_OUT */ + int recv_index = (endpoint_index == BULK_OUT_A) ? 0 : 1; + #endif /* CONFIG_OTG_NETWORK_DOUBLE_OUT */ + int alloc_length = usbd_endpoint_transferSize(function_instance, endpoint_index, hs); + int status = urb->status; + + void *os_data; + u8 *os_buffer; + + + if (TRACE_VERY_VERBOSE) { + + TRACE_MSG4(NTT, "status: %d actual_length: %d bus status: %d device_state: %d", + urb->status, urb->actual_length, + usbd_get_device_status(function_instance), usbd_get_device_state(function_instance) + ); + + TRACE_NRECV(NTT, 32, urb->buffer); + TRACE_MSG0(NTT, "--"); + TRACE_RECV(NTT, urb->actual_length, urb->buffer); + } + + + otg_atomic_dec(&npd->recv_urbs_started[recv_index]); + + /* process the data */ + if (urb->status == USBD_URB_OK) + npd->net_recv_urb(urb, rc); + + if (urb->status == USBD_URB_CANCELLED) + net_os_dealloc_buffer(function_instance, urb->function_privdata, urb->buffer); + + /* disconnect os_data buffer from urb */ + urb->function_privdata = NULL; + urb->buffer = NULL; + urb->function_instance = NULL; + urb->status = USBD_URB_OK; + usbd_free_urb(urb); + + if ((USBD_OK == usbd_get_device_status(function_instance)) && + (STATE_CONFIGURED == usbd_get_device_state(function_instance))) { + + blan_start_recv(function_instance); + } + else { + TRACE_MSG0(NTT, "NOT RESTARTING"); + } + + return 0; +} + +int blan_start_recv_endpoint(struct usbd_function_instance *function_instance, + int endpoint_index, otg_atomic_t *recv_urbs_started, char *msg) +{ + struct usb_network_private *npd = function_instance->privdata; + int i; + int hs = usbd_high_speed(function_instance); + int alloc_length = usbd_endpoint_transferSize(function_instance, endpoint_index, hs); + + if (TRACE_VERBOSE) + TRACE_MSG4(NTT, "endpoint_index: %d recv_urbs_started: %d alloc_length: %d %s", + endpoint_index, otg_atomic_read(recv_urbs_started), alloc_length, msg); + + while (otg_atomic_read(recv_urbs_started) < npd->max_recv_urbs ) { + u8 *os_buffer = NULL; + void *os_data = NULL; + struct usbd_urb *urb; + + #ifdef DEBUG_CRC_SEEN + if (npd->seen_crc_error) { + TRACE_MSG0(NTT, "CRC ERROR NOT RESTARTING"); + break; + } + #endif /* DEBUG_CRC_SEEN */ + + /* get os buffer - os_buffer is data, os_data is the os data structure */ + os_data = net_os_alloc_buffer(function_instance, &os_buffer, alloc_length); + + /* allocate urb with no buffer */ + /*allocate urb without buffer */ + urb = usbd_alloc_urb(function_instance, endpoint_index, 0, net_fd_recv_urb2); + + /* start urb with buffer pointing at the os_buffer in os_data structure */ + if (os_buffer && urb) { + + urb->function_privdata = os_data; + urb->buffer = os_buffer; + urb->flags |= npd->recv_urb_flags; + urb->alloc_length = urb->buffer_length = alloc_length; + if (usbd_start_out_urb(urb)) { + net_os_dealloc_buffer(function_instance, os_data, os_buffer); + urb->function_privdata = NULL; + urb->buffer = NULL; + usbd_free_urb(urb); + } + otg_atomic_inc(recv_urbs_started); + continue; + } + TRACE_MSG1(NTT, "recv_urbs_started: %d FAILED EARLY", otg_atomic_read(recv_urbs_started)); + + if (os_buffer || os_data) + net_os_dealloc_buffer(function_instance, os_data, os_buffer); + if (urb) + urb->buffer = NULL; + usbd_free_urb(urb); + break; + } + if (TRACE_VERBOSE) + TRACE_MSG1(NTT, "recv_urbs_started: %d", otg_atomic_read(recv_urbs_started)); + return 0; +} + +#if defined(CONFIG_OTG_NETWORK_BLAN_CRC) || defined(CONFIG_OTG_NETWORK_SAFE_CRC) +/*! blan_start_recv - start recv urb(s) + * + *@param function_instance - pointer to this function instance + *@return none + */ +int blan_start_recv(struct usbd_function_instance *function_instance) +{ + struct usb_network_private *npd = function_instance->privdata; + TRACE_MSG0(NTT, "DIRECT"); + blan_start_recv_endpoint(function_instance, BULK_OUT_A, &npd->recv_urbs_started[0], "BULK_OUT_A"); + #ifdef CONFIG_OTG_NETWORK_DOUBLE_OUT + blan_start_recv_endpoint(function_instance, BULK_OUT_B, &npd->recv_urbs_started[1], "BULK_OUT_B"); + #endif /* CONFIG_OTG_NETWORK_DOUBLE_OUT */ + return 0; +} +#else /* defined(CONFIG_OTG_NETWORK_BLAN_CRC) || defined(CONFIG_OTG_NETWORK_SAFE_CRC) */ + +/*! blan_start_recv_task - start recv urb(s) + * + * For high speed devices we can optimize using FAST_RECV flag to get urb handed + * directly to recv urb function from ISR. But then we do not want to allocate new + * recv urbs, so must implement a task for that. + * + *@param data - pointer to this function instance + *@return none + */ +void * blan_start_recv_task(otg_task_arg_t data) +{ + struct usbd_function_instance *function_instance = (struct usbd_function_instance *)data; + struct usb_network_private *npd = function_instance->privdata; + TRACE_MSG0(NTT, "--"); + blan_start_recv_endpoint(function_instance, BULK_OUT_A, &npd->recv_urbs_started[0], "BULK_OUT_A"); + #ifdef CONFIG_OTG_NETWORK_DOUBLE_OUT + blan_start_recv_endpoint(function_instance, BULK_OUT_B, &npd->recv_urbs_started[1], "BULK_OUT_B"); + #endif /* CONFIG_OTG_NETWORK_DOUBLE_OUT */ + return NULL; +} +/*! blan_start_recv - start recv urb(s) + * + *@param function_instance - pointer to this function instance + *@return none + */ +int blan_start_recv(struct usbd_function_instance *function_instance) +{ + struct usb_network_private *npd = function_instance->privdata; + TRACE_MSG0(NTT, "SCHEDULE"); + otg_up_work(npd->blan_recv_task); + return 0; +} +#endif /* defined(CONFIG_OTG_NETWORK_BLAN_CRC) || defined(CONFIG_OTG_NETWORK_SAFE_CRC) */ + +/*! net_fd_start_recv_mdlm - start recv urb(s) + * + * For high speed devices we can optimize using FAST_RECV flag to get urb handed + * directly to recv urb function from ISR. But then we do not want to allocate new + * recv urbs, so must implement a task for that. + * + *@param data - pointer to this function instance + *@return none + */ +int net_fd_start_recv_mdlm (struct usbd_function_instance *function_instance) +{ + blan_start_recv(function_instance); + return 0; +} + +/* ********************************************************************************************* */ + + +/*! net_fd_function_enable + * @brief enable the function driver + * + * Called for usbd_function_enable() from usbd_register_device() + * + * @param function_instance - pointer ot instance of this function + * @return int indicating function driver enable operation result + */ + +int blan_fd_function_enable (struct usbd_function_instance *function_instance) +{ + struct usbd_class_network_channel_descriptor *channel = &blan_class_5 ; + struct usb_network_private *npd = NULL; + u32 recv_urb_flags; +#if 0 +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,17) + struct usbd_class_ethernet_networking_descriptor *ethernet = &blan_class_4; + char address_str[14]; + snprintf(address_str, 13, "%02x%02x%02x%02x%02x%02x", + local_dev_addr[0], local_dev_addr[1], local_dev_addr[2], + local_dev_addr[3], local_dev_addr[4], local_dev_addr[5]); +#else + char address_str[20]; + sprintf(address_str, "%02x%02x%02x%02x%02x%02x", + local_dev_addr[0], local_dev_addr[1], local_dev_addr[2], + local_dev_addr[3], local_dev_addr[4], local_dev_addr[5]); +#endif + ethernet->iMACAddress = usbd_alloc_string(address_str); +#endif + struct usbd_class_ethernet_networking_descriptor *ethernet = &blan_class_4; + char address_str[20]; + + sprintf(address_str, "%02x%02x%02x%02x%02x%02x", 0, 0, 0, 0, 0, 0); + ethernet->iMACAddress = usbd_alloc_string(function_instance, address_str); + + //channel->iName = usbd_alloc_string(function_instance, system_utsname.nodename); + + //TRACE_MSG3(NTT, "name: %s strings index imac: %d name: %d", + // system_utsname.nodename, ethernet->iMACAddress, channel->iName); + + #if defined(CONFIG_OTG_NETWORK_BLAN_CRC) || defined(CONFIG_OTG_NETWORK_SAFE_CRC) + recv_urb_flags = 0; + #else /* defined(CONFIG_OTG_NETWORK_BLAN_CRC) || defined(CONFIG_OTG_NETWORK_SAFE_CRC) */ + recv_urb_flags = USBD_URB_FAST_RETURN | USBD_URB_FAST_FINISH; + #endif /* defined(CONFIG_OTG_NETWORK_BLAN_CRC) || defined(CONFIG_OTG_NETWORK_SAFE_CRC) */ + + + THROW_IF(net_fd_function_enable(function_instance, + network_blan, net_fd_recv_urb_mdlm, + net_fd_start_xmit_mdlm, + blan_start_recv, recv_urb_flags + ), error); + + THROW_UNLESS((npd = function_instance->privdata), error); + + #if !defined(CONFIG_OTG_NETWORK_BLAN_CRC) && !defined(CONFIG_OTG_NETWORK_SAFE_CRC) + THROW_UNLESS((npd->blan_recv_task = otg_task_init2("blanrcv", blan_start_recv_task, function_instance, NTT)), error); + //npd->blan_recv_task->taskdebug = TRUE; + otg_task_start(npd->blan_recv_task); + #endif /* !defined(CONFIG_OTG_NETWORK_BLAN_CRC) && !defined(CONFIG_OTG_NETWORK_SAFE_CRC) */ + + return 0; + + CATCH(error) { + return -EINVAL; + } +} + +/*! blan_fd_function_disable - disable the function driver + * + * @param function_instance - pointer to function instance + * + * @return none + * + */ +void blan_fd_function_disable (struct usbd_function_instance *function_instance) +{ + struct usb_network_private *npd = function_instance->privdata; + + #if !defined(CONFIG_OTG_NETWORK_BLAN_CRC) && !defined(CONFIG_OTG_NETWORK_SAFE_CRC) + if (npd->blan_recv_task) { + otg_task_exit(npd->blan_recv_task); + npd->blan_recv_task = NULL; + } + #endif /* !defined(CONFIG_OTG_NETWORK_BLAN_CRC) && !defined(CONFIG_OTG_NETWORK_SAFE_CRC) */ + net_fd_function_disable(function_instance); +} + + +/*! + * blan_fd_set_configuration - called to indicate set configuration request was received + * @param function_instance + * @param configuration + * @return int + */ +int blan_fd_set_configuration (struct usbd_function_instance *function_instance, int configuration) +{ + struct usb_network_private *npd = function_instance->privdata; + int hs = usbd_high_speed(function_instance); + + //struct usbd_interface_instance *interface_instance = (struct usbd_interface_instance *)function_instance; + + TRACE_MSG2(NTT, "CONFIGURED: %d ip_addr: %08x", configuration, npd->ip_addr); + + //net_check_mesg(mesg_configured); + if (npd->eem_os_buffer) + net_os_dealloc_buffer(function_instance, npd->eem_os_data, npd->eem_os_buffer); + + npd->max_recv_urbs = hs ? NETWORK_START_URBS * 3 : NETWORK_START_URBS; + otg_atomic_clr(&npd->recv_urbs_started[0]); + #ifdef CONFIG_OTG_NETWORK_DOUBLE_OUT + otg_atomic_clr(&npd->recv_urbs_started[1]); + #endif /* CONFIG_OTG_NETWORK_DOUBLE_OUT */ + npd->eem_os_data = npd->eem_os_buffer = NULL; + //npd->flags |= NETWORK_CONFIGURED; + npd->altsetting = 0; + + if ((npd->flags & NETWORK_OPEN)) + net_os_send_notification_later(function_instance); + + TRACE_MSG1(NTT, "START RECV npd->flags: %04x", npd->flags); + + net_fd_start(function_instance); + + TRACE_MSG1(NTT, "CONFIGURED npd->flags: %04x", npd->flags); + return 0; +} + +/*! + * @brief blan_fd_reset - called to indicate bus has been reset + * @param function_instance + * @return int + */ +int blan_fd_reset (struct usbd_function_instance *function_instance) +{ + struct usb_network_private *npd = function_instance->privdata; + TRACE_MSG1(NTT,"RESET %08x",npd->ip_addr); + return net_fd_stop (function_instance); +} + + +/*! blan_fd_urb_received_ep0 - callback for sent URB + * + * Handles notification that an urb has been sent (successfully or otherwise). + * + * @return non-zero for failure. + */ +int blan_fd_urb_received_ep0 (struct usbd_urb *urb, int urb_rc) +{ + TRACE_MSG2(NTT,"urb: %p status: %d", urb, urb->status); + + RETURN_EINVAL_IF (USBD_URB_OK != urb->status); + + // TRACE_MSG1(NTT,"%s", urb->buffer); // QQSV is this really a NUL-terminated string??? + + return -EINVAL; // caller will de-allocate +} +/*! blan_fd_device_request + * @brief process a received SETUP URB + * + * Processes a received setup packet and CONTROL WRITE data. + * Results for a CONTROL READ are placed in urb->buffer. + * + * @param function_instance - pointer to this function instance + * @param request - received request to interface or endpoint + * @return non-zero for failure. + */ +int blan_fd_device_request (struct usbd_function_instance *function_instance, struct usbd_device_request *request) +{ + struct usb_network_private *npd = function_instance->privdata; + struct usbd_urb *urb; + int index; + + /* Verify that this is a USB Class request per CDC specification or a vendor request. + */ + RETURN_ZERO_IF (!(request->bmRequestType & (USB_REQ_TYPE_CLASS | USB_REQ_TYPE_VENDOR))); + + /* Determine the request direction and process accordingly + */ + switch (request->bmRequestType & (USB_REQ_DIRECTION_MASK | USB_REQ_TYPE_MASK)) { + + case USB_REQ_HOST2DEVICE | USB_REQ_TYPE_VENDOR: + + switch (request->bRequest) { + case MCCI_ENABLE_CRC: + //if (make_crc_table()) + // return -EINVAL; + //npd->encapsulation = simple_crc; + return 0; + + case BELCARRA_PING: + TRACE_MSG1(NTT,"H2D VENDOR IP: %08x", npd->ip_addr); + if ((npd->network_type == network_blan)) + net_os_send_notification_later(function_instance); + break; + + #if !defined(CONFIG_OTG_NETWORK_BLAN_DO_NOT_SETTIME) || !defined(CONFIG_OTG_NETWORK_SAFE_DO_NOT_SETTIME) + case BELCARRA_SETTIME: + npd->rfc868time = ntohl( request->wValue << 16 | request->wIndex); + net_os_settime(function_instance, npd->rfc868time); + break; + #endif + case BELCARRA_SETIP: + #ifdef OTG_BIG_ENDIAN + npd->ip_addr = ntohl( request->wValue | request->wIndex<< 16 ); + #else /* OTG_BIG_ENDIAN */ + npd->ip_addr = ntohl( request->wValue << 16 | request->wIndex); + #endif /* OTG_BIG_ENDIAN */ + TRACE_MSG1(NTT, "npd->ip_addr: %08x", npd->ip_addr); + // XXX need to get in correct order here + // XXX what about locally set mac addr + // XXX UNLESS(npd->local_dev_set) { + if (!(npd->override_MAC)) { + npd->local_dev_addr[0] = 0xfe | 0x02; /* locally administered */ + npd->local_dev_addr[1] = 0x00; + npd->local_dev_addr[2] = (npd->ip_addr >> 24) & 0xff; + npd->local_dev_addr[3] = (npd->ip_addr >> 16) & 0xff; + npd->local_dev_addr[4] = (npd->ip_addr >> 8) & 0xff; + npd->local_dev_addr[5] = (npd->ip_addr >> 0) & 0xff; + } + // XXX } + break; + + case BELCARRA_SETMSK: + #ifdef OTG_BIG_ENDIAN + npd->network_mask = ntohl( request->wValue | request->wIndex << 16); + #else /* OTG_BIG_ENDIAN */ + npd->network_mask = ntohl( request->wValue << 16 | request->wIndex); + #endif /* OTG_BIG_ENDIAN */ + TRACE_MSG1(NTT, "npd->network_mask: %08x", npd->network_mask); + break; + + case BELCARRA_SETROUTER: + #ifdef OTG_BIG_ENDIAN + npd->router_ip = ntohl( request->wValue | request->wIndex << 16); + #else /* OTG_BIG_ENDIAN */ + npd->router_ip = ntohl( request->wValue << 16 | request->wIndex); + #endif /* OTG_BIG_ENDIAN */ + TRACE_MSG1(NTT, "npd->router_ip: %08x", npd->router_ip); + break; + + case BELCARRA_SETDNS: + #ifdef OTG_BIG_ENDIAN + npd->dns_server_ip = ntohl( request->wValue | request->wIndex < 16); + #else /* OTG_BIG_ENDIAN */ + npd->dns_server_ip = ntohl( request->wValue << 16 | request->wIndex); + #endif /* OTG_BIG_ENDIAN */ + break; + #ifdef CONFIG_OTG_NETWORK_BLAN_FERMAT + case BELCARRA_SETFERMAT: + npd->fermat = 1; + break; + #endif + #ifdef CONFIG_OTG_NETWORK_BLAN_HOSTNAME + case BELCARRA_HOSTNAME: + TRACE_MSG0(NTT,"HOSTNAME"); + RETURN_EINVAL_IF(!(urb = usbd_alloc_urb_ep0(function_instance, le16_to_cpu(request->wLength), + blant_fd_urb_received_ep0) )); + + RETURN_ZERO_IF(!usbd_start_out_urb(urb)); // return if no error + usbd_free_urb(urb); // de-alloc if error + return -EINVAL; + #endif + #ifdef CONFIG_OTG_NETWORK_BLAN_DATA_NOTIFY_OK + case BELCARRA_DATA_NOTIFY: + TRACE_MSG0(NTT,"DATA NOTIFY"); + npd->data_notify = 1; + return -EINVAL; + #endif + } + switch (request->bRequest) { + case BELCARRA_SETIP: + case BELCARRA_SETMSK: + case BELCARRA_SETROUTER: + TRACE_MSG5(NTT, "npd->network_mask: %08x npd->ip_addr: %08x npd->router_ip: %08x flags: %08x %s", + npd->network_mask, npd->ip_addr, npd->router_ip, npd->flags, + (npd->flags & NETWORK_CONFIGURED) ? "CONFIGURED" : ""); + + BREAK_UNLESS (npd->network_mask && npd->ip_addr && npd->router_ip && (npd->flags & NETWORK_CONFIGURED)); + // let the os layer know, if it's interested. + #ifdef CONFIG_OTG_NETWORK_BLAN_AUTO_CONFIG + net_os_config(function_instance); + net_os_hotplug(function_instance); + #endif /* CONFIG_OTG_NETWORK_BLAN_AUTO_CONFIG */ + break; + } + return 0; + default: + break; + } + return -EINVAL; +} + +/* ********************************************************************************************* */ +/*! blan_fd_function_ops - operations table for network function driver + */ +struct usbd_function_operations blan_fd_function_ops = { + .function_enable = blan_fd_function_enable, + .function_disable = blan_fd_function_disable, + + .device_request = blan_fd_device_request, + .endpoint_cleared = net_fd_endpoint_cleared, + + .set_configuration = blan_fd_set_configuration, + .set_interface = NULL, + .reset = blan_fd_reset, + .suspended = net_fd_suspended, + .resumed = net_fd_resumed, +}; + +/*! function driver description + */ +struct usbd_interface_driver blan_interface_driver = { + .driver = { + .name = "net-blan-if", + .fops = &blan_fd_function_ops, }, + .interfaces = sizeof (blan_interfaces) / sizeof (struct usbd_interface_description), + .interface_list = blan_interfaces, + .endpointsRequested = ENDPOINTS, + .requestedEndpoints = net_fd_endpoint_requests, + + .bFunctionClass = USB_CLASS_COMM, + .bFunctionSubClass = COMMUNICATIONS_ENCM_SUBCLASS, + .bFunctionProtocol = COMMUNICATIONS_NO_PROTOCOL, + #if !defined(CONFIG_OTG_NETWORK_DOUBLE_IN) && !defined(CONFIG_OTG_NETWORK_DOUBLE_OUT) + .iFunction = "Belcarra USBLAN - MDLM/BLAN", + #elif defined(CONFIG_OTG_NETWORK_DOUBLE_IN) && !defined(CONFIG_OTG_NETWORK_DOUBLE_OUT) + .iFunction = "Belcarra USBLAN - MDLM/BLAN (DIN", + #elif !defined(CONFIG_OTG_NETWORK_DOUBLE_IN) && defined(CONFIG_OTG_NETWORK_DOUBLE_OUT) + .iFunction = "Belcarra USBLAN - MDLM/BLAN (DOUT)", + #elif defined(CONFIG_OTG_NETWORK_DOUBLE_IN) && defined(CONFIG_OTG_NETWORK_DOUBLE_OUT) + .iFunction = "Belcarra USBLAN - MDLM/BLAN (DIN/DOUT)", + #endif /* defined(CONFIG_OTG_NETWORK_DOUBLE_IN) && defined(CONFIG_OTG_NETWORK_DOUBLE_OUT) */ +}; + +/* ********************************************************************************************* */ + +/*! int blan_mod_init() + * @brief - set interface bmDataCapabilities, and register driver to initialize module + * @return int for driver register result + */ +int blan_mod_init (void) +{ + struct usbd_class_ethernet_networking_descriptor *ethernet; + struct usbd_class_network_channel_descriptor *channel; + + int len = 0; + char buf[255]; + + buf[0] = 0; + +#ifdef CONFIG_OTG_NETWORK_BLAN_FERMAT + TRACE_MSG0(NTT,"fermat"); + fermat_init(); +#endif + + //blan_alternate_descriptions[0].endpoints = Usb_network_private.have_interrupt ? 3 : 2; + +#if 0 + // Update the iMACAddress field in the ethernet descriptor + { +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,17) + char address_str[14]; + snprintf(address_str, 13, "%02x%02x%02x%02x%02x%02x", + local_dev_addr[0], local_dev_addr[1], local_dev_addr[2], + local_dev_addr[3], local_dev_addr[4], local_dev_addr[5]); +#else + char address_str[20]; + sprintf(address_str, "%02x%02x%02x%02x%02x%02x", + local_dev_addr[0], local_dev_addr[1], local_dev_addr[2], + local_dev_addr[3], local_dev_addr[4], local_dev_addr[5]); +#endif + TRACE_MSG0(NTT,"alloc mac string"); + //if ((ethernet = &blan_class_4)) + // ethernet->iMACAddress = usbd_alloc_string(function_instance, address_str); + TRACE_MSG0(NTT,"alloc mac string done"); + } + TRACE_MSG0(NTT,"alloc channel string"); + //if ((channel = &blan_class_5)) + // channel->iName = usbd_alloc_string(function_instance, system_utsname.nodename); + TRACE_MSG0(NTT,"alloc channel string done"); +#endif +#ifdef CONFIG_OTG_NETWORK_BLAN_PADBYTES + blan_class_3.bPad = CONFIG_OTG_NETWORK_BLAN_PADBYTES; + len += sprintf(buf + len, "PADBYTES: %02x ", blan_class_3.bPad); +#endif +#ifdef CONFIG_OTG_NETWORK_BLAN_PADBEFORE + blan_class_3.bmDataCapabilities |= BMDATA_PADBEFORE; + len += sprintf(buf + len, "PADBEFORE: %02x ", blan_class_3.bmDataCapabilities); +#endif +#ifdef CONFIG_OTG_NETWORK_BLAN_PADAFTER + blan_class_3.bmDataCapabilities |= BMDATA_PADAFTER; + len += sprintf(buf + len, "PADAFTER: %02x ", blan_class_3.bmDataCapabilities); +#endif +#ifdef CONFIG_OTG_NETWORK_BLAN_CRC + blan_class_3.bmDataCapabilities |= BMDATA_CRC; + len += sprintf(buf + len, "CRC: %02x ", blan_class_3.bmDataCapabilities); +#endif +#ifdef CONFIG_OTG_NETWORK_BLAN_FERMAT + blan_class_3.bmDataCapabilities |= BMDATA_FERMAT; + len += sprintf(buf + len, "FERMAT: %02x ", blan_class_3.bmDataCapabilities); +#endif +#ifdef CONFIG_OTG_NETWORK_BLAN_HOSTNAME + blan_class_3.bmDataCapabilities |= BMDATA_HOSTNAME; + len += sprintf(buf + len, "HOSTNAME: %02x ", blan_class_3.bmDataCapabilities); +#endif +#ifdef CONFIG_OTG_NETWORK_BLAN_NONBRIDGED + #ifdef CONFIG_OTG_NETWORK_BLAN_AUTO_CONFIG + #warning BLAN_AUTO_CONFIG ignored when BLAN_NONBRIDGED enabled + #endif /* CONFIG_OTG_NETWORK_BLAN_AUTO_CONFIG */ + blan_class_3.bmNetworkCapabilities |= BMNETWORK_NONBRIDGED | BMNETWORK_NO_VENDOR; + len += sprintf(buf + len, "NONBRIDGE: %02x ", blan_class_3.bmNetworkCapabilities); +#endif +#ifdef CONFIG_OTG_NETWORK_BLAN_DATA_NOTIFY_OK + blan_class_3.bmNetworkCapabilities |= BMNETWORK_DATA_NOTIFY_OK; + len += sprintf(buf + len, "DATA NOTIFY: %02x ", blan_class_3.bmNetworkCapabilities); +#endif +#ifndef CONFIG_OTG_NETWORK_BLAN_AUTO_CONFIG + blan_class_3.bmNetworkCapabilities |= BMNETWORK_NO_VENDOR; + len += sprintf(buf + len, "NO VENDOR: %02x ", blan_class_3.bmNetworkCapabilities); +#endif + if (strlen(buf)) + printk(KERN_INFO"%s: %s\n", __FUNCTION__, buf); + + return usbd_register_interface_function (&blan_interface_driver, "net-blan-if", NULL); +} + +/*! + * @brief blan_mod_exit + * @return none + */ +void blan_mod_exit(void) +{ + usbd_deregister_interface_function (&blan_interface_driver); +} + +#else /* CONFIG_OTG_NETWORK_BLAN */ +/*! + * @brief blan_mod_init + * @return int + */ +int blan_mod_init (void) +{ + return 0; +} +/*! + * @brief blan_mod_exit + * @return none + */ +void blan_mod_exit(void) +{ +} +#endif /* CONFIG_OTG_NETWORK_BLAN */ diff --git a/drivers/otg/functions/network/ecm-if.c b/drivers/otg/functions/network/ecm-if.c new file mode 100644 index 000000000000..d7ce6a3ee61d --- /dev/null +++ b/drivers/otg/functions/network/ecm-if.c @@ -0,0 +1,652 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/functions/network/ecm.c - Network Function Driver + * @(#) balden@belcarra.com|otg/functions/network/ecm-if.c|20070326205353|49276 + * + * Copyright (c) 2002-2006 Belcarra Technologies Corp + * Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + * + * By: + * Chris Lynne <cl@belcarra.com> + * Stuart Lynne <sl@belcarra.com> + * Bruce Balden <balden@belcarra.com> + * + */ +/*! + * @file otg/functions/network/ecm-if.c + * @brief This file implements the required descriptors to implement + * a basic network device with two interfaces. + * + * The CDC ECM network driver implements the CDC Network Configuration. Two + * interfaces with two BULK data endpoints and an INTERRUPT endpoint. + * + * @ingroup NetworkFunction + */ + + +#include <otg/otg-compat.h> +#include <otg/otg-module.h> + +#include <otg/usbp-chap9.h> +#include <otg/usbp-cdc.h> +#include <otg/usbp-func.h> + +#include "network.h" +#include "net-os.h" + +#define NTT network_fd_trace_tag + +#if defined(CONFIG_OTG_NETWORK_ECM) || defined(_OTG_DOXYGEN) + +/* USB CDC Configuration ******************************************************************** */ + +/*! @name CDC ECM descriptors group + * + * @{ + */ +static struct usbd_class_header_function_descriptor ecm_class_1 = { + bFunctionLength: 0x05, + bDescriptorType: USB_DT_CLASS_SPECIFIC, + bDescriptorSubtype: USB_ST_HEADER, + bcdCDC: __constant_cpu_to_le16(0x0110), +}; + + +static struct usbd_class_ethernet_networking_descriptor ecm_class_2 = { + bFunctionLength: 0x0D, + bDescriptorType: USB_DT_CLASS_SPECIFIC, + bDescriptorSubtype: USB_ST_ENF, + iMACAddress: 0x00, + bmEthernetStatistics: 0x00, + wMaxSegmentSize: __constant_cpu_to_le16(0x05ea), + wNumberMCFilters: 0, + bNumberPowerFilters: 0, +}; + +static struct usbd_class_union_function_descriptor ecm_class_3 = +{ + bFunctionLength: 0x05, + bDescriptorType: USB_DT_CLASS_SPECIFIC, + bDescriptorSubtype: USB_ST_UF, + bMasterInterface: 0x00, + bSlaveInterface: { 1 }, // This will be updated + +}; + +static usbd_class_descriptor_t *ecm_comm_class_descriptors[] = { + (struct usbd_generic_class_descriptor *) &ecm_class_1, + (struct usbd_generic_class_descriptor *) &ecm_class_2, + (struct usbd_generic_class_descriptor *) &ecm_class_3, +}; + +/*! + * @} + */ + +/*!@name Data Alternate Interface Description List + * + * @{ + */ +static struct usbd_alternate_description ecm_comm_alternate_descriptions[] = { + { + .iInterface = "Belcarra USBLAN - CDC/ECM", + .bInterfaceClass = USB_CLASS_COMM, + .bInterfaceSubClass = COMMUNICATIONS_ENCM_SUBCLASS, + .bInterfaceProtocol = 0, + .classes = sizeof (ecm_comm_class_descriptors) / sizeof (struct usbd_generic_class_descriptor *), + .class_list = ecm_comm_class_descriptors, + .endpoints = 1, + .endpoint_index = cdc_int_endpoint_index, + }, +}; + +static struct usbd_alternate_description ecm_data_alternate_descriptions[] = { + { + .iInterface = "Belcarra USBLAN - CDC/ECM No Data", + .bInterfaceClass = USB_CLASS_DATA, // XXX + .bInterfaceSubClass = 0, // XXX + .bInterfaceProtocol = 0, // XXX + }, + { + .iInterface = "Belcarra USBLAN - CDC/ECM Data", + .bInterfaceClass = USB_CLASS_DATA, // XXX + .bInterfaceSubClass = 0x0, // XXX + .bInterfaceProtocol = 0x0, // XXX + .endpoints = 2, + .endpoint_index = cdc_data_endpoint_index, + }, +}; + +/*! + * @} + */ + + +/* CDC Interface descriptions and descriptors + */ +/*! Interface Description List + */ +struct usbd_interface_description ecm_interfaces[] = { + { + .alternates = sizeof (ecm_comm_alternate_descriptions) / sizeof (struct usbd_alternate_description), + .alternate_list = ecm_comm_alternate_descriptions, + }, + { + .alternates = sizeof (ecm_data_alternate_descriptions) / sizeof (struct usbd_alternate_description), + .alternate_list = ecm_data_alternate_descriptions, + }, +}; + +/* ********************************************************************************************* */ +/*! net_fd_start_xmit_nocrc + * @brief - start sending a buffer + * + * @param function_instance - pointer to this function instance + * @param buffer buffer containing data to send + * @param len - length of data to send + * @param data instance data + * @return: 0 if all OK + * -EINVAL, -EUNATCH, -ENOMEM + * rc from usbd_start_in_urb() if that fails (is != 0, may be one of err values above) + * Note: -ECOMM is interpreted by calling routine as signal to leave IF stopped. + */ +int net_fd_start_xmit_nocrc (struct usbd_function_instance *function_instance, u8 *buffer, int len, void *data) +{ + struct usb_network_private *npd = function_instance->privdata; + struct usbd_urb *urb = NULL; + int rc; + int in_pkt_sz; + u8 *cp; + u32 crc; + #ifndef CONFIG_OTG_NETWORK_DOUBLE_IN + int xmit_index = 0; + int endpoint_index = BULK_IN_A; + #else /* CONFIG_OTG_NETWORK_DOUBLE_IN */ + int xmit_index = (otg_atomic_read(&npd->xmit_urbs_started[0]) <= otg_atomic_read(&npd->xmit_urbs_started[1])) + ? 0 : 1; + int endpoint_index = (xmit_index) ? BULK_IN_B : BULK_IN_A; + #endif /* CONFIG_OTG_NETWORK_DOUBLE_IN */ + + TRACE_MSG7(NTT,"npd: %p function: %p flags: %04x len: %d endpoint_index: %d xmit_started: %d %d", + npd, function_instance, npd->flags, len, endpoint_index, + otg_atomic_read(&npd->xmit_urbs_started[0]), otg_atomic_read(&npd->xmit_urbs_started[1])); + + in_pkt_sz = usbd_endpoint_wMaxPacketSize(function_instance, endpoint_index, usbd_high_speed(function_instance)); + + /* allocate urb 5 bytes larger than required */ + if (!(urb = usbd_alloc_urb (function_instance, endpoint_index, len + 5 + in_pkt_sz, net_fd_urb_sent_bulk ))) { + u8 epa = usbd_endpoint_bEndpointAddress(function_instance, endpoint_index, usbd_high_speed(function_instance)); + TRACE_MSG2(NTT,"urb alloc failed len: %d endpoint: %02x", len, epa); + printk(KERN_ERR"%s: urb alloc failed len: %d endpoint: %02x\n", __FUNCTION__, len, epa); + return -ENOMEM; + } + + urb->actual_length = len; + + memcpy (urb->buffer, buffer, len); + urb->function_privdata = data; + urb->actual_length = len; + otg_atomic_add(urb->actual_length, &npd->queued_bytes); + otg_atomic_inc(&npd->xmit_urbs_started[xmit_index]); + if ((rc = usbd_start_in_urb (urb))) { + TRACE_MSG1(NTT,"FAILED: %d", rc); + printk(KERN_ERR"%s: FAILED: %d\n", __FUNCTION__, rc); + urb->function_privdata = NULL; + otg_atomic_sub(urb->actual_length, &npd->queued_bytes); + otg_atomic_dec(&npd->xmit_urbs_started[xmit_index]); + usbd_free_urb (urb); + return rc; + } + return 0; +} + +/*! net_fd_start_xmit_ecm + * @brief - start sending to host + * @param function_instance + * @param buffer data storage area + * @param len - length of data to transmit + * @param data - instance private data + * + * @return: 0 if all OK + * -EINVAL, -EUNATCH, -ENOMEM + * rc from usbd_start_in_urb() if that fails (is != 0, may be one of err values above) + * Note: -ECOMM is interpreted by calling routine as signal to leave IF stopped. + */ +int net_fd_start_xmit_ecm (struct usbd_function_instance *function_instance, u8 *buffer, int len, void *data) +{ + struct usb_network_private *npd = function_instance->privdata; + struct usbd_urb *urb = NULL; + int rc; + int in_pkt_sz; + u32 crc; + //#ifndef CONFIG_OTG_NETWORK_DOUBLE_IN + int xmit_index = 0; + int endpoint_index = BULK_IN_A; + //#else /* CONFIG_OTG_NETWORK_DOUBLE_IN */ + //int xmit_index = (otg_atomic_read(&npd->xmit_urbs_started[0]) <= otg_atomic_read(&npd->xmit_urbs_started[1])) + // ? 0 : 1; + //int endpoint_index = (xmit_index) ? BULK_IN_B : BULK_IN_A; + //#endif /* CONFIG_OTG_NETWORK_DOUBLE_IN */ + + TRACE_MSG7(NTT,"npd: %p function: %p flags: %04x len: %d endpoint_index: %d xmit_started: %d %d", + npd, function_instance, npd->flags, len, endpoint_index, + otg_atomic_read(&npd->xmit_urbs_started[0]), otg_atomic_read(&npd->xmit_urbs_started[1])); + + in_pkt_sz = usbd_endpoint_wMaxPacketSize(function_instance, endpoint_index, usbd_high_speed(function_instance)); + + //TRACE_MSG0(NTT,"SIMPLE_CRC"); + // allocate urb 5 bytes larger than required + if (!(urb = usbd_alloc_urb (function_instance, endpoint_index, len + 5 + 4 + in_pkt_sz, net_fd_urb_sent_bulk ))) { + u8 epa = usbd_endpoint_bEndpointAddress(function_instance, endpoint_index, usbd_high_speed(function_instance)); + TRACE_MSG2(NTT,"urb alloc failed len: %d endpoint: %02x", len, epa); + printk(KERN_ERR"%s: urb alloc failed len: %d endpoint: %02x\n", __FUNCTION__, len, epa); + return -ENOMEM; + } + urb->actual_length = len; + + #if defined(CONFIG_OTG_NETWORK_BLAN_CRC) || defined(CONFIG_OTG_NETWORK_SAFE_CRC) + + // copy and crc len bytes + crc = crc32_copy(urb->buffer, buffer, len, CRC32_INIT); + + if ((urb->actual_length % in_pkt_sz) == (in_pkt_sz - 4)) { + + #undef CONFIG_OTG_NETWORK_PADBYTE + #ifdef CONFIG_OTG_NETWORK_PADBYTE + // add a pad byte if required to ensure a short packet, usbdnet driver + // will correctly handle pad byte before or after CRC, but the MCCI driver + // wants it before the CRC. + crc = crc32_pad(urb->buffer + urb->actual_length, 1, crc); + urb->actual_length++; + #else /* CONFIG_OTG_NETWORK_PADBYTE */ + urb->flags |= USBD_URB_SENDZLP; + TRACE_MSG2(NTT,"setting ZLP: urb: %p flags: %x", urb, urb->flags); + #endif /* CONFIG_OTG_NETWORK_PADBYTE */ + } + crc = ~crc; + urb->buffer[urb->actual_length++] = crc & 0xff; + urb->buffer[urb->actual_length++] = (crc >> 8) & 0xff; + urb->buffer[urb->actual_length++] = (crc >> 16) & 0xff; + urb->buffer[urb->actual_length++] = (crc >> 24) & 0xff; + #if defined(CONFIG_OTG_NETWORK_BLAN_FERMAT) + if (npd->fermat) fermat_encode(urb->buffer, urb->actual_length); + #endif + + #else /* defined(CONFIG_OTG_NETWORK_BLAN_CRC) ...*/ + + memcpy (urb->buffer, buffer, len); + if ((urb->actual_length % in_pkt_sz) == (in_pkt_sz)) { + /* no longer in Kconfig - change undef to define to active padbyte */ + #undef CONFIG_OTG_NETWORK_PADBYTE + #ifdef CONFIG_OTG_NETWORK_PADBYTE + urb->actual_length++; + #else /* CONFIG_OTG_NETWORK_PADBYTE */ + urb->flags |= USBD_URB_SENDZLP; + TRACE_MSG2(NTT,"setting ZLP: urb: %p flags: %x", urb, urb->flags); + #endif /* CONFIG_OTG_NETWORK_PADBYTE */ + } + + + #endif /* defined(CONFIG_OTG_NETWORK_BLAN_CRC) ...*/ + + //TRACE_MSG3(NTT,"urb: %p buf: %p priv: %p", urb, data, urb->function_privdata); + urb->function_privdata = data; + + otg_atomic_add(urb->actual_length, &npd->queued_bytes); + otg_atomic_inc(&npd->xmit_urbs_started[xmit_index]); + if ((rc = usbd_start_in_urb (urb))) { + + TRACE_MSG1(NTT,"FAILED: %d", rc); + printk(KERN_ERR"%s: FAILED: %d\n", __FUNCTION__, rc); + urb->function_privdata = NULL; + otg_atomic_sub(urb->actual_length, &npd->queued_bytes); + otg_atomic_dec(&npd->xmit_urbs_started[xmit_index]); + usbd_free_urb (urb); + + return rc; + } + return 0; +} + +/*! net_fd_recv_urb_ecm + * @brief - callback to process a received URB + * + * @param urb - received urb + * @param rc dummy + * @return non-zero for failure. + */ +int net_fd_recv_urb_ecm(struct usbd_urb *urb, int rc) +{ + struct usbd_function_instance *function_instance = urb->function_instance; + struct usb_network_private *npd = function_instance->privdata; + void *os_data = NULL; + u8 *os_buffer, *net_frame; + int crc_bad = 0; + int trim = 0; + int len; + u32 crc; + #ifndef CONFIG_OTG_NETWORK_DOUBLE_OUT + int endpoint_index = BULK_OUT_A; + #else /* CONFIG_OTG_NETWORK_DOUBLE_OUT */ + int endpoint_index = BULK_OUT_A; + #endif /* CONFIG_OTG_NETWORK_DOUBLE_OUT */ + int out_pkt_sz = usbd_endpoint_wMaxPacketSize(function_instance, endpoint_index, usbd_high_speed(function_instance)); + len = urb->actual_length; + trim = 0; + + TRACE_MSG2(NTT, "status: %d actual_length: %d", urb->status, urb->actual_length); + //RETURN_EINVAL_IF (urb->status == USBD_URB_OK); + + THROW_UNLESS((os_data = net_os_alloc_buffer(function_instance, &os_buffer, len)), error); + net_frame = os_buffer; + + //TRACE_MSG2(NTT, "os_data: %x os_buffer: %x", os_data, os_buffer); + + + /* + * The CRC can be sent in two ways when the size of the transfer + * ends up being a multiple of the packetsize: + * + * | + * <data> <CRC><CRC><CRC><CRC>|<???> case 1 + * <data> <NUL><CRC><CRC><CRC>|<CRC> case 2 + * <data> <NUL><CRC><CRC><CRC><CRC>| case 3 + * <data> <NUL><CRC><CRC><CRC>|<CRC> | case 4 + * | + * + * This complicates CRC checking, there are four scenarios: + * + * 1. length is 1 more than multiple of packetsize with a trailing byte + * 2. length is 1 more than multiple of packetsize + * 3. length is multiple of packetsize + * 4. none of the above + * + * Finally, even though we always compute CRC, we do not actually throw + * things away until and unless we have previously seen a good CRC. + * This allows backwards compatibility with hosts that do not support + * adding a CRC to the frame. + * + */ + + // test if 1 more than packetsize multiple + if (1 == (len % out_pkt_sz)) { + #if defined(CONFIG_OTG_NETWORK_BLAN_CRC) || defined(CONFIG_OTG_NETWORK_SAFE_CRC) + // copy and CRC up to the packetsize boundary + crc = crc32_copy(os_buffer, urb->buffer, len - 1, CRC32_INIT); + os_buffer += len - 1; + + // if the CRC is good then this is case 1 + if (CRC32_GOOD != crc) { + + crc = crc32_copy(os_buffer, urb->buffer + len - 1, 1, crc); + os_buffer += 1; + + if (CRC32_GOOD != crc) { + //crc_errors[len%64]++; + TRACE_MSG2(NTT,"A CRC error %08x %03x", crc, urb->framenum); + THROW_IF(npd->seen_crc, crc_error); + } + else + npd->seen_crc = 1; + } + else + npd->seen_crc = 1; + + #endif /* defined(CONFIG_OTG_NETWORK_BLAN_CRC) ...*/ + } + else { + + #if defined(CONFIG_OTG_NETWORK_BLAN_CRC) || defined(CONFIG_OTG_NETWORK_SAFE_CRC) + crc = crc32_copy(os_buffer, urb->buffer, len, CRC32_INIT); + os_buffer += len; + + if (CRC32_GOOD != crc) { + //crc_errors[len%64]++; + TRACE_MSG2(NTT,"B CRC error %08x %03x", crc, urb->framenum); + THROW_IF(npd->seen_crc, crc_error); + } + else + npd->seen_crc = 1; + #else /* !defined(CONFIG_OTG_NETWORK_BLAN_CRC) ...*/ + memcpy (os_buffer, urb->buffer, len); + #endif /* !defined(CONFIG_OTG_NETWORK_BLAN_CRC) ...*/ + } + // trim IFF we are paying attention to crc + #if defined(CONFIG_OTG_NETWORK_BLAN_CRC) || defined(CONFIG_OTG_NETWORK_SAFE_CRC) + if (npd->seen_crc) + trim = 4; + #endif /* defined(CONFIG_OTG_NETWORK_BLAN_CRC) ...*/ + + + if (net_fd_recv_buffer(function_instance, net_frame, len, os_data, crc_bad, trim)) { + TRACE_MSG0(NTT, "FAILED"); + net_os_dealloc_buffer(function_instance, os_data, os_buffer); + } + + // catch a simple error, just increment missed error and general error + CATCH(error) { + //TRACE_MSG4(NTT,"CATCH(error) urb: %p status: %d len: %d function: %p", + // urb, urb->status, urb->actual_length, function_instance); + // catch a CRC error + CATCH(crc_error) { + crc_bad = 1; + } + } + return 0; + //return usbd_start_out_urb (urb); +} + + + +/* ********************************************************************************************* */ +#ifdef CONFIG_OTG_NETWORK_START_SINGLE +#define NETWORK_START_URBS 1 +#else +#define NETWORK_START_URBS 2 +#endif + +/*! net_fd_start_recv_ecm - start recv urb(s) + * + * @param function_instance - pointer to this function instance + * @return none + */ +int net_fd_start_recv_ecm(struct usbd_function_instance *function_instance) +{ + struct usb_network_private *npd = function_instance->privdata; + int i; + int network_start_urbs = NETWORK_START_URBS; + int hs = usbd_high_speed(function_instance); + int endpoint_index = BULK_OUT_A; + + if (hs) + network_start_urbs = NETWORK_START_URBS * 3; + + + for (i = 0; i < network_start_urbs; i++) { + struct usbd_urb *urb; + BREAK_IF(!(urb = usbd_alloc_urb(function_instance, endpoint_index, + usbd_endpoint_transferSize(function_instance, endpoint_index, hs), + net_fd_recv_urb))); + TRACE_MSG5(NTT,"i: %d urb: %p priv: %p npd: %p function: %p", + i, urb, urb->function_privdata, npd, function_instance); + + //urb->function_privdata = npd; + if (usbd_start_out_urb(urb)) { + urb->function_privdata = NULL; + usbd_free_urb(urb); + } + } + return 0; +} +/* ********************************************************************************************* */ + +/*! ecm_fd_function_enable + * @brief - enable the function driver + * + * Called for usbd_function_enable() from usbd_register_device() + * + * @param function_instance - function instance pointer + * @return int - 0 for success + */ + +int ecm_fd_function_enable (struct usbd_function_instance *function_instance) +{ + struct usb_network_private *npd; + struct usbd_class_ethernet_networking_descriptor *ethernet = &ecm_class_2; + + char address_str[14]; + + /* do normal network function enable first - this creates network private data */ + RETURN_EINVAL_IF(net_fd_function_enable(function_instance, network_ecm, + net_fd_recv_urb_ecm, + net_fd_start_xmit_ecm, + net_fd_start_recv_ecm, 0 + )); + + npd = function_instance->privdata; + + /* if successful then lets allocate iMACAddress string */ + + TRACE_MSG6(NTT, "local: %02x:%02x:%02x:%02x:%02x:%02x", + npd->local_dev_addr[0], npd->local_dev_addr[1], npd->local_dev_addr[2], + npd->local_dev_addr[3], npd->local_dev_addr[4], npd->local_dev_addr[5]); + + TRACE_MSG6(NTT, "remote: %02x:%02x:%02x:%02x:%02x:%02x", + npd->remote_dev_addr[0], npd->remote_dev_addr[1], npd->remote_dev_addr[2], + npd->remote_dev_addr[3], npd->remote_dev_addr[4], npd->remote_dev_addr[5]); + + snprintf(address_str, 13, "%02x%02x%02x%02x%02x%02x", + npd->remote_dev_addr[0], npd->remote_dev_addr[1], npd->remote_dev_addr[2], + npd->remote_dev_addr[3], npd->remote_dev_addr[4], npd->remote_dev_addr[5]); + + ethernet->iMACAddress = usbd_alloc_string(function_instance, address_str); + + return 0; +} + + +/*! ecm_fd_set_configuration + * @brief - called to indicate set configuration request was received + * @param function_instance + * @param configuration + * @return int + */ +int ecm_fd_set_configuration (struct usbd_function_instance *function_instance, int configuration) +{ + struct usb_network_private *npd = function_instance->privdata; + + //struct usbd_interface_instance *interface_instance = (struct usbd_interface_instance *)function_instance; + //net_check_mesg(mesg_configured); + if (npd->eem_os_buffer) + net_os_dealloc_buffer(function_instance, npd->eem_os_data, npd->eem_os_buffer); + + npd->eem_os_data = npd->eem_os_buffer = NULL; + //npd->flags |= NETWORK_CONFIGURED; + npd->altsetting = 0; + TRACE_MSG3(NTT, "CONFIGURED: %d ipaddr: %08x flags: %04x", configuration, npd->ip_addr, npd->flags); + return 0; +} + + + +/*!ecm_fd_set_interface + * + * @brief called to indicate set interface request was received + * @param function_instance + * @param wIndex + * @param altsetting + * @return int + */ +int ecm_fd_set_interface (struct usbd_function_instance *function_instance, int wIndex, int altsetting) +{ + struct usb_network_private *npd = function_instance->privdata; + + TRACE_MSG2(NTT, "SET INTERFACE[%02x] altsetting: %02x", wIndex, altsetting); + npd->altsetting = altsetting; + TRACE_MSG3(NTT, "ipaddr: %08x flags: %04x altsetting: %02x", + npd->ip_addr, npd->flags, npd->altsetting); + return (altsetting ? net_fd_start : net_fd_stop) (function_instance); +} + + + +/* ********************************************************************************************* */ +/*! ecm_fd_function_ops - operations table for network function driver + */ +struct usbd_function_operations ecm_fd_function_ops = { + .function_enable = ecm_fd_function_enable, + .function_disable = net_fd_function_disable, + + .device_request = net_fd_device_request, + .endpoint_cleared = net_fd_endpoint_cleared, + + .set_configuration = ecm_fd_set_configuration, + .set_interface = ecm_fd_set_interface, + .reset = net_fd_reset, + .suspended = net_fd_suspended, + .resumed = net_fd_resumed, +}; + + +/*! function driver description + */ +struct usbd_interface_driver ecm_interface_driver = { + .driver = { + .name = "cdc-ecm-if", + .fops = &ecm_fd_function_ops, }, + .interfaces = sizeof (ecm_interfaces) / sizeof (struct usbd_interface_description), + .interface_list = ecm_interfaces, + .endpointsRequested = ENDPOINTS, + .requestedEndpoints = net_fd_endpoint_requests, + + .bFunctionClass = USB_CLASS_COMM, + .bFunctionSubClass = COMMUNICATIONS_ENCM_SUBCLASS, + .bFunctionProtocol = COMMUNICATIONS_NO_PROTOCOL, + .iFunction = "Belcarra USBLAN - CDC/ECM", +}; + +/* ********************************************************************************************* */ + +/*! emc_mod_init + * @brief initialize ecm interface module + * @return int + */ +int ecm_mod_init (void) +{ + return usbd_register_interface_function (&ecm_interface_driver, "cdc-ecm-if", NULL); +} +/*! ecm_mod_exit + * @brief cleanup system resource + * @return void + */ + +void ecm_mod_exit(void) +{ + usbd_deregister_interface_function (&ecm_interface_driver); +} + +#else /* CONFIG_OTG_NETWORK_ECM */ +/*! + * @brief ecm_mod_init + * @return int + */ +int ecm_mod_init (void) +{ + return 0; +} + +void ecm_mod_exit(void) +{ +} +#endif /* CONFIG_OTG_NETWORK_ECM */ diff --git a/drivers/otg/functions/network/eem-if.c b/drivers/otg/functions/network/eem-if.c new file mode 100644 index 000000000000..d0195043c7f1 --- /dev/null +++ b/drivers/otg/functions/network/eem-if.c @@ -0,0 +1,835 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/functions/network/eem.c - Network Function Driver + * @(#) sl@belcarra.com/whiskey.enposte.net|otg/functions/network/eem-if.c|20070315225839|19712 + * + * Copyright (c) 2005-2006 Belcarra Technologies Corp + * Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + * + * By: + * Chris Lynne <cl@belcarra.com> + * Stuart Lynne <sl@belcarra.com> + * Bruce Balden <balden@belcarra.com> + * + */ +/*! + * @file otg/functions/network/eem-if.c + * @brief This file implements the required descriptors to implement + * a eem network device with a single interface. + * + * The EEM network driver implements a very simple descriptor set, + * a single interface with two BULK data endpoints. + * + * The EEM protocol implements a different data encapsulation than + * the other procotols (CDC ECM, MDLM-BLAN etc.) which can support + * data streaming. + * + * @ingroup NetworkFunction + */ + + +#include <otg/otg-compat.h> +#include <otg/otg-module.h> + +#include <otg/usbp-chap9.h> +#include <otg/usbp-cdc.h> +#include <otg/usbp-func.h> + +#include "network.h" +#include "net-os.h" + +#define NTT network_fd_trace_tag + +#if defined(CONFIG_OTG_NETWORK_EEM) || defined(_OTG_DOXYGEN) + +/* USB EEM Configuration ******************************************************************** */ + +/*! Data Alternate Interface Description List + */ +static struct usbd_alternate_description eem_data_alternate_descriptions[] = { + { + .iInterface = "Belcarra - CDC/EEM", + .bInterfaceClass = USB_CLASS_COMM, + .bInterfaceSubClass = COMMUNICATIONS_EEM_SUBCLASS, + .bInterfaceProtocol = COMMUNICATIONS_EEM_PROTOCOL, + .endpoints = 2, + .endpoint_index = cdc_data_endpoint_index, + }, +}; + + +/* EEM Interface descriptions and descriptors + */ +/*! Interface Description List + */ +struct usbd_interface_description eem_interfaces[] = { + { + .alternates = sizeof (eem_data_alternate_descriptions) / sizeof (struct usbd_alternate_description), + .alternate_list = eem_data_alternate_descriptions, + }, +}; + + +/* ********************************************************************************************* */ + +/*! net_fd_urb_sent_zle - callback for completed BULK ZLE URB + * + * Handles notification that an ZLE urb has been sent (successfully or otherwise). + * + * @param urb Pointer to the urb that has been sent. + * @param urb_rc Result code from the send operation. + * + * @return non-zero for failure. + */ +int net_fd_urb_sent_zle (struct usbd_urb *urb, int urb_rc) +{ + struct usbd_function_instance *function_instance = urb->function_instance; + struct usb_network_private *npd = function_instance->privdata; + unsigned long flags; + void *buf; + int rc = -EINVAL; + int in_pkt_sz; + + TRACE_MSG6(NTT,"urb: %p urb_rc: %d length: %d queue: %d avg: %d samples: %d", + urb, urb_rc, urb->actual_length, otg_atomic_read(&npd->queued_frames), + npd->avg_queue_frames / npd->samples, npd->samples); + + usbd_free_urb (urb); + rc = 0; + return rc; +} + +/*! net_fd_urb_sent_eem_bulk - callback for completed BULK xmit URB + * + * Handles notification that an urb has been sent (successfully or otherwise). + * + * @param urb Pointer to the urb that has been sent. + * @param urb_rc Result code from the send operation. + * + * @return non-zero for failure. + */ +int net_fd_urb_sent_eem_bulk (struct usbd_urb *urb, int urb_rc) +{ + struct usbd_function_instance *function_instance = urb->function_instance; + struct usb_network_private *npd = function_instance->privdata; + int rc; + int in_pkt_sz; + int endpoint_index = BULK_IN_A; + + rc = net_fd_urb_sent_bulk(urb, urb_rc); + + #ifdef CONFIG_OTG_NETWORK_EEM_STREAM + /* see if we should send ZLE */ + if (otg_atomic_read(&npd->queued_frames) || (npd->network_type != network_eem)) + return rc; + + /* Nothing left to send so stop the host transfer by sending a ZLE */ + UNLESS((usbd_get_device_status(function_instance) == USBD_OK)) + return rc; + + in_pkt_sz = usbd_endpoint_wMaxPacketSize(function_instance, endpoint_index, usbd_high_speed(function_instance)); + if ((urb = usbd_alloc_urb (function_instance, endpoint_index, in_pkt_sz, net_fd_urb_sent_zle ))) { + npd->eem_send_zle_data = 0; + #ifdef CONFIG_OTG_NETWORK_EEM_ZLE + urb->actual_length = 2 + (npd->eem_send_zle_data ? 1 : 0); + TRACE_MSG2(NTT, "Sending ZLE: length: %d %d", urb->actual_length, npd->eem_send_zle_data); + urb->buffer[0] = 0; + urb->buffer[1] = 0; + urb->buffer[3] = 0; + #else /* CONFIG_OTG_NETWORK_EEM_ZLE */ + urb->actual_length = 0; + TRACE_MSG2(NTT, "Sending ZLP: length: %d %d", urb->actual_length, npd->eem_send_zle_data); + urb->flags |= USBD_URB_SENDZLP; + #endif /* CONFIG_OTG_NETWORK_EEM_ZLE */ + + if ((rc = usbd_start_in_urb (urb))) { + TRACE_MSG1(NTT,"FAILED: %d", rc); + printk(KERN_ERR"%s: FAILED: %d\n", __FUNCTION__, rc); + urb->function_privdata = NULL; + usbd_free_urb (urb); + } + } + #endif /* CONFIG_OTG_NETWORK_EEM_STREAM */ + return rc; +} + +/*! net_fd_urb_sent_eem_echo - callback for completed BULK xmit URB + * + * Handles notification that an urb has been sent (successfully or otherwise). + * + * @param urb Pointer to the urb that has been sent. + * @param urb_rc Result code from the send operation. + * + * @return non-zero for failure. + */ +int net_fd_urb_sent_eem_echo (struct usbd_urb *urb, int urb_rc) +{ + struct usbd_function_instance *function_instance = urb->function_instance; + struct usb_network_private *npd = function_instance->privdata; + int in_pkt_sz; + unsigned long flags; + + TRACE_MSG2(NTT, "urb: %x rc: %d", urb, urb_rc); + usbd_free_urb (urb); + + return 0; +} + +/*! net_fd_start_xmit_eem + * @brief start sending data, Called to send a network frame via EEM. + + * + * @param function_instance - function instance pointer + * @param buffer - pointer to data to send + * @param len - length of buffer + * @param data - instance private data + * @return: 0 if all OK + * -EINVAL, -EUNATCH, -ENOMEM + * rc from usbd_start_in_urb() if that fails (is != 0, may be one of err values above) + * Note: -ECOMM is interpreted by calling routine as signal to leave IF stopped. + */ +int net_fd_start_xmit_eem (struct usbd_function_instance *function_instance, u8 *buffer, int len, void *data) +{ + struct usb_network_private *npd = function_instance->privdata; + struct usbd_urb *urb = NULL; + int rc; + int in_pkt_sz; + u8 *cp; + u32 crc = 0; + int length; + #ifndef CONFIG_OTG_NETWORK_DOUBLE_IN + int xmit_index = 0; + int endpoint_index = BULK_IN_A; + #else /* CONFIG_OTG_NETWORK_DOUBLE_IN */ + int xmit_index = (otg_atomic_read(&npd->xmit_urbs_started[0]) <= otg_atomic_read(&npd->xmit_urbs_started[1])) + ? 0 : 1; + int endpoint_index = (xmit_index) ? BULK_IN_B : BULK_IN_A; + #endif /* CONFIG_OTG_NETWORK_DOUBLE_IN */ + + in_pkt_sz = usbd_endpoint_wMaxPacketSize(function_instance, endpoint_index, usbd_high_speed(function_instance)); + + /* allocate urb 5 bytes larger than required */ + length = len + 5 + 4 + in_pkt_sz; + if (!(urb = usbd_alloc_urb (function_instance, endpoint_index, length, net_fd_urb_sent_eem_bulk ))) + { + u8 epa = usbd_endpoint_bEndpointAddress(function_instance, endpoint_index, usbd_high_speed(function_instance)); + TRACE_MSG2(NTT,"urb alloc failed len: %d endpoint: %02x", len, epa); + printk(KERN_ERR"%s: urb alloc failed len: %d endpoint: %02x\n", __FUNCTION__, len, epa); + return -ENOMEM; + } + memset(urb->buffer, 0, length); + urb->function_privdata = data; + + /* if EEM packet is multiple of packetsize we may need to append ZLE (two NULL bytes) + */ + urb->actual_length = len + 2 + (npd->eem_send_zle_data ? 1 : 0) + 4; + cp = npd->eem_send_zle_data ? urb->buffer + 1 : urb->buffer; + npd->eem_send_zle_data = FALSE; + + /* EEM packet header - set bmCRC if EEM CRC is enabled */ + #if defined(CONFIG_OTG_NETWORK_EEM_CRC) + /* little endian */ + cp[0] = (len + 4) & 0xff; + cp[1] = 0x40 | (((len + 4) >> 8) & 0x3f); + cp += 2; + crc = crc32_copy(cp, buffer, len, CRC32_INIT); + crc = ~crc; + /* append CRC - network byte order */ + cp += len; + *cp++ = crc & 0xff; + *cp++ = (crc >> 8) & 0xff; + *cp++ = (crc >> 16) & 0xff; + *cp++ = (crc >> 24) & 0xff; + #else /* !defined(CONFIG_OTG_NETWORK_EEM_CRC) */ + /* little endian */ + cp[0] = (len + 4) & 0xff; + cp[1] = (((len + 4) >> 8) & 0x3f); + cp += 2; + memcpy (cp, buffer, len); + /* append 0xdeadbeef sentinel - network byte order */ + cp += len; + *cp++ = 0xde; + *cp++ = 0xad; + *cp++ = 0xbe; + *cp++ = 0xef; + #endif /* !defined(CONFIG_OTG_NETWORK_EEM_CRC) */ + + #ifdef CONFIG_OTG_NETWORK_EEM_STREAM + /* if actual length is odd then our padding will have start of ZLE but without data + * set flag and pad with nulls to packetsize + */ + npd->eem_send_zle_data = (urb->actual_length & 1); + while (urb->actual_length % in_pkt_sz) + urb->actual_length++; + #else /* CONFIG_OTG_NETWORK_EEM_STREAM */ + /* send ZLP if neccessary */ + UNLESS ((urb->actual_length % in_pkt_sz)) { + #ifdef CONFIG_OTG_NETWORK_EEM_ZLE + TRACE_MSG0(NTT,"ZLE"); + urb->actual_length++; + urb->actual_length++; + #else /* CONFIG_OTG_NETWORK_EEM_ZLE */ + TRACE_MSG0(NTT,"ZLP"); + urb->flags |= USBD_URB_SENDZLP; + #endif /* CONFIG_OTG_NETWORK_EEM_ZLE */ + } + #endif /* CONFIG_OTG_NETWORK_EEM_STREAM */ + + TRACE_MSG4(NTT, "len: %d crc: %08x actual_length: %d zle_data: %d", len, crc, urb->actual_length, npd->eem_send_zle_data); + + otg_atomic_add(urb->actual_length, &npd->queued_bytes); + otg_atomic_inc(&npd->xmit_urbs_started[xmit_index]); + if ((rc = usbd_start_in_urb (urb))) { + TRACE_MSG1(NTT,"FAILED: %d", rc); + urb->function_privdata = NULL; + otg_atomic_sub(urb->actual_length, &npd->queued_bytes); + otg_atomic_dec(&npd->xmit_urbs_started[xmit_index]); + usbd_free_urb (urb); + return rc; + } + return 0; +} + +/*! net_fd_start_echo_eem + * @brief start sending data, called to send a network frame via EEM. + * + * @param function_instance - function instance pointer + * @param buffer - pointer to data to send + * @param len - length of data to send + * @param data - pointer to instance private data + * @param eem_command - eem command + * @return: 0 if all OK + * -EINVAL, -EUNATCH, -ENOMEM + * rc from usbd_start_in_urb() if that fails (is != 0, may be one of err values above) + * Note: -ECOMM is interpreted by calling routine as signal to leave IF stopped. + */ +int net_fd_start_echo_eem (struct usbd_function_instance *function_instance, u8 *buffer, int len, void *data, u8 eem_command) +{ + struct usb_network_private *npd = function_instance->privdata; + struct usbd_urb *urb = NULL; + int rc; + int in_pkt_sz; + int xmit_index = 0; + int endpoint_index = BULK_IN_A; + u8 *cp; + u32 crc = 0; + + in_pkt_sz = usbd_endpoint_wMaxPacketSize(function_instance, endpoint_index, usbd_high_speed(function_instance)); + + //printk(KERN_INFO"%s:\n", __FUNCTION__); + + /* allocate urb 5 bytes larger than required */ + if (!(urb = usbd_alloc_urb (function_instance, endpoint_index, len + 5 + 4 + in_pkt_sz, net_fd_urb_sent_eem_echo ))) { + u8 epa = usbd_endpoint_bEndpointAddress(function_instance, endpoint_index, usbd_high_speed(function_instance)); + TRACE_MSG2(NTT,"urb alloc failed len: %d endpoint: %02x", len, epa); + printk(KERN_ERR"%s: urb alloc failed len: %d endpoint: %02x\n", __FUNCTION__, len, epa); + return -ENOMEM; + } + urb->function_privdata = data; + + /* if EEM packet is multiple of packetsize we may need to append ZLE (two NULL bytes) + */ + urb->actual_length = len + 2 + (npd->eem_send_zle_data ? 1 : 0) + 4; + cp = npd->eem_send_zle_data ? urb->buffer + 1 : urb->buffer; + npd->eem_send_zle_data = FALSE; + + /* EEM packet header - set bmCRC if EEM CRC is enabled, little endian */ + cp[0] = (len + 4) & 0xff; + cp[1] = 0x80 | (eem_command << 11) | (((len + 4) >> 8) & 0x07); + cp += 2; + memcpy(cp, buffer, len); + + TRACE_MSG4(NTT, "len: %d crc: %08x actual_length: %d zle_data: %d", len, crc, urb->actual_length, npd->eem_send_zle_data); + + otg_atomic_add(urb->actual_length, &npd->queued_bytes); + otg_atomic_inc(&npd->xmit_urbs_started[xmit_index]); + if ((rc = usbd_start_in_urb (urb))) { + TRACE_MSG1(NTT,"FAILED: %d", rc); + urb->function_privdata = NULL; + otg_atomic_sub(urb->actual_length, &npd->queued_bytes); + otg_atomic_dec(&npd->xmit_urbs_started[xmit_index]); + usbd_free_urb (urb); + return rc; + } + return 0; +} + + +/*! net_fd_eem_recv_data + * + * @brief Accumulate data from an EEM Data Packet, when finished pass to OS layer to + * push up to network layer. + * + * @param function_instance - function instance pointer + * @param eem_data_type + * @param frame_length - frame length + * @param urb_buffer - + * @param remainder + * @param shortTransfer + * + * + * @return Number of bytes consumed from urb buffer. + */ +STATIC int net_fd_eem_recv_data(struct usbd_function_instance *function_instance, eem_data_type_t eem_data_type, + int frame_length, + u8 *urb_buffer, int remainder, BOOL shortTransfer) +{ + struct usb_network_private *npd = function_instance->privdata; + void *os_data; + u8 *os_buffer; + int os_buffer_used, copy_length; + u32 eem_crc, valid_crc; + BOOL crc_bad; + + // XXX return MIN(remainder, frame_length); + + /* Restart filling if in the middle of receiving an EEM Data Packet. + */ + if (npd->eem_os_buffer) { + //printk(KERN_INFO"%s: WWWW\n", __FUNCTION__); + os_data = npd->eem_os_data; + os_buffer = npd->eem_os_buffer; + os_buffer_used = npd->eem_os_buffer_used; + eem_data_type = npd->eem_data_type; + eem_crc = npd->eem_crc; + npd->eem_os_data = npd->eem_os_buffer = NULL; + frame_length = npd->eem_frame_length; + } + /* Otherwise allocate a new os buffer + */ + else { + //printk(KERN_INFO"%s: XXXX\n", __FUNCTION__); + UNLESS((os_data = net_os_alloc_buffer(function_instance, &os_buffer, frame_length))) { + return MIN(remainder, frame_length); + } + os_buffer_used = 0; + eem_crc = CRC32_INIT; + } + + /* Determine copy length and then copy using memcpy or crc copy as + * appropriate. + */ + copy_length = MIN(remainder, frame_length - os_buffer_used); + + //printk(KERN_INFO"%s: YYYY\n", __FUNCTION__); + switch (eem_data_type) { + case eem_frame_crc_data: + eem_crc = crc32_copy(os_buffer + os_buffer_used, urb_buffer, copy_length/* - 1*/, eem_crc); + valid_crc = CRC32_GOOD; + break; + case eem_frame_no_crc_data: + memcpy (os_buffer + os_buffer_used, urb_buffer, copy_length); + urb_buffer += copy_length - 4; + eem_crc = *urb_buffer++ << 24; + eem_crc |= *urb_buffer++ << 16; + eem_crc |= *urb_buffer++ << 8; + eem_crc |= *urb_buffer++; + valid_crc = 0xdeadbeef; + break; + case eem_echo_data: + case eem_echo_response_data: + memcpy (os_buffer + os_buffer_used, urb_buffer, copy_length); + eem_crc = valid_crc = 0; + break; + default: + return copy_length; + } + //printk(KERN_INFO"%s: ZZZZ\n", __FUNCTION__); + urb_buffer += copy_length; + remainder -= copy_length; + os_buffer_used += copy_length; + + //printk(KERN_INFO"%s: AAAA\n", __FUNCTION__); + + /* Check for incomplete frame and short transfer. This is an + * error, as we are expecting more data and EEM Data cannot + * cross a bulk transfer boundard (short packet). + */ + if (shortTransfer && (os_buffer_used < frame_length)) + return copy_length; + + //printk(KERN_INFO"%s: BBBB\n", __FUNCTION__); + + /* Save intermediate information if not complete. + */ + if (os_buffer_used < frame_length) { + npd->eem_os_data = os_data; + npd->eem_os_buffer = os_buffer; + npd->eem_frame_length = frame_length; + npd->eem_os_buffer_used = os_buffer_used; + npd->eem_data_type = eem_data_type; + npd->eem_crc = eem_crc; + return copy_length; + } + + //printk(KERN_INFO"%s: CCCC\n", __FUNCTION__); + + switch (eem_data_type) { + case eem_frame_crc_data: + case eem_frame_no_crc_data: + //printk(KERN_INFO"%s: DDDD\n", __FUNCTION__); + /* Push the frame up as we have completed it. */ + if ((crc_bad = (valid_crc != eem_crc))) + TRACE_MSG3(NTT, "CRC error %08x %08x remainder: %d", eem_crc, valid_crc, remainder); + //printk(KERN_INFO"%s: EEEE\n", __FUNCTION__); + if (net_fd_recv_buffer(function_instance, os_buffer, copy_length, os_data, crc_bad, 4)) { + TRACE_MSG0(NTT, "FAILED"); + net_os_dealloc_buffer(function_instance, os_data, os_buffer); + } + //printk(KERN_INFO"%s: FFFF\n", __FUNCTION__); + break; + case eem_echo_data: + /* send data back as echo response */ + if (net_fd_start_echo_eem (function_instance, os_buffer, os_buffer_used, os_data, EEM_ECHO_RESPONSE)) { + TRACE_MSG0(NTT, "FAILED"); + net_os_dealloc_buffer(function_instance, os_data, os_buffer); + } + break; + case eem_echo_response_data: + /* discard */ + TRACE_MSG1(NTT, "RESPONSE len: %d", copy_length); + break; + default: + return copy_length; + } + //printk(KERN_INFO"%s: GGGG\n", __FUNCTION__); + + /* tell caller how much data we processed + */ + return copy_length; +} + +/*! net_fd_recv_urb_eem + * + * @brief callback to process a received URB + * Process incoming stream of EEM packets. + * + * There are three special cases: + * + * 1. EEM Data Packet not complete at the end of a bulk transfer. + * 2. EEM Command Packet not complete at the end of a bulk transfer. + * 3. EEM Packets cannot cross short transfer boundary. + * + * N.B. EEM 5.1 says "EEM packets shall not be split across USB transfers." + * But we cannot determine what the maximum host transfer size is, so the + * only hard boundary is if the transfer is terminated with a short packet, + * Zero Length EEM Packet (ZLE) or Zero Length Packet (ZLP). + * + * @param urb received urb + * @param rc dummy + * @return non-zero for failure. + */ +int net_fd_recv_urb_eem(struct usbd_urb *urb, int rc) +{ + struct usbd_function_instance *function_instance = urb->function_instance; + struct usb_network_private *npd = function_instance->privdata; + u8 *urb_buffer = urb->buffer; + int remainder = urb->actual_length; + u32 temmp=0; + int endpoint_index = BULK_IN_A; + + int maximumPacketSize = usbd_endpoint_wMaxPacketSize(function_instance, endpoint_index, + usbd_high_speed(function_instance)); + BOOL shortTransfer = (remainder % maximumPacketSize); + + TRACE_MSG2(NTT, "status: %d actual_length: %d", urb->status, urb->actual_length); + + while (remainder) { + + u8 buf0; // first byte of EEM packet header + u8 buf1; // second byte of EEM packet header + eem_data_type_t eem_data_type = eem_no_data; + + int frame_length, consumed = 0; + + /* Are we in the middle of processing an EEM Data Packet? + * Process this data and continue. + */ + if (npd->eem_os_buffer) { + consumed -= net_fd_eem_recv_data(function_instance, 0, 0, urb_buffer, remainder, shortTransfer); + remainder -= consumed; + urb_buffer += consumed; + continue; + } + + /* Get first byte of EEM packet, there may be a fragment left + * from previous transfer. + */ + if (npd->eem_fragment_valid) { + buf0 = npd->eem_fragment_data; + buf1 = *urb_buffer++; + npd->eem_fragment_valid = FALSE; + } + else { + buf0 = *urb_buffer++; + buf1 = *urb_buffer++; + remainder--; + + /* Check if this is a fragment. save if found + */ + if (shortTransfer && (remainder < 1)) { + return 0; + } + if (remainder < 1) { + npd->eem_fragment_valid = TRUE; + npd->eem_fragment_data = buf0; + return 0; + } + } + + TRACE_MSG3(NTT, "remainder: %d EEM: %02x %02x", remainder, buf1, buf0); + + /* Check for a Zero Length EEM (ZLE). + * A null byte, following by any byte, Skip both and continue if found. + */ + UNLESS (buf0 || buf1) { + remainder--; + //TRACE_MSG1(NTT, "ZLE: %02x", buf1); + continue; + } + + /* Check for an EEM Command Packet. + */ + if (buf1 & 0x80) { + + u8 bmEEMCmd = (buf1 >> 3) & 0x7; + u8 bmEEMCmdParam = ((buf1 & 0x7) << 8) | buf0; + remainder --; + + TRACE_MSG3(NTT, "bmEEMCmd: %d bmEEMCmdParam: %06x remainder: %d", bmEEMCmd, bmEEMCmdParam, remainder); + + /* XXX Still todo... + */ + switch (bmEEMCmd) { + /* host sent echo, get data and send back as echo response */ + case EEM_ECHO: + eem_data_type = eem_echo_data; + break; + + /* receiving response from echo we sent to host */ + case EEM_ECHO_RESPONSE: + eem_data_type = eem_echo_response_data; + break; + + /* host has timed out response complete hint command */ + case EEM_TICKLE: + TRACE_MSG0(NTT, "EEM TICKLE"); + break; + + /* These are not allowed from Host */ + case EEM_SUSPEND_HINT: + case EEM_RESPONSE_HINT: + case EEM_RESPONSE_COMPLETE_HINT: + TRACE_MSG1(NTT, "EEM COMMAND - BAD %02x", bmEEMCmd); + break; + + } + /* if not echo or echo response we are finished */ + CONTINUE_UNLESS((EEM_ECHO == bmEEMCmd) || (EEM_ECHO_RESPONSE == bmEEMCmd)); + + /* echo or echo response, get data length */ + frame_length = ((buf1 & 0x07) << 8) | buf0; + } + /* otherwise must be EEM Data */ + else { + /* Must be an EEM Data Packet. Determine frame length, + * advance past header and process data. + */ + u32 bmCRC = bmCRC = buf1 & 0x40; + frame_length = ((buf1 & 0x3f) << 8) | buf0; + remainder--; + eem_data_type = bmCRC ? eem_frame_crc_data : eem_frame_no_crc_data; + } + + /* process received data - either frame or echo or echo response */ + consumed = net_fd_eem_recv_data(function_instance, eem_data_type, frame_length, urb_buffer, + remainder, shortTransfer); + remainder -= consumed; + urb_buffer += consumed; + } + +#if 0 //CONFIG_OTG_LATENCY_CHECK + otg_get_ocd_info(NULL, &urb->goto_net_ticks, NULL); + //urb->goto_net_ticks=__raw_readl(MXC_GPT_GPTCNT); +/*Calculate BH waiting time*/ + LTRACE_MSG4(NTT, "urb pointer:%x, rcv urb:%x, bh start:%x,goto net:%x",urb, urb->urb_rcved_ticks,urb->bh_start_ticks,urb->goto_net_ticks); + if ( urb->bh_start_ticks < urb->urb_rcved_ticks ) temmp=(0xffffffff- urb->urb_rcved_ticks + urb->bh_start_ticks); + else temmp=urb->bh_start_ticks - urb->urb_rcved_ticks; + + temmp=10000*temmp/LATCH; // latch=10MS, SO temmp should be in uS + LTRACE_MSG4(NTT, "urbrcved=>bhstart time(uS):%d, Start:%x, End:%x, LATCH:%x",temmp,urb->urb_rcved_ticks,urb->bh_start_ticks,LATCH); + + +/*Calculate BH process time*/ + if (urb->goto_net_ticks < urb->bh_start_ticks) temmp=(0xffffffff- urb->bh_start_ticks + urb->goto_net_ticks); + else temmp=urb->goto_net_ticks - urb->bh_start_ticks; + + temmp=10000*temmp/LATCH; // latch=10MS, SO temmp should be in uS + LTRACE_MSG4(NTT, "bh=>net time(uS):%d, Start:%x, End:%x, LATCH:%x",temmp,urb->bh_start_ticks,urb->goto_net_ticks,LATCH); +#endif + return 0; +} + +/* ********************************************************************************************* */ +#ifdef CONFIG_OTG_NETWORK_START_SINGLE +#define NETWORK_START_URBS 1 +#else +#define NETWORK_START_URBS 2 +#endif + +/*! net_fd_start_recv_eem - start recv urb(s) + * + * @param function_instance - pointer to this function instance + * @return none + */ +int net_fd_start_recv_eem(struct usbd_function_instance *function_instance) +{ + struct usb_network_private *npd = function_instance->privdata; + int i; + int network_start_urbs = NETWORK_START_URBS; + int hs = usbd_high_speed(function_instance); + int endpoint_index = BULK_OUT_A; + + if (hs) + network_start_urbs = NETWORK_START_URBS * 3; + + for (i = 0; i < network_start_urbs; i++) { + struct usbd_urb *urb; + BREAK_IF(!(urb = usbd_alloc_urb(function_instance, endpoint_index, + usbd_endpoint_transferSize(function_instance, endpoint_index, hs), + net_fd_recv_urb))); + TRACE_MSG5(NTT,"i: %d urb: %p priv: %p npd: %p function: %p", + i, urb, urb->function_privdata, npd, function_instance); + + //urb->function_privdata = npd; + if (usbd_start_out_urb(urb)) { + urb->function_privdata = NULL; + usbd_free_urb(urb); + } + } + return 0; +} +/* ********************************************************************************************* */ + +/*! eem_fd_function_enable + * @brief enable the function driver + * Called for usbd_function_enable() from usbd_register_device() + * + * @param function_instance - function instance pointer + * @return int to indicate operation result + */ + +int eem_fd_function_enable (struct usbd_function_instance *function_instance) +{ + int rc; + printk(KERN_INFO"%s:\n", __FUNCTION__); + rc = net_fd_function_enable(function_instance, network_eem, net_fd_recv_urb_eem, + net_fd_start_xmit_eem, + net_fd_start_recv_eem, 0); + return rc; +} + +/*! + * eem_fd_set_configuration - called to indicate set configuration request was received + * @param function_instance + * @param configuration + * @return int + */ +int eem_fd_set_configuration (struct usbd_function_instance *function_instance, int configuration) +{ + char * test = "This is a test."; + int rc; + printk(KERN_INFO"%s:\n", __FUNCTION__); + rc = net_fd_set_configuration(function_instance, configuration); + RETURN_EINVAL_IF(rc); + net_fd_start_echo_eem (function_instance, test, strlen(test) + 1, NULL, EEM_ECHO); + return 0; +} + + +/* ********************************************************************************************* */ +/*! eem_fd_function_ops - operations table for network function driver + */ +struct usbd_function_operations eem_fd_function_ops = { + .function_enable = eem_fd_function_enable, + .function_disable = net_fd_function_disable, + + .device_request = net_fd_device_request, + .endpoint_cleared = net_fd_endpoint_cleared, + + .set_configuration = eem_fd_set_configuration, + .set_interface = NULL, + .reset = net_fd_reset, + .suspended = net_fd_suspended, + .resumed = net_fd_resumed, +}; + + +/*! function driver description + */ +struct usbd_interface_driver eem_interface_driver = { + .driver = { + .name = "cdc-eem-if", + .fops = &eem_fd_function_ops, }, + .interfaces = sizeof (eem_interfaces) / sizeof (struct usbd_interface_description), + .interface_list = eem_interfaces, + .endpointsRequested = 2, + .requestedEndpoints = cdc_data_endpoint_requests, + + .bFunctionClass = USB_CLASS_COMM, + .bFunctionSubClass = COMMUNICATIONS_EEM_SUBCLASS, + .bFunctionProtocol = COMMUNICATIONS_EEM_PROTOCOL, + .iFunction = "Belcarra USBLAN - EEM", +}; + +/* ********************************************************************************************* */ + +/*! int eem_mod_init () + * @brief eem_mod_init + * @return int + */ +int eem_mod_init (void) +{ + printk(KERN_INFO"%s:\n", __FUNCTION__); + return usbd_register_interface_function (&eem_interface_driver, "cdc-eem-if", NULL); +} + +/*! eem_mod_exit() +* @brief eem_mod_exit + * @return none + */ +void eem_mod_exit(void) +{ + printk(KERN_INFO"%s:\n", __FUNCTION__); + usbd_deregister_interface_function (&eem_interface_driver); +} + +#else /* CONFIG_OTG_NETWORK_EEM */ +/*! + * @brief eem_mod_init + * @return int + */ +int eem_mod_init (void) +{ + return 0; +} + +/*! + * @brief eem_mod_exit + * @return none + */ +void eem_mod_exit(void) +{ +} +#endif /* CONFIG_OTG_NETWORK_EEM */ diff --git a/drivers/otg/functions/network/fermat.c b/drivers/otg/functions/network/fermat.c new file mode 100644 index 000000000000..9ec0ab27cf48 --- /dev/null +++ b/drivers/otg/functions/network/fermat.c @@ -0,0 +1,177 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/functions/network/fermat.c - Network Function Driver + * @(#) sl@belcarra.com/whiskey.enposte.net|otg/functions/network/fermat.c|20070425221028|05672 + * + * Copyright (c) 2003-2006 Belcarra Technologies Corp + * Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + * + * By: + * Bruce Balden <balden@belcarra.com> + * + */ +/*! + * @file otg/functions/network/fermat.c + * @brief This implements a special data munging function that + * randomizes data. This is a very specific fix for a device + * that had trouble with runs of zeros. + * + * @ingroup NetworkFunction + */ + +#include <otg/otg-compat.h> + +#if defined(CONFIG_OTG_LNX) || defined(_OTG_DOXYGEN) + + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/version.h> + +#ifdef CONFIG_OTG_NETWORK_BLAN_FERMAT + +#include "fermat.h" + +#ifndef FERMAT_DEFINED +typedef unsigned char BYTE; +typedef struct fermat { + int length; + BYTE power[256]; +} FERMAT; +#endif +/*! int fermat_setup(FERMAT *, int ) + * @brief set up fermat table + * @param p - pointer to FERMAT + * @param seed - seed to generate FERMAT data + * @return number of FERMAT length + */ + +static int fermat_setup(FERMAT *p, int seed){ + int i = 0; + unsigned long x,y; + y = 1; + do{ + x = y; + p->power[i] = ( x == 256 ? 0 : x); + y = ( seed * x ) % 257; + i += 1; + }while( y != 1); + p->length = i; + return i; +} + +/*! void fermat_xform(FERMAT*, BYTE*, int) + * @brief transform data using fermat table + * @param p - pointer to fermat data + * @param data - pointer to data to transform + * @param length - length of data to transform + * @return none + */ + +static void fermat_xform(FERMAT *p, BYTE *data, int length){ + BYTE *pw = p->power; + int i, j; + BYTE * q ; + for(i = 0, j=0, q = data; i < length; i++, j++, q++){ + if(j>=p->length){ + j = 0; + } + *q ^= pw[j]; + } +} + +static FERMAT default_fermat; +static const int primitive_root = 5; +/*! void fermat_init() + * @brief initialize fermat using privitive_root as seed + * + * @return none + */ +void fermat_init(){ + (void) fermat_setup(&default_fermat, primitive_root); +} + +// Here are the public official versions. +// Change the primitive_root above to another primitive root +// if you need better scatter. Possible values are 3 and 7 + +/*! void fermat_encode( BYTE*, int) + * @brief using fermat encode data + * + * @param data - pointer to data to encode + * @param length - data length + * @return none + */ +void fermat_encode(BYTE *data, int length){ + fermat_xform(&default_fermat, data, length); +} + +/*! void fermat_decode(BYTE* int) + * @brief decode data + * + * @param data - pointer to data to decode + * @param length - data lenght + * @return none + */ +void fermat_decode(BYTE *data, int length){ + fermat_xform(&default_fermat, data, length); +} + + +// Note: the seed must be a "primitive root" of 257. This means that +// the return value of the setup routine must be 256 (otherwise the +// seed is not a primitive root. The routine will still work fine +// but will be less pseudo-random. + +#undef TEST +#if TEST +#include <stdio.h> +#include <memory.h> + +/*! main + * @brief - Use FERMAT in two ways: to encode, and to generate test data. + */ + +main(){ + //Note 3, 5, and 7 are primitive roots of 257 + // 11 is not a primitive root + FERMAT three, five, seven; + + FERMAT three2; + printf("Cycle lengths: 3,5,7 %d %d %d \n", + fermat_setup(&three, 3), + fermat_setup(&five, 5), + fermat_setup(&seven, 7)); + three2=three; // Copy data from three + fermat_xform(&three,three2.power,three2.length); + fermat_xform(&five,three2.power,three2.length); + fermat_xform(&seven,three2.power,three2.length); + fermat_xform(&seven,three2.power,three2.length); + fermat_xform(&five,three2.power,three2.length); + fermat_xform(&three,three2.power,three2.length); + + //At this stage, three2 and three should be identical + if(memcpy(&three,&three2,sizeof(FERMAT))){ + printf("Decoded intact\n"); + } + + fermat_init(); + fermat_encode(three2.power,256); + +} +#endif + +#endif /* CONFIG_OTG_NETWORK_BLAN_FERMAT */ + +#endif /* defined(CONFIG_OTG_LNX) */ diff --git a/drivers/otg/functions/network/fermat.h b/drivers/otg/functions/network/fermat.h new file mode 100644 index 000000000000..d8a38980d2b5 --- /dev/null +++ b/drivers/otg/functions/network/fermat.h @@ -0,0 +1,46 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/functions/network/fermat.h - Network Function Driver + * @(#) sl@belcarra.com/whiskey.enposte.net|otg/functions/network/fermat.h|20061218212925|58148 + * + * Copyright (c) 2003-2006 Belcarra Technologies Corp + * Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + * + * By: + * Bruce Balden <balden@belcarra.com> + * + */ +/*! + * @file otg/functions/network/fermat.h + * @brief Fermat related data structures. + * + * + * @ingroup NetworkFunction + */ + +#ifndef FERMAT_DEFINED +#define FERMAT_DEFINED 1 +typedef unsigned char BYTE; +/*! create an alias for fermat + * @brief typedef struct fermat FERMAT + */ +typedef struct fermat { + int length; + BYTE power[256]; +} FERMAT; + +void fermat_init(void); +void fermat_encode(BYTE *data, int length); +void fermat_decode(BYTE *data, int length); +#endif diff --git a/drivers/otg/functions/network/net-fd.c b/drivers/otg/functions/network/net-fd.c new file mode 100644 index 000000000000..7c93d555db62 --- /dev/null +++ b/drivers/otg/functions/network/net-fd.c @@ -0,0 +1,906 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/functions/network/net-fd.c - Network Function Driver + * @(#) sl@belcarra.com/whiskey.enposte.net|otg/functions/network/net-fd.c|20070814184652|40728 + * + * Copyright (c) 2002-2006 Belcarra Technologies Corp + * Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + * + * By: + * Chris Lynne <cl@belcarra.com> + * Stuart Lynne <sl@belcarra.com> + * Bruce Balden <balden@belcarra.com> + * + */ +/*! + * @file otg/functions/network/net-fd.c + * @brief The lower edge (USB Device Function) implementation of + * the Network Function Driver. This performs the core protocol + * handling and data encpasulation. + * + * This implements the lower edge (USB Device) layer of the Network Function + * Driver. Specifically the data encapsulation, envent and protocol handlers. + * + * This network function driver intended to interoperate with + * Belcarra's USBLAN Class drivers. + * + * These are available for Windows, Linux and Mac OSX. For more + * information and to download a copy for testing: + * + * http://www.belcarra.com/usblan/ + * + * Alternately it should be compatible with any CDC-ECM or CDC-EEM + * Class driver. + * + * Experimental Streaming mode is available for EEM. This simply sends + * all frames padded so that they do not terminate the transfer at the + * receiving end. The bulk sent callback will send a ZLE frame IFF there + * is not pending traffic. This will terminate the transfer at the host. + * + * The host should attempt to match it's receiving buffer to a size that + * optimizes the amount of data without unduly impacting latency. For a + * full speed host, something around 35*64 or 2240 bytes would be 1.9mS of + * data and allow for about .1mS for servicing. + * + * @ingroup NetworkFunction + */ + + +#include <otg/otg-compat.h> +#include <otg/otg-module.h> +//#include <linux/list.h> +//#include <linux/ctype.h> + +#include <otg/usbp-chap9.h> +#include <otg/usbp-cdc.h> +#include <otg/usbp-func.h> + +#include <otg/otg-trace.h> +#include <otg/otg-api.h> + +#include "network.h" +#include "net-os.h" +#ifdef CONFIG_OTG_NETWORK_BLAN_FERMAT +#include "fermat.h" +#endif + +#define TRACE_VERBOSE_SEND 0 +#define TRACE_VERBOSE_RECV 0 +#define TRACE_VERY_VERBOSE 0 + +static char * local_dev_addr_str; +static char * remote_dev_addr_str; +static BOOL override_MAC; +static int infrastructure_device; + +/* ********************************************************************************************** */ +#if !defined (CONFIG_OTG_NETWORK_INTERVAL) +#define CONFIG_OTG_NETWORK_INTERVAL 1 +#endif /* !defined (CONFIG_OTG_NETWORK_INTERVAL) */ + +/*! Endpoint Request List + */ + +#if !defined(CONFIG_OTG_NETWORK_DOUBLE_IN) && !defined(CONFIG_OTG_NETWORK_DOUBLE_OUT) +struct usbd_endpoint_request net_fd_endpoint_requests[ENDPOINTS+1] = { + { BULK_OUT_A, 1, 0, 0, USB_DIR_OUT | USB_ENDPOINT_BULK, MAXFRAMESIZE + 48, MAXFRAMESIZE + 512, 0, }, + { BULK_IN_A, 1, 0, 0, USB_DIR_IN | USB_ENDPOINT_BULK, MAXFRAMESIZE + 48, MAXFRAMESIZE + 512, 0, }, + { INT_IN, 1, 0, 0, USB_DIR_IN | USB_ENDPOINT_INTERRUPT | USB_ENDPOINT_OPT, 16, 64, CONFIG_OTG_NETWORK_INTERVAL, }, + { 0, }, +}; +u8 net_fd_endpoint_index[ENDPOINTS] = { BULK_OUT_A, BULK_IN_A, INT_IN, }; + +#elif defined(CONFIG_OTG_NETWORK_DOUBLE_IN) && !defined(CONFIG_OTG_NETWORK_DOUBLE_OUT) +struct usbd_endpoint_request net_fd_endpoint_requests[ENDPOINTS+1] = { + { BULK_OUT_A, 1, 0, 0, USB_DIR_OUT | USB_ENDPOINT_BULK, MAXFRAMESIZE + 48, MAXFRAMESIZE + 512, 0, }, + { BULK_IN_A, 1, 0, 0, USB_DIR_IN | USB_ENDPOINT_BULK, MAXFRAMESIZE + 48, MAXFRAMESIZE + 512, 0, }, + { BULK_IN_B, 1, 0, 0, USB_DIR_IN | USB_ENDPOINT_BULK, MAXFRAMESIZE + 48, MAXFRAMESIZE + 512, 0, }, + { INT_IN, 1, 0, 0, USB_DIR_IN | USB_ENDPOINT_INTERRUPT | USB_ENDPOINT_OPT, 16, 64, CONFIG_OTG_NETWORK_INTERVAL, }, + { 0, }, +}; +u8 net_fd_endpoint_index[ENDPOINTS] = { BULK_OUT_A, BULK_IN_A, BULK_OUT_B, BULK_IN_B, INT_IN, }; + + +#elif !defined(CONFIG_OTG_NETWORK_DOUBLE_IN) && defined(CONFIG_OTG_NETWORK_DOUBLE_OUT) +struct usbd_endpoint_request net_fd_endpoint_requests[ENDPOINTS+1] = { + { BULK_OUT_A, 1, 0, 0, USB_DIR_OUT | USB_ENDPOINT_BULK, MAXFRAMESIZE + 48, MAXFRAMESIZE + 512, 0, }, + { BULK_IN_A, 1, 0, 0, USB_DIR_IN | USB_ENDPOINT_BULK, MAXFRAMESIZE + 48, MAXFRAMESIZE + 512, 0, }, + { BULK_OUT_B, 1, 0, 0, USB_DIR_OUT | USB_ENDPOINT_BULK, MAXFRAMESIZE + 48, MAXFRAMESIZE + 512, 0, }, + { INT_IN, 1, 0, 0, USB_DIR_IN | USB_ENDPOINT_INTERRUPT | USB_ENDPOINT_OPT, 16, 64, CONFIG_OTG_NETWORK_INTERVAL, }, + { 0, }, +}; +u8 net_fd_endpoint_index[ENDPOINTS] = { BULK_OUT_A, BULK_IN_A, BULK_OUT_B, BULK_IN_B, INT_IN, }; + + +#elif defined(CONFIG_OTG_NETWORK_DOUBLE_IN) && defined(CONFIG_OTG_NETWORK_DOUBLE_OUT) +struct usbd_endpoint_request net_fd_endpoint_requests[ENDPOINTS+1] = { + { BULK_OUT_A, 1, 0, 0, USB_DIR_OUT | USB_ENDPOINT_BULK, MAXFRAMESIZE + 48, MAXFRAMESIZE + 512, 0, }, + { BULK_IN_A, 1, 0, 0, USB_DIR_IN | USB_ENDPOINT_BULK, MAXFRAMESIZE + 48, MAXFRAMESIZE + 512, 0, }, + { BULK_OUT_B, 1, 0, 0, USB_DIR_OUT | USB_ENDPOINT_BULK, MAXFRAMESIZE + 48, MAXFRAMESIZE + 512, 0, }, + { BULK_IN_B, 1, 0, 0, USB_DIR_IN | USB_ENDPOINT_BULK, MAXFRAMESIZE + 48, MAXFRAMESIZE + 512, 0, }, + { INT_IN, 1, 0, 0, USB_DIR_IN | USB_ENDPOINT_INTERRUPT | USB_ENDPOINT_OPT, 16, 64, CONFIG_OTG_NETWORK_INTERVAL, }, + { 0, }, +}; +u8 net_fd_endpoint_index[ENDPOINTS] = { BULK_OUT_A, BULK_IN_A, BULK_OUT_B, BULK_IN_B, INT_IN, }; + +#endif /* defined(CONFIG_OTG_NETWORK_DOUBLE_IN) && defined(CONFIG_OTG_NETWORK_DOUBLE_OUT) */ + + +/*! Endpoint Request List + */ +struct usbd_endpoint_request cdc_data_endpoint_requests[2+1] = { + { BULK_OUT_A, 1, 0, 0, USB_DIR_OUT | USB_ENDPOINT_BULK, MAXFRAMESIZE + 48, MAXFRAMESIZE + 512, 0, }, + { BULK_IN_A, 1, 0, 0, USB_DIR_IN | USB_ENDPOINT_BULK, MAXFRAMESIZE + 48, MAXFRAMESIZE + 512, 0, }, + { 0, }, +}; +u8 cdc_data_endpoint_index[2] = { BULK_OUT_A, BULK_IN_A, }; + +/*! Endpoint Request List + */ +struct usbd_endpoint_request cdc_int_endpoint_requests[1+1] = { + { INT_IN, 1, 0, 0, USB_DIR_IN | USB_ENDPOINT_INTERRUPT | USB_ENDPOINT_OPT, 16, 64, CONFIG_OTG_NETWORK_INTERVAL, }, + { 0, }, +}; + +u8 cdc_int_endpoint_index[1] = { INT_IN, }; + + +/* ********************************************************************************************** */ + +u32 *network_crc32_table; + +/*! make_crc_table + * @brief Generate the crc32 table + * + * @return non-zero if malloc fails + */ +STATIC int make_crc_table(void) +{ + u32 n; + RETURN_ZERO_IF(network_crc32_table); + RETURN_ENOMEM_IF(!(network_crc32_table = (u32 *)ckmalloc(256*4))); + for (n = 0; n < 256; n++) { + int k; + u32 c = n; + for (k = 0; k < 8; k++) { + c = (c & 1) ? (CRC32_POLY ^ (c >> 1)) : (c >> 1); + } + network_crc32_table[n] = c; + } + return 0; +} + +/* ********************************************************************************************** */ + +/*! net_fd_urb_sent_int - callback for completed INT URB + * + * Handles notification that an urb has been sent (successfully or otherwise). + * + * @param urb - pointer to urb to send + * @param urb_rc + * + * @return non-zero for failure. + */ +STATIC int net_fd_urb_sent_int (struct usbd_urb *urb, int urb_rc) +{ + //int rc = -EINVAL; + struct usbd_function_instance *function_instance = urb->function_instance; + struct usb_network_private *npd = function_instance->privdata; + + //TRACE_MSG3(NTT,"urb: %p npd: %p urb_rc: %d", urb, npd, urb_rc); + + npd->int_urb = NULL; + usbd_free_urb (urb); + return 0; +} + +/*! net_fd_send_int_notification - send an interrupt notification response + * + * Generates a response urb on the notification (INTERRUPT) endpoint. + * + * This is called from either a scheduled task or from the process context + * that calls network_open() or network_close(). + * This must be called with interrupts locked out as net_fd_event_handler can + * change the NETWORK_CONFIGURED status + * + * @param function_instance - pointer to function instance + * @param connected - connect status + * @param data - + * + * @return none + * + */ +void net_fd_send_int_notification(struct usbd_function_instance *function_instance, int connected, int data) +{ + struct usb_network_private *npd = function_instance ? function_instance->privdata : NULL; + struct usbd_urb *urb; + struct cdc_notification_descriptor *cdc; + int rc; + + //TRACE_MSG3(NTT,"npd: %p function: %p flags: %04x", npd, function_instance, npd->flags); + + do { + BREAK_IF(!function_instance); + + BREAK_IF(npd->network_type != network_blan); + BREAK_IF(!npd->have_interrupt); + + BREAK_IF(!(npd->flags & NETWORK_CONFIGURED)); + + TRACE_MSG3(NTT,"connected: %d network: %d %d", connected, + npd->network_type, network_blan); + + BREAK_IF(usbd_get_device_status(function_instance) != USBD_OK); + + BREAK_IF(!(urb = usbd_alloc_urb (function_instance, INT_IN, + sizeof(struct cdc_notification_descriptor), net_fd_urb_sent_int))); + + urb->actual_length = sizeof(struct cdc_notification_descriptor); + memset(urb->buffer, 0, sizeof(struct cdc_notification_descriptor)); + + cdc = (struct cdc_notification_descriptor *)urb->buffer; + + cdc->bmRequestType = 0xa1; + + if (data) { + cdc->bNotification = 0xf0; + cdc->wValue = 1; + } + else { + cdc->bNotification = 0x00; + cdc->wValue = connected ? 0x01 : 0x00; + } + cdc->wIndex = 0x00; // XXX interface - check that this is correct + + + npd->int_urb = urb; + TRACE_MSG1(NTT,"int_urb: %p", urb); + BREAK_IF (!(rc = usbd_start_in_urb (urb))); + + TRACE_MSG1(NTT,"usbd_start_in_urb failed err: %x", rc); + npd->int_urb = NULL; + usbd_free_urb (urb); + + } while(0); +} + +/* ********************************************************************************************** */ + +/*! net_fd_start_xmit - start sending a buffer + * + * Called with net_os_mutex_enter()d. + * + * @param function_instance - pointer to function instance + * @param buffer - container to data to transmit + * @param len - data length + * @param data + * + * @return: 0 if all OK + * -EINVAL, -EUNATCH, -ENOMEM + * rc from usbd_start_in_urb() if that fails (is != 0, may be one of err values above) + * Note: -ECOMM is interpreted by calling routine as signal to leave IF stopped. + */ +int net_fd_start_xmit (struct usbd_function_instance *function_instance, u8 *buffer, int len, void *data) +{ + struct usb_network_private *npd = function_instance->privdata; + struct usbd_urb *urb = NULL; + int rc; + + if (TRACE_VERBOSE_SEND) + TRACE_MSG4(NTT, "os: %p buffer: %p len: %d flags: %04x", data, buffer, len, npd->flags); + RETURN_EUNATCH_UNLESS(npd->flags & NETWORK_CONFIGURED); + RETURN_EINVAL_IF(usbd_get_device_status(function_instance) != USBD_OK); + + //TRACE_MSG2(NTT, "queued: %d bytes: %d", npd->queued_frames, npd->queued_bytes); + + if (TRACE_VERBOSE_SEND) { + TRACE_NSEND(NTT, 32, buffer); + if (TRACE_VERY_VERBOSE) { + TRACE_SEND(NTT, len, buffer); + } + } + + UNLESS ((rc = npd->net_start_xmit (function_instance, buffer, len, data))) + otg_atomic_inc(&npd->queued_frames); + + return rc; +} + +/* ********************************************************************************************** */ + +int net_fd_check_tp_response(struct usbd_function_instance *function_instance, u8 *buffer, int length); +int net_fd_check_rarp_response(struct usbd_function_instance *function_instance, u8 *buffer, int length); + +/*! net_fd_recv_buffer - forward a received URB, or clean up after a bad one. + * + * Common point for transmitting data to net-fd upper OS layer for pushing + * to Host OS Network Layer. + * + * @param function_instance - pointer to functionn instance + * @param os_buffer - pointer to os buffer + * @param length - os buffer length + * @param os_data - + * @param crc_bad + * @param trim + * @return 0 for exception + */ +int net_fd_recv_buffer(struct usbd_function_instance *function_instance, u8 *os_buffer, int length, + void *os_data, int crc_bad, int trim) +{ + struct usb_network_private *npd = function_instance->privdata; + + if (TRACE_VERBOSE_RECV) { + + TRACE_MSG6(NTT, "os_buffer: %x length: %d os_data: %x crc_bad: %d trim: %d flags: %04x", + os_buffer, length, os_data, crc_bad, trim, npd->flags); + + TRACE_NRECV(NTT, 32, os_buffer); + TRACE_MSG0(NTT, "--"); + if (TRACE_VERY_VERBOSE) { + TRACE_RECV(NTT, length, os_buffer); + } + } + + #if defined(CONFIG_OTG_NETWORK_RARPD_AUTO_CONFIG) + /* check for RARPD request */ + if (net_fd_check_rarpd_request(function_instance, os_buffer, length)) { + + TRACE_MSG0(NTT, "RARPD REQUEST"); + + /* send reply or ignore and send our own request */ + ((npd->flags & NETWORK_INFRASTRUCTURE) ? + net_fd_send_rarpd_reply : net_fd_send_rarpd_request) (function_instance); + + THROW(handled); + } + + /* check for rarpd response IFF not infrastruture */ + UNLESS ((npd->flags & NETWORK_INFRASTRUCTURE) || !net_fd_check_rarpd_reply(function_instance, os_buffer, length)) { + TRACE_MSG0(NTT, "RARPD REPLY RESPONSE"); + + /* configure */ + net_os_config(function_instance); + net_os_hotplug(function_instance); + + #if defined(CONFIG_OTG_NETWORK_RFC868_AUTO_CONFIG) + net_fd_send_tp_request(function_instance); + #endif /* defined(CONFIG_OTG_NETWORK_RFC868_AUTO_CONFIG) */ + THROW(handled); + } + + #if defined(CONFIG_OTG_NETWORK_RFC868_AUTO_CONFIG) + UNLESS ((npd->flags & NETWORK_INFRASTRUCTURE) || !net_fd_check_tp_response(function_instance, os_buffer, length)) { + TRACE_MSG0(NTT, "TIME PROTOCOL RESPONSE"); + + /* set time */ + net_os_settime(function_instance, ntohl(npd->rfc868time)); + THROW( handled); + } + + #endif /* defined(CONFIG_OTG_NETWORK_RFC868_AUTO_CONFIG) */ + #endif /* defined(CONFIG_OTG_NETWORK_RARPD_AUTO_CONFIG) */ + + return (net_os_recv_buffer(function_instance, os_data, os_buffer, crc_bad, length, trim)); + + CATCH(handled) { + /* de-allocate os buffer */ + net_os_dealloc_buffer(function_instance, os_data, os_buffer); + return 0; + } +} + +/* ********************************************************************************************** */ + +/*! net_fd_recv_urb - callback to process a received URB + * + * @param urb - pointer to copy of received urb, + * @param rc - receiving urb result code + * + * @return non-zero for failure. + */ +int net_fd_recv_urb(struct usbd_urb *urb, int rc) +{ + struct usbd_function_instance *function_instance = urb->function_instance; + struct usb_network_private *npd = function_instance->privdata; + + TRACE_MSG2(NTT, "status: %d actual_length: %d", urb->status, urb->actual_length); + +#ifdef CONFIG_OTG_LATENCY_CHECK + otg_get_ocd_info(NULL, &urb->bh_start_ticks, NULL); + //urb->bh_start_ticks=__raw_readl(MXC_GPT_GPTCNT); + //LTRACE_MSG2(NTT, "Callback urb pointer:%x, ticks:%x",urb,urb->bh_start_ticks); + +#endif + #if 0 + TRACE_MSG8(NTT, "[%02x %02x %02x %02x %02x %02x %02x %02x]", + urb->buffer[0], urb->buffer[1], + urb->buffer[2], urb->buffer[3], + urb->buffer[4], urb->buffer[5], + urb->buffer[6], urb->buffer[7] + ); + TRACE_MSG8(NTT, "[%02x %02x %02x %02x %02x %02x %02x %02x]", + urb->buffer[0+8], urb->buffer[1+8], + urb->buffer[2+8], urb->buffer[3+8], + urb->buffer[4+8], urb->buffer[5+8], + urb->buffer[6+8], urb->buffer[7+8] + ); + #endif + + if (TRACE_VERBOSE_RECV) + TRACE_NRECV(NTT, MIN(32, urb->actual_length), urb->buffer); + + if (urb->status == USBD_URB_OK) + npd->net_recv_urb(urb, rc); + + urb->status = USBD_OK; + return usbd_start_out_urb (urb); +} + +/* ********************************************************************************************** */ + +/*! net_fd_device_request - process a received SETUP URB + * + * Processes a received setup packet and CONTROL WRITE data. + * Results for a CONTROL READ are placed in urb->buffer. + * + * @param function_instance - pointer to function instance + * @param request - pointer to usbd_device_request + * + * @return non-zero for failure. + */ +int net_fd_device_request (struct usbd_function_instance *function_instance, struct usbd_device_request *request) +{ + //struct usb_network_private *npd = function_instance->privdata; + //struct usbd_urb *urb; + //int index; + + /* Verify that this is a USB Class request per CDC specification or a vendor request. + */ + RETURN_ZERO_IF (!(request->bmRequestType & (USB_REQ_TYPE_CLASS | USB_REQ_TYPE_VENDOR))); + + return -EINVAL; +} + + +/* ********************************************************************************************** */ + + +/*! net_fd_endpoint_cleared - + */ +void net_fd_endpoint_cleared (struct usbd_function_instance *function_instance, int bEndpointAddress) +{ + //TRACE_MSG1(NTT, "bEndpointAddress: %02x", bEndpointAddress); +} + + +/*! net_fd_urb_sent_bulk - callback for completed BULK xmit URB + * + * Handles notification that an urb has been sent (successfully or otherwise). + * + * @param urb Pointer to the urb that has been sent. + * @param urb_rc Result code from the send operation. + * + * @return non-zero for failure. + */ +int net_fd_urb_sent_bulk (struct usbd_urb *urb, int urb_rc) +{ + struct usbd_function_instance *function_instance = urb->function_instance; + struct usb_network_private *npd = function_instance->privdata; + void *buf; + int rc = -EINVAL; + int endpoint_index = urb->endpoint_index; + + + npd->avg_queue_frames += otg_atomic_read(&npd->queued_frames); + npd->samples++; + + //TRACE_MSG6(NTT,"urb: %p urb_rc: %d length: %d queue: %d avg: %d samples: %d", + // urb, urb_rc, urb->actual_length, + // npd->queued_frames, + // npd->avg_queue_frames / npd->samples, + // npd->samples); + + otg_atomic_dec(&npd->queued_frames); + otg_atomic_sub(urb->actual_length, &npd->queued_bytes); + #ifndef CONFIG_OTG_NETWORK_DOUBLE_IN + #else /* CONFIG_OTG_NETWORK_DOUBLE_IN */ + //otg_atomic_dec(&npd->xmit_urbs_started[endpoint_index]); + #endif /* CONFIG_OTG_NETWORK_DOUBLE_IN */ + do { + + BREAK_IF(!urb); + buf = urb->function_privdata; + urb->function_privdata = NULL; + + #ifndef CONFIG_OTG_NETWORK_DOUBLE_IN + TRACE_MSG6(NTT,"urb: %p buf: %p endpoint_index: %d buffer: %d alloc: %d actual: %d", + urb, buf, endpoint_index, + urb->buffer_length, + urb->alloc_length, + urb->actual_length + ); + #else /* CONFIG_OTG_NETWORK_DOUBLE_IN */ + TRACE_MSG7(NTT,"urb: %p buf: %p endpoint_index: %d started: %d buffer: %d alloc: %d actual: %d", + urb, buf, endpoint_index, + otg_atomic_read(&npd->xmit_urbs_started[endpoint_index]), + urb->buffer_length, + urb->alloc_length, + urb->actual_length + ); + #endif /* CONFIG_OTG_NETWORK_DOUBLE_IN */ + + net_os_xmit_done(urb->function_instance, buf, urb_rc); + #if defined(CONFIG_OTG_NETWORK_BLAN_CRC) || defined(CONFIG_OTG_NETWORK_SAFE_CRC) + #else /* defined(CONFIG_OTG_NETWORK_BLAN_CRC) || defined(CONFIG_OTG_NETWORK_SAFE_CRC) */ + #ifdef CONFIG_OTG_NETWORK_XMIT_OS + urb->buffer = NULL; + #endif /* CONFIG_OTG_NETWORK_XMIT_OS */ + #endif /* defined(CONFIG_OTG_NETWORK_BLAN_CRC) || defined(CONFIG_OTG_NETWORK_SAFE_CRC) */ + /* Now urb has it's own buffer*/ + usbd_free_urb (urb); + rc = 0; + + } while (0); + + return rc; +} + + +/* ********************************************************************************************** */ + +/*! hexdigit - + * + * Converts characters in [0-9A-F] to 0..15, characters in [a-f] to 42..47, and all others to 0. + * + * @param c - character to convert + * @return the converted decimal value + */ + u8 hexdigit (char c) +{ + return isxdigit (c) ? (isdigit (c) ? (c - '0') : (c - 'A' + 10)) : 0; +} + +/*! set_address -generate a device address from mac-address_str + * @param mac_address_str - sting used to generate device address + * @param dev_addr - pointer to generated device address + */ +void set_address(char *mac_address_str, u8 *dev_addr) +{ + int i; + if (mac_address_str && strlen(mac_address_str)) { + for (i = 0; i < NET_ETH_ALEN; i++) { + dev_addr[i] = + hexdigit (mac_address_str[i * 3]) << 4 | + hexdigit (mac_address_str[i * 3 + 1]); + } + } + else { + otg_get_random_bytes(dev_addr, NET_ETH_ALEN); + dev_addr[0] = (dev_addr[0] & 0xfe) | 0x02; + mac_address_str = "RANDOM"; + } + TRACE_MSG7(NTT, "net addr: %02x:%02x:%02x:%02x:%02x:%02x %s", + dev_addr[0], dev_addr[1], dev_addr[2], + dev_addr[3], dev_addr[4], dev_addr[5], + mac_address_str); +} + +/*! net_fd_function_enable - enable the function driver + * + * Called for usbd_function_enable() from usbd_register_device() + * + * @param function_instance - pointer to this function instance + * @param network_type + * @param net_recv_urb + * @param net_start_xmit + * @param net_start_recv + * @param recv_urb_flags + * + * @return int - non-zero for exception + */ + +int net_fd_function_enable (struct usbd_function_instance * function_instance, network_type_t network_type, + net_recv_urb_proc_t net_recv_urb, + net_start_xmit_proc_t net_start_xmit, + net_start_recv_proc_t net_start_recv, + u32 recv_urb_flags + ) +{ + struct usb_network_private *npd = NULL; + + /* This will link the usb_network_private structure into function_instance->privdata */ + net_os_enable(function_instance); + npd = function_instance->privdata; + + npd->network_type = network_type; + npd->net_recv_urb = net_recv_urb; + npd->net_start_xmit = net_start_xmit; + npd->net_start_recv = net_start_recv; + npd->recv_urb_flags = recv_urb_flags; + npd->override_MAC = override_MAC; + + net_os_mutex_enter(function_instance); + + #if 1 + set_address(local_dev_addr_str, npd->local_dev_addr); + + TRACE_MSG7(NTT, "net npd->local_ addr: %02x:%02x:%02x:%02x:%02x:%02x org MAC string:%s", + npd->local_dev_addr[0], npd->local_dev_addr[1], npd->local_dev_addr[2], + npd->local_dev_addr[3], npd->local_dev_addr[4], npd->local_dev_addr[5], + local_dev_addr_str); + npd->local_dev_set = TRUE; + #else + if (local_dev_addr_str && strlen(local_dev_addr_str)) { + set_address(local_dev_addr_str, npd->local_dev_addr); + npd->local_dev_set = TRUE; + } + #endif + if (remote_dev_addr_str && strlen(remote_dev_addr_str)) { + set_address(remote_dev_addr_str, npd->remote_dev_addr); + npd->remote_dev_set = TRUE; + } + + npd->have_interrupt = usbd_endpoint_bEndpointAddress(function_instance, INT_IN, + usbd_high_speed(function_instance)) ? 1 : 0; + + npd->flags |= NETWORK_ENABLED | (infrastructure_device ? NETWORK_INFRASTRUCTURE : 0); + + net_os_mutex_exit(function_instance); + CATCH(error) { + // XXX MODULE UNLOCK HERE + return -EINVAL; + } + return 0; +} + +/*! net_fd_function_disable - disable the function driver + * + * @param function_instance - pointer to function instance + * + * @return none + * + */ +void net_fd_function_disable (struct usbd_function_instance *function_instance) +{ + struct usb_network_private *npd = function_instance->privdata; + net_os_mutex_enter(function_instance); + npd->flags &= ~NETWORK_ENABLED; + net_os_mutex_exit(function_instance); + + TRACE_MSG0(NTT,"--"); + net_fd_stop (function_instance); + + if (npd->eem_os_buffer) + net_os_dealloc_buffer(function_instance, npd->eem_os_data, npd->eem_os_buffer); + + /* this will disconnect function_instance->privdata */ + net_os_disable(function_instance); + +} + +/* ********************************************************************************************** */ + +extern void net_fd_send_rarp_request(struct usbd_function_instance *function_instance); + +/*! + * net_fd_start - start network + * @param function_instance + * @return int + */ +int net_fd_start (struct usbd_function_instance *function_instance) +{ + struct usb_network_private *npd = function_instance->privdata; + int numm; + TRACE_MSG0(NTT,"entered"); + npd->flags |= NETWORK_CONFIGURED; + + if ((npd->network_type == network_blan) && (npd->flags & NETWORK_OPEN)) + net_os_send_notification_later(function_instance); + + net_os_carrier_on(function_instance); + + + /* Let the OS layer know, if it's interested. */ + net_os_config(function_instance); + net_os_hotplug(function_instance); + + /* send RARPD request if not an infrastructure device */ + #if 0 + #if defined(CONFIG_OTG_NETWORK_RARPD_AUTO_CONFIG) + UNLESS(infrastructure_device) + net_fd_send_rarp_request(function_instance); + #endif /* defined(CONFIG_OTG_NETWORK_RARPD_AUTO_CONFIG) */ + #endif + TRACE_MSG1(NTT, "CONFIGURED npd->flags: %04x", npd->flags); + return 0; +} + +/*! + * @brief net_fd_stop - stop network + * @param function_instance + * @return int + */ +int net_fd_stop (struct usbd_function_instance *function_instance) +{ + struct usb_network_private *npd = function_instance->privdata; + + TRACE_MSG0(NTT,"entered"); + + // Return if argument is null. + + // XXX flush + + npd->flags &= ~NETWORK_CONFIGURED; + npd->int_urb = NULL; + + // Disable our net-device. + // Apparently it doesn't matter if we should do this more than once. + + net_os_carrier_off(function_instance); + + // If we aren't already tearing things down, do it now. + if (!(npd->flags & NETWORK_DESTROYING)) { + npd->flags |= NETWORK_DESTROYING; + //npd->device = NULL; + } + + npd->seen_crc = 0; + + // Let the OS layer know, if it's interested. + net_os_config(function_instance); + net_os_hotplug(function_instance); + + // XXX flush + // Release any queued urbs + TRACE_MSG1(NTT, "RESET npd->flags: %04x", npd->flags); + return 0; +} + +/*! + * net_fd_set_configuration - called to indicate set configuration request was received + * @param function_instance + * @param configuration + * @return int + */ +int net_fd_set_configuration (struct usbd_function_instance *function_instance, int configuration) +{ + struct usb_network_private *npd = function_instance->privdata; + int hs = usbd_high_speed(function_instance); + + TRACE_MSG2(NTT, "CONFIGURED: %d ip_addr: %08x", configuration, npd->ip_addr); + + if (npd->eem_os_buffer) + net_os_dealloc_buffer(function_instance, npd->eem_os_data, npd->eem_os_buffer); + + npd->eem_os_data = npd->eem_os_buffer = NULL; + //npd->flags |= NETWORK_CONFIGURED; + npd->altsetting = 0; + npd->ip_addr = 0; + npd->max_recv_urbs = hs ? NETWORK_START_URBS * 3 : NETWORK_START_URBS; + + return net_fd_start(function_instance); +} + +/*! + * @brief net_fd_reset - called to indicate bus has been reset + * @param function_instance + * @return int + */ +int net_fd_reset (struct usbd_function_instance *function_instance) +{ + struct usb_network_private *npd = function_instance->privdata; + + #ifndef CONFIG_OTG_NETWORK_DOUBLE_OUT + int numm = 0; + numm = usbd_endpoint_urb_num (function_instance, BULK_OUT_A); + TRACE_MSG2(NTT,"RESET %08x urb_number: %d",npd->ip_addr, numm); + #else /* CONFIG_OTG_NETWORK_DOUBLE_OUT */ + int numm_a = 0; + int numm_b = 0; + numm_a = usbd_endpoint_urb_num (function_instance, BULK_OUT_A); + numm_b = usbd_endpoint_urb_num (function_instance, BULK_OUT_B); + TRACE_MSG3(NTT,"RESET %08x urb_number: %d:%d",npd->ip_addr, numm_a, numm_b); + #endif /* CONFIG_OTG_NETWORK_DOUBLE_OUT */ + npd->ip_addr = 0; + return net_fd_stop (function_instance); +} + + +/*! + * @brief net_fd_suspended - called to indicate bus has been suspended + * @param function_instance + * @return int + */ +int net_fd_suspended (struct usbd_function_instance *function_instance) +{ + + TRACE_MSG0(NTT, "SUSPENDED"); + return net_fd_stop (function_instance); +} + + +/*! + * net_fd_resumed - called to indicate bus has been resumed + * @param function_instance + * @return int + */ +int net_fd_resumed (struct usbd_function_instance *function_instance) +{ + //struct usbd_interface_instance *interface_instance = (struct usbd_interface_instance *)function_instance; + + TRACE_MSG0(NTT, "RESUMED"); + return net_fd_start(function_instance); +} + + + +/* ********************************************************************************************** */ + +#if 0 +/*! macstrtest - + */ +static int macstrtest(char *mac_address_str) +{ + int l = 0; + + if (mac_address_str) { + l = strlen(mac_address_str); + } + return ((l != 0) && (l != 12)); +} +#endif + +/*! + * @brief net_fd_init - function driver usb part intialization + * + * @param info_str + * @param local + * @param remote + * @param override_mac + * @param override_personal + * @param override_infrastructure + * @return non-zero for failure. + */ +int net_fd_init(char *info_str, char *local, char *remote, BOOL override_mac, BOOL override_personal, BOOL override_infrastructure) +{ + local_dev_addr_str = local; + remote_dev_addr_str = remote; + if (override_mac) override_MAC= TRUE; + #if defined(OTG_NETWORK_INFRASTRUCTURE) + infrastructure_device = !override_personal; + #else /* defined(OTG_NETWORK_INFRASTRUCTURE) */ + infrastructure_device = override_infrastructure; + #endif /* defined(OTG_NETWORK_INFRASTRUCTURE) */ + + TRACE_MSG3(NTT, "local: %s remote: %s Mode; %s Device", + local_dev_addr_str ? local_dev_addr_str : "", + remote_dev_addr_str ? remote_dev_addr_str : "", + infrastructure_device ? "Infrastructure" : "Personal" + ); + + return make_crc_table(); +} + +/*! + * @brief net_fd_exit - driver exit + * + * Cleans up the module. Deregisters the function driver and destroys the network object. + */ +void net_fd_exit(void) +{ + if (network_crc32_table) { + lkfree(network_crc32_table); + network_crc32_table = NULL; + } +} diff --git a/drivers/otg/functions/network/net-ip.c b/drivers/otg/functions/network/net-ip.c new file mode 100644 index 000000000000..1ae79a8cf01c --- /dev/null +++ b/drivers/otg/functions/network/net-ip.c @@ -0,0 +1,606 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/functions/network/net-ip.c - Network Function Driver + * @(#) sl@belcarra.com/whiskey.enposte.net|otg/functions/network/net-ip.c|20070425221028|12267 + * + * Copyright (c) 2002-2006 Belcarra Technologies Corp + * Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + * + * By: + * Stuart Lynne <sl@belcarra.com> + * + */ +/*! + * @file otg/functions/network/net-ip.c + * @brief Network Function Driver private defines + * + * This contains support for requesting and checking Time Protocol or RARP + * requests and replies. + * + * @ingroup NetworkFunction + */ + + + +/* Belcarra public interfaces */ +#include <otg/otg-compat.h> +#include <otg/otg-module.h> +#include <otg/usbp-chap9.h> +#include <otg/usbp-func.h> + + +#include "network.h" +#include "net-os.h" + +#define NTT network_fd_trace_tag +extern otg_tag_t network_fd_trace_tag; + +//#define OFFSET(s,e) (&((s *)0)->e) + +#define ETH_ALEN 6 +#define IP_ALEN 4 +//#define INADDR_BROADCAST ((u32) 0xffffffff) +#define IPPROTO_UDP 17 +#define IPVERSION 4 +#define ETHERTYPE_IP 0x0800 +#define ETHERTYPE_ARP 0x0806 +#define ETHERTYPE_RARP 0x8035 + +#define ARP_ETHERTYPE 0x01 +#define ARP_REQUEST 0x01 +#define ARP_REPLY 0x02 +#define RARP_REQUEST 0x03 +#define RARP_REPLY 0x04 + +#define RARPD_REQUEST 0x08 +#define RARPD_REPLY 0x09 +#define RARPD_ERROR 0x0a + +#define IP_RF 0x8000 +#define IP_DF 0x4000 +#define IP_MF 0x2000 +#define IP_OFFMASK 0x1fff + +/* TOD protocol - RFC868 */ +#define TP_PORT 37 +#define RFC868_EPOCH 0x232661280 +#define RFC868_OFFSET_TO_EPOCH 0x83AA7E80 // RFC868 - seconds from midnight on 1 Jan 1900 GMT to Midnight 1 Jan 1970 GMT + + + +u8 *mac_bcast_addr = "\xff\xff\xff\xff\xff\xff"; +u8 *mac_zero_addr = "\x00\x00\x00\x00\x00\x00"; + +u8 *well_known_host_addr = "\x02\x00\x00\x00\x00\x01"; +u8 *well_known_peripheral_addr = "\x02\x00\x00\x00\x00\x00"; + +/*! @name Structure definitions for each frame section + * + * @{ + */ +/*! @struct my_ether_header net-ip.c "otg/functions/network/net-ip.c" + * + * @brief ether header wrapper + */ +struct my_ether_header { + u8 ether_dhost[ETH_ALEN]; + u8 ether_shost[ETH_ALEN]; + u16 ether_type; +} __attribute__ ((__packed__)); + + +/* ********************************************************************************************** */ +/*! @struct arp_header net-ip.c "otg/functions/network/net-ip.c" + * + * @brief arp_header struct + */ +struct arp_header { + u16 arp_hard; + u16 arp_prot; + u8 arp_hsize; + u8 arp_psize; + u16 arp_op; +} __attribute__ ((__packed__)); + +/*! @struct arp_message net-ip.c "otg/functions/network/net-ip.c" + * + * @brief arp message struct + */ +struct arp_message { + u8 arp_sha[ETH_ALEN]; + u32 arp_sip; + u8 arp_tha[ETH_ALEN]; + u32 arp_tip; +} __attribute__ ((__packed__)); + +/*! @struct arp_frame net-ip.c "otg/functions/network/net-ip.c" + * + * @brief arp frame data structure + */ +struct arp_frame { + struct my_ether_header eh; + struct arp_header aph; + struct arp_message apm; +} __attribute__ ((__packed__)); + + +/* ********************************************************************************************** */ +/*! @struct my_ip net-ip.c "otg/functions/network/net-ip.c" + * + * @brief ip packet structure + */ +struct my_ip { + union { + u8 ip_v; // LSB 4 bits + u8 ip_hl; // MSB 4 bits + }__attribute__ ((__packed__)) ip; + u8 ip_tos; + s16 ip_len; + s16 ip_id; + s16 ip_off; + u8 ip_ttl; + u8 ip_p; + s16 ip_sum; + u32 ip_src; + u32 ip_dst; + +} __attribute__ ((__packed__)); + +/*! @struct my_udphdr net-ip.c "otg/functions/network/net-ip.c" + * + * @brief udb header structure + */ +struct my_udphdr { + u16 uh_sport; + u16 uh_dport; + u16 uh_ulen; + u16 uh_sum; +} __attribute__ ((__packed__)); + +/*! @struct tp_message net-ip.c "otg/functions/network/net-ip.c" + * + * @brief tp message data structure + */ +struct tp_message { + u32 time; +} __attribute__ ((__packed__)); + +/*! @struct tp_frame net-ip.c "otg/functions/network/net-ip.c" + * + * @brief tp frame data structure + */ +struct tp_frame { + struct my_ether_header eh; + struct my_ip iph; + struct my_udphdr udph; + struct tp_message tp_message; +} __attribute__ ((__packed__)); + +/* @} */ +/* ********************************************************************************************** */ +/*! checksum - compute and return checksum for length length ++ * @param buf - buffer ++ * @param length - buffer length ++ * ++ * @return checksum value ++ */ + +u16 checksum(void *buf, int length) { + u32 sum; + u16 *ip = (u16 *) buf; + for (sum = 0; length > 1; length -= 2 ) sum += *ip++; + + // process extra byte if there is one + if( length > 0 ) + sum += * (u8 *) ip; + + // munge + while (sum >> 16) sum = (sum & 0xffff) + (sum >> 16); + + return (u16) ~sum; +} + +/* ********************************************************************************************** */ +extern u32 ip_addr; +extern u32 router_ip; +extern u32 network_mask; +extern u32 dns_server_ip; +extern BOOL do_settime; +#if defined(LINUX24) +extern struct timeval net_fd_tv; +#else +extern struct timespec net_fd_tv; +#endif + +static u32 ip_id = 1; + + +/* ********************************************************************************************** */ + + /*! net_fd_check_tp_response - check for Time Protocol (UDP) response + * @param function_instance - function instance pointer + * @param buffer - buffer area + * @param length - buffer lenght + * @return BOOL value + */ +BOOL net_fd_check_tp_response(struct usbd_function_instance *function_instance, u8 *buffer, int length) +{ + struct usb_network_private *npd = function_instance->privdata; + + struct tp_frame *frame = (struct tp_frame *)buffer; + + unsigned int ip_len; + + struct my_ether_header *eh = (struct my_ether_header *) &frame->eh; + struct my_ip *iph = (struct my_ip *) &frame->iph; + struct my_udphdr *udph = (struct my_udphdr *) &frame->udph; + struct tp_message *tp_message = (struct tp_message *) &frame->tp_message; + + + //TRACE_MSG1(NTT, "length: %d", length); + //TRACE_MSG2(NTT, "length: %d sizeof(my_ether_header): %d", length, sizeof(struct my_ether_header)); + + RETURN_FALSE_IF(length < (sizeof(struct my_ether_header) + sizeof(struct my_ip))); /* sanity check */ + + + //TRACE_MSG6(NTT, "ether_dhost: %02x:%02x:%02x:%02x:%02x:%02x", eh->ether_dhost[0], eh->ether_dhost[1], + // eh->ether_dhost[2], eh->ether_dhost[3], eh->ether_dhost[4], eh->ether_dhost[5]); + //TRACE_MSG6(NTT, "local_addr: %02x:%02x:%02x:%02x:%02x:%02x", npd->local_dev_addr[0], npd->local_dev_addr[1], + // npd->local_dev_addr[2], npd->local_dev_addr[3], npd->local_dev_addr[4], npd->local_dev_addr[5]); + RETURN_FALSE_IF(memcmp(eh->ether_dhost, npd->local_dev_addr, ETH_ALEN)); /* check if broadcast */ + + //TRACE_MSG2(NTT, "ether_type: %04x %04x", ETHERTYPE_IP, ntohs(eh->ether_type)); + RETURN_FALSE_IF(ETHERTYPE_IP != ntohs(eh->ether_type)); /* check ethernet frame type */ + + //TRACE_MSG1(NTT, "ip_v: %04x", iph->ip.ip_v & 0xf0 >> 4 ); + + RETURN_FALSE_IF(IPVERSION != (iph->ip.ip_v & 0xf0) >> 4); /* check for ipv4 */ + + //TRACE_MSG3(NTT, "ip_hl: %04x %04x %04x", iph->ip.ip_v & 0xf, 5, (length - sizeof(struct my_ether_header) )); + + RETURN_FALSE_IF((iph->ip.ip_hl & 0x0f) < 5); /* check ipv4 header length ok */ + RETURN_FALSE_IF( ((iph->ip.ip_hl & 0x0f) * 4) > (u8)(length - sizeof(struct my_ether_header))); + + //TRACE_MSG2(NTT, "ip_len: %04x %04x", iph->ip_len, (length - sizeof(struct my_ether_header))); + RETURN_FALSE_IF(ntohs(iph->ip_len) > (length - sizeof(struct my_ether_header))); /* check ipv4 total length ok */ + + //TRACE_MSG2(NTT, "ip_p: %04x %04x", iph->ip_p, IPPROTO_UDP); + RETURN_FALSE_IF(IPPROTO_UDP != iph->ip_p); /* check for UDP */ + + udph = (struct my_udphdr *) (((u8*) iph) + ((iph->ip.ip_hl & 0x0f) * 4)); /* check for TP port 37 */ + //TRACE_MSG2(NTT, "uh_dport: %04x uh_sport: %04x", udph->uh_dport, udph->uh_sport); + RETURN_FALSE_IF((TP_PORT != ntohs(udph->uh_dport)) || ((TP_PORT != ntohs(udph->uh_sport)))); + + //TRACE_MSG3(NTT, "found port: uh_ulen: %x %x sizeof: %x", + // udph->uh_ulen, ntohs(udph->uh_ulen), sizeof(struct tp_message)); + + RETURN_FALSE_UNLESS(ntohs(udph->uh_ulen)); /* check if zero length payload */ + //TRACE_MSG0(NTT, "uh_ulen ok"); + + /* get time, convert to Unix time - seconds since midnight 1 jan 1970 + */ + npd->rfc868time = tp_message->time; + //net_os_settime(function_instance, ntohl(tp_message->time)); + + TRACE_MSG0(NTT, "found"); + return TRUE; +} + + +/*! net_fd_send_tp_request - allocate and send Time Protocol (UDP) request + * + * @param function_instance - function instance + * @return none + */ +void net_fd_send_tp_request(struct usbd_function_instance *function_instance) +{ + struct usb_network_private *npd = function_instance->privdata; + struct tp_frame tp_frame; + struct tp_frame *tx = &tp_frame; + //struct tp_message *todpmsg = &tp_frame.tp_message; + + + char buffer[6]; + + //TRACE_MSG2(NTT, "sizeof(my_ip) sizeof(tp_frame): %d XXX", sizeof(struct my_ip), sizeof(struct tp_frame)); + + memset(&tp_frame, 0, sizeof(struct tp_frame)); + + /* ethernet frame - provisionally setup with client mac address */ + tx->eh.ether_type = htons(ETHERTYPE_IP); + memset(tx->eh.ether_dhost, 0xff, ETH_ALEN); + memcpy(buffer, npd->local_dev_addr, ETH_ALEN); + memcpy(tx->eh.ether_shost, buffer, ETH_ALEN); + memcpy(tx->eh.ether_shost, npd->local_dev_addr, ETH_ALEN); + + /* ip header */ + tx->iph.ip.ip_v = (IPVERSION << 4) | 5; + tx->iph.ip_src = htonl(npd->ip_addr); + tx->iph.ip_dst = htonl(npd->router_ip); + tx->iph.ip_p = IPPROTO_UDP; + + + /* udp - checksum will be added later */ + tx->udph.uh_sport = htons(TP_PORT); + tx->udph.uh_dport = htons(TP_PORT); + tx->udph.uh_ulen = htons(sizeof(struct my_udphdr) + sizeof(struct tp_message)); + + //TRACE_MSG6(NTT, "port: %04x %04x %04x:%04x %04x:%04x", tx->udph.uh_sport, tx->udph.uh_dport, + // TP_PORT, htons(TP_PORT), 0x1234, htons(0x1234)); + + /* udp checksum - we can check sum entire ip packet as rest is zero */ + tx->udph.uh_sum = checksum( &(tx->iph), + sizeof(struct my_ip) + sizeof(struct my_udphdr) + sizeof(struct tp_message)); + + /* ip - done here after udp checksum has */ + tx->iph.ip.ip_hl |= sizeof(struct my_ip) >> 2; // XXX + tx->iph.ip.ip_v |= IPVERSION << 4; // XXX + + tx->iph.ip_id = htons(ip_id++); + tx->iph.ip_ttl = 0x80; + + tx->iph.ip_len = htons(sizeof(struct my_ip) + sizeof(struct my_udphdr) + sizeof(struct tp_message)); + tx->iph.ip_sum = checksum(&(tx->iph), sizeof(struct my_ip)); + + net_fd_start_xmit(function_instance, (u8 *) tx, sizeof(tp_frame), NULL); + + TRACE_MSG1(NTT, "EXIT: tp_frame: %p", &tp_frame); +} + +/* ********************************************************************************************** */ + +/*! net_fd_check_rarp_reply - check for RARP (UDP) response + * + * @param function_instance - function instance + * @param buffer - buffer pointer + * @param length - buffer length + * @return BOOL value + */ +BOOL net_fd_check_rarpd_reply(struct usbd_function_instance *function_instance, u8 *buffer, int length) +{ + struct usb_network_private *npd = function_instance->privdata; + + struct arp_frame *frame = (struct arp_frame *)buffer; + + //unsigned int ip_len; + + + struct my_ether_header *eh = (struct my_ether_header *) &frame->eh; + struct arp_header *aph = (struct arp_header *) &frame->aph; + struct arp_message *apm = (struct arp_message *) &frame->apm; + + + //TRACE_MSG2(NTT, "length: %d sizeof(my_ether): %d", length, sizeof(struct arp_frame)); + RETURN_FALSE_IF(length < (sizeof(struct arp_frame))); /* sanity check */ + + //TRACE_MSG6(NTT, "ether_dhost: %02x:%02x:%02x:%02x:%02x:%02x", + // eh->ether_dhost[0], eh->ether_dhost[1], + // eh->ether_dhost[2], eh->ether_dhost[3], + // eh->ether_dhost[4], eh->ether_dhost[5]); + + //TRACE_MSG6(NTT, "local_addr: %02x:%02x:%02x:%02x:%02x:%02x", + // well_known_peripheral_addr[0], well_known_peripheral_addr[1], + // well_known_peripheral_addr[2], well_known_peripheral_addr[3], + // well_known_peripheral_addr[4], well_known_peripheral_addr[5]); + + RETURN_FALSE_IF(memcmp(eh->ether_dhost, well_known_peripheral_addr, ETH_ALEN)); /* check if broadcast */ + + //TRACE_MSG2(NTT, "ether_type: %04x %04x", ETHERTYPE_RARP, ntohs(eh->ether_type)); + RETURN_FALSE_IF(ETHERTYPE_RARP != ntohs(eh->ether_type)); /* ethernet frame type RARP */ + + + //TRACE_MSG2(NTT, "arp_hard: %04x %04x", ARP_ETHERTYPE, ntohs(aph->arp_hard)); + RETURN_FALSE_IF(ARP_ETHERTYPE != ntohs(aph->arp_hard)); /* check hard type */ + + //TRACE_MSG2(NTT, "arp_op: %04x %04x", RARPD_REPLY, ntohs(aph->arp_op)); + RETURN_FALSE_IF(RARPD_REPLY != ntohs(aph->arp_op)); /* check op for RARP request */ + + //TRACE_MSG2(NTT, "arp_prot: %04x %04x", ETHERTYPE_IP, ntohs(aph->arp_prot)); + RETURN_FALSE_IF(ETHERTYPE_IP != ntohs(aph->arp_prot)); /* check prot */ + + //TRACE_MSG2(NTT, "hsize: %04x psize: %04x", aph->arp_hsize, aph->arp_psize); + RETURN_FALSE_IF(ETH_ALEN != aph->arp_hsize); /* check hard size */ + RETURN_FALSE_IF(4 != aph->arp_psize); /* check prot size */ + + /* check if for us */ + //TRACE_MSG1(NTT, "arp_tip: %08x", apm->arp_tip); + //RETURN_FALSE_IF(apm->arp_tip != htonl(ip_server(oldFrame))); + + + // XXX what about locally set addr + // XXX UNLESS(memcmp(npd->local_dev_addr, mac_zero_addr, ETH_ALEN)) + // memcpy(npd->local_dev_addr, apm->arp_tha, ETH_ALEN); + TRACE_MSG0(NTT, "sos908"); + if(!(npd->override_MAC)) memcpy(npd->local_dev_addr, apm->arp_tha, ETH_ALEN); + //TRACE_MSG6(NTT, "arp_tha: %02x:%02x:%02x:%02x:%02x:%02x", apm->arp_tha[0], apm->arp_tha[1], + // apm->arp_tha[2], apm->arp_tha[3], apm->arp_tha[4], apm->arp_tha[5]); + + npd->ip_addr = ntohl(apm->arp_tip); + npd->router_ip = ntohl(apm->arp_sip); + + //TRACE_MSG1(NTT, "arp_tip: %08x", apm->arp_tip); + + //net_os_config(function_instance); + //net_os_hotplug(function_instance); + + //net_fd_send_tp_request(function_instance); + + + TRACE_MSG0(NTT, "found"); + return TRUE; +} + + /*! net_fd_check_rarp_request - check for RARP (UDP) response + * + * @param function_instance - function instance + * @param buffer - buffer pointer + * @param length - buffer length + * @return BOOL value + */ +BOOL net_fd_check_rarpd_request(struct usbd_function_instance *function_instance, u8 *buffer, int length) +{ + struct usb_network_private *npd = function_instance->privdata; + + struct arp_frame *frame = (struct arp_frame *)buffer; + + //unsigned int ip_len; + + + struct my_ether_header *eh = (struct my_ether_header *) &frame->eh; + struct arp_header *aph = (struct arp_header *) &frame->aph; + //struct arp_message *apm = (struct arp_message *) &frame->apm; + + + //TRACE_MSG2(NTT, "length: %d sizeof(my_ether): %d", length, sizeof(struct arp_frame)); + RETURN_FALSE_IF(length < (sizeof(struct arp_frame))); /* sanity check */ + + //TRACE_MSG6(NTT, "ether_dhost: %02x:%02x:%02x:%02x:%02x:%02x", eh->ether_dhost[0], eh->ether_dhost[1], + // eh->ether_dhost[2], eh->ether_dhost[3], eh->ether_dhost[4], eh->ether_dhost[5]); + //TRACE_MSG6(NTT, "local_addr: %02x:%02x:%02x:%02x:%02x:%02x", npd->local_dev_addr[0], npd->local_dev_addr[1], + // npd->local_dev_addr[2], npd->local_dev_addr[3], npd->local_dev_addr[4], npd->local_dev_addr[5]); + + //TRACE_MSG2(NTT, "ether_type: %04x %04x", ETHERTYPE_RARP, ntohs(eh->ether_type)); + + RETURN_FALSE_IF(memcmp(eh->ether_dhost, mac_bcast_addr, ETH_ALEN)); /* check if broadcast */ + RETURN_FALSE_IF(ETHERTYPE_RARP != ntohs(eh->ether_type)); /* ethernet frame type RARP */ + + + TRACE_MSG2(NTT, "arp_hard: %04x %04x", ARP_ETHERTYPE, ntohs(aph->arp_hard)); + RETURN_FALSE_IF(ARP_ETHERTYPE != ntohs(aph->arp_hard)); /* check hard type */ + + TRACE_MSG2(NTT, "arp_op: %04x %04x", RARPD_REPLY, ntohs(aph->arp_op)); + RETURN_FALSE_IF(RARPD_REQUEST != ntohs(aph->arp_op)); /* check op for RARP request */ + + TRACE_MSG2(NTT, "arp_prot: %04x %04x", ETHERTYPE_IP, ntohs(aph->arp_prot)); + RETURN_FALSE_IF(ETHERTYPE_IP != ntohs(aph->arp_prot)); /* check prot */ + + //TRACE_MSG2(NTT, "hsize: %04x psize: %04x", aph->arp_hsize, aph->arp_psize); + RETURN_FALSE_IF(ETH_ALEN != aph->arp_hsize); /* check hard size */ + RETURN_FALSE_IF(4 != aph->arp_psize); /* check prot size */ + + /* check if for us */ + //TRACE_MSG1(NTT, "arp_tip: %08x", apm->arp_tip); + //THROW_IF(apm->arp_tip != htonl(ip_server(oldFrame))); + // + + //UNLESS(memcmp(npd->local_dev_addr, mac_zero_addr, ETH_ALEN)) + // memcpy(npd->local_dev_addr, apm->arp_tha, ETH_ALEN); + + //TRACE_MSG6(NTT, "arp_tha: %02x:%02x:%02x:%02x:%02x:%02x", apm->arp_tha[0], apm->arp_tha[1], + // apm->arp_tha[2], apm->arp_tha[3], apm->arp_tha[4], apm->arp_tha[5]); + + //npd->ip_addr = ntohl(apm->arp_tip); + //npd->router_ip = ntohl(apm->arp_sip); + + //TRACE_MSG1(NTT, "arp_tip: %08x", apm->arp_tip); + + //net_os_config(function_instance); + //net_os_hotplug(function_instance); + + //net_fd_send_tp_request(function_instance); + + + TRACE_MSG0(NTT, "found"); + return TRUE; +} + +/*! net_fd_send_rarpd_request - allocate and send RARP request + * + * @param function_instance - function instance + * @return none + */ +void net_fd_send_rarpd_request(struct usbd_function_instance *function_instance) +{ + struct usb_network_private *npd = function_instance->privdata; + struct arp_frame arp_frame; + //struct arp_header *aph = (struct arp_header *) &arp_frame.aph; + //struct arp_message *apm = (struct arp_message *) &arp_frame.apm; + struct arp_frame *tx = &arp_frame; + + //TRACE_MSG1(NTT, "sizeof(arp_frame): %d XXX", sizeof(struct arp_frame)); + + memset(tx, 0, sizeof(struct arp_frame)); + + /* copy original frame */ + //memcpy(tx, rx, sizeof(struct arp_frame)); + + /* set mac addresses */ + memset(tx->eh.ether_dhost, 0xff, ETH_ALEN); + memcpy(tx->eh.ether_shost, well_known_peripheral_addr, ETH_ALEN); + + /* set target ethernet addr */ + memset(tx->apm.arp_tha, 0xff, ETH_ALEN+4); + memcpy(tx->apm.arp_sha, npd->local_dev_addr, ETH_ALEN); + + tx->eh.ether_type = htons(ETHERTYPE_RARP); + tx->aph.arp_prot = htons(ETHERTYPE_IP ); + tx->aph.arp_hard = htons(ARP_ETHERTYPE ); + tx->aph.arp_op = htons(RARPD_REQUEST); + tx->aph.arp_hsize = ETH_ALEN; + tx->aph.arp_psize = 4; + tx->apm.arp_sip = htonl(0); + tx->apm.arp_tip = htonl(0); + + // checksum + + net_fd_start_xmit(function_instance, (u8 *) tx, sizeof(arp_frame), NULL); + + TRACE_MSG1(NTT, "frame: %p exit", &arp_frame); +} + + + /*! net_fd_send_rarpd_reply - allocate and send RARP request + * @param function_instance - function instance + * @return none + */ +void net_fd_send_rarpd_reply(struct usbd_function_instance *function_instance) +{ + struct usb_network_private *npd = function_instance->privdata; + struct arp_frame arp_frame; + //struct arp_header *aph = (struct arp_header *) &arp_frame.aph; + //struct arp_message *apm = (struct arp_message *) &arp_frame.apm; + struct arp_frame *tx = &arp_frame; + + //TRACE_MSG1(NTT, "sizeof(arp_frame): %d XXX", sizeof(struct arp_frame)); + + memset(tx, 0, sizeof(struct arp_frame)); + + /* copy original frame */ + //memcpy(tx, rx, sizeof(struct arp_frame)); + + /* set mac addresses */ + memset(tx->eh.ether_dhost, 0xff, ETH_ALEN); + memcpy(tx->eh.ether_shost, npd->local_dev_addr, ETH_ALEN); + + /* set target ethernet addr */ + memset(tx->apm.arp_tha, 0xff, ETH_ALEN+4); + memcpy(tx->apm.arp_sha, npd->local_dev_addr, ETH_ALEN); + + tx->eh.ether_type = htons(ETHERTYPE_RARP); + tx->aph.arp_prot = htons(ETHERTYPE_IP ); + tx->aph.arp_hard = htons(ARP_ETHERTYPE ); + tx->aph.arp_op = htons(RARPD_REQUEST); + tx->aph.arp_hsize = ETH_ALEN; + tx->aph.arp_psize = 4; + tx->apm.arp_sip = htonl(0); + tx->apm.arp_tip = htonl(0); + + // checksum + + net_fd_start_xmit(function_instance, (u8 *) tx, sizeof(arp_frame), NULL); + + TRACE_MSG1(NTT, "frame: %p exit", &arp_frame); +} + +/* End of FILE */ diff --git a/drivers/otg/functions/network/net-l24-fix.c b/drivers/otg/functions/network/net-l24-fix.c new file mode 100644 index 000000000000..c744b46e8be1 --- /dev/null +++ b/drivers/otg/functions/network/net-l24-fix.c @@ -0,0 +1,89 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/functions/network/net-l24-fix.c - Network Function Driver + * @(#) sl@belcarra.com/whiskey.enposte.net|otg/functions/network/net-l24-fix.c|20070814002638|51510 + * + * Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + * + * By: + * Stuart Lynne <sl@belcarra.com> + * Bruce Balden <balden@belcarra.com> + * + * + */ +/*! + * @file otg/functions/network/net-l24-fix.c + * @brief The Linux 2.4 OS specific upper edge (network interface) + * implementation for the Network Function Driver. + * + * This file implements a standard Linux network driver interface and + * the standard Linux 2.4 module init and exit functions. + * + * If compiled into the kernel, this driver can be used with NFSROOT to + * provide the ROOT filesystem. Please note that the kernel NFSROOT support + * (circa 2.4.20) can have problems if there are multiple interfaces. So + * it is best to ensure that there are no other network interfaces compiled + * in. + * + * + * @ingroup NetworkFunction + * @ingroup LINUXOS + */ + +#include <asm/uaccess.h> +#include <asm/system.h> +#include <linux/bitops.h> +#include <linux/capability.h> +#include <linux/module.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/string.h> +#include <linux/mm.h> +#include <linux/socket.h> +#include <linux/sockios.h> +#include <linux/in.h> +#include <linux/errno.h> +#include <linux/interrupt.h> +#include <linux/if_ether.h> +#include <linux/inet.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> +#include <linux/rtnetlink.h> +#include <linux/init.h> +#include <linux/notifier.h> +#include <linux/inetdevice.h> +#include <linux/igmp.h> +#ifdef CONFIG_SYSCTL +#include <linux/sysctl.h> +#endif +#include <linux/kmod.h> + +#include <net/arp.h> +#include <net/ip.h> +#include <net/route.h> +#include <net/ip_fib.h> + + +#if defined(CONFIG_OTG_NETWORK_BLAN_AUTO_CONFIG) || defined(CONFIG_OTG_NETWORK_RARPD_AUTO_CONFIG) +extern int devinet_ioctl(unsigned int , void __user *); + +int local_devinet_ioctl(unsigned int cmd, void __user *data) +{ + return devinet_ioctl(cmd, data); +} + +EXPORT_SYMBOL(local_devinet_ioctl); +#endif /* defined(CONFIG_OTG_NETWORK_BLAN_AUTO_CONFIG) || defined(CONFIG_OTG_NETWORK_RARPD_AUTO_CONFIG) */ diff --git a/drivers/otg/functions/network/net-l24-os.c b/drivers/otg/functions/network/net-l24-os.c new file mode 100644 index 000000000000..30b8da94fea9 --- /dev/null +++ b/drivers/otg/functions/network/net-l24-os.c @@ -0,0 +1,1682 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/functions/network/net-l24-os.c - Network Function Driver + * @(#) sl@belcarra.com/whiskey.enposte.net|otg/functions/network/net-l24-os.c|20070814184652|52070 + * + * Copyright (c) 2002-2006 Belcarra Technologies Corp + * Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + * + * By: + * Stuart Lynne <sl@belcarra.com> + * Bruce Balden <balden@belcarra.com> + * + * + */ +/* + * @file otg/functions/network/net-l24-os.c + * @brief The Linux 2.4 OS specific upper edge (network interface) + * implementation for the Network Function Driver. + * + * This file implements a standard Linux network driver interface and + * the standard Linux 2.4 module init and exit functions. + * + * If compiled into the kernel, this driver can be used with NFSROOT to + * provide the ROOT filesystem. Please note that the kernel NFSROOT support + * (circa 2.4.20) can have problems if there are multiple interfaces. So + * it is best to ensure that there are no other network interfaces compiled + * in. + * @ingroup NetworkFunction + * @ingroup LINUXOS + */ + + +#include <otg/otg-compat.h> + +#if defined(CONFIG_OTG_LNX) || defined(_OTG_DOXYGEN) + +/* OS-specific #includes */ +#include <linux/kernel.h> +#include <linux/version.h> +#include <linux/ctype.h> +#include <linux/netdevice.h> +#include <linux/skbuff.h> +#include <linux/etherdevice.h> +#include <linux/in.h> +#include <linux/inetdevice.h> + + +/* Belcarra public interfaces */ +#include <otg/otg-compat.h> +#include <otg/otg-module.h> +#include <otg/usbp-chap9.h> +#include <otg/usbp-func.h> + +/* network private interfaces */ +#include "network.h" +#include "net-os.h" + +#define TRACE_VERBOSE 0 + +MOD_AUTHOR ("sl@belcarra.com, tbr@belcarra.com, balden@belcarra.com"); + +EMBED_LICENSE(); + +MOD_DESCRIPTION ("USB Network Function"); +EMBED_USBD_INFO ("network_fd 2.0-beta"); + +MOD_PARM_STR (local_dev_addr, "Local Device Address", NULL); +MOD_PARM_STR (remote_dev_addr, "Remote Device Address", NULL); +MOD_PARM_BOOL (override_mac, "Override MAC address", 0); + +MOD_PARM_BOOL (personal_device, "Do not implement an infrastructure device", FALSE); +MOD_PARM_BOOL (infrastructure_device, "Implement an infrastructure device", FALSE); + +int blan_mod_init(void); +void blan_mod_exit(void); + +int basic_mod_init(void); +void basic_mod_exit(void); + +int basic2_mod_init(void); +void basic2_mod_exit(void); + +int ecm_mod_init(void); +void ecm_mod_exit(void); + +int safe_mod_init(void); +void safe_mod_exit(void); + +int eem_mod_init(void); +void eem_mod_exit(void); + +#define NTT network_fd_trace_tag +otg_tag_t network_fd_trace_tag; +wait_queue_head_t usb_netif_wq; +#ifdef CONFIG_OTG_NET_NFS_SUPPORT +int usb_is_configured; +#endif + +/* Module Parameters ************************************************************************* */ + + +/* End of Module Parameters ****************************************************************** */ + +static u8 zeros[ETH_ALEN]; + +/* Prevent overlapping of bus administrative functions: + * + * network_function_enable + * network_function_disable + * network_hard_start_xmit + */ +DECLARE_MUTEX(usbd_network_sem); + +struct net_device Network_net_device; +struct net_device_stats Network_net_device_stats; /* network device statistics */ +struct usb_network_private Network_private; + +void notification_schedule_bh (void); +int network_urb_sent_int (struct usbd_urb *urb, int urb_rc); + +static u32 network_router_ip = 0; + +extern void usbd_write_info_message(struct usbd_function_instance*, char *msg); + + +//_________________________________________________________________________________________________ + +/* + * Synchronization + * + * + * Notification bottom half + * + * This is a scheduled task that will send an interrupt notification. Because it + * is called from the task scheduler context it needs to verify that the device is + * still usable. + * + * static int network_send_int_blan(struct usbd_simple_instance *, int ) + * static void notification_bh (void *) + * void notification_schedule_bh (void) + * + * + * Netdevice functions + * + * These are called by the Linux network layer. They must be protected by irq locks + * if necessary to prevent interruption by IRQ level events. + * + * int network_init (struct net_device *net_device) + * void network_uninit (struct net_device *net_device) + * int network_open (struct net_device *net_device) + * int network_stop (struct net_device *net_device) + * struct net_device_stats *network_get_stats (struct net_device *net_device) + * int network_set_mac_addr (struct net_device *net_device, void *p) + * void network_tx_timeout (struct net_device *net_device) + * int network_set_config (struct net_device *net_device, struct ifmap *map) + * int network_stop (struct net_device *net_device) + * int network_hard_start_xmit (struct sk_buff *skb, struct net_device *net_device) + * int network_do_ioctl (struct net_device *net_device, struct ifreq *rp, int cmd) + * + * + * Data bottom half functions + * + * These are called from the bus bottom half handler. + * + * static int network_recv (struct usb_network_private *, struct net_device *, struct sk_buff *) + * int network_recv_urb (struct usbd_urb *) + * int network_urb_sent (struct usbd_urb *, int ) + * + * + * Hotplug bottom half: + * + * This is a scheduled task that will send do a hotplug call. Because it is + * called from the task scheduler context it needs to verify that the + * device is still usable. + * + * static int hotplug_attach (u32 ip, u32 mask, u32 router, int attach) + * static void hotplug_bh (void *data) + * void net_os_hotplug (void) + * + * + * Irq level functions: + * + * These are called at interrupt time do process or respond to USB setup + * commands. + * + * int network_device_request (struct usbd_device_request *) + * void network_event_handler (struct usbd_simple_instance *function, usbd_device_event_t event, int data) + * + * + * Enable and disable functions: + * + * void network_function_enable (struct usbd_simple_instance *, struct usbd_simple_instance *) + * void network_function_disable (struct usbd_simple_instance *function) + * + * + * Driver initialization and exit: + * + * static int network_create (struct usb_network_private *) + * static void network_destroy (struct usb_network_private *) + * + * int network_modinit (void) + * void network_modexit (void) + */ + + +//_______________________________USB part Functions_________________________________________ + +/*! net_os_mutex_enter - enter mutex region + * @param function_instance - pointer to function instance + * @return none + */ +void net_os_mutex_enter(struct usbd_function_instance *function_instance) +{ + struct usb_network_private *npd = + (struct usb_network_private *) (function_instance ? function_instance->privdata : NULL); + down(&usbd_network_sem); +} + +/* net_os_mutex_exit - exit mutex region + * @param function_instance pointer to function instance + * @return none + */ +void net_os_mutex_exit(struct usbd_function_instance *function_instance) +{ + struct usb_network_private *npd = + (struct usb_network_private *) (function_instance ? function_instance->privdata : NULL); + up(&usbd_network_sem); +} + +/*! notification_bh - Bottom half handler to send a notification status + * + * Send a notification with open/close status + * + * It should not be possible for this to be called more than once at a time + * as it is only called via schedule_task() which protects against a second + * invocation. + * + * @param data pointer to usbd_function_instance + * @return none + */ +STATIC void *notification_bh (void *data) +{ + struct usb_network_private *npd = (struct usb_network_private *) data; + struct usbd_function_instance *function_instance = (struct usbd_function_instance *) npd->function_instance; + // XXX unsigned long flags; + RETURN_NULL_UNLESS(npd); + // XXX local_irq_save(flags); + net_fd_send_int_notification(function_instance, npd->flags & NETWORK_OPEN, 0); + // XXX local_irq_restore(flags); + return NULL; +} + +/* notification_schedule_bh - schedule a call for notification_bh + * + * @param function_instance pointer to function instance + * @reutrn none + */ +void net_os_send_notification_later(struct usbd_function_instance *function_instance) +{ + struct usb_network_private *npd = + (struct usb_network_private *) (function_instance ? function_instance->privdata : NULL); + RETURN_UNLESS(npd); + otg_workitem_start(npd->notification_workitem); +} + +/*! net_os_xmit_done - called from USB part when a transmit completes, good or bad. + * + * @param function_instance pointer to function instance + * @param data pointer passed to fd_ops->start_ximit() + * @param tx_rc urb transmit result code, which is USBD_URB_ERROR, USBD_URB_CANCELLED or... + * @return non-zero only if network does not exist, ow 0. + */ +int net_os_xmit_done(struct usbd_function_instance *function_instance, void *data, int tx_rc) +{ + struct sk_buff *skb = (struct sk_buff *) data; + struct usb_network_private *npd = + (struct usb_network_private *) (function_instance ? function_instance->privdata : NULL); + struct net_device *net_device = npd ? npd->net_device : NULL; + int rc = 0; + + //TRACE_MSG4(NTT, "function: %x npd: %x skb: %x", function_instance, npd, skb, net_device); + + RETURN_ZERO_UNLESS(skb); + dev_kfree_skb_any(skb); + + RETURN_ZERO_UNLESS(net_device); + if ( net_os_queue_stopped(function_instance)) + net_os_wake_queue(function_instance); + + RETURN_ZERO_UNLESS(npd); + + switch (tx_rc) { + case USBD_URB_ERROR: + npd->net_device_stats->tx_errors++; + npd->net_device_stats->tx_dropped++; + break; + case USBD_URB_CANCELLED: + npd->net_device_stats->tx_errors++; + npd->net_device_stats->tx_carrier_errors++; + break; + default: + break; + } + + return 0; +} + +/*! net_os_dealloc_buffer - deallocate data buffer + * @param function_instance pointer to function instance + * @param data pointer to data buffer to deallocate + * @param buffer * @return none + * @return none + */ +void net_os_dealloc_buffer(struct usbd_function_instance *function_instance, void *data, void *buffer) +{ + struct usb_network_private *npd = + (struct usb_network_private *) (function_instance ? function_instance->privdata : NULL); + struct sk_buff *skb = data; + RETURN_UNLESS(skb); + dev_kfree_skb_any(skb); + RETURN_UNLESS(npd); + npd->net_device_stats->rx_dropped++; +} + +/*! net_os_alloc_buffer - allocate a buffer + * @param function_instance pointer to function instance + * @param cp + * @param n + * @return allocated buffer of void pointer type + */ +void *net_os_alloc_buffer(struct usbd_function_instance *function_instance, u8 **cp, int n) +{ + struct usb_network_private *npd = + (struct usb_network_private *) (function_instance ? function_instance->privdata : NULL); + struct sk_buff *skb; + + /* allocate skb of appropriate length, reserve 2 to align ip + */ + RETURN_NULL_UNLESS ((skb = dev_alloc_skb(n+32))); + //TRACE_MSG1(NTT, "skb: %x", skb); + + skb_reserve(skb, 4); + while (((int)skb->data) & 0xf) + skb_reserve(skb, 1); + *cp = skb_put(skb, n); + + + + //TRACE_MSG7(NTT, "skb: %x head: %x data: %x tail: %x end: %x len: %d tail-data: %d", + // skb, skb->head, skb->data, skb->tail, skb->end, skb->len, skb->tail - skb->data); + + return (void*) skb; +} + +/*! network_recv - function to process an received data URB + * + * Passes received data to the network layer. Passes skb to network layer. + * @param function_instance pointer to function instance + * @param net_device pointer to current net device + * @param skb pointer to sk_buff struct + * + * + * @return non-zero for failure. + */ +STATIC __inline__ int network_recv (struct usbd_function_instance *function_instance, + struct net_device *net_device, struct sk_buff *skb) +{ + struct usb_network_private *npd = + (struct usb_network_private *) (function_instance ? function_instance->privdata : NULL); + int rc; + + RETURN_ZERO_UNLESS(npd); +#if 0 + printk(KERN_INFO"%s: len: %x head: %p data: %p tail: %p\n", __FUNCTION__, + skb->len, skb->head, skb->data, skb->tail); + { + u8 *cp = skb->data; + int i; + for (i = 0; i < skb->len; i++) { + if ((i%32) == 0) { + printk("\nrx[%2x] ", i); + } + printk("%02x ", *cp++); + } + printk("\n"); + } +#endif + +#if 0 + TRACE_MSG4(NTT, "len: %x head: %p data: %p tail: %p", + skb->len, skb->head, skb->data, skb->tail); + { + int i; + + for (i = 0; i < skb->len; i += 8) { + u8 *cp = skb->data + i; + TRACE_MSG8(NTT, "[ %02x %02x %02x %02x %02x %02x %02x %02x]", + cp[0], cp[1], + cp[2], cp[3], + cp[4], cp[5], + cp[6], cp[7] + ); + } + } +#endif + + /* refuse if no device present + */ + if (!netif_device_present (net_device)) { + TRACE_MSG0(NTT,"device not present"); + printk(KERN_INFO"%s: device not present\n", __FUNCTION__); + return -EINVAL; + } + + /* refuse if no carrier + */ + if (!netif_carrier_ok (net_device)) { + TRACE_MSG0(NTT,"no carrier"); + printk(KERN_INFO"%s: no carrier\n", __FUNCTION__); + return -EINVAL; + } + + /* refuse if the net device is down + */ + if (!(net_device->flags & IFF_UP)) { + TRACE_MSG1(NTT,"USB Net interface is not up net_dev->flags: %x", net_device->flags); +// printk(KERN_INFO"%s: USB Net interface is not up net_dev->flags: %x\n", __FUNCTION__, net_device->flags); + //npd->net_device_stats->rx_dropped++; + return -EINVAL; + } + + skb->dev = net_device; + skb->pkt_type = PACKET_HOST; + skb->protocol = eth_type_trans (skb, net_device); + skb->ip_summed = CHECKSUM_UNNECESSARY; + + /* pass it up to kernel networking layer + */ + if ((rc = netif_rx (skb))) + TRACE_MSG1(NTT,"netif_rx rc: %d", rc); + + npd->net_device_stats->rx_bytes += skb->len; + npd->net_device_stats->rx_packets++; + + return 0; +} + +/*! net_os_recv_buffer - forward a received URB, or clean up after a bad one. + * @param function_instance pointer to function instance + * @param os_data == NULL --> just accumulate stats (count 1 bad buff) + * @param os_buffer + * @param crc_bad != 0 --> count a crc error and free data/skb + * @param length + * @param trim --> amount to trim from valid skb + * @return non-zero for error + */ +int net_os_recv_buffer(struct usbd_function_instance *function_instance, void *os_data, + void *os_buffer, int crc_bad, int length, int trim) +{ + struct usb_network_private *npd = + (struct usb_network_private *) (function_instance ? function_instance->privdata : NULL); + struct sk_buff *skb = (struct sk_buff *) os_data; + + RETURN_EAGAIN_UNLESS(npd); + + //TRACE_MSG7(NTT, "skb: %x head: %x data: %x tail: %x end: %x len: %d tail-data: %d", + // skb, skb->head, skb->data, skb->tail, skb->end, skb->len, skb->tail - skb->data); + + //TRACE_MSG3(NTT, "npd: %x skb: %x flags: %04x", npd, skb, npd->flags); + + UNLESS (skb) { + /* Lower layer never got around to allocating an skb, but + * needs to count a packet it can't forward. + */ + npd->net_device_stats->rx_frame_errors++; + npd->net_device_stats->rx_errors++; + TRACE_MSG0(NTT, "NO SKB"); + return -EAGAIN; + } + +#if 0 + /* There is an skb, either forward it or free it. + */ + if (crc_bad) { + npd->net_device_stats->rx_crc_errors++; + npd->net_device_stats->rx_errors++; + TRACE_MSG0(NTT, "BAD CRC"); + return -EAGAIN; + } +#endif + /* is the network up? + */ + UNLESS (npd->net_device) { + + // Something wrong, free the skb + //dev_kfree_skb_any (skb); + // The received buffer didn't get forwarded, so... + npd->net_device_stats->rx_dropped++; + TRACE_MSG0(NTT, "NO NETWORK"); + return -EAGAIN; + } + + /* Trim if necessary and pass up + */ + if (trim) + skb_trim(skb, skb->len - trim); + + // 6 6 2 46 + // 802.3 requires minimum 46 bytes of data, + // + + return network_recv(function_instance, npd->net_device, skb); +} + +extern void *config_bh (void *data); +STATIC void *hotplug_bh (void *data); + +/*! net_os_enable - called to enable network device + * @param function_instance pointer to function instance + * + * @return none + * + */ +void net_os_enable(struct usbd_function_instance *function_instance) +{ + struct usb_network_private *npd = &Network_private; + + function_instance->privdata = npd; + + TRACE_MSG3(NTT,"npd: %p function: %p, f->p: %p", npd, function_instance, function_instance->privdata); + + memset(&Network_net_device_stats, 0, sizeof Network_net_device_stats); + npd->function_instance = function_instance; + npd->max_queued_frames = 10; + npd->max_queued_bytes = 20000; + + #if defined(CONFIG_OTG_NETWORK_BLAN_AUTO_CONFIG) || defined(CONFIG_OTG_NETWORK_RARPD_AUTO_CONFIG) + THROW_UNLESS((npd->config_otgtask = otg_task_init2("netcfg", config_bh, npd, NTT)), error); + //npd->config_otgtask->debug = TRUE; + otg_task_start(npd->config_otgtask); + #endif /* defined(CONFIG_OTG_NETWORK_BLAN_AUTO_CONFIG) || defined(CONFIG_OTG_NETWORK_RARPD_AUTO_CONFIG) */ + + #ifdef CONFIG_OTG_NETWORK_HOTPLUG + THROW_UNLESS(npd->hotplug_workitem = otg_workitem_init("hotplug", hotplug_bh, npd, NTT), error); + //npd->hotplug_workitem->debug = TRUE; + #endif /* CONFIG_OTG_NETWORK_HOTPLUG */ + + THROW_UNLESS(npd->notification_workitem = otg_workitem_init("netint", notification_bh, npd, NTT), error); + //npd->notification_workitem->debug = TRUE; + + /* set the network device address from the local device address + */ + //memcpy(npd->net_device->dev_addr, npd->local_dev_addr, ETH_ALEN); + + CATCH(error) { + TRACE_MSG0(NTT,"FAILED"); + + #if defined(CONFIG_OTG_NETWORK_BLAN_AUTO_CONFIG) || defined(CONFIG_OTG_NETWORK_RARPD_AUTO_CONFIG) + if (npd->config_otgtask) { + otg_task_exit(npd->config_otgtask); + npd->config_otgtask = NULL; + } + #endif /* defined(CONFIG_OTG_NETWORK_BLAN_AUTO_CONFIG) || defined(CONFIG_OTG_NETWORK_RARPD_AUTO_CONFIG) */ + #ifdef CONFIG_OTG_NETWORK_HOTPLUG + if (npd->hotplug_workitem) otg_workitem_exit(npd->hotplug_workitem); + npd->hotplug_workitem = NULL; + #endif /* CONFIG_OTG_NETWORK_HOTPLUG */ + if (npd->notification_workitem) otg_workitem_exit(npd->notification_workitem); + npd->notification_workitem = NULL; + } +} + +/*! net_os_disable - called to disable netwok device + * @param function_instance pointer to function instance + * @return none + * + */ +extern void net_os_disable(struct usbd_function_instance *function_instance) +{ + struct usb_network_private *npd = + (struct usb_network_private *) (function_instance ? function_instance->privdata : NULL); + + RETURN_UNLESS(npd && npd->net_device); + //npd->net_device->priv = NULL; + //npd->net_device = NULL; + function_instance->privdata = NULL; + npd->function_instance = NULL; + + #if defined(CONFIG_OTG_NETWORK_BLAN_AUTO_CONFIG) || defined(CONFIG_OTG_NETWORK_RARPD_AUTO_CONFIG) + if (npd->config_otgtask) { + otg_task_exit(npd->config_otgtask); + npd->config_otgtask = NULL; + } + #endif /* defined(CONFIG_OTG_NETWORK_BLAN_AUTO_CONFIG) || defined(CONFIG_OTG_NETWORK_RARPD_AUTO_CONFIG) */ + #ifdef CONFIG_OTG_NETWORK_HOTPLUG + if (npd->hotplug_workitem) otg_workitem_exit(npd->hotplug_workitem); + npd->hotplug_workitem = NULL; + #endif /* CONFIG_OTG_NETWORK_HOTPLUG */ + if (npd->notification_workitem) otg_workitem_exit(npd->notification_workitem); + npd->notification_workitem = NULL; +} + +/*! net_os_carrier_on - ??? + * @param function_instance pointer to function instance + * @return none + * + */ +void net_os_carrier_on(struct usbd_function_instance *function_instance) +{ + struct usb_network_private *npd = + (struct usb_network_private *) (function_instance ? function_instance->privdata : NULL); + + RETURN_UNLESS(npd); + netif_carrier_on(npd->net_device); + net_os_wake_queue(function_instance); +#ifdef CONFIG_OTG_NET_NFS_SUPPORT + RETURN_UNLESS (usb_is_configured); + wake_up(&usb_netif_wq); + usb_is_configured = 1; +#endif + //otg_up_work(npd->config_otgtask); +} + +/*! net_os_carrier_off -??? + * @param function_instance pointer to function instance + * + * @return none + * + */ +void net_os_carrier_off(struct usbd_function_instance *function_instance) +{ + struct usb_network_private *npd = + (struct usb_network_private *) (function_instance ? function_instance->privdata : NULL); + + RETURN_UNLESS(npd); + net_os_stop_queue(function_instance); + netif_carrier_off(npd->net_device); + //otg_up_work(npd->config_otgtask); +} + +/*! net_os_queue_stopped --??? + * @param function_instance + * + * @return result code + * + */ +int net_os_queue_stopped(struct usbd_function_instance *function_instance) +{ + struct usb_network_private *npd = + (struct usb_network_private *) (function_instance ? function_instance->privdata : NULL); + int rc; + + RETURN_ZERO_UNLESS(npd); + rc = netif_queue_stopped(npd->net_device); + //TRACE_MSG3(NTT, "stopped: %d stops: %d restarts: %d", rc, npd->stops, npd->restarts); + return rc; +} + +/*! net_os_wake_queue -??? + * @param function_instance function instance pointer + * @return none + * + */ +void net_os_wake_queue(struct usbd_function_instance *function_instance) +{ + struct usb_network_private *npd = + (struct usb_network_private *) (function_instance ? function_instance->privdata : NULL); + + RETURN_UNLESS(npd); + //TRACE_MSG3(NTT, "stopped: %d stops: %d restarts: %d", netif_queue_stopped(npd->net_device), npd->stops, npd->restarts); + netif_wake_queue (npd->net_device); + npd->restarts++; +} + +/*! net_os_stop_queue - ??? + * @param function_instance + * @return none + * + */ +void net_os_stop_queue(struct usbd_function_instance *function_instance) +{ + struct usb_network_private *npd = + (struct usb_network_private *) (function_instance ? function_instance->privdata : NULL); + + RETURN_UNLESS(npd); + //TRACE_MSG3(NTT, "stopped: %d stops: %d restarts: %d", netif_queue_stopped(npd->net_device), npd->stops, npd->restarts); + netif_stop_queue(npd->net_device); + npd->stops++; +} + +//_______________________________Network Layer Functions____________________________________ + +/* + * In general because the functions are called from an independant layer it is necessary + * to verify that the device is still ok and to lock interrupts out to prevent in-advertant + * closures while in progress. + */ + +/*! network_init - Initializes the specified network device. + * + * @param net_device net_device pointer + * @return non-zero for failure. + */ +/*! hexdigit - + * + * Converts characters in [0-9A-F] to 0..15, characters in [a-f] to 42..47, and all others to 0. + * + * @param c - character to convert + * @return the converted decimal value + */ + +extern u8 hexdigit (char c); +extern void set_address(char *mac_address_str, u8 *dev_addr); + + +STATIC int network_init (struct net_device *net_device) +{ + + TRACE_MSG0(NTT,"no-op"); + set_address(MODPARM(local_dev_addr), net_device->dev_addr); + return 0; +} + + +/*! network_uninit Uninitializes the specified network device. + * @param net_device network device pointer + * @return none + */ +STATIC void network_uninit (struct net_device *net_device) +{ + //TRACE_MSG0(NTT,"no-op"); + return; +} + + +/*! network_open Opens the specified network device. + * + * @param net_device network device pointer + * @return non-zero for failure. + */ +STATIC int network_open (struct net_device *net_device) +{ + struct usb_network_private *npd = (struct usb_network_private *) net_device->priv; + struct usbd_function_instance *function_instance = (struct usbd_function_instance *) npd->function_instance; + + // XXX unsigned long flags; + + RETURN_ZERO_UNLESS(function_instance); + RETURN_ZERO_UNLESS(npd); + + if (npd){ + + npd->flags |= NETWORK_OPEN; + memcpy(net_device->dev_addr, npd->local_dev_addr, ETH_ALEN); + + } + net_os_wake_queue (function_instance); + + // XXX local_irq_save(flags); + net_fd_send_int_notification(function_instance, 1, 0); + // XXX local_irq_restore(flags); + +#ifdef CONFIG_OTG_NET_NFS_SUPPORT + if (!usb_is_configured) { + if (!in_interrupt()) { + printk(KERN_ERR"Please replug USB cable and then ifconfig host interface.\n"); + interruptible_sleep_on(&usb_netif_wq); + } + else { + printk(KERN_ERR"Warning! In interrupt\n"); + } + } +#endif + return 0; +} + + +/*! network_stop Stops the specified network device. + * + * @param net_device - pointer to network device + * + * @return non-zero for failure. + */ +STATIC int network_stop (struct net_device *net_device) +{ + struct usb_network_private *npd = (struct usb_network_private *) net_device->priv; + struct usbd_function_instance *function_instance = (struct usbd_function_instance *) npd->function_instance; + // XXX unsigned long flags; + + + //TRACE_MSG0(NTT,"-"); + + RETURN_ZERO_UNLESS(npd); + npd->flags &= ~NETWORK_OPEN; + // XXX local_irq_save(flags); + net_fd_send_int_notification(function_instance, 0, 0); + // XXX local_irq_restore(flags); + + return 0; +} + + +/*! network_get_stats Gets statistics from the specified network device. + * + * @param net_device - pointer to network device + * + * @returns pointer to net_device_stats structure with the required information. + */ +STATIC struct net_device_stats *network_get_stats (struct net_device *net_device) +{ + struct usb_network_private *npd = (struct usb_network_private *) net_device->priv; + struct usbd_function_instance *function_instance = (struct usbd_function_instance *) npd->function_instance; + + //if (npd) + // return &npd->stats; + //else + RETURN_NULL_UNLESS(npd && npd->net_device_stats); + return npd->net_device_stats; /* network device statistics */ +} + + +/*! network_set_mac_addr Sets the MAC address of the specified network device. Fails if the device is in use. + * + * @param net_device pointer to network device + * @param p - pointer to sockaddr to asign to this device + * + * @return non-zero for failure. + */ +STATIC int network_set_mac_addr (struct net_device *net_device, void *p) +{ + struct usb_network_private *npd = (struct usb_network_private *) net_device->priv; + struct usbd_function_instance *function_instance = (struct usbd_function_instance *) npd->function_instance; + struct sockaddr *addr = p; + // XXX unsigned long flags; + + TRACE_MSG0(NTT,"--"); + + RETURN_EBUSY_IF(netif_running (net_device)); + RETURN_ZERO_UNLESS(npd); + // XXX local_irq_save(flags); + memcpy(net_device->dev_addr, addr->sa_data, net_device->addr_len); + memcpy(npd->local_dev_addr, npd->net_device->dev_addr, ETH_ALEN); + // XXX local_irq_restore(flags); + return 0; +} + + +/*! network_tx_timeout Tells the specified network device that its current transmit attempt has timed out. + * @param net_device pointer to network device + * @return none + */ +STATIC void network_tx_timeout (struct net_device *net_device) +{ + struct usb_network_private *npd = (struct usb_network_private *) net_device->priv; + struct usbd_function_instance *function_instance = (struct usbd_function_instance *) npd->function_instance; + +#if 0 + npd->net_device_stats.tx_errors++; + npd->net_device_stats.tx_dropped++; + usbd_cancel_urb_irq (npd->bus, NULL); // QQSV +#endif +#if 0 + // XXX attempt to wakeup the host... + if ((npd->network_type == network_blan) && (npd->flags & NETWORK_OPEN)) { + notification_schedule_bh(); + } +#endif +} + + +/** network_set_config Sets the specified network device's configuration. Fails if the device is in use. + * + * @param net_device pointer to network device + * @param map An ifmap structure containing configuration values. + * Those values which are non-zero/non-null update the corresponding fields + * in net_device. + * + * @returns non-zero for failure. + */ +STATIC int network_set_config (struct net_device *net_device, struct ifmap *map) +{ + RETURN_EBUSY_IF(netif_running (net_device)); + if (map->base_addr) + net_device->base_addr = map->base_addr; + if (map->mem_start) + net_device->mem_start = map->mem_start; + if (map->irq) + net_device->irq = map->irq; + return 0; +} + + +/*! network_change_mtu Sets the specified network device's MTU. Fails if the new value is larger and + * the device is in use. + * + * @param net_device pointer to network device + * @param mtu - transmit unit limit + * @return non-zero for failure. + */ +STATIC int network_change_mtu (struct net_device *net_device, int mtu) +{ + struct usb_network_private *npd = (struct usb_network_private *) net_device->priv; + struct usbd_function_instance *function_instance = (struct usbd_function_instance *) npd->function_instance; + + RETURN_EBUSY_IF(netif_running (net_device)); + RETURN_ZERO_UNLESS(npd); + RETURN_EBUSY_IF(mtu > npd->mtu); + npd->mtu = mtu; + return 0; +} + +//_________________________________________________________________________________________________ +// network_hard_start_xmit + +/*! net_os_start_xmit - start sending an skb, with usbd_network_sem already held. + * @param skb - ??? + * @param net_device pointer to network device + * + * @return non-zero (1) if busy. QQSV - this code always returns 0. + */ +STATIC __inline__ int net_os_start_xmit (struct sk_buff *skb, struct net_device *net_device) +{ + struct usb_network_private *npd = (struct usb_network_private *) net_device->priv; + struct usbd_function_instance *function_instance = (struct usbd_function_instance *) npd->function_instance; + int rc; + + RETURN_ZERO_UNLESS(npd); + + if (!netif_carrier_ok(net_device)) { + dev_kfree_skb_any (skb); + npd->net_device_stats->tx_dropped++; + return 0; + } + + // stop queue, it will be restarted only when we are ready for another skb + net_os_stop_queue(function_instance); + //npd->stopped++; + + // Set the timestamp for tx timeout + net_device->trans_start = jiffies; + + // XXX request IN, should start a timer to resend this. + // XXX net_fd_send_int_notification(function_instance, 0, 1); + + if (TRACE_VERBOSE) + TRACE_MSG8(NTT,"SKB: %p head: %p data: %p tail: %p end: %p headroom: %d len: %d tailroom: %d", + skb, skb->head, skb->data, skb->tail, skb->end, + (skb->data - skb->head), (skb->tail - skb->data), (skb->end - skb->tail)); + + switch ((rc = net_fd_start_xmit(function_instance, skb->data, skb->len, (void*)skb))) { + case 0: + TRACE_MSG1(NTT,"OK: %d", rc); + npd->net_device_stats->tx_packets++; + npd->net_device_stats->tx_bytes += skb->len; + if ((atomic_read(&npd->queued_frames) < npd->max_queued_frames) && + (atomic_read(&npd->queued_bytes) < npd->max_queued_bytes)) + net_os_wake_queue (function_instance); + break; + + case -EINVAL: + case -EUNATCH: + TRACE_MSG1(NTT,"not attached, send failed: %d", rc); + printk(KERN_ERR"%s: not attached, send failed: %d\n", __FUNCTION__, rc); + npd->net_device_stats->tx_errors++; + npd->net_device_stats->tx_carrier_errors++; + net_os_wake_queue(function_instance); + break; + + case -ENOMEM: + TRACE_MSG1(NTT,"no mem, send failed: %d", rc); + printk(KERN_ERR"%s: no mem, send failed: %d\n", __FUNCTION__, rc); + npd->net_device_stats->tx_errors++; + npd->net_device_stats->tx_fifo_errors++; + net_os_wake_queue(function_instance); + break; + + case -ECOMM: + TRACE_MSG2(NTT,"comm failure, send failed: %d %p", rc, net_device); + printk(KERN_ERR"%s: comm failure, send failed: %d %p\n", __FUNCTION__, rc, net_device); + // Leave the IF queue stopped. + npd->net_device_stats->tx_dropped++; + break; + + } + if (0 != rc) { + dev_kfree_skb_any (skb); + // XXX this is what we should do, blows up on some 2.4.20 kernels + // return(NET_XMIT_DROP); + return 0; + } + return 0; +} + +/*! + * network_hard_start_xmit - start sending an skb. Called by the OS network layer. + * @param skb - ??? + * @param net_device pointer to network device + * + * @return non-zero (1) if busy. QQSV - this code always returns 0. + */ +STATIC int network_hard_start_xmit (struct sk_buff *skb, struct net_device *net_device) +{ + int rc = 0; + //down(&usbd_network_sem); + rc = net_os_start_xmit(skb, net_device); + //up(&usbd_network_sem); + return rc; +} + + +/*! network_do_ioctl - perform an ioctl call + * + * Carries out IOCTL commands for the specified network device. + * + * @param net_device network device pointer + * @param rp Points to an ifreq structure containing the IOCTL parameter(s). + * @param cmd The IOCTL command. + * + * @return non-zero for failure. + */ +STATIC int network_do_ioctl (struct net_device *net_device, struct ifreq *rp, int cmd) +{ + return -ENOIOCTLCMD; +} + +//_________________________________________________________________________________________________ +/// @var network_device Network_net_device +/// +struct net_device Network_net_device = { + .get_stats = network_get_stats, + .tx_timeout = network_tx_timeout, + .do_ioctl = network_do_ioctl, + .set_config = network_set_config, + .set_mac_address = network_set_mac_addr, + .hard_start_xmit = network_hard_start_xmit, + .change_mtu = network_change_mtu, + .init = network_init, + .uninit = network_uninit, + .open = network_open, + .stop = network_stop, + .priv = NULL, + .name = "usbl0", +}; + + +//_________________________________________________________________________________________________ +/*! net_os_settime - set os time + * + * @param function_instance function_insance pointer + * @param rfc868seconds + * @return none + */ +void net_os_settime(struct usbd_function_instance *function_instance, u32 rfc868seconds) +{ + #if defined(LINUX24) + struct timeval net_fd_tv; + #else + struct timespec net_fd_tv; + #endif + /* wIndex and wLength contain RFC868 time - seconds since midnight 1 + * jan 1900 and convert to Unix time - seconds since midnight 1 jan + * 1970 + */ + TRACE_MSG1(NTT, "RFC868 seconds: %d", rfc868seconds); + memset(&net_fd_tv, 0, sizeof(net_fd_tv)); + net_fd_tv.tv_sec = rfc868seconds; + net_fd_tv.tv_sec -= RFC868_OFFSET_TO_EPOCH; + do_settimeofday(&net_fd_tv); +} + +//_________________________________________________________________________________________________ + +#if defined(CONFIG_OTG_NETWORK_BLAN_AUTO_CONFIG) || defined(CONFIG_OTG_NETWORK_RARPD_AUTO_CONFIG) +int local_devinet_ioctl(unsigned int cmd, void __user *data); +/*! sock_ioctl - perform an ioctl call to inet device + * @param cmd - io control command + * @param ifreq - + * @return result code + */ +static int sock_ioctl(u32 cmd, struct ifreq *ifreq) +{ + int rc = 0; + mm_segment_t save_get_fs = get_fs(); + TRACE_MSG1(NTT, "cmd: %x", cmd); + set_fs(get_ds()); + rc = local_devinet_ioctl(cmd, ifreq); + set_fs(save_get_fs); + return rc; +} + +/*! sock_addr - setup a socket address for specified interface + * @param ifname - interface name + * @param cmd - ioctl command + * @param s_addr socket address + * @return io control operation result code + */ +static int sock_addr(char * ifname, u32 cmd, u32 s_addr) +{ + struct ifreq ifreq; + struct sockaddr_in *sin = (void *) &(ifreq.ifr_ifru.ifru_addr); + + TRACE_MSG2(NTT, "ifname: %s addr: %x", ifname, ntohl(s_addr)); + + memset(&ifreq, 0, sizeof(ifreq)); + strcpy(ifreq.ifr_ifrn.ifrn_name, ifname); + + sin->sin_family = AF_INET; + sin->sin_addr.s_addr = s_addr; + + return sock_ioctl(cmd, &ifreq); +} + + +/*! sock_flags - set flags for specified interface + * @param ifname - interface name + * @param oflags + * @param sflags + * @param rflags + * @return int as result code + */ +static int sock_flags(char * ifname, u16 oflags, u16 sflags, u16 rflags) +{ + int rc = 0; + struct ifreq ifreq; + + TRACE_MSG4(NTT, "ifname: %s oflags: %x s_flags: %x r_flags: %x", ifname, oflags, sflags, rflags); + + memset(&ifreq, 0, sizeof(ifreq)); + strcpy(ifreq.ifr_ifrn.ifrn_name, ifname); + + oflags |= sflags; + oflags &= ~rflags; + ifreq.ifr_flags = oflags; + + TRACE_MSG1(NTT, "-> ifr_flags: %x ", ifreq.ifr_flags); + + THROW_IF ((rc = sock_ioctl(SIOCSIFFLAGS, &ifreq)), error); + + TRACE_MSG1(NTT, "<- ifr_flags: %x ", ifreq.ifr_flags); + + CATCH(error) { + TRACE_MSG1(NTT, "ifconfig: cannot get/set interface flags (%d)", rc); + return rc; + } + return rc; +} + +/*! network_attach - called to configure interface dnd evice is attached/deatched + * + * This will use socket calls to configure the interface to the supplied + * ip address and make it active. + * + * @param function_instance function instance pointer + * @param ip ip address + * @param make network maks + * @param router router ip address + * @return 0 on success + */ +STATIC int network_attach(struct usbd_function_instance *function_instance, u32 ip, u32 mask, u32 router, int attach) +{ + struct usb_network_private *npd = + (struct usb_network_private *) (function_instance ? function_instance->privdata : NULL); + int err = 0; + + + RETURN_ZERO_UNLESS(npd); + + if (attach) { + u16 oflags = (npd && npd->net_device) ? npd->net_device->flags : 0; + TRACE_MSG5(NTT, "ATTACH npd: %x ip: %08x mask: %08x router: %08x attach: %d", npd, ip, mask, router, attach); + usbd_write_info_message(function_instance, "NETWORK: ATTACH"); + + if (npd->net_device) + memcpy(npd->net_device->dev_addr, npd->local_dev_addr, ETH_ALEN); + + /* setup ip address, netwask, and broadcast address */ + if (ip) { + char buf[40]; + THROW_IF ((err = sock_addr("usbl0", SIOCSIFADDR, htonl(ip))), error); + if (mask) { + THROW_IF ((err = sock_addr("usbl0", SIOCSIFNETMASK, htonl(mask))), error); + THROW_IF ((err = sock_addr("usbl0", SIOCSIFBRDADDR, htonl(ip | ~mask))), error); + } + /* bring the interface up */ + THROW_IF ((err = sock_flags("usbl0", oflags, IFF_UP, 0)), error); + + snprintf(buf, sizeof(buf), "NETWORK: usbl0 %d.%d.%d.%d", + ip >> 24 & 0xff, ip >> 16 & 0xff, ip >> 8 & 0xff, ip & 0xff); + + usbd_write_info_message(function_instance, buf); + } + } + else { + u16 oflags = (npd && npd->net_device) ? npd->net_device->flags : 0; + TRACE_MSG5(NTT, "DETACH npd: %x ip: %08x mask: %08x router: %08x attach: %d", npd, ip, mask, router, attach); + usbd_write_info_message(function_instance, "NETWORK: DETACH"); + /* bring the interface down */ + THROW_IF ((err = sock_addr("usbl0", SIOCSIFADDR, htonl(0))), error); + THROW_IF ((err = sock_flags("usbl0", oflags, 0, IFF_UP)), error); + } + + /* XXX needs to be fixed for multi-interface */ + network_router_ip = npd->router_ip; + + CATCH(error) { + printk(KERN_INFO"%s: ERROR\n", __FUNCTION__); + TRACE_MSG1(NTT, "ifconfig: cannot configure interface (%d)", err); + return err; + } + return 0; +} + +/*! config_bh() - configure network + * + * Check connected status and load/unload as appropriate. + * @param data pointer to function instance + * @return none + */ +void *config_bh (void *data) +{ + struct usb_network_private *npd = (struct usb_network_private *) data; + struct usbd_function_instance *function_instance = (struct usbd_function_instance *) npd->function_instance; + + RETURN_NULL_UNLESS(npd); + + TRACE_MSG5(NTT, "function: %x enabled: %x status: %x state: %x npd->config_status: %d", + function_instance, + npd->flags & NETWORK_ENABLED, + usbd_get_device_status(function_instance), + usbd_get_device_state(function_instance), + npd->config_status); + +#if 1 + while (TRUE) { + + if (npd->flags & NETWORK_CONFIGURED) + + { + TRACE_MSG0(NTT,"call npd->net_start_recv"); + npd->net_start_recv(function_instance); + TRACE_MSG2(NTT,"BUS state: %d status: %d", usbd_get_device_state(function_instance), + usbd_get_device_status(function_instance)); + if ((config_attached != npd->config_status) && npd->ip_addr) { + network_attach (function_instance, npd->ip_addr, npd->network_mask, npd->router_ip, 1); + TRACE_MSG0(NTT,"ATTACH"); + npd->config_status = config_attached; + continue; + } + } + else + { + if (config_detached != npd->config_status) { + + network_attach (function_instance, npd->ip_addr, npd->network_mask, npd->router_ip, 0); + TRACE_MSG0(NTT,"DETACH"); + npd->config_status = config_detached; + continue; + } + + } + break; + } + //sleep(1); + return NULL; + +#else + if ((npd->flags & NETWORK_ENABLED ) && + (function_instance && (USBD_OK == usbd_get_device_status(function_instance)) + && (STATE_CONFIGURED == usbd_get_device_state(function_instance)) && npd->ip_addr)) + { + TRACE_MSG2(NTT,"BUS state: %d status: %d", usbd_get_device_state(function_instance), + usbd_get_device_status(function_instance)); + + + if (config_attached != npd->config_status) { + TRACE_MSG0(NTT,"ATTACH"); + npd->config_status = config_attached; + network_attach (function_instance, npd->ip_addr, npd->network_mask, npd->router_ip, 1); + } + return NULL; + } + + + if (config_detached != npd->config_status) { + TRACE_MSG0(NTT,"DETACH"); + npd->config_status = config_detached; + network_attach (function_instance, npd->ip_addr, npd->network_mask, npd->router_ip, 0); + } + return NULL; +#endif +} + +/*! + * net_os_config - schedule a call to config bottom half + * + * @param function_instance pointer to function instance + * + * @return none + */ +void net_os_config(struct usbd_function_instance *function_instance) +{ + struct usb_network_private *npd = + (struct usb_network_private *) (function_instance ? function_instance->privdata : NULL); + + TRACE_MSG0(NTT, "CONFIG"); + RETURN_UNLESS(npd); + //do_settime = FALSE; + otg_up_work(npd->config_otgtask); +} +#else /* defined(CONFIG_OTG_NETWORK_BLAN_AUTO_CONFIG) || defined(CONFIG_OTG_NETWORK_RARPD_AUTO_CONFIG) */ +void net_os_config(struct usbd_function_instance *function_instance) +{ + TRACE_MSG0(NTT, "NOT ENABLED"); + // Do nothing, config not enabled. +} +#endif /* defined(CONFIG_OTG_NETWORK_BLAN_AUTO_CONFIG) || defined(CONFIG_OTG_NETWORK_RARPD_AUTO_CONFIG) */ +//______________________________________ Hotplug Functions ________________________________________ + +#ifdef CONFIG_OTG_NETWORK_HOTPLUG + +#define AGENT "network_fd" +#ifdef CONFIG_OTG_NETWORK_HOTPLUG_PATH +static char hotplug_path[] = CONFIG_OTG_NETWORK_HOTPLUG_PATH; +#else +static char hotplug_path[]=""; +#endif + +/*! hotplug_attach - call hotplug + * + * @param function_instance function instance pointer + * @param ip ip address + * @param mask maks addrss + * @param router router address + * @param attach operation indicator for attach/deatch + * @return 0 on success + */ +STATIC int hotplug_attach(struct usbd_function_instance *function_instance, u32 ip, u32 mask, u32 router, int attach) +{ + struct usb_network_private *npd = + (struct usb_network_private *) (function_instance ? function_instance->privdata : NULL); + static int count = 0; + char *argv[3]; + char *envp[10]; + char ifname[20+12 + IFNAMSIZ]; + int i; + char count_str[20]; + + RETURN_EINVAL_UNLESS(npd); + RETURN_EINVAL_IF(!hotplug_path[0]); + + argv[0] = hotplug_path; + argv[1] = AGENT; + argv[2] = 0; + + sprintf (ifname, "INTERFACE=%s", npd->net_device->name); + sprintf (count_str, "COUNT=%d", count++); + + i = 0; + envp[i++] = "HOME=/"; + envp[i++] = "PATH=/sbin:/bin:/usr/sbin:/usr/bin"; + envp[i++] = ifname; + + + if (attach) { + unsigned char *cp; + char ip_str[20+32]; + char mask_str[20+32]; + char router_str[20+32]; + u32 nh; + + nh = htonl(ip); + cp = (unsigned char*) &nh; + sprintf (ip_str, "IP=%d.%d.%d.%d", cp[0], cp[1], cp[2], cp[3]); + + nh = htonl(mask); + cp = (unsigned char*) &nh; + sprintf (mask_str, "MASK=%d.%d.%d.%d", cp[0], cp[1], cp[2], cp[3]); + + nh = htonl(router); + cp = (unsigned char*) &nh; + sprintf (router_str, "ROUTER=%d.%d.%d.%d", cp[0], cp[1], cp[2], cp[3]); + + //TRACE_MSG3(NTT, "attach %s %s %s", ifname, ip_str, count_str); + + envp[i++] = "ACTION=attach"; + envp[i++] = ip_str; + envp[i++] = mask_str; + envp[i++] = router_str; + + TRACE_MSG3(NTT, "attach ip: %08x mask: %08x router: %08x", ip, mask, router); + } + else { + //TRACE_MSG2(NTT,"detach %s %s", ifname, count_str); + envp[i++] = "ACTION=detach"; + TRACE_MSG3(NTT, "detach ip: %08x mask: %08x router: %08x", ip, mask, router); + } + + envp[i++] = count_str; + envp[i++] = 0; + + return call_usermodehelper (argv[0], argv, envp,0); +} + + +/*! hotplug_bh - bottom half handler to call hotplug script to signal ATTACH or DETACH + * + * Check connected status and load/unload as appropriate. + * + * It should not be possible for this to be called more than once at a time + * as it is only called via schedule_task() which protects against a second + * invocation. + * + * @param data - pointer to data to pass to bottom half handler + * @return none + */ +STATIC void *hotplug_bh (void *data) +{ + struct usb_network_private *npd = (struct usb_network_private *) data; + struct usbd_function_instance *function_instance = (struct usbd_function_instance *) npd->function_instance; + + RETURN_NULL_UNLESS(npd); + + function_instance = (struct usbd_function_instance *)npd->function_instance; + + TRACE_MSG2(NTT, "function: %x npd->hotplug_status: %d", function_instance, npd->hotplug_status); + + + if ((npd->flags & NETWORK_ENABLED ) && (function_instance && (USBD_OK == usbd_get_device_status(function_instance)) + && (STATE_CONFIGURED == usbd_get_device_state(function_instance)))) + { + TRACE_MSG2(NTT,"BUS state: %d status: %d", usbd_get_device_state(function_instance), + usbd_get_device_status(function_instance)); + if (hotplug_attached != npd->hotplug_status) { + TRACE_MSG0(NTT,"ATTACH"); + npd->hotplug_status = hotplug_attached; + hotplug_attach (function_instance, npd->ip_addr, npd->network_mask, npd->router_ip, 1); + } + return NULL; + } + + if (hotplug_detached != npd->hotplug_status) { + TRACE_MSG0(NTT,"DETACH"); + npd->hotplug_status = hotplug_detached; + hotplug_attach (function_instance, npd->ip_addr, npd->network_mask, npd->router_ip, 0); + } + return NULL; +} + +/*! + * net_os_hotplug - schedule a call to hotplug bottom half + * @param function_instance function instance pointer + * @return none + */ +void net_os_hotplug(struct usbd_function_instance *function_instance) +{ + struct usb_network_private *npd = + (struct usb_network_private *) (function_instance ? function_instance->privdata : NULL); + RETURN_UNLESS(npd); + #if 0 + SET_WORK_ARG(npd->hotplug_bh, function_instance); + SCHEDULE_WORK(npd->hotplug_bh); + #else + otg_workitem_start(npd->hotplug_workitem); + #endif +} +#else /* CONFIG_OTG_NETWORK_HOTPLUG */ +void net_os_hotplug(struct usbd_function_instance *function_instance) +{ + TRACE_MSG0(NTT, "NOT ENABLED"); + // Do nothing, hotplug not configured. +} +#endif /* CONFIG_OTG_NETWORK_HOTPLUG */ + +//_________________________________________________________________________________________________ + +/*! network_create - create and initialize network private structure + * + * @returns non-zero for failure. + */ +STATIC int network_create(void) +{ + TRACE_MSG0(NTT,"entered"); + + /* Set some fields to generic defaults and register the network device with the kernel networking code */ + + Network_private.net_device = &Network_net_device; + Network_private.net_device_stats = &Network_net_device_stats; + Network_net_device.priv = &Network_private; + + ether_setup(&Network_net_device); + RETURN_EINVAL_IF (register_netdev(&Network_net_device)); + + netif_stop_queue(&Network_net_device); + netif_carrier_off(&Network_net_device); + Network_net_device.flags &= ~IFF_UP; + + + TRACE_MSG0(NTT,"finis"); + return 0; +} + +/*! network_destroy - destroy network private struture + * + * Destroys the network interface referenced by the global variable @c Network_net_device. + */ +STATIC void network_destroy(void) +{ + netif_stop_queue(&Network_net_device); + netif_carrier_off(&Network_net_device); + unregister_netdev(&Network_net_device); + + Network_private.net_device = NULL; + Network_private.net_device_stats = NULL; + Network_net_device.priv = NULL; +} + + +//______________________________________module_init and module_exit________________________________ +#if defined(CONFIG_PROC_FS) && \ + (defined(CONFIG_OTG_NETWORK_BLAN_AUTO_CONFIG) || defined(CONFIG_OTG_NETWORK_RARPD_AUTO_CONFIG)) + +/*! network_proc_read_ip - implement proc file system read. + * Standard proc file system read function. + * + * @param file + * @param buf -storage for read ip value + * @param count + * @param pos position from which to read + * @return read lenth + */ +static ssize_t network_proc_read_ip (struct file *file, char *buf, size_t count, loff_t * pos) +{ + unsigned long page; + int len = 0; + char *bp; + int index = (*pos)++; + + RETURN_ZERO_IF(index); + + // get a page, max 4095 bytes of data... + RETURN_ENOMEM_UNLESS ((page = GET_KERNEL_PAGE())); + + bp = (char *) page; + + len = sprintf(bp, "%d.%d.%d.%d\n", + (network_router_ip >> 24) & 0xff, + (network_router_ip >> 16) & 0xff, + (network_router_ip >> 8) & 0xff, + (network_router_ip) & 0xff + ); + + if (copy_to_user(buf, (char *) page, len)) + len = -EFAULT; + + free_page (page); + return len; +} + +/*! @var struct file_operations otg_message_proc_swithch_functions + */ +static struct file_operations otg_message_proc_switch_functions = { + read: network_proc_read_ip, +}; +#endif /* defined(CONFIG_OTG_NETWORK_BLAN_AUTO_CONFIG) || defined(CONFIG_OTG_NETWORK_RARPD_AUTO_CONFIG) */ + + + +/*! network_modinit - driver intialization, + * + * @returns non-zero for failure. + */ +STATIC int network_modinit (void) +{ + #if defined(CONFIG_PROC_FS) && \ + (defined(CONFIG_OTG_NETWORK_BLAN_AUTO_CONFIG) || defined(CONFIG_OTG_NETWORK_RARPD_AUTO_CONFIG)) + struct proc_dir_entry *p; + #endif /* defined(CONFIG_OTG_NETWORK_BLAN_AUTO_CONFIG) || defined(CONFIG_OTG_NETWORK_RARPD_AUTO_CONFIG) */ + + BOOL blan = FALSE, basic = FALSE, basic2 = FALSE, ecm = FALSE, eem = FALSE, + safe = FALSE, net_fd = FALSE, rnddis = FALSE, procfs = FALSE; + + NTT = otg_trace_obtain_tag(NULL, "network-if"); + UNLESS (blan = BOOLEAN(!blan_mod_init())) blan_mod_exit(); + UNLESS (basic = BOOLEAN(!basic_mod_init())) basic_mod_exit(); + UNLESS (basic2 = BOOLEAN(!basic2_mod_init())) basic2_mod_exit(); + UNLESS (ecm = BOOLEAN(!ecm_mod_init())) ecm_mod_exit(); + UNLESS (safe = BOOLEAN(!safe_mod_init())) safe_mod_exit(); + THROW_UNLESS (eem = BOOLEAN(!eem_mod_init()), error); + /* do we have something enabled? */ + THROW_UNLESS ((eem || blan || basic || basic2 || ecm || safe),error); + + /* verify that they didn't attempt to override both personal and infrastructure override */ + THROW_IF (MODPARM(personal_device) && MODPARM(infrastructure_device), error); + + /* check that we have remote address if in infrastructure mode */ + #if defined(OTG_NETWORK_INFRASTRUCTURE) + THROW_UNLESS(MODPARM(personal_device) || MODPARM(remote_dev_addr), error); + #else /* defined(OTG_NETWORK_INFRASTRUCTURE) */ + THROW_UNLESS(!MODPARM(infrastructure_device) || MODPARM(remote_dev_addr), error); + #endif /* defined(OTG_NETWORK_INFRASTRUCTURE) */ + + + + THROW_UNLESS (net_fd = BOOLEAN(!net_fd_init("network", + MODPARM(local_dev_addr), + MODPARM(remote_dev_addr), + MODPARM(override_mac), + MODPARM(personal_device), + MODPARM(infrastructure_device) + )), error); + + + init_waitqueue_head(&usb_netif_wq); + + #if defined(CONFIG_PROC_FS) && \ + (defined(CONFIG_OTG_NETWORK_BLAN_AUTO_CONFIG) || defined(CONFIG_OTG_NETWORK_RARPD_AUTO_CONFIG)) + if ((p = create_proc_entry ("network_ip", 0666, 0))) { + p->proc_fops = &otg_message_proc_switch_functions; + } + procfs = TRUE; + #endif /* defined(CONFIG_OTG_NETWORK_BLAN_AUTO_CONFIG) || defined(CONFIG_OTG_NETWORK_RARPD_AUTO_CONFIG) */ + + THROW_IF (network_create(), error); + + return 0; + + CATCH(error) { + if (net_fd) net_fd_exit(); + + #if defined(CONFIG_PROC_FS) && \ + (defined(CONFIG_OTG_NETWORK_BLAN_AUTO_CONFIG) || defined(CONFIG_OTG_NETWORK_RARPD_AUTO_CONFIG)) + if (procfs) remove_proc_entry("network_ip", NULL); + #endif /* defined(CONFIG_OTG_NETWORK_BLAN_AUTO_CONFIG) || defined(CONFIG_OTG_NETWORK_RARPD_AUTO_CONFIG) */ + otg_trace_invalidate_tag(NTT); + return -EINVAL; + } +} + +//_____________________________________________________________________________ + +/*! network_modexit - driver exit + * + * Cleans up the module. Deregisters the function driver and destroys the network object. + */ +STATIC void network_modexit (void) +{ + network_destroy(); + net_fd_exit(); + + #ifdef CONFIG_PROC_FS + remove_proc_entry("network_ip", NULL); + #endif /* CONFIG_PROC_FS */ + + blan_mod_exit(); + basic_mod_exit(); + basic2_mod_exit(); + ecm_mod_exit(); + safe_mod_exit(); + eem_mod_exit(); + //rndis_mod_exit(); + + + otg_trace_invalidate_tag(NTT); +} + +//_________________________________________________________________________________________________ + +module_init (network_modinit); +module_exit (network_modexit); + +#endif /* defined(CONFIG_OTG_LNX) */ diff --git a/drivers/otg/functions/network/net-os.h b/drivers/otg/functions/network/net-os.h new file mode 100644 index 000000000000..4182b21ef8c1 --- /dev/null +++ b/drivers/otg/functions/network/net-os.h @@ -0,0 +1,141 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/functions/network/net-os.h + * @(#) sl@belcarra.com|otg/functions/network/net-os.h|20060908200949|09138 + * + * Copyright (c) 2002-2006 Belcarra Technologies Corp + * Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + * + * By: + * Stuart Lynne <sl@belcarra.com>, + * Bruce Balden <balden@belcarra.com> + */ + +/*! + * @file otg/functions/network/net-os.h + * @brief Structures and function definitions required for implementation + * of the OS specific upper edge (network interface) for the Network + * Function Driver. + * + * A USB network driver is composed of two pieces: + * 1) An OS specific piece that handles creating and operating + * a network device for the given OS. + * 2) A USB specific piece that interfaces either with the host + * usbcore layer, or with the device usbdcore layer. + * + * If the USB piece interfaces with the host usbcore layer you get + * a network class driver. If the USB piece interfaces with the usbdcore + * layer you get a network function driver. + * + * This file describes the functions exported by the various net-*-os.c + * files (implementing (1)) for use in net-fd.c (2). + * + * + * @ingroup NetworkFunction + */ + +#ifndef NET_OS_H +#define NET_OS_H 1 + +/* + * net_os_mutex_enter - enter mutex region + */ +extern void net_os_mutex_enter(struct usbd_function_instance *); + +/* + * net_os_mutex_exit - exit mutex region + */ +extern void net_os_mutex_exit(struct usbd_function_instance *); + +/* + * net_os_send_notification_later - schedule a callback to the USB part to send + * an INT notification. + */ +extern void net_os_send_notification_later(struct usbd_function_instance *); + +/* + * net_os_xmit_done - called from USB part when a transmit completes, good or bad. + * tx_rc is SEND_FINISHED_ERROR, SEND_CANCELLED or... + * buff_ctx is the pointer passed to fd_ops->start_xmit(). + * Return non-zero only if network does not exist, ow 0. + */ +extern int net_os_xmit_done(struct usbd_function_instance *function, void *buff_ctx, int tx_rc); + +/* + * + */ +extern void *net_os_alloc_buffer(struct usbd_function_instance *, u8 **cp, int n); +extern void net_os_dealloc_buffer(struct usbd_function_instance *function_instance, void *data, void *buffer); + +/* + * net_os_recv_buffer - forward a received URB, or clean up after a bad one. + * buff_ctx == NULL --> just accumulate stats (count 1 bad buff) + * crc_bad != 0 --> count a crc error and free buff_ctx/skb + * trim --> amount to trim from valid buffer (i.e. shorten + * from amount requested in net_os_alloc_buff(..... n). + * This will be called from interrupt context. + */ +extern int net_os_recv_buffer(struct usbd_function_instance *, void *os_data, + void *os_buffer, int crc_bad, int length, int trim); + +/* + * net_os_config - schedule a network hotplug event if the OS supports hotplug. + */ +extern void net_os_config(struct usbd_function_instance *); + +/* + * net_os_settime - schedule a network hotplug event if the OS supports hotplug. + */ +extern void net_os_settime(struct usbd_function_instance *, u32); + +/* + * net_os_hotplug - schedule a network hotplug event if the OS supports hotplug. + */ +extern void net_os_hotplug(struct usbd_function_instance *); + +/*! net_os_enable + * + */ +extern void net_os_enable(struct usbd_function_instance *function); + +/*! net_os_disable + * + */ +extern void net_os_disable(struct usbd_function_instance *function); + +/*! net_os_carrier_on + * + */ +extern void net_os_carrier_on(struct usbd_function_instance *); + +/*! net_os_carrier_off + * + */ +extern void net_os_carrier_off(struct usbd_function_instance *); + +/*! net_os_queue_stopped + * + */ +int net_os_queue_stopped(struct usbd_function_instance *function_instance); + +/*! net_os_wake_queue + * + */ +void net_os_wake_queue(struct usbd_function_instance *function_instance); + +/*! net_os_stop_queue + * + */ +void net_os_stop_queue(struct usbd_function_instance *function_instance); +#endif diff --git a/drivers/otg/functions/network/network.h b/drivers/otg/functions/network/network.h new file mode 100644 index 000000000000..bc988ea63adb --- /dev/null +++ b/drivers/otg/functions/network/network.h @@ -0,0 +1,630 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/functions/network/network.h - Network Function Driver + * @(#) sl@belcarra.com/whiskey.enposte.net|otg/functions/network/network.h|20070814184652|05358 + * + * Copyright (c) 2002-2006 Belcarra Technologies Corp + * Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + * + * By: + * Chris Lynne <cl@belcarra.com> + * Stuart Lynne <sl@belcarra.com> + * Bruce Balden <balden@belcarra.com> + * + */ +/*! + * @defgroup NetworkFunction Network CDC Interface Function + * @ingroup InterfaceFunctions + */ +/*! + * @file otg/functions/network/network.h + * @brief Network Function Driver private defines + * + * This defines the internal and private structures required + * by the Network Function Driver. + * + * @ingroup NetworkFunction + */ + +/*! This is a test + */ +#ifndef NETWORK_FD_H +#define NETWORK_FD_H 1 + +#undef CONFIG_OTG_NETWORK_DOUBLE_OUT +//#define CONFIG_OTG_NETWORK_DOUBLE_OUT +#undef CONFIG_OTG_NETWORK_DOUBLE_IN +//#define CONFIG_OTG_NETWORK_DOUBLE_IN + +#undef CONFIG_OTG_NETWORK_HS + +#undef CONFIG_OTG_NETWORK_XMIT_OS +#undef CONFIG_OTG_NETWORK_XMIT_OS + +#include <otg/otg-trace.h> + +#define NTT network_fd_trace_tag +extern otg_tag_t network_fd_trace_tag; + +#ifdef CONFIG_OTG_NETWORK_START_SINGLE +#define NETWORK_START_URBS 1 +#else +#define NETWORK_START_URBS 2 +#endif + + +// Some platforms need to get rid of "static" when using GDB or looking at ksyms +//#define STATIC +#define STATIC static + +typedef enum network_config_status { + config_unknown, + config_attached, + config_detached +} network_config_status_t; + +typedef enum network_hotplug_status { + hotplug_unknown, + hotplug_attached, + hotplug_detached +} network_hotplug_status_t; + +typedef enum network_type { + network_unknown, + network_ecm, + network_eem, + network_blan, + network_safe, + network_basic, + network_basic2, +} network_type_t; + +typedef enum eem_data_type { + eem_no_data, + eem_frame_crc_data, + eem_frame_no_crc_data, + eem_echo_data, + eem_echo_response_data +} eem_data_type_t; + +#if 0 +struct usb_network_params { + // enabling switchs + u32 cdc; + u32 basic; + u32 basic2; + u32 safe; + u32 blan; + // capability flags + u32 cdc_capable; + u32 basic_capable; + u32 basic2_capable; + u32 safe_capable; + u32 blan_capable; + // overrides + u32 vendor_id; + u32 product_id; + char *local_mac_address_str; + char *remote_mac_address_str; + // other switches + u32 ep0test; + u32 zeroconf; +}; +#endif + +#define CONFIG_OTG_NETWORK_ALLOW_SETID 1 + +//#define NETWORK_CREATED 0x01 +//#define NETWORK_REGISTERED 0x02 + +#define NETWORK_INFRASTRUCTURE 0x01 +#define NETWORK_DESTROYING 0x04 +#define NETWORK_ENABLED 0x08 +#define NETWORK_CONFIGURED 0x10 +#define NETWORK_OPEN 0x20 + + +#define MCCI_ENABLE_CRC 0x03 +#define BELCARRA_GETMAC 0x04 + +#define BELCARRA_SETTIME 0x04 +#define BELCARRA_SETIP 0x05 +#define BELCARRA_SETMSK 0x06 +#define BELCARRA_SETROUTER 0x07 +#define BELCARRA_SETDNS 0x08 +#define BELCARRA_PING 0x09 +#define BELCARRA_SETFERMAT 0x0a +#define BELCARRA_HOSTNAME 0x0b + + +// RFC868 - seconds from midnight on 1 Jan 1900 GMT to Midnight 1 Jan 1970 GMT +#define RFC868_OFFSET_TO_EPOCH 0x83AA7E80 + +#define NET_ETH_ALEN 6 + +typedef int (*net_recv_urb_proc_t) (struct usbd_urb *urb, int rc); +typedef int (*net_start_xmit_proc_t) (struct usbd_function_instance *, u8 *, int, void*); +typedef int (*net_start_recv_proc_t) (struct usbd_function_instance *); + +/*! @struct usb_network_private network.h otg/functions/network/networks.h + */ + +struct usb_network_private { + + //struct net_device_stats stats; /* network device statistics */ + + int flags; + int altsetting; + struct net_device *net_device; + struct net_device_stats *net_device_stats; + struct usbd_function_instance *function_instance; + struct usbd_simple_driver *simple_driver; + //struct usb_network_params *params; + //struct net_usb_services *fd_ops; + unsigned int maxtransfer; + //rwlock_t rwlock; + + network_config_status_t config_status; + network_hotplug_status_t hotplug_status; + network_type_t network_type; + + int state; + + int mtu; + int use_crc; + int seen_crc; + int seen_crc_error; +#if defined(CONFIG_OTG_NETWORK_BLAN_FERMAT) + int fermat; +#endif + + unsigned int stops; + unsigned int restarts; + + int max_recv_urbs; + otg_atomic_t recv_urbs_started[2]; + otg_atomic_t xmit_urbs_started[2]; + + unsigned int max_queued_frames; + unsigned int max_queued_bytes; + + otg_atomic_t queued_frames; + otg_atomic_t queued_bytes; + + time_t avg_queue_frames; + + time_t jiffies; + unsigned long samples; + + int have_interrupt; + + int data_notify; + + struct usbd_urb *int_urb; + + #if 0 + struct OLD_WORK_ITEM notification_bh; + #if defined(CONFIG_OTG_NETWORK_BLAN_AUTO_CONFIG) || defined(CONFIG_OTG_NETWORK_RARPD_AUTO_CONFIG) + struct OLD_WORK_ITEM config_bh; + #endif /* defined(CONFIG_OTG_NETWORK_BLAN_AUTO_CONFIG) || defined(CONFIG_OTG_NETWORK_RARPD_AUTO_CONFIG) */ + #ifdef CONFIG_HOTPLUG + struct OLD_WORK_ITEM hotplug_bh; + #endif /* CONFIG_HOTPLUG */ + #else + struct otg_workitem *notification_workitem; + + #if !defined(CONFIG_OTG_NETWORK_BLAN_CRC) && !defined(CONFIG_OTG_NETWORK_SAFE_CRC) + struct otg_task *blan_recv_task; + #endif /* !defined(CONFIG_OTG_NETWORK_BLAN_CRC) && !defined(CONFIG_OTG_NETWORK_SAFE_CRC) */ + + #if defined(CONFIG_OTG_NETWORK_CONNECT) + struct otg_workitem *connect_workitem; + #endif /* defined(CONFIG_OTG_NETWORK_CONNECT) */ + #if defined(CONFIG_OTG_NETWORK_DISCONNECT) + struct otg_workitem *disconnect_workitem; + #endif /* defined(CONFIG_OTG_NETWORK_DISCONNECT) */ + + #if defined(CONFIG_OTG_NETWORK_BLAN_AUTO_CONFIG) || defined(CONFIG_OTG_NETWORK_RARPD_AUTO_CONFIG) + struct otg_workitem *config_workitem; + struct otg_task *config_otgtask; + #endif /* defined(CONFIG_OTG_NETWORK_BLAN_AUTO_CONFIG) || defined(CONFIG_OTG_NETWORK_RARPD_AUTO_CONFIG) */ + #ifdef CONFIG_HOTPLUG + struct otg_workitem *hotplug_workitem; + #endif /* CONFIG_HOTPLUG */ + #endif + + u8 local_dev_addr[NET_ETH_ALEN]; + BOOL local_dev_set; + u8 remote_dev_addr[NET_ETH_ALEN]; + BOOL remote_dev_set; + + + BOOL eem_fragment_valid; // a fragment of eem packet exists + u8 eem_fragment_data; // the eem packet fragment + + void *eem_os_data; // partially filled network buffer + u8 *eem_os_buffer; // partially filled network buffer + int eem_frame_length; // frame length for allocated network buffer + int eem_os_buffer_used; // amount of data in network buffer + + eem_data_type_t eem_data_type; + + u8 eem_bmCRC; // bmCRC flag + u32 eem_crc; // crc for previous data if bmCRC set + + BOOL eem_send_zle_data; // sent ZLE command byte, still need it's data byte + + u32 ip_addr; + u32 router_ip; + u32 network_mask; + u32 dns_server_ip; + + u32 rfc868time; + + char * local_dev_addr_str; + char * remote_dev_addr_str; + BOOL override_MAC; + int infrastructure_device; + + void *privdata; + net_recv_urb_proc_t net_recv_urb; + net_start_xmit_proc_t net_start_xmit; + net_start_recv_proc_t net_start_recv; + + u32 recv_urb_flags; +}; + +// XXX this needs to be co-ordinated with rndis.c maximum's +#define MAXFRAMESIZE 2000 + +#if !defined(CONFIG_OTG_MANUFACTURER) + #define CONFIG_OTG_MANUFACTURER "Belcarra" +#endif + + +#if !defined(CONFIG_OTG_SERIAL_NUMBER_STR) + #define CONFIG_OTG_SERIAL_NUMBER_STR "" +#endif + +/* + * Lineo specific + */ + +#define VENDOR_SPECIFIC_CLASS 0xff +#define VENDOR_SPECIFIC_SUBCLASS 0xff +#define VENDOR_SPECIFIC_PROTOCOL 0xff + +/* + * Lineo Classes + */ +#define LINEO_CLASS 0xff + +#define LINEO_SUBCLASS_BASIC_NET 0x01 +#define LINEO_SUBCLASS_BASIC_SERIAL 0x02 + +/* + * Lineo Protocols + */ +#define LINEO_BASIC_NET_CRC 0x01 +#define LINEO_BASIC_NET_CRC_PADDED 0x02 + +#define LINEO_BASIC_SERIAL_CRC 0x01 +#define LINEO_BASIC_SERIAL_CRC_PADDED 0x02 + + +/* + * endpoint and interface indexes + */ + + +#if !defined(CONFIG_OTG_NETWORK_DOUBLE_IN) && !defined(CONFIG_OTG_NETWORK_DOUBLE_OUT) +#define BULK_OUT_A 0x00 +#define BULK_IN_A 0x01 +#define INT_IN 0x02 +#define ENDPOINTS 0x03 + +#elif defined(CONFIG_OTG_NETWORK_DOUBLE_IN) && !defined(CONFIG_OTG_NETWORK_DOUBLE_OUT) +#define BULK_OUT_A 0x00 +#define BULK_IN_A 0x01 +#define BULK_IN_B 0x02 +#define INT_IN 0x03 +#define ENDPOINTS 0x04 + +#elif !defined(CONFIG_OTG_NETWORK_DOUBLE_IN) && defined(CONFIG_OTG_NETWORK_DOUBLE_OUT) +#define BULK_OUT_A 0x00 +#define BULK_IN_A 0x01 +#define BULK_OUT_B 0x02 +#define INT_IN 0x03 +#define ENDPOINTS 0x04 + +#elif defined(CONFIG_OTG_NETWORK_DOUBLE_IN) && defined(CONFIG_OTG_NETWORK_DOUBLE_OUT) +#define BULK_OUT_A 0x00 +#define BULK_IN_A 0x01 +#define BULK_OUT_B 0x02 +#define BULK_IN_B 0x03 +#define INT_IN 0x04 +#define ENDPOINTS 0x05 + +#endif /* defined(CONFIG_OTG_NETWORK_DOUBLE_IN) && defined(CONFIG_OTG_NETWORK_DOUBLE_OUT) */ + +#if 0 +#ifndef CONFIG_OTG_NETWORK_HS +#define BULK_OUT 0x00 +#define BULK_IN 0x01 +#define INT_IN 0x02 +#define ENDPOINTS 0x03 +#else /* CONFIG_OTG_NETWORK_HS */ +#define BULK_OUT_A 0x00 +#define BULK_IN_A 0x01 +#define BULK_OUT_B 0x02 +#define BULK_IN_B 0x03 +#define INT_IN 0x04 +#define ENDPOINTS_HS 0x05 +#endif /* CONFIG_OTG_NETWORK_HS */ +#endif + +#define COMM_INTF 0x00 +#define DATA_INTF 0x01 + +extern struct usbd_endpoint_request net_fd_endpoint_requests[ENDPOINTS+1]; +extern u8 net_fd_endpoint_index[ENDPOINTS]; +extern u8 network_requested_endpoints[ENDPOINTS+1]; +extern u16 network_requested_transferSizes[ENDPOINTS+1]; + +extern struct usbd_endpoint_request cdc_int_endpoint_requests[1+1]; +extern struct usbd_endpoint_request cdc_data_endpoint_requests[2+1]; + + +extern u8 cdc_data_endpoint_index[2]; +extern u8 cdc_int_endpoint_index[1]; + +/* bmDataCapabilities */ +#define BMDATA_CRC 0x01 +#define BMDATA_PADBEFORE 0x02 +#define BMDATA_PADAFTER 0x04 +#define BMDATA_FERMAT 0x08 +#define BMDATA_HOSTNAME 0x10 + +/* bmNetworkCapabilities */ +#define BMNETWORK_SET_PACKET_OK 0x01 +#define BMNETWORK_NONBRIDGED 0x02 +#define BMNETWORK_DATA_NOTIFY_OK 0x04 +#define BMNETWORK_NO_VENDOR 0x08 + + +/* + * BLAN Data Plane + */ +//#define CONFIG_OTG_NETWORK_PADBYTES 8 +//#define CONFIG_OTG_NETWORK_PADAFTER 1 +//#undef CONFIG_OTG_NETWORK_PADBEFORE +//#define CONFIG_OTG_NETWORK_CRC 1 + + + +//extern struct usb_network_private Usb_network_private; +//extern u8 local_dev_addr[ETH_ALEN]; +//extern u8 remote_dev_addr[ETH_ALEN]; + +extern struct usbd_function_operations net_fd_function_ops; + +/*! @struct usbd_class_safe_networking_mdlm_descriptor network.h otg/functions/network/network.h + */ +struct usbd_class_safe_networking_mdlm_descriptor { + u8 bFunctionLength; // 0x06 + u8 bDescriptorType; // 0x24 + u8 bDescriptorSubtype; // 0x13 + u8 bGuidDescriptorType; // 0x00 + u8 bmNetworkCapabilities; + u8 bmDataCapabilities; +} __attribute__ ((packed)); + +/*! @struct usbd_class_blan_networking_mdlm_descriptor network.h otg/functions/network/network.h + */ +struct usbd_class_blan_networking_mdlm_descriptor { + u8 bFunctionLength; // 0x07 + u8 bDescriptorType; // 0x24 + u8 bDescriptorSubtype; // 0x13 + u8 bGuidDescriptorType; // 0x01 + u8 bmNetworkCapabilities; + u8 bmDataCapabilities; + u8 bPad; +} __attribute__ ((packed)); + + + +#if 0 +#define NETWORK_CREATED 0x01 +#define NETWORK_REGISTERED 0x02 +#define NETWORK_DESTROYING 0x04 +#define NETWORK_ENABLED 0x08 +#define NETWORK_ATTACHED 0x10 +#define NETWORK_OPEN 0x20 + + +#define MCCI_ENABLE_CRC 0x03 +#define BELCARRA_GETMAC 0x04 + +#define BELCARRA_SETTIME 0x04 +#define BELCARRA_SETIP 0x05 +#define BELCARRA_SETMSK 0x06 +#define BELCARRA_SETROUTER 0x07 +#define BELCARRA_SETDNS 0x08 +#define BELCARRA_PING 0x09 +#define BELCARRA_SETFERMAT 0x0a +#define BELCARRA_HOSTNAME 0x0b +#define BELCARRA_DATA_NOTIFY 0x0c +#endif + +#define RFC868_OFFSET_TO_EPOCH 0x83AA7E80 // RFC868 - seconds from midnight on 1 Jan 1900 GMT to Midnight 1 Jan 1970 GMT + + +extern u32 *network_crc32_table; + +#define CRC32_INIT 0xffffffff // Initial FCS value +#define CRC32_GOOD 0xdebb20e3 // Good final FCS value + +#define CRC32_POLY 0xedb88320 // Polynomial for table generation + +#define COMPUTE_FCS(val, c) (((val) >> 8) ^ network_crc32_table[((val) ^ (c)) & 0xff]) + + +#if 0 +#if !defined(CONFIG_OTG_NETWORK_CRC_DUFF4) && !defined(CONFIG_OTG_NETWORK_CRC_DUFF8) +/** + * Copies a specified number of bytes, computing the 32-bit CRC FCS as it does so. + * + * dst Pointer to the destination memory area. + * src Pointer to the source memory area. + * len Number of bytes to copy. + * val Starting value for the CRC FCS. + * + * Returns Final value of the CRC FCS. + * + * @sa crc32_pad + */ +static u32 __inline__ crc32_copy (u8 *dst, u8 *src, int len, u32 val) +{ + for (; len-- > 0; val = COMPUTE_FCS (val, *dst++ = *src++)); + return val; +} + +#endif /* DUFFn */ +#endif + + + +int net_fd_device_request (struct usbd_function_instance *function_instance, struct usbd_device_request *request); +void net_fd_event_handler (struct usbd_function_instance *function_instance, usbd_device_event_t event, int data); +void net_fd_endpoint_cleared (struct usbd_function_instance *function, int bEndpointAddress); +int net_fd_set_configuration (struct usbd_function_instance *function, int configuration); +//int net_fd_set_configuration_blan (struct usbd_function_instance *function, int configuration); +//int net_fd_set_configuration_cdc (struct usbd_function_instance *function, int configuration); +//int net_fd_set_interface_cdc (struct usbd_function_instance *function, int wIndex, int altsetting); +int net_fd_start (struct usbd_function_instance *function); +int net_fd_stop (struct usbd_function_instance *function); +int net_fd_reset (struct usbd_function_instance *function); +int net_fd_suspended (struct usbd_function_instance *function); +int net_fd_resumed (struct usbd_function_instance *function); + +int net_fd_function_enable (struct usbd_function_instance *, network_type_t, + net_recv_urb_proc_t , net_start_xmit_proc_t, net_start_recv_proc_t, u32 ); + +void net_fd_function_disable (struct usbd_function_instance *); + +int net_fd_start_xmit (struct usbd_function_instance *, u8 *, int , void *); + +void net_fd_send_int_notification(struct usbd_function_instance *, int connected, int data); + +void net_fd_exit(void); +int net_fd_init(char *info_str, char *, char *, BOOL, BOOL, BOOL); +int net_fd_urb_sent_bulk (struct usbd_urb *urb, int urb_rc); + +//int net_fd_start_xmit_eem (struct usbd_function_instance *function_instance, u8 *buffer, int len, void *data); +int net_fd_start_xmit_mdlm (struct usbd_function_instance *function_instance, u8 *buffer, int len, void *data); +//int net_fd_start_xmit_crc (struct usbd_function_instance *function_instance, u8 *buffer, int len, void *data); +//int net_fd_recv_urb_cdc(struct usbd_urb *urb, int rc); +int net_fd_recv_urb_mdlm(struct usbd_urb *urb, int rc); +//int net_fd_recv_urb_eem(struct usbd_urb *urb, int rc); + +int net_fd_start_recv_mdlm (struct usbd_function_instance *function_instance); +int net_fd_start_recv_eem (struct usbd_function_instance *function_instance); +int net_fd_start_recv_ecm (struct usbd_function_instance *function_instance); + +int net_fd_urb_sent_bulk (struct usbd_urb *urb, int urb_rc); +int net_fd_recv_buffer(struct usbd_function_instance *function_instance, u8 *os_buffer, int length, + void *os_data, int crc_bad, int trim); + +BOOL net_fd_check_rarpd_reply(struct usbd_function_instance *function_instance, u8 *buffer, int length); +BOOL net_fd_check_rarpd_request(struct usbd_function_instance *function_instance, u8 *buffer, int length); +BOOL net_fd_check_tp_response(struct usbd_function_instance *function_instance, u8 *buffer, int length); +void net_fd_send_rarpd_reply(struct usbd_function_instance *function_instance); +void net_fd_send_rarpd_request(struct usbd_function_instance *function_instance); +void net_fd_send_tp_request(struct usbd_function_instance *function_instance); + +int net_fd_recv_urb(struct usbd_urb *urb, int rc); + +/* crc */ +/* + * If the following are defined we implement the crc32_copy routine using + * Duff's device. This will unroll the copy loop by either 4 or 8. Do not + * use these without profiling to test if it actually helps on any specific + * device. + */ +#undef CONFIG_OTG_NETWORK_CRC_DUFF4 +#undef CONFIG_OTG_NETWORK_CRC_DUFF8 + +extern u32 *network_crc32_table; + +#define CRC32_INIT 0xffffffff // Initial FCS value +#define CRC32_GOOD 0xdebb20e3 // Good final FCS value + +#define CRC32_POLY 0xedb88320 // Polynomial for table generation + +#define COMPUTE_FCS(val, c) (((val) >> 8) ^ network_crc32_table[((val) ^ (c)) & 0xff]) + + +/*! crc32_copy + * Copies a specified number of bytes, computing the 32-bit CRC FCS as it does so. + * + * @param dst Pointer to the destination memory area. + * @param src Pointer to the source memory area. + * @param len Number of bytes to copy. + * @param val Starting value for the CRC FCS. + * + * @return Final value of the CRC FCS. + * + * @sa crc32_pad + */ +static u32 __inline__ crc32_copy (u8 *dst, u8 *src, int len, u32 val) +{ + //UNLESS (network_crc32_table) + // printk(KERN_INFO"%s:\n", __FUNCTION__); + for (; len-- > 0; val = COMPUTE_FCS (val, *dst++ = *src++)); + return val; +} + +/*! crc32_nocopy + * Computes the 32-bit CRC FCS across a buffer. + * + * @param src Pointer to the source memory area. + * @param len Number of bytes to copy. + * @param val Starting value for the CRC FCS. + * + * @return Final value of the CRC FCS. + * + * @sa crc32_pad + */ +static u32 __inline__ crc32_nocopy (u8 *src, int len, u32 val) +{ + //UNLESS (network_crc32_table) + // printk(KERN_INFO"%s:\n", __FUNCTION__); + for (; len-- > 0; val = COMPUTE_FCS (val, *src++)); + return val; +} + + +/*! crc32_pad - pad and calculate crc32 + * + * @return CRC FCS + */ +static u32 __inline__ crc32_pad (u8 *dst, int len, u32 val) +{ + for (; len-- > 0; val = COMPUTE_FCS (val, *dst++ = '\0')); + return val; +} + + + +#endif /* NETWORK_FD_H */ diff --git a/drivers/otg/functions/network/network_fd.agent b/drivers/otg/functions/network/network_fd.agent new file mode 100644 index 000000000000..acb81e812231 --- /dev/null +++ b/drivers/otg/functions/network/network_fd.agent @@ -0,0 +1,83 @@ +#!/bin/sh +# +# Network hotplug policy agent for Linux 2.4 kernels +# +# Kernel NET hotplug params include: +# +# ACTION=%s [register or unregister] +# INTERFACE=%s +# +# HISTORY: +# + +#echo $0: $* $INTERFACE $ACTION $IP $COUNT >> /tmp/NETWORK_FD +#set >> /tmp/NETWORK_FD + +#exit 0 + +cd /etc/hotplug + +if [ "$INTERFACE" = "" ]; then + exit 0 +fi + +case $ACTION in +attach) + + case $INTERFACE in + + usbl*) + if [ "$IP" != "0.0.0.0" ] ; then + + ifconfig $INTERFACE $IP up + + elif which pump > /dev/null ; then + + pump -i $INTERFACE + + elif which udhcpc > /dev/null ; then + + udhcpc -i $INTERFACE & + + else + ifconfig $INTERFACE 192.168.1.1 up + fi + + ;; + + usbb*) + ifconfig $INTERFACE 192.168.1.1 up + ;; + + esac + ;; + + +detach) + + case $INTERFACE in + + usbl*) + ifconfig $INTERFACE 0.0.0.0 down + ;; + + usbb*) + ifconfig $INTERFACE 0.0.0.0 down + ;; + + esac + ;; + +*) + + exit 0 + ;; + +esac + +/etc/rc.d/init.d/S91samba stop +sleep 2 +/etc/rc.d/init.d/S91samba start + + +exit 0 diff --git a/drivers/otg/functions/network/otg-config.h b/drivers/otg/functions/network/otg-config.h new file mode 100644 index 000000000000..b46de0600b4c --- /dev/null +++ b/drivers/otg/functions/network/otg-config.h @@ -0,0 +1,276 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * @(#) balden@belcarra.com|otg/functions/network/otg-config.h|20060419204258|52982 + * + */ + + +/* + * tristate " Network Function Driver" + */ +#define CONFIG_OTG_NETWORK + + +/* + * hex "VendorID (hex value)" + * default "0x15ec" + */ +#define CONFIG_OTG_NETWORK_VENDORID + +/* + * hex "ProductID (hex value)" + * default "0xf001" + */ +#define CONFIG_OTG_NETWORK_PRODUCTID + +/* + * hex "bcdDevice (binary-coded decimal)" + * default "0x0100" + */ +#define CONFIG_OTG_NETWORK_BCDDEVICE + +/* + * string "iManufacturer (string)" + * default "Belcarra" + * This will be used as the iManufacturer string descriptor. + */ +#define CONFIG_OTG_NETWORK_MANUFACTURER + +/* + * string "iProduct (string)" + * default "Belcarra Network Device" + * This will be used as the iProduct string descriptor. + */ +#define CONFIG_OTG_NETWORK_PRODUCT_NAME + +/* + * bool 'Enable MDLM-BLAN networking' + * BLAN supports non-infrastructure devices in virtual + * bridged network environment. + */ +#define CONFIG_OTG_NETWORK_BLAN + +/* + * string " iConfiguration (string)" + * default "BLAN Net Cfg" + * This will be used as the Configuration string descriptor + * for the BLAN configuration. + */ +#define CONFIG_OTG_NETWORK_BLAN_DESC + +/* + * string " iInterface (string)" + * default "Comm/Data Intf" + * This will be used as the Interface string descriptor + * for the BLAN configuration. + */ +#define CONFIG_OTG_NETWORK_BLAN_INTF + + +/* + * bool " append 32bit CRC" + * default TRUE + * Setting this allows the host and device to verify that + * all network frames have been successfully transferred. + */ +#define CONFIG_OTG_NETWORK_BLAN_CRC + +/* + * bool " Automatically configure the network interface" + * default TRUE + * The driver will automatically configure the network interface + * based on the IPADDR sent from the host to the device during + * enumeration. This eliminates the need for hotplug. + */ +#define CONFIG_OTG_NETWORK_BLAN_AUTO_CONFIG + + +/* + * bool " Pad after" + * default FALSE + * This is used get around some buggy hardware, it + * is generally not used. Say No. + */ +#define CONFIG_OTG_NETWORK_BLAN_PADAFTER + +/* + * bool " Pad before" + * default FALSE + * This is used get around some buggy hardware, it + * is generally not used. Say No. + */ +#define CONFIG_OTG_NETWORK_BLAN_PADBEFORE + +/* + * int " Pad bytes" + * default "8" + * This is used get around some buggy hardware, it + * is generally not used. Leave at default. + */ +#define CONFIG_OTG_NETWORK_BLAN_PADBYTES + +/* + * bool " Fermat" + * default FALSE + * This is used get around some buggy hardware, it + * is generally not used. Say No. + */ +#define CONFIG_OTG_NETWORK_BLAN_FERMAT + +/* + * bool " Non-Bridged" + * default FALSE + * Tell host to not to enable bridge. + */ +#define CONFIG_OTG_NETWORK_BLAN_NONBRIDGED + +/* + * bool 'Enable MDLM-SAFE networking' + * SAFE supports infrastructure devices but does not + * require support for SET INTERFACE or interrupt endpoints + */ +#define CONFIG_OTG_NETWORK_SAFE + +/* + * string " SAFE Data Interface iConfiguration (string)" + * default "SAFE Net Cfg" + * This will be used as the Configuration string descriptor + * for the SAFE configuration. + */ +#define CONFIG_OTG_NETWORK_SAFE_DESC + +/* + * string " SAFE Data Interface iInterface (string)" + * default "Data Intf" + * This will be used as the Interface string descriptor + * for the SAFE configuration. + */ +#define CONFIG_OTG_NETWORK_SAFE_INTF + +/* + * bool " append 32bit CRC" + * default TRUE + * Setting this allows the host and device to verify that + * all network frames have been successfully transferred. + */ +#define CONFIG_OTG_NETWORK_SAFE_CRC + +/* + * bool " Pad before" + * default FALSE + * This is used get around some buggy hardware, it + * is generally not used. Say No. + */ +#define CONFIG_OTG_NETWORK_SAFE_PADBEFORE + +/* + * bool " Bridged" + * default FALSE + * Tell host to enable bridge. + */ +#define CONFIG_OTG_NETWORK_SAFE_BRIDGED + +/* + * bool 'Enable CDC networking' + * CDC implements the USB CDC Class Specification + * to support infra-structure devices + */ +#define CONFIG_OTG_NETWORK_CDC + +/* + * string " CDC iConfiguration (string)" + * default "SAFE Net Cfg" + * This will be used as the Configuration string descriptor + * for the CDC configuration. + */ +#define CONFIG_OTG_NETWORK_CDC_DESC + +/* + * string " CDC Comm Interface iInterface (string)" + * default "Comm Intf" + * This will be used as the Interface string descriptor + * for the CDC configuration. + */ +#define CONFIG_OTG_NETWORK_CDC_COMM_INTF + +/* + * string " CDC Data (diabled) iInterface (string)" + * default "Data (Disabled) Intf" + * This will be used as the Interface string descriptor + * for the CDC no DATA interface. + */ +#define CONFIG_OTG_NETWORK_CDC_NODATA_INTF + +/* + * string " CDC Data Interface iInterface (string)" + * default "Data Intf" + * This will be used as the Interface string descriptor + * for the CDC Data Interface. + */ +#define CONFIG_OTG_NETWORK_CDC_DATA_INTF + + +/* + * bool 'Enable Basic network' + * Implement a very simple network configuration + * with a single data interface. + */ +#define CONFIG_OTG_NETWORK_BASIC + +/* + * string " BASIC Data Interface iConfiguration (string)" + * default "BASIC Net Cfg" + * This will be used as the Configuration string descriptor + * for the BLAN configuration. + */ +#define CONFIG_OTG_NETWORK_BASIC_DESC + +/* + * string " BASIC Data Interface iInterface (string)" + * default "Data Intf" + * This will be used as the Interface string descriptor + * for the BLAN data interface. + */ +#define CONFIG_OTG_NETWORK_BASIC_INTF + +/* + * bool 'Enable Basic2 network' + * Implement a very simple network configuration with + * two interfaces. + */ +#define CONFIG_OTG_NETWORK_BASIC2 + +/* + * string " BASIC2 Data Interface iConfiguration (string)" + * default "BASIC Net Cfg" + * This will be used as the Configuration string descriptor + * for the BASIC2 configuration. + */ +#define CONFIG_OTG_NETWORK_BASIC2_DESC + +/* + * string " BASIC2 Comm Interface iInterface (string)" + * default "Comm Intf" + * This will be used as the Interface string descriptor + * for the BASIC2 configuration. + */ +#define CONFIG_OTG_NETWORK_BASIC2_COMM_INTF + +/* + * string " BASIC2 Data Interface iInterface (string)" + * default "Data Intf" + * This will be used as the Interface string descriptor + * for the BASIC2 configuration. + */ +#define CONFIG_OTG_NETWORK_BASIC2_DATA_INTF diff --git a/drivers/otg/functions/network/safe-if.c b/drivers/otg/functions/network/safe-if.c new file mode 100644 index 000000000000..b040bfe75ed9 --- /dev/null +++ b/drivers/otg/functions/network/safe-if.c @@ -0,0 +1,284 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/functions/network/safe.c - Network Function Driver + * @(#) sl@belcarra.com/whiskey.enposte.net|otg/functions/network/safe-if.c|20070220211521|55659 + * + * Copyright (c) 2002-2006 Belcarra Technologies Corp + * Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + * + * By: + * Chris Lynne <cl@belcarra.com> + * Stuart Lynne <sl@belcarra.com> + * Bruce Balden <balden@belcarra.com> + * + * + */ +/*! + * @file otg/functions/network/safe-if.c + * @brief This file implements the required descriptors to implement + * a SAFE network device with a single interface. + * + * The SAFE network driver implements the SAFE protocol descriptors. + * + * The SAFE protocol is designed to support smart devices that want + * to create a virtual network between them host and other similiar + * devices. + * + * @ingroup NetworkFunction + */ + + +#include <otg/otg-compat.h> +#include <otg/otg-module.h> + +#include <otg/usbp-chap9.h> +#include <otg/usbp-cdc.h> +#include <otg/usbp-func.h> + +#include "network.h" + +#define NTT network_fd_trace_tag + +#if defined(CONFIG_OTG_NETWORK_SAFE) || defined(_OTG_DOXYGEN) + +/* USB SAFE Configuration ******************************************************************** */ + +/* @name Safe Ethernet Configuration + * + * @{ + */ + +static struct usbd_class_header_function_descriptor safe_class_1 = { + bFunctionLength:0x05, + bDescriptorType: USB_DT_CLASS_SPECIFIC, + bDescriptorSubtype: USB_ST_HEADER, + bcdCDC: __constant_cpu_to_le16(0x0110), //0x10, 0x01, +}; + +static struct usbd_class_mdlm_descriptor safe_class_2 = { + bFunctionLength: 0x15, + bDescriptorType: USB_DT_CLASS_SPECIFIC, + bDescriptorSubtype: USB_ST_MDLM, + bcdVersion: __constant_cpu_to_le16( 0x0100), //0x00, 0x01, + bGUID: { + 0x5d, 0x34, 0xcf, 0x66, 0x11, 0x18, 0x11, 0xd6, /* bGUID */ + 0xa2, 0x1a, 0x00, 0x01, 0x02, 0xca, 0x9a, 0x7f, /* bGUID */ }, +}; + +static struct usbd_class_safe_descriptor safe_class_3 = { + bFunctionLength: 0x06, + bDescriptorType: USB_DT_CLASS_SPECIFIC, + bDescriptorSubtype: USB_ST_MDLMD, + bGuidDescriptorType: 0x00, + bmNetworkCapabilities: 0x00, + bmDataCapabilities: 0x00, /* bDetailData */ +}; + +static struct usbd_class_ethernet_networking_descriptor safe_class_4 = { + bFunctionLength: 0x0d, + bDescriptorType: USB_DT_CLASS_SPECIFIC, + bDescriptorSubtype: USB_ST_ENF, + iMACAddress: 0x0, + bmEthernetStatistics: __constant_cpu_to_le32(0x00), + wMaxSegmentSize: __constant_cpu_to_le16(0x5ea), + wNumberMCFilters: __constant_cpu_to_le16(0x00), + bNumberPowerFilters: 0, +}; + + +static struct usbd_generic_class_descriptor *safe_comm_class_descriptors[] = { + (struct usbd_generic_class_descriptor *) &safe_class_1, + (struct usbd_generic_class_descriptor *) &safe_class_2, + (struct usbd_generic_class_descriptor *) &safe_class_3, + (struct usbd_generic_class_descriptor *) &safe_class_4, }; + +/* + * @} + */ + + +/*! Data Alternate Interface Description List + */ +static struct usbd_alternate_description safe_alternate_descriptions[] = { + { + .iInterface = "Belcarra USBLAN - MDLM/SAFE", + .bInterfaceClass = COMMUNICATIONS_INTERFACE_CLASS, + .bInterfaceSubClass = COMMUNICATIONS_MDLM_SUBCLASS, + .bInterfaceProtocol = COMMUNICATIONS_NO_PROTOCOL, + .classes = sizeof (safe_comm_class_descriptors) / sizeof (struct usbd_generic_class_descriptor *), + .class_list = safe_comm_class_descriptors, + .endpoints = ENDPOINTS, + .endpoint_index = net_fd_endpoint_index, + }, +}; +/* Interface descriptions and descriptors + */ +/*! Interface Description List + */ +static struct usbd_interface_description safe_interfaces[] = { + { + .alternates = sizeof (safe_alternate_descriptions) / sizeof (struct usbd_alternate_description), + .alternate_list = safe_alternate_descriptions, + }, +}; + + +/* ********************************************************************************************* */ + +/*! net_fd_function_enable - enable the function driver + * + * Called for usbd_function_enable() from usbd_register_device() + * + * @param function_instance pointer to function instance + * @return int + */ + +int safe_fd_function_enable (struct usbd_function_instance *function_instance) +{ + struct usbd_class_ethernet_networking_descriptor *ethernet = &safe_class_4; + //struct usbd_class_network_channel_descriptor *channel = &safe_class_5 ; + +#if 0 +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,17) + char address_str[14]; + snprintf(address_str, 13, "%02x%02x%02x%02x%02x%02x", + local_dev_addr[0], local_dev_addr[1], local_dev_addr[2], + local_dev_addr[3], local_dev_addr[4], local_dev_addr[5]); +#else + char address_str[20]; + sprintf(address_str, "%02x%02x%02x%02x%02x%02x", + local_dev_addr[0], local_dev_addr[1], local_dev_addr[2], + local_dev_addr[3], local_dev_addr[4], local_dev_addr[5]); +#endif +#endif + //ethernet->iMACAddress = usbd_alloc_string(address_str); + //channel->iName = usbd_alloc_string(system_utsname.nodename); + + return net_fd_function_enable(function_instance, + network_blan, net_fd_recv_urb_mdlm, + net_fd_start_xmit_mdlm, + net_fd_start_recv_mdlm, 0 + ); +} + + +/* ********************************************************************************************* */ +/*! safe_fd_function_ops - operations table for network function driver + */ +struct usbd_function_operations safe_fd_function_ops = { + .function_enable = safe_fd_function_enable, + .function_disable = net_fd_function_disable, + + .device_request = net_fd_device_request, + .endpoint_cleared = net_fd_endpoint_cleared, + + .set_configuration = net_fd_set_configuration, + .set_interface = NULL, + .reset = net_fd_reset, + .suspended = net_fd_suspended, + .resumed = net_fd_resumed, +}; + +/*! function driver description + */ +struct usbd_interface_driver safe_interface_driver = { + .driver = { + .name = "net-safe-if", + .fops = &safe_fd_function_ops, }, + .interfaces = sizeof (safe_interfaces) / sizeof (struct usbd_interface_description), + .interface_list = safe_interfaces, + .endpointsRequested = ENDPOINTS, + .requestedEndpoints = net_fd_endpoint_requests, + + .bFunctionClass = USB_CLASS_COMM, + .bFunctionSubClass = COMMUNICATIONS_ENCM_SUBCLASS, + .bFunctionProtocol = COMMUNICATIONS_NO_PROTOCOL, + .iFunction = "Belcarra USBLAN - MDLM/SAFE", +}; + +/* ********************************************************************************************* */ + +/*! safe_mod_ini + * @return interface driver register result code + */ +int safe_mod_init (void) +{ + struct usbd_class_ethernet_networking_descriptor *ethernet; + struct usbd_class_network_channel_descriptor *channel; + + int len = 0; + char buf[255]; + + buf[0] = 0; + +#if 0 + // Update the iMACAddress field in the ethernet descriptor + { + char address_str[14]; + snprintf(address_str, 13, "%02x%02x%02x%02x%02x%02x", + remote_dev_addr[0], remote_dev_addr[1], remote_dev_addr[2], + remote_dev_addr[3], remote_dev_addr[4], remote_dev_addr[5]); + if ((ethernet = &safe_class_4)) { + if (ethernet->iMACAddress) { + usbd_free_string_descriptor(ethernet->iMACAddress); + } + ethernet->iMACAddress = usbd_alloc_string(function_instance, address_str); + } + } +#endif + +#ifdef CONFIG_OTG_NETWORK_SAFE_PADBEFORE + safe_class_3.bmDataCapabilities |= BMDATA_PADBEFORE; + len += sprintf(buf + len, "PADBEFORE: %02x ", safe_class_3.bmDataCapabilities); +#endif +#ifdef CONFIG_OTG_NETWORK_SAFE_CRC + safe_class_3.bmDataCapabilities |= BMDATA_CRC; + len += sprintf(buf + len, "CRC: %02x ", safe_class_3.bmDataCapabilities); +#endif +#ifndef CONFIG_OTG_NETWORK_SAFE_BRIDGED + safe_class_3.bmNetworkCapabilities |= BMNETWORK_NONBRIDGED; + len += sprintf(buf + len, "NONBRIDGE: %02x ", safe_class_3.bmNetworkCapabilities); +#endif +#ifndef CONFIG_OTG_NETWORK_SAFE_AUTO_CONFIG + safe_class_3.bmNetworkCapabilities |= BMNETWORK_NO_VENDOR; + len += sprintf(buf + len, "NO VENDOR: %02x ", safe_class_3.bmNetworkCapabilities); +#endif + if (strlen(buf)) + printk(KERN_INFO"%s: %s\n", __FUNCTION__, buf); + + return usbd_register_interface_function (&safe_interface_driver, "net-safe-if", NULL); +} + +/*! safe_mod_exit + */ +void safe_mod_exit(void) +{ + usbd_deregister_interface_function (&safe_interface_driver); +} + +#else /* CONFIG_OTG_NETWORK_SAFE */ +/*! + * @brief safe_mod_init + * @return none + */ +int safe_mod_init (void) +{ + return 0; +} +/*! safe_mod_exit + */ +void safe_mod_exit(void) +{ +} +#endif /* CONFIG_OTG_NETWORK_SAFE */ diff --git a/drivers/otg/hardware/Kconfig-imx31ads b/drivers/otg/hardware/Kconfig-imx31ads new file mode 100644 index 000000000000..7ad69c8c967a --- /dev/null +++ b/drivers/otg/hardware/Kconfig-imx31ads @@ -0,0 +1,71 @@ +# +# @(#) balden@belcarra.com/seth2.rillanon.org|otg/platform/imx31ads/Kconfig-imx31ads|20070614183949|43876 +# Copyright (c) 2006 Belcarra Technologies 2005 Corp +# + +# CONFIG_OTG_PLATFORM_OTG # Offer OTG Configuration + +# CONFIG_OTG_IMX31EVB + +# CONFIG_OTG_ISP1301_IMX31ADS # Compile ISP1301 IMX31ADS +# CONFIG_OTG_MC13738_IMX31ADS # Compile MC13783 IMX31ADS + +# CONFIG_OTG_IMX31ADS # IMX31ADS +# CONFIG_OTG_IMX31ADS # Use ISP1301 +# CONFIG_OTG_IMX31ADS # Use MC13783 +# CONFIG_OTG_IMX31ADS # Use MC13783 +# CONFIG_OTG_PLATFORM # Compile as OTG Dual-role Device +# CONFIG_OTG_TRADITIONAL # Compile as Traditional USB Peripheral + +config OTG_IMX31ADS + tristate "Freescale iMX31ADS Evaluation Board" + depends on OTG && (MACH_MX31ADS) + + ---help--- + This implements On-The-Go USB Support for the iMX31ADS EVB. + +if ( OTG_IMX31ADS != 'n') + + #config OTG_PLATFORM_OTG + # bool + # default OTG_IMX31ADS + + config OTG_PLATFORM_USB + bool + default USB_ARC_OTG + + config OTG_PLATFORM_USBD + bool + default !USB_ARC_OTG + + config OTG_HIGH_SPEED_CAPABLE + bool + default OTG_IMX31ADS + + #config OTG_REMOTE_WAKEUP_CAPABLE + # bool + # default OTG_IMX31ADS + + #config OTG_BUS_POWERED_CAPABLE + # bool + # default OTG_IMX31ADS + + config OTG_BTC_ARC + bool + default OTG_IMX31ADS + + + config OTG_ARC + bool + default USB_ARC + + config OTG_ARC_OTGHS + bool + default USB_ARC_OTGHS + + config OTG_ARC_OTGFS + bool + default USB_ARC_OTGFS + + +endif diff --git a/drivers/otg/hardware/Kconfig-isp1301 b/drivers/otg/hardware/Kconfig-isp1301 new file mode 100644 index 000000000000..6bd7a0720f61 --- /dev/null +++ b/drivers/otg/hardware/Kconfig-isp1301 @@ -0,0 +1,15 @@ + + +# @(#) sl@belcarra.com|otg/platform/isp1301/Kconfig-isp1301|20060719063203|59940 +# Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp +#menu "Phillips ISP1301 Support" + + + config OTG_ISP1301_PROCFS + depends on OTG_ISP1301 && PROC_FS + bool "ISP1301 Proc FS debug" + ---help--- + Compile debug support, implements /proc/isp1301 which + will display all of the ISP1301 registers. + +#endmenu diff --git a/drivers/otg/hardware/Kconfig-zasevb b/drivers/otg/hardware/Kconfig-zasevb new file mode 100644 index 000000000000..9bf8da75077e --- /dev/null +++ b/drivers/otg/hardware/Kconfig-zasevb @@ -0,0 +1,221 @@ +# +# @(#) sp/root@belcarra.com/debian-black.(none)|otg/platform/zasevb/Kconfig-zasevb|20070829214932|07457 +# Copyright (c) 2005 Belcarra +# Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp +# + +# CONFIG_OTG_PLATFORM_OTG # Offer OTG Configuration + +# CONFIG_OTG_ZASEVB # Make +# CONFIG_OTG_ISP1301 # Make ISP1301 driver + +# CONFIG_OTG_ISP1301_ZASEVB # Compile ISP1301 ZASEVB +# CONFIG_OTG_MC13738_ZASEVB # Compile MC13783 ZASEVB + +# CONFIG_OTG_ZASEVB # ZASEVB +# CONFIG_OTG_ZASEVB_ISP1301 # Use ISP1301 +# CONFIG_OTG_ZASEVB_MC13783_CONNECTIVITY # Use MC13783 +# CONFIG_OTG_ZASEVB_MC13783_POWERIC # Use MC13783 +# CONFIG_OTG_PLATFORM # Compile as OTG Dual-role Device +# CONFIG_OTG_TRADITIONAL # Compile as Traditional USB Peripheral + +config OTG_ZASEVB + tristate "Freescale ZAS Evaluation Board using TDI FS USB" + depends on OTG && (MACH_MXC27530EVB || MACH_MXC30030EVB || MACH_MXC30030ADS || \ + MACH_MXC91131EVB || MACH_I30030ADS || MACH_I30030EVB) + + ---help--- + This implements On-The-Go USB Support for the ZAS EVB using the TDI Full Speed USB Device Controller. + +choice + prompt "Select OTG Transceiver" + depends on OTG_ZASEVB + config OTG_ZASEVB_ISP1301 + bool 'Use the BaseBoard ISP1301 (S2.1 on S2.2 on)' + depends on OTG_ZASEVB !=n + ---help--- + This will use the ISP1301 on the EVB Base Board. + Switch 2-1 and switch 2-2 should both be in the ON position. + + SW2.1 ON + SW2.2 ON + + + config OTG_ZASEVB_MC13783_LEGACY + bool 'Use the MC13783 Transceiver (legacy API)' + depends on OTG_ZASEVB && (MACH_MXC30030ADS || MACH_MXC30030EVB || MACH_I30030ADS || \ + MACH_I30030EVB || MACH_MXC27530EVB) && (MXC_MC13783_CONNECTIVITY || MC13783_CONNECTIVITY) && \ + (!(MXC_MC13783_PMIC || MXC_PMIC_MC13783)) + ---help--- + This will use the Freescale MC13783 and the Freescale Connectivity driver. + For BrassBoard works without any special settings. + + For ZAS EVB with MC13783 daughter card, Switch 2-1 should be on, Switch 2-2 + should be off. + If you are using MC13783 daughter card with MXC91231 or MXC275-30 EVB, you + should set S6.1: OFF and S6.2: ON. + If you are using MC13783 daughter card with MXC91321 or MXC91331 or i.300-30 EVB + or MXC300-30 EVB, you should set S6.1: ON and S6.2: OFF. + + + config OTG_ZASEVB_MC13783_PMIC + bool 'Use the MC13783 Transceiver (PMIC API)' + depends on OTG_ZASEVB && (MACH_MXC30030ADS || MACH_MXC30030EVB || MACH_I30030ADS || \ + MACH_I30030EVB || MACH_MXC27530EVB) && (MXC_MC13783_CONNECTIVITY || MC13783_CONNECTIVITY ) && \ + (MXC_MC13783_PMIC || MXC_PMIC_MC13783) + ---help--- + This will use the Freescale MC13783 and the Freescale Connectivity driver. + For BrassBoard works without any special settings. + + For ZAS EVB with MC13783 daughter card, Switch 2-1 should be on, Switch 2-2 + should be off. + If you are using MC13783 daughter card with MXC91231 or MXC275-30 EVB, you + should set S6.1: OFF and S6.2: ON. + If you are using MC13783 daughter card with MXC91321 or MXC91331 or i.300-30 EVB + or MXC300-30 EVB, you should set S6.1: ON and S6.2: OFF. + +endchoice +choice + prompt "Select OTG Transceiver Configuration" + depends on OTG && OTG_ZASEVB + + config OTG_ZASEVB_DIFFERENTIAL_UNIDIRECTIONAL + bool 'Differential Unidirectional (6 wire)' + depends on (ARCH_MXC91331 || ARCH_MXC91321) + ---help--- + Both the USBOTG HWMODE and Transceiver need to be configured + for the data transceiver connection. + + This selects a Differential unidirectional 6 wire connection. + + On MC13783 daughter board + SW4.1 = OFF + SW4.2 = OFF + SW4.3 = OFF + SW4.4 = ON + + + config OTG_ZASEVB_DIFFERENTIAL_BIDIRECTIONAL + bool 'Differential Bidirectional (4 wire)' + depends on ARCH_MXC91231 || ARCH_MXC91131 + ---help--- + Both the USBOTG HWMODE and Transceiver need to be configured + for the data transceiver connection. + + This selects a Differential bidirectional 4 wire connection. + + config OTG_ZASEVB_SINGLE_ENDED_UNIDIRECTIONAL + bool 'Singled Ended Unidirectional (6 wire)' + depends on (ARCH_MXC91331 || ARCH_MXC91321) + ---help--- + Both the USBOTG HWMODE and Transceiver need to be configured + for the data transceiver connection. + + This selects a Single-Ended unidirectional 6 wire connection. + + config OTG_ZASEVB_SINGLE_ENDED_BIDIRECTIONAL + bool 'Singled Ended Bidirectional (3 wire)' + depends on ARCH_MXC91231 || ARCH_MXC91131 + ---help--- + Both the USBOTG HWMODE and Transceiver need to be configured + for the data transceiver connection. + + On MC13783 daughter board + SW4.1 = OFF + SW4.2 = OFF or ON (different from data sheet !!) + SW4.3 = OFF + SW4.4 = ON + + This selects a Single-Ended bidirectional 3 wire connection. + +endchoice +choice + prompt "Select Timer" + depends on OTG && OTG_ZASEVB + ---help--- + Select OTG timer according to platform structure + + config OTG_HRT +# depends on HIGH_RES_TIMERS + bool 'High Resolution Timer' + ---help--- + Using high resolution timer, if present and enabled (preferred) + + config OTG_TIMER_NONE + bool 'No OTG Timer -- certain features will not work properly' + ---help--- + Select this option to have no timer source available to OTG + +# config OTG_GPTR +# depends on (HIGH_RES_TIMERS=n) +# bool 'General Purpose Timer' +# ---help--- +# Using the shared or non-shared general purpose timer +# Warning: this option is DEPRECATED. Please enable HIGH_RES_TIMERS + +endchoice + +if ( OTG_ZASEVB != 'n') + + config OTG_PLATFORM_OTG + bool + default OTG_ZASEVB + + config OTG_PLATFORM_USB + bool + default OTG_ZASEVB + + config OTG_PLATFORM_USBD + bool + default OTG_ZASEVB + + #config OTG_HIGH_SPEED_CAPABLE + # bool + # default OTG_ZASEVB + + config OTG_REMOTE_WAKEUP_CAPABLE + bool + default OTG_ZASEVB + + config OTG_BUS_POWERED_CAPABLE + bool + default OTG_ZASEVB + + config OTG_FREESCALE_TDI + bool + default OTG_ZASEVB + + config OTG_ISP1301 + bool + depends on OTG_ZASEVB_ISP1301 + default OTG_ZASEVB + + config OTG_ZASEVB_MC13783_CONNECTIVITY + bool + depends on OTG_ZASEVB_MC13783_LEGACY + default OTG_ZASEVB + + config OTG_ZASEVB_MC13783_POWERIC + bool + depends on OTG_ZASEVB_MC13783_LEGACY + default OTG_ZASEVB + + # config OTG_MXC_SELF_POWERED + # bool 'Enable if device is Self-Powered' + # default y + + #if ( OTG_MXC_SELF_POWERED = 'n') + # config OTG_MXC_SELF_BMAXPOWER + # int 'bMaxPower - 2mA units' + # default 1 + # ---help--- + # The amount of power the device will draw in 2mA units. + # Must be non-zero even for self powered device. + #endif + + # config OTG_MXC_REMOTE_WAKEUP + # bool 'Enable if you want device to implement Remote Wakeup' + # default y + + +endif diff --git a/drivers/otg/hardware/Kconfig-zasevb-arc b/drivers/otg/hardware/Kconfig-zasevb-arc new file mode 100644 index 000000000000..71aac5367d8c --- /dev/null +++ b/drivers/otg/hardware/Kconfig-zasevb-arc @@ -0,0 +1,51 @@ +# +# @(#) sp@belcarra.com|otg/platform/zasevb/Kconfig-zasevb-arc|20070629180415|22301 +# Copyright (c) 2005 Belcarra +# Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp +# + +# CONFIG_OTG_PLATFORM_OTG # Offer OTG Configuration + +# CONFIG_OTG_ZASEVB_ARC # Make + +# CONFIG_OTG_PLATFORM # Compile as OTG Dual-role Device +# CONFIG_OTG_TRADITIONAL # Compile as Traditional USB Peripheral + +config OTG_ZASEVB_ARC + tristate "Freescale ZAS Evaluation Board using ARC HS USB" + depends on OTG && ((MACH_MXC30020EVB) || (MACH_MXC30031ADS)) + + ---help--- + This implements On-The-Go USB Support for the ZAS EVB using the ARC High Speed USB Device Controller. + +if ( OTG_ZASEVB_ARC != 'n') + + config OTG_PLATFORM_OTG + bool + default OTG_ZASEVB_ARC + + config OTG_PLATFORM_USB + bool + default OTG_ZASEVB_ARC + + config OTG_PLATFORM_USBD + bool + default OTG_ZASEVB_ARC + + config OTG_HIGH_SPEED_CAPABLE + bool + default OTG_ZASEVB_ARC + + config OTG_REMOTE_WAKEUP_CAPABLE + bool + default OTG_ZASEVB_ARC + + config OTG_BUS_POWERED_CAPABLE + bool + default OTG_ZASEVB_ARC + + config OTG_BTC_ARC + bool + default OTG_ZASEVB_ARC + +endif diff --git a/drivers/otg/hardware/Makefile b/drivers/otg/hardware/Makefile new file mode 100644 index 000000000000..58453d7b6efc --- /dev/null +++ b/drivers/otg/hardware/Makefile @@ -0,0 +1,14 @@ +# +# @(#) sl@belcarra.com/whiskey.enposte.net|otg/platform/freescale/Makefile-l26|20070525074749|26894 +# Copyright (c) 2006-2007 Belcarra Technologies 2005 Corp + + +ifdef CONFIG_OTG_ZASEVB +include drivers/otg/hardware/Makefile-zasevb-l26 +endif +ifdef CONFIG_OTG_ZASEVB_ARC +include drivers/otg/hardware/Makefile-zasevb-arc-l26 +endif +ifdef CONFIG_OTG_IMX31ADS +include drivers/otg/hardware/Makefile-imx31ads-l26 +endif diff --git a/drivers/otg/hardware/Makefile-imx31ads-l26 b/drivers/otg/hardware/Makefile-imx31ads-l26 new file mode 100644 index 000000000000..cc35de2e7434 --- /dev/null +++ b/drivers/otg/hardware/Makefile-imx31ads-l26 @@ -0,0 +1,35 @@ +# +# @(#) sl@belcarra.com/whiskey.enposte.net|otg/platform/imx31ads/Makefile-imx31ads-l26|20070615010012|14157 +# Copyright (c) 2005 Belcarra +# Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + +#OTG=$(TOPDIR)/drivers/otg +#OTGCORE_DIR=$(OTG)/otgcore +#USBCORE=$(TOPDIR)/drivers/usb +#EXTRA_CFLAGS += -I$(OTG) -Wno-unused -Wno-format -I$(USBCORE) -I$(OTGCORE_DIR) -I$(MXCATLASDIR) +#EXTRA_CFLAGS_nostdinc += -I$(OTG) -Wno-unused -Wno-format -I$(USBCORE) -I$(OTGCORE_DIR) -I$(MXCATLASDIR) + + +OTG=$(TOPDIR)/drivers/otg +EXTRA_CFLAGS += -Wno-missing-prototypes -Wno-unused -Wno-format -I$(OTG) +EXTRA_CFLAGS_nostdinc += -Wno-missing-prototypes -Wno-unused -Wno-format -I$(OTG) + + +# ######################################################################################################### + +# +# USB Configurations - peripheral, host or both +# +# Produce imx31ads module. +# + +ifeq ($(CONFIG_OTG_USB_PERIPHERAL),y) +obj-$(CONFIG_OTG_IMX31ADS) += imx31ads_usb.o +endif + + +ximx31ads_usb-objs := imx31ads-l26.o otg-dev.o pcd.o arc-dev.o \ + l26-ocd-dev.o l26-ocd.o arc-ocd.o arc-tcd.o arc-pcd.o + +imx31ads_usb-objs := imx31ads-l26.o otg-dev.o pcd.o arc-dev.o \ + l26-ocd-dev.o l26-ocd.o arc-pcd.o diff --git a/drivers/otg/hardware/Makefile-zasevb-arc-l26 b/drivers/otg/hardware/Makefile-zasevb-arc-l26 new file mode 100644 index 000000000000..d27f85df5f7c --- /dev/null +++ b/drivers/otg/hardware/Makefile-zasevb-arc-l26 @@ -0,0 +1,32 @@ +# +# @(#) sl@belcarra.com/whiskey.enposte.net|otg/platform/zasevb/Makefile-zasevb-arc-l26|20070615010012|07869 +# Copyright (c) 2005 Belcarra +# Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + +#EXTRA_CFLAGS += -I$(OTG) -Wno-unused -Wno-format -I$(USBCORE) -I$(OTGCORE_DIR) -I$(MXCATLASDIR) +#EXTRA_CFLAGS_nostdinc += -I$(OTG) -Wno-unused -Wno-format -I$(USBCORE) -I$(OTGCORE_DIR) -I$(MXCATLASDIR) + + +OTG=$(TOPDIR)/drivers/otg +EXTRA_CFLAGS += -Wno-missing-prototypes -Wno-unused -Wno-format -I$(OTG) +EXTRA_CFLAGS_nostdinc += -Wno-missing-prototypes -Wno-unused -Wno-format -I$(OTG) + + +# ######################################################################################################### + +# +# USB Configurations - peripheral, host or both +# +# Produce zasevb module. +# + +ifeq ($(CONFIG_OTG_USB_PERIPHERAL),y) +obj-$(CONFIG_OTG_ZASEVB_ARC) += zasevb_usb.o +endif + + +ozasevb_usb-objs := zasevb-arc-l26.o otg-dev.o pcd.o arc-dev.o \ + l26-ocd-dev.o l26-ocd.o arc-tcd.o arc-pcd.o arc-ocd.o + +zasevb_usb-objs := zasevb-arc-l26.o otg-dev.o pcd.o arc-dev.o \ + l26-ocd-dev.o l26-ocd.o arc-pcd.o diff --git a/drivers/otg/hardware/Makefile-zasevb-l26 b/drivers/otg/hardware/Makefile-zasevb-l26 new file mode 100644 index 000000000000..d6989e4294aa --- /dev/null +++ b/drivers/otg/hardware/Makefile-zasevb-l26 @@ -0,0 +1,131 @@ +# +# @(#) sl@belcarra.com/whiskey.enposte.net|otg/platform/zasevb/Makefile-zasevb-l26|20061222212434|28091 +# Copyright (c) 2005 Belcarra +# Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + +OTG=$(TOPDIR)/drivers/otg +OTGCORE_DIR=$(OTG)/otgcore +USBCORE=$(TOPDIR)/drivers/usb +MXCATLASDIR=$(TOPDIR)/drivers/mxc/mc13783_legacy +EXTRA_CFLAGS += -I$(OTG) -Wno-unused -Wno-format -I$(USBCORE) -I$(OTGCORE_DIR) -I$(MXCATLASDIR) +EXTRA_CFLAGS_nostdinc += -I$(OTG) -Wno-unused -Wno-format -I$(USBCORE) -I$(OTGCORE_DIR) -I$(MXCATLASDIR) + +# ######################################################################################################### + +# +# USB Configurations - peripheral, host or both +# +# Produce zasevb_usb module. +# + +ifeq ($(CONFIG_OTG_USB_PERIPHERAL),y) +obj-$(CONFIG_OTG_ZASEVB) += zasevb_usb.o +endif + +ifeq ($(CONFIG_OTG_USB_HOST),y) +obj-$(CONFIG_OTG_ZASEVB) += zasevb_usb.o +endif + +ifeq ($(CONFIG_OTG_USB_PERIPHERAL_OR_HOST),y) +obj-$(CONFIG_OTG_ZASEVB) += zasevb_usb.o +endif + +zasevb_usb-objs := zasevb-l26.o +zasevb_usb-objs += mxc-ocd.o +zasevb_usb-objs += mxc-procfs.o + + +ifdef CONFIG_ARCH_MXC91321 +zasevb_usb-objs += mxc91321-gpio.o +endif + +ifdef CONFIG_ARCH_MXC91331 +zasevb_usb-objs += mxc91331-gpio.o +endif + +ifdef CONFIG_ARCH_MXC91231 +zasevb_usb-objs += mxc91231-gpio.o +endif + +ifdef CONFIG_ARCH_MXC91131 +zasevb_usb-objs += mxc91131-gpio.o +endif + + + +zasevb_usb-$(CONFIG_OTG_USB_PERIPHERAL) += pcd.o mxc-pcd.o +zasevb_usb-$(CONFIG_OTG_USB_HOST) += pcd.o mxc-pcd.o +zasevb_usb-$(CONFIG_OTG_USB_HOST) += mxc-hcd.o mxc-l26.o +zasevb_usb-$(CONFIG_OTG_USB_PERIPHERAL_OR_HOST) += pcd.o mxc-pcd.o +zasevb_usb-$(CONFIG_OTG_USB_PERIPHERAL_OR_HOST) += mxc-hcd.o mxc-l26.o + +#zasevb_usb-$(CONFIG_OTG_GPTR) += mxc-gptcr.o l26-ocd.o +#zasevb_usb-$(CONFIG_OTG_HRT) += mxc-hrt.o l26-ocd.o +zasevb_usb-$(CONFIG_OTG_GPTR) += mxc-gptcr.o +zasevb_usb-$(CONFIG_OTG_HRT) += mxc-hrt.o + +zasevb_usb-${CONFIG_OTG_ZASEVB_ISP1301} += zasevb-isp1301.o isp1301.o \ + isp1301-procfs.o i2c-l26.o + +zasevb_usb-${CONFIG_OTG_ZASEVB_MC13783_CONNECTIVITY} += zasevb-mc13783.o mc13783.o +zasevb_usb-$(CONFIG_OTG_ZASEVB_MC13783_PMIC) += zasevb-pmic.o mxc-pmic.o +zasevb_usb-${CONFIG_ZASEVB_MC13783_POWERIC} += zasevb-poweric.o +zasevb_usb-$(CONFIG_OTG_ZASEVB_PMIC) += zasevb-sc55112.o sc55112.o + +# ######################################################################################################### + +# +# OTG Configurations - peripheral with SRP or full OTG Device +# +# Produce zasevb_otg module. +# + +ifeq ($(CONFIG_OTG_BDEVICE_WITH_SRP),y) +obj-$(CONFIG_OTG_ZASEVB) += zasevb_otg.o +endif + +ifeq ($(CONFIG_OTG_DEVICE),y) +obj-$(CONFIG_OTG_ZASEVB) += zasevb_otg.o +endif + +zasevb_otg-objs := zasevb-l26.o +zasevb_otg-objs += mxc-ocd.o +zasevb_otg-objs += mxc-procfs.o + + +ifdef CONFIG_ARCH_MXC91321 +zasevb_otg-objs += mxc91321-gpio.o +endif + +ifdef CONFIG_ARCH_MXC91331 +zasevb_otg-objs += mxc91331-gpio.o +endif + +ifdef CONFIG_ARCH_MXC91231 +zasevb_otg-objs += mxc91231-gpio.o +endif + +ifdef CONFIG_ARCH_MXC91131 +zasevb_otg-objs += mxc91131-gpio.o +endif + + + +zasevb_otg-$(CONFIG_OTG_BDEVICE_WITH_SRP) += pcd.o mxc-pcd.o +zasevb_otg-$(CONFIG_OTG_DEVICE) += pcd.o mxc-pcd.o + +zasevb_otg-$(CONFIG_OTG_DEVICE) += mxc-hcd.o mxc-l26.o +#zasevb_otg-$(CONFIG_OTG_GPTR) += mxc-gptcr.o l26-ocd.o +#zasevb_otg-$(CONFIG_OTG_HRT) += mxc-hrt.o l26-ocd.o +zasevb_otg-$(CONFIG_OTG_GPTR) += mxc-gptcr.o +zasevb_otg-$(CONFIG_OTG_HRT) += mxc-hrt.o + + +zasevb_otg-${CONFIG_OTG_ZASEVB_ISP1301} += zasevb-isp1301.o isp1301.o \ + isp1301-procfs.o i2c-l26.o + + +zasevb_otg-${CONFIG_OTG_ZASEVB_MC13783_CONNECTIVITY} += zasevb-mc13783.o mc13783.o +zasevb_otg-$(CONFIG_OTG_ZASEVB_MC13783_PMIC) += zasevb-pmic.o mxc-pmic.o +zasevb_otg-${CONFIG_ZASEVB_MC13783_POWERIC} += zasevb-poweric.o +zasevb_otg-$(CONFIG_OTG_ZASEVB_PMIC) += zasevb-sc55112.o sc55112.o diff --git a/drivers/otg/hardware/arc-dev.c b/drivers/otg/hardware/arc-dev.c new file mode 100644 index 000000000000..c6070e1783ed --- /dev/null +++ b/drivers/otg/hardware/arc-dev.c @@ -0,0 +1,291 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/hardware/arc-dev.c - MX31 Device driver + * @(#) balden@belcarra.com/seth2.rillanon.org|otg/platform/arc/arc-dev.c|20070921192720|26657 + * + * Copyright (c) 2006-2007 Belcarra Technologies 2005 Corp + * + * By: + * Shahrad Payandeh Lynne <sp@lbelcarra.com>, + * Stuart Lynne <sl@belcarra.com> + */ +/*! + * @file otg/hardware/arc-dev.c + * @brief Belcarra Freescale ARC Device driver. + * + * @ingroup ARC + * @ingroup OTGDEV + * @ingroup LINUXOS + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> + +#include <linux/usb.h> +#include <linux/delay.h> + +#include <otg/otg-compat.h> +#include <otg/otg-api.h> +#include <otg/otg-dev.h> + +#include <otg/otg-utils.h> + +#include <asm/arch/gpio.h> +#include <asm/memory.h> +#include <asm/io.h> + +#include <linux/proc_fs.h> +#include <linux/seq_file.h> + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,15) +#include <linux/platform_device.h> +#endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,15) */ + +//#include <asm/arch/arc_otg.h> + +#define MX31_USB_NAME "arc_udc" + +static const char driver_name[] = MX31_USB_NAME; + + +/* ********************************************************************************************* */ +/* arc Proc FS + */ + +#define SHOW(s, r, c) seq_printf(s, "%20s [%8x] %08x %s\n", #r, &r, r, c) +#define BITS(s, c) seq_printf(s, "%24s: ", c); +#define BIT(s, r, m) seq_printf(s, "%s%s ", #m, (r & m) ? "" : "/"); +#define NL(s) seq_printf(s, "\n"); + +/*! arc_dev_show - called to display information + * @param s + * @param unused + */ +static int arc_dev_show(struct seq_file *s, void *unused) +{ + int etdn; + unsigned long flags; + u32 u; + + seq_printf(s, "MX31 USB\n\n"); + + seq_printf(s, "\nOTG Registers\n"); + + //SHOW(s, UOG_ID, "Host ID"); + seq_printf(s, "\n"); + + return 0; +} + +/*! arc_dev_open - open a file + * @param inode + * @param file + */ + +static int arc_dev_open(struct inode *inode, struct file *file) +{ + return single_open(file, arc_dev_show, PDE(inode)->data); +} + +/*! struct file_operatons arc_dev_proc_ops */ +static struct file_operations arc_dev_proc_ops = { + .open = arc_dev_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static const char proc_filename[] = "arcusb"; + +/* ********************************************************************************************* */ +/*! + * arc_udc_isr() - architecture isr wrapper + * @param irq + * @param data + * @param r + */ +irqreturn_t arc_udc_isr(int irq, void *data) +{ + +// printk(KERN_INFO"%s: --\n", __FUNCTION__); + return otg_dev_isr(irq,data); +} + + +/* ********************************************************************************************* */ +/*! arc_dev_remove - called to remove + * @param device - device instance + * @param otg_device_driver - otg driver instance + */ +static int __init_or_module +arc_dev_remove(struct device *device, struct otg_device_driver *otg_device_driver) +{ + struct otg_dev *otg_dev = dev_get_drvdata(device); + + otg_dev_remove(device, otg_device_driver); + + return 0; +} + + +static void arc_dev_release(struct device *dev) +{ +} + +/*! arc_dev_probe - called to initialize + * @param device - device + * @param otg_device_driver - otg driver instance + */ +static int +arc_dev_probe(struct device *device, struct otg_device_driver *otg_device_driver) +{ + struct platform_device *platform_device = to_platform_device(device); + int probe = -1; + + + /* verify correct driver name + */ + THROW_IF(strcmp(platform_device->name, driver_name), error); + + device->release = arc_dev_release; + + + + /* do generic device probe, this will allocate resources (e.g. irqs) + * and call the sub-driver probe functions. + */ + THROW_IF((probe = otg_dev_probe(device, otg_device_driver, NULL)), error); + printk(KERN_INFO"%s: device: %p\n", __FUNCTION__, platform_device); + + return 0; + + CATCH(error) { + return -ENODEV; + } +} + +/* ********************************************************************************************* */ +/*! Linux 2.6 Device Driver Support + * + * The Linux 2.6 Core requires that host driver be registered as a device driver. These + * implement the requirements for the Linux Device Driver support. + * + */ + +/* ********************************************************************************************* */ +/*! arc_device_suspend - called to suspend udc + * @param device - device + * @param state - + */ +static int +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,15) +arc_device_suspend(struct device *device, pm_message_t state) +#else /* LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,15) */ +arc_device_suspend(struct device *device, u32 state, u32 phase) +#endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,15) */ + +{ + //printk(KERN_INFO"%s:\n", __FUNCTION__); + return -EINVAL; +} + +/*! arc_device_resume - called to resume udc + * @param device - device + */ +static int +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,15) +arc_device_resume(struct device *device) +#else /* LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,15) */ +arc_device_resume(struct device *device, u32 phase) +#endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,15) */ +{ + printk(KERN_INFO"%s:\n", __FUNCTION__); + return -EINVAL; +} + +/* ********************************************************************************************* */ +/*! arc_dev_driver - define a device_driver structure for this driver + */ +static struct device_driver arc_dev_driver = { + + .name = MX31_USB_NAME, + .bus = &platform_bus_type, + + .suspend = arc_device_suspend, + .resume = arc_device_resume, + +}; + +/* ********************************************************************************************* */ +/*! Module init/exit + * + * Register this a linux device driver and platform. This provides the necessary framework + * expected by the Linux 2.6 USB Core. + * + */ + +/*! arc_dev_module_init + * @param otg_device_driver - platform device driver structure + * @param device_probe - platform probe function + * @param device_remove - platform rmove function + */ +int arc_dev_module_init(struct otg_device_driver *otg_device_driver, + int (*device_probe)(struct device *), + int (*device_remove)(struct device *)) +{ + int driver_registered = -1; + struct proc_dir_entry *pde = NULL; + + printk(KERN_INFO"%s: platform: %s driver: %s\n", + __FUNCTION__, otg_device_driver->name, arc_dev_driver.name) ; + + /* Setup the otg_device_driver probe function to point back to this one, + * and the actual driver probe to the provided function. This allows the + * platform driver to provide the overall device structure. + * + * We also provide a wrapper ISR in case we need to do something before + * or after the otg_dev_isr() function has run. + */ + THROW_UNLESS(otg_device_driver, error); + THROW_UNLESS(device_probe, error); + arc_dev_driver.probe = device_probe; + arc_dev_driver.remove = device_remove; + otg_device_driver->probe = arc_dev_probe; + otg_device_driver->remove = arc_dev_remove; + otg_device_driver->isr = arc_udc_isr; + + /* register the arc-dev drivers + */ + THROW_IF((driver_registered = driver_register(&arc_dev_driver)), error); + THROW_UNLESS((pde = create_proc_entry(proc_filename, 0, NULL)), error); + pde->proc_fops = &arc_dev_proc_ops; + return 0; + CATCH(error) { + printk(KERN_INFO"%s: FAILED\n", __FUNCTION__); + if (pde) remove_proc_entry(proc_filename, NULL); + UNLESS (driver_registered) driver_unregister(&arc_dev_driver); + return -EINVAL; + } +} + +/*! arc_dev_module_exit + */ +void arc_dev_module_exit(struct otg_device_driver *otg_device_driver) +{ + otg_device_driver->probe = NULL; + otg_device_driver->isr = NULL; + remove_proc_entry(proc_filename, NULL); + driver_unregister(&arc_dev_driver); +} diff --git a/drivers/otg/hardware/arc-hardware.h b/drivers/otg/hardware/arc-hardware.h new file mode 100644 index 000000000000..452e89f71f09 --- /dev/null +++ b/drivers/otg/hardware/arc-hardware.h @@ -0,0 +1,494 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * Copyright 2004-2006 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/* + * @file otg/hardware/arc_hardware.h + * @brief Freescale USB device/endpoint management registers + * @ingroup USB + */ + +#ifndef __ARCOTG_UDC_H +#define __ARCOTG_UDC_H + +#define TRUE 1 +#define FALSE 0 + +#define ARC_EP0 0 +#define ARC_DIR_OUT 0 +#define ARC_DIR_IN 1 + + +/* ### define USB registers here + */ +#define USB_MAX_ENDPOINTS 8 +#define USB_MAX_PIPES (USB_MAX_ENDPOINTS*2) +#define USB_MAX_CTRL_PAYLOAD 64 +#define USB_DR_SYS_OFFSET 0x400 + +#define USB_DR_OFFSET 0x3100 + +struct usb_dr_device { + /* Capability register */ + u32 id; + u32 res1[63]; + u16 caplength; /* Capability Register Length */ + u16 hciversion; /* Host Controller Interface Version */ + u32 hcsparams; /* Host Controller Structual Parameters */ + u32 hccparams; /* Host Controller Capability Parameters */ + u32 res2[5]; + u32 dciversion; /* Device Controller Interface Version */ + u32 dccparams; /* Device Controller Capability Parameters */ + u32 res3[6]; + /* Operation register */ + u32 usbcmd; /* USB Command Register */ + u32 usbsts; /* USB Status Register */ + u32 usbintr; /* USB Interrupt Enable Register */ + u32 frindex; /* Frame Index Register */ + u32 res4; + u32 deviceaddr; /* Device Address */ + u32 endpointlistaddr; /* Endpoint List Address Register */ + u32 res5; + u32 burstsize; /* Master Interface Data Burst Size Register */ + u32 txttfilltuning; /* Transmit FIFO Tuning Controls Register */ + u32 res6[6]; + u32 configflag; /* Configure Flag Register */ + u32 portsc1; /* Port 1 Status and Control Register */ + u32 res7[7]; + u32 otgsc; /* On-The-Go Status and Control */ + u32 usbmode; /* USB Mode Register */ + u32 endptsetupstat; /* Endpoint Setup Status Register */ + u32 endpointprime; /* Endpoint Initialization Register */ + u32 endptflush; /* Endpoint Flush Register */ + u32 endptstatus; /* Endpoint Status Register */ + u32 endptcomplete; /* Endpoint Complete Register */ + u32 endptctrl[8 * 2]; /* Endpoint Control Registers */ +} __attribute__ ((packed)); + +/* ep0 transfer state */ +#define WAIT_FOR_SETUP 0 +#define DATA_STATE_XMIT 1 +#define DATA_STATE_NEED_ZLP 2 +#define WAIT_FOR_OUT_STATUS 3 +#define DATA_STATE_RECV 4 + +/* Frame Index Register Bit Masks */ +#define USB_FRINDEX_MASKS (0x3fff) +/* USB CMD Register Bit Masks */ +#define USB_CMD_RUN_STOP (0x00000001) +#define USB_CMD_CTRL_RESET (0x00000002) +#define USB_CMD_PERIODIC_SCHEDULE_EN (0x00000010) +#define USB_CMD_ASYNC_SCHEDULE_EN (0x00000020) +#define USB_CMD_INT_AA_DOORBELL (0x00000040) +#define USB_CMD_ASP (0x00000300) +#define USB_CMD_ASYNC_SCH_PARK_EN (0x00000800) +#define USB_CMD_SUTW (0x00002000) +#define USB_CMD_ATDTW (0x00004000) +#define USB_CMD_ITC (0x00FF0000) + +/* bit 15,3,2 are frame list size */ +#define USB_CMD_FRAME_SIZE_1024 (0x00000000) +#define USB_CMD_FRAME_SIZE_512 (0x00000004) +#define USB_CMD_FRAME_SIZE_256 (0x00000008) +#define USB_CMD_FRAME_SIZE_128 (0x0000000C) +#define USB_CMD_FRAME_SIZE_64 (0x00008000) +#define USB_CMD_FRAME_SIZE_32 (0x00008004) +#define USB_CMD_FRAME_SIZE_16 (0x00008008) +#define USB_CMD_FRAME_SIZE_8 (0x0000800C) + +/* bit 9-8 are async schedule park mode count */ +#define USB_CMD_ASP_00 (0x00000000) +#define USB_CMD_ASP_01 (0x00000100) +#define USB_CMD_ASP_10 (0x00000200) +#define USB_CMD_ASP_11 (0x00000300) +#define USB_CMD_ASP_BIT_POS (8) + +/* bit 23-16 are interrupt threshold control */ +#define USB_CMD_ITC_NO_THRESHOLD (0x00000000) +#define USB_CMD_ITC_1_MICRO_FRM (0x00010000) +#define USB_CMD_ITC_2_MICRO_FRM (0x00020000) +#define USB_CMD_ITC_4_MICRO_FRM (0x00040000) +#define USB_CMD_ITC_8_MICRO_FRM (0x00080000) +#define USB_CMD_ITC_16_MICRO_FRM (0x00100000) +#define USB_CMD_ITC_32_MICRO_FRM (0x00200000) +#define USB_CMD_ITC_64_MICRO_FRM (0x00400000) +#define USB_CMD_ITC_BIT_POS (16) + +/* USB STS Register Bit Masks */ +#define USB_STS_INT (0x00000001) +#define USB_STS_ERR (0x00000002) +#define USB_STS_PORT_CHANGE (0x00000004) +#define USB_STS_FRM_LST_ROLL (0x00000008) +#define USB_STS_SYS_ERR (0x00000010) +#define USB_STS_IAA (0x00000020) +#define USB_STS_RESET (0x00000040) +#define USB_STS_SOF (0x00000080) +#define USB_STS_SUSPEND (0x00000100) +#define USB_STS_HC_HALTED (0x00001000) +#define USB_STS_RCL (0x00002000) +#define USB_STS_PERIODIC_SCHEDULE (0x00004000) +#define USB_STS_ASYNC_SCHEDULE (0x00008000) + +/* USB INTR Register Bit Masks */ +#define USB_INTR_INT_EN (0x00000001) +#define USB_INTR_ERR_INT_EN (0x00000002) +#define USB_INTR_PTC_DETECT_EN (0x00000004) +#define USB_INTR_FRM_LST_ROLL_EN (0x00000008) +#define USB_INTR_SYS_ERR_EN (0x00000010) +#define USB_INTR_ASYN_ADV_EN (0x00000020) +#define USB_INTR_RESET_EN (0x00000040) +#define USB_INTR_SOF_EN (0x00000080) +#define USB_INTR_DEVICE_SUSPEND (0x00000100) + +/* Device Address bit masks */ +#define USB_DEVICE_ADDRESS_MASK (0xFE000000) +#define USB_DEVICE_ADDRESS_BIT_POS (25) + +/* endpoint list address bit masks */ +#define USB_EP_LIST_ADDRESS_MASK (0xfffff800) + +/* PORTSCX Register Bit Masks */ +#define PORTSCX_CURRENT_CONNECT_STATUS (0x00000001) +#define PORTSCX_CONNECT_STATUS_CHANGE (0x00000002) +#define PORTSCX_PORT_ENABLE (0x00000004) +#define PORTSCX_PORT_EN_DIS_CHANGE (0x00000008) +#define PORTSCX_OVER_CURRENT_ACT (0x00000010) +#define PORTSCX_OVER_CURRENT_CHG (0x00000020) +#define PORTSCX_PORT_FORCE_RESUME (0x00000040) +#define PORTSCX_PORT_SUSPEND (0x00000080) +#define PORTSCX_PORT_RESET (0x00000100) +#define PORTSCX_LINE_STATUS_BITS (0x00000C00) +#define PORTSCX_PORT_POWER (0x00001000) +#define PORTSCX_PORT_INDICTOR_CTRL (0x0000C000) +#define PORTSCX_PORT_TEST_CTRL (0x000F0000) +#define PORTSCX_WAKE_ON_CONNECT_EN (0x00100000) +#define PORTSCX_WAKE_ON_CONNECT_DIS (0x00200000) +#define PORTSCX_WAKE_ON_OVER_CURRENT (0x00400000) +#define PORTSCX_PHY_LOW_POWER_SPD (0x00800000) +#define PORTSCX_PORT_FORCE_FULL_SPEED (0x01000000) +#define PORTSCX_PORT_SPEED_MASK (0x0C000000) +#define PORTSCX_PORT_WIDTH (0x10000000) +#define PORTSCX_PHY_TYPE_SEL (0xC0000000) + +/* bit 11-10 are line status */ +#define PORTSCX_LINE_STATUS_SE0 (0x00000000) +#define PORTSCX_LINE_STATUS_JSTATE (0x00000400) +#define PORTSCX_LINE_STATUS_KSTATE (0x00000800) +#define PORTSCX_LINE_STATUS_UNDEF (0x00000C00) +#define PORTSCX_LINE_STATUS_BIT_POS (10) + +/* bit 15-14 are port indicator control */ +#define PORTSCX_PIC_OFF (0x00000000) +#define PORTSCX_PIC_AMBER (0x00004000) +#define PORTSCX_PIC_GREEN (0x00008000) +#define PORTSCX_PIC_UNDEF (0x0000C000) +#define PORTSCX_PIC_BIT_POS (14) + +/* bit 19-16 are port test control */ +#define PORTSCX_PTC_DISABLE (0x00000000) +#define PORTSCX_PTC_JSTATE (0x00010000) +#define PORTSCX_PTC_KSTATE (0x00020000) +#define PORTSCX_PTC_SEQNAK (0x00030000) +#define PORTSCX_PTC_PACKET (0x00040000) +#define PORTSCX_PTC_FORCE_EN (0x00050000) +#define PORTSCX_PTC_BIT_POS (16) + +/* bit 27-26 are port speed */ +#define PORTSCX_PORT_SPEED_FULL (0x00000000) +#define PORTSCX_PORT_SPEED_LOW (0x04000000) +#define PORTSCX_PORT_SPEED_HIGH (0x08000000) +#define PORTSCX_PORT_SPEED_UNDEF (0x0C000000) +#define PORTSCX_SPEED_BIT_POS (26) + +/* bit 28 is parallel transceiver width for UTMI interface */ +#define PORTSCX_PTW (0x10000000) +#define PORTSCX_PTW_8BIT (0x00000000) +#define PORTSCX_PTW_16BIT (0x10000000) + +/* bit 31-30 are port transceiver select */ +#define PORTSCX_PTS_UTMI (0x00000000) +#define PORTSCX_PTS_ULPI (0x80000000) +#define PORTSCX_PTS_FSLS (0xC0000000) +#define PORTSCX_PTS_BIT_POS (30) + +/* USB MODE Register Bit Masks */ +#define USB_MODE_CTRL_MODE_IDLE (0x00000000) +#define USB_MODE_CTRL_MODE_DEVICE (0x00000002) +#define USB_MODE_CTRL_MODE_HOST (0x00000003) +#define USB_MODE_CTRL_MODE_RSV (0x00000001) +#define USB_MODE_SETUP_LOCK_OFF (0x00000008) +#define USB_MODE_STREAM_DISABLE (0x00000010) +/* Endpoint Flush Register */ +#define EPFLUSH_TX_OFFSET (0x00010000) +#define EPFLUSH_RX_OFFSET (0x00000000) + +/* Endpoint Setup Status bit masks */ +#define EP_SETUP_STATUS_MASK (0x0000003F) +#define EP_SETUP_STATUS_EP0 (0x00000001) + +/* ENDPOINTCTRLx Register Bit Masks */ +#define EPCTRL_TX_ENABLE (0x00800000) +#define EPCTRL_TX_DATA_TOGGLE_RST (0x00400000) /* Not EP0 */ +#define EPCTRL_TX_DATA_TOGGLE_INH (0x00200000) /* Not EP0 */ +#define EPCTRL_TX_TYPE (0x000C0000) +#define EPCTRL_TX_DATA_SOURCE (0x00020000) /* Not EP0 */ +#define EPCTRL_TX_EP_STALL (0x00010000) +#define EPCTRL_RX_ENABLE (0x00000080) +#define EPCTRL_RX_DATA_TOGGLE_RST (0x00000040) /* Not EP0 */ +#define EPCTRL_RX_DATA_TOGGLE_INH (0x00000020) /* Not EP0 */ +#define EPCTRL_RX_TYPE (0x0000000C) +#define EPCTRL_RX_DATA_SINK (0x00000002) /* Not EP0 */ +#define EPCTRL_RX_EP_STALL (0x00000001) + +/* bit 19-18 and 3-2 are endpoint type */ +#define EPCTRL_EP_TYPE_CONTROL (0) +#define EPCTRL_EP_TYPE_ISO (1) +#define EPCTRL_EP_TYPE_BULK (2) +#define EPCTRL_EP_TYPE_INTERRUPT (3) +#define EPCTRL_TX_EP_TYPE_SHIFT (18) +#define EPCTRL_RX_EP_TYPE_SHIFT (2) + +/* SNOOPn Register Bit Masks */ +#define SNOOP_ADDRESS_MASK (0xFFFFF000) +#define SNOOP_SIZE_ZERO (0x00) /* snooping disable */ +#define SNOOP_SIZE_4KB (0x0B) /* 4KB snoop size */ +#define SNOOP_SIZE_8KB (0x0C) +#define SNOOP_SIZE_16KB (0x0D) +#define SNOOP_SIZE_32KB (0x0E) +#define SNOOP_SIZE_64KB (0x0F) +#define SNOOP_SIZE_128KB (0x10) +#define SNOOP_SIZE_256KB (0x11) +#define SNOOP_SIZE_512KB (0x12) +#define SNOOP_SIZE_1MB (0x13) +#define SNOOP_SIZE_2MB (0x14) +#define SNOOP_SIZE_4MB (0x15) +#define SNOOP_SIZE_8MB (0x16) +#define SNOOP_SIZE_16MB (0x17) +#define SNOOP_SIZE_32MB (0x18) +#define SNOOP_SIZE_64MB (0x19) +#define SNOOP_SIZE_128MB (0x1A) +#define SNOOP_SIZE_256MB (0x1B) +#define SNOOP_SIZE_512MB (0x1C) +#define SNOOP_SIZE_1GB (0x1D) +#define SNOOP_SIZE_2GB (0x1E) /* 2GB snoop size */ + +/* pri_ctrl Register Bit Masks */ +#define PRI_CTRL_PRI_LVL1 (0x0000000C) +#define PRI_CTRL_PRI_LVL0 (0x00000003) + +/* si_ctrl Register Bit Masks */ +#define SI_CTRL_ERR_DISABLE (0x00000010) +#define SI_CTRL_IDRC_DISABLE (0x00000008) +#define SI_CTRL_RD_SAFE_EN (0x00000004) +#define SI_CTRL_RD_PREFETCH_DISABLE (0x00000002) +#define SI_CTRL_RD_PREFEFETCH_VAL (0x00000001) + +/* control Register Bit Masks */ +#define USB_CTRL_IOENB (0x00000004) +#define USB_CTRL_ULPI_INT0EN (0x00000001) + +/*! + * Endpoint Queue Head data struct + * Rem: all the variables of qh are LittleEndian Mode + * and NEXT_POINTER_MASK should operate on a LittleEndian, Phy Addr + */ +struct ep_queue_head { + /*! + * Mult(31-30) , Zlt(29) , Max Pkt len and IOS(15) + */ + u32 max_pkt_length; + + /*! + * Current dTD Pointer(31-5) + */ + u32 curr_dtd_ptr; + + /*! + * Next dTD Pointer(31-5), T(0) + */ + u32 next_dtd_ptr; + + /*! + * Total bytes (30-16), IOC (15), MultO(11-10), STS (7-0) + */ + u32 size_ioc_int_sts; + + /*! + * Buffer pointer Page 0 (31-12) + */ + u32 buff_ptr0; + + /*! + * Buffer pointer Page 1 (31-12) + */ + u32 buff_ptr1; + + /*! + * Buffer pointer Page 2 (31-12) + */ + u32 buff_ptr2; + + /*! + * Buffer pointer Page 3 (31-12) + */ + u32 buff_ptr3; + + /*! + * Buffer pointer Page 4 (31-12) + */ + u32 buff_ptr4; + + /*! + * reserved field 1 + */ + u32 res1; + /*! + * Setup data 8 bytes + */ + u8 setup_buffer[8]; /* Setup data 8 bytes */ + + /*! + * reserved field 2,pad out to 64 bytes + */ + u32 res2[4]; +}; + +/* Endpoint Queue Head Bit Masks */ +#define EP_QUEUE_HEAD_MULT_POS (30) +#define EP_QUEUE_HEAD_ZLT_SEL (0x20000000) +#define EP_QUEUE_HEAD_MAX_PKT_LEN_POS (16) +#define EP_QUEUE_HEAD_MAX_PKT_LEN(ep_info) (((ep_info)>>16)&0x07ff) +#define EP_QUEUE_HEAD_IOS (0x00008000) +#define EP_QUEUE_HEAD_NEXT_TERMINATE (0x00000001) +#define EP_QUEUE_HEAD_IOC (0x00008000) +#define EP_QUEUE_HEAD_MULTO (0x00000C00) +#define EP_QUEUE_HEAD_STATUS_HALT (0x00000040) +#define EP_QUEUE_HEAD_STATUS_ACTIVE (0x00000080) +#define EP_QUEUE_CURRENT_OFFSET_MASK (0x00000FFF) +#define EP_QUEUE_HEAD_NEXT_POINTER_MASK (0xFFFFFFE0) +#define EP_QUEUE_FRINDEX_MASK (0x000007FF) +#define EP_MAX_LENGTH_TRANSFER (0x4000) + +/*! + * Endpoint Transfer Descriptor data struct + * Rem: all the variables of td are LittleEndian Mode + */ +struct ep_td_struct { + /*! + * Next TD pointer(31-5), T(0) set indicate invalid + */ + u32 next_td_ptr; + + /*! + * Total bytes (30-16), IOC (15),MultO(11-10), STS (7-0) + */ + u32 size_ioc_sts; + + /*! + * Buffer pointer Page 0 + */ + u32 buff_ptr0; + + /*! + * Buffer pointer Page 1 + */ + u32 buff_ptr1; + + /*! + * Buffer pointer Page 2 + */ + u32 buff_ptr2; + + /*! + * Buffer pointer Page 3 + */ + u32 buff_ptr3; + + /*! + * Buffer pointer Page 4 + */ + u32 buff_ptr4; + + dma_addr_t td_dma; + struct ep_td_struct *next_td_virt; + + /*! + * make it an even 8 words + */ + u32 res[7]; +}; + + + +/*! + * Endpoint Transfer Descriptor bit Masks + */ +#define DTD_NEXT_TERMINATE (0x00000001) +#define DTD_IOC (0x00008000) +#define DTD_STATUS_ACTIVE (0x00000080) +#define DTD_STATUS_HALTED (0x00000040) +#define DTD_STATUS_DATA_BUFF_ERR (0x00000020) +#define DTD_STATUS_TRANSACTION_ERR (0x00000008) +#define DTD_RESERVED_FIELDS (0x80007300) +#define DTD_ADDR_MASK (0xFFFFFFE0) +#define DTD_PACKET_SIZE (0x7FFF0000) +#define DTD_LENGTH_BIT_POS (16) +#define DTD_ERROR_MASK (DTD_STATUS_HALTED | \ + DTD_STATUS_DATA_BUFF_ERR | \ + DTD_STATUS_TRANSACTION_ERR) + + +/*-------------------------------------------------------------------------*/ + +/* + * memory kmalloc with alignning size and zero the content + * @base :output parameter. It store the base address before align. + * Return value it the address after align + * + */ +static void *KMALLOC_ALIGN(size_t size, int flags, unsigned int align, + void **base) +{ +/* #define MY_ALIGN(n) ((n)+(-(n) & (align - 1))) */ + + *base = kmalloc(size + align, flags); + if (*base == NULL) + return NULL; + memset(*base, 0, (size + align)); + return (void *)ALIGN((unsigned int)(*base), align); +} + +/* Bulk only class request */ +#define USB_BULK_RESET_REQUEST 0xff + + +typedef enum arc_belcarra_transceiver_event { + arc_vbus_valid, + arc_vbus_invalid, +} arc_belcarra_transceiver_event_t; + + + + +#endif /* __ARCOTG_UDC_H */ diff --git a/drivers/otg/hardware/arc-ocd.c b/drivers/otg/hardware/arc-ocd.c new file mode 100644 index 000000000000..d2f9b5261425 --- /dev/null +++ b/drivers/otg/hardware/arc-ocd.c @@ -0,0 +1,158 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/hardware/arc-ocd.c -- Generic Linux 2.6 Timer + * @(#) sl@belcarra.com/whiskey.enposte.net|otg/platform/arc/arc-ocd.c|20070612232808|00655 + * + * Copyright (c) 2005-2007 Belcarra Technologies 2005 Corp + * + * By: + * Shahrad Payandeh Lynne <sp@lbelcarra.com>, + * Stuart Lynne <sl@belcarra.com> + * + */ +/*! + * @file otg/hardware/arc-ocd.c + * @brief Belcarra Freescale ARC Device driver. + * + * Provides ticks via gettimeofday() and optional a OTG timer + * using high resolution timer. + * + * CONFIG_HIGH_RES_TIMERS=y + * + * Optionally provides accurate OTG timer using Linux High + * Resolution Timer. + * + * CONFIG_HIGH_RES_TIMERS=n + * + * Start timer is a dummy if High Resolution Timers not + * enabled. + * + * @ingroup ARC + * @ingroup OCD + * @ingroup OTGDEV + * @ingroup LINUXOS + */ + + +#include <otg/pcd-include.h> +#include <otg/otg-dev.h> + +#include "arc-hardware.h" + +//#warning NEED CONDITIONAL for imx-regs.h + +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/time.h> + +#include <asm/hardware.h> +#include <asm/io.h> +#include <asm/leds.h> +#include <asm/irq.h> +#include <asm/mach/arch.h> +#include <asm/mach/time.h> + + +#define MXC_GPT_GPTCNT (IO_ADDRESS(GPT1_BASE_ADDR + 0x24)) + + + +/* ********************************************************************************************* */ + +/*! + * arc_ocd_start_timer() - start a timer for otg state machine + * Set or reset timer to interrupt in number of uS (micro-seconds). + * + * @param otg + * @param usec + */ +int arc_ocd_start_timer(struct otg_instance *otg, int usec) +{ + otg_event(otg, TMOUT, otg->ocd->TAG, "l26 Timer - FAKE TMOUT"); + return 0; +} + +/* arc_ocd_ticks - get current ticks + */ +otg_tick_t arc_ocd_ticks (void) +{ + unsigned long ticks = 0; + + ticks = __raw_readl(MXC_GPT_GPTCNT); + + return (otg_tick_t) ticks; +} + +/* arc_ocd_elapsed - return micro-seconds between two tick values + */ +otg_tick_t arc_ocd_elapsed(otg_tick_t *t1, otg_tick_t *t2) +{ + otg_tick_t ticks = (((*t1 > *t2) ? (*t1 - *t2) : (*t2 - *t1))); + //return (otg_tick_t) ticks / 16; // 381 vs 367 + //return (otg_tick_t) (ticks * 2) / 31; // 201 vs 188 + return (otg_tick_t) ticks / 17; // 185 vs 189 +} + + +struct ocd_ops arc_ocd_ops = { + .start_timer = arc_ocd_start_timer, + .ticks = arc_ocd_ticks, + .elapsed = arc_ocd_elapsed, +}; + + +static void +arc_ocd_dev_remove(struct otg_dev *otg_dev) +{ +} + +static int +arc_ocd_dev_probe(struct otg_dev *otg_dev) +{ + return 0; +} + + +/* ********************************************************************************************* */ +static struct otg_dev_driver arc_ocd_driver = { + .name = "arc_udc", + .id = OTG_DRIVER_OCD, + + .probe = arc_ocd_dev_probe, + .remove = arc_ocd_dev_remove, + + .ops = &arc_ocd_ops, +}; + +/* ********************************************************************************************* */ + +/*! arc_ocd_module_init() - module init + */ +int arc_ocd_module_init (struct otg_device_driver *otg_device_driver) +{ + printk(KERN_INFO"%s:\n", __FUNCTION__); + return otg_dev_register_driver(otg_device_driver, &arc_ocd_driver); +} + +/*! + * arc_ocd_module_exit() - module exit + */ +void arc_ocd_module_exit (struct otg_device_driver *otg_device_driver) +{ + printk(KERN_INFO"%s:\n", __FUNCTION__); + otg_dev_unregister_driver (otg_device_driver, &arc_ocd_driver); +} diff --git a/drivers/otg/hardware/arc-pcd.c b/drivers/otg/hardware/arc-pcd.c new file mode 100644 index 000000000000..03e37695d7ee --- /dev/null +++ b/drivers/otg/hardware/arc-pcd.c @@ -0,0 +1,1088 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/hardware/arc-pcd.c -- Freescale HS (ARC) USBOTG Peripheral Controller driver + * @(#) sl@belcarra.com/whiskey.enposte.net|otg/platform/arc/arc-pcd.c|20070918212334|07623 + * + * Copyright (c) 2007 Belcarra Technologies 2005 Corp + * + * By: + * Shahrad Payandeh Lynne <sp@lbelcarra.com>, + * Stuart Lynne <sl@lbelcarra.com>, + */ +/*! + * @file otg/hardware/arc-pcd.c + * @brief USB Peripheral Controller Driver + * This implements the Freescale HS (ARC) USBOTG Peripheral Controller Driver. + * + * @ingroup ARC + * @ingroup PCD + * @ingroup OTGDEV + * @ingroup LINUXOS + */ + +#include <otg/pcd-include.h> +#include <otg/otg-dev.h> +#include <linux/dma-mapping.h> +#include <asm/arch/arc_otg.h> +#include <linux/dmapool.h> +#include <linux/delay.h> +#include "arc-hardware.h" +#include "arc.h" + +#define HAVE_PLATFORM_ARC_REMOTE_WAKEUP 1 + +#define TRACE_VERBOSE 1 +#define UDC_MAX_ENDPOINTS 16 +#define UDC_NAME "arc" + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,15) +#include <linux/platform_device.h> +#endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,15) */ +#include <linux/fsl_devices.h> +#include <linux/usb/fsl_xcvr.h> + +/* ep_qh_base store the base address before 2K align */ +static struct ep_queue_head *ep_qh_base; +static struct arcotg_udc *udc_controller; +extern void fsl_platform_set_vbus_power(struct fsl_usb2_platform_data *pdata, int on); + +/*! arc_read_setup_buffer + */ +static void arc_read_setup_buffer(struct pcd_instance *pcd, u8 * buffer_ptr) +{ + struct ep_queue_head *qh = &udc_controller->ep_qh[0 * 2 + ARC_DIR_OUT]; + int timeout; + + consistent_sync(qh, sizeof(struct ep_queue_head), DMA_FROM_DEVICE); + + /* C.f 39.16.3.2.1 Setup Phase - Setup Packet Handling (2.3 hardware and later) + */ + + /* 1. Clear ENDPTSETUPSTAT */ + UOG_ENDPTSETUPSTAT |= (1 << 0); /* Clear recieve endpoint status */ + + do { + /* 2. set tripwire */ + UOG_USBCMD |= USB_CMD_SUTW; + + /* 3. read setup buffer */ + memcpy(buffer_ptr, (u8 *) qh->setup_buffer, 8); + + /* 4. check tripwire, loop if reset */ + } while (!(UOG_USBCMD & USB_CMD_SUTW)); + + /* 5. reset tripwire */ + UOG_USBCMD &= ~USB_CMD_SUTW; + + // XXX This needs to be fixed, should not need to resort to timeout + timeout = 10000000; + while ((UOG_ENDPTSETUPSTAT & 1) && --timeout) { continue; } + if (timeout == 0) printk(KERN_ERR "%s: TIMEOUT\n", __FUNCTION__); +} + +/*! arc_read_rcv_buffer + * + * Recover number of bytes DMA'd to receive buffer, sync. + */ +int arc_read_rcv_buffer (struct pcd_instance *pcd, struct usbd_endpoint_instance *endpoint) +{ + struct arc_private_struct *privdata = endpoint->privdata; + struct ep_td_struct *curr_td = privdata->cur_dtd; + struct ep_queue_head *qh = privdata->cur_dqh; + struct usbd_urb *rx_urb = endpoint->rcv_urb; + + /* sync qh and td structures, note that urb-buffer was invalidated in arc_add_buffer_to_dtd() */ + consistent_sync(qh, sizeof(struct ep_queue_head), DMA_FROM_DEVICE); + consistent_sync(curr_td, sizeof(struct ep_td_struct), DMA_FROM_DEVICE); + + if (rx_urb) { + int length = rx_urb->buffer_length - + ((le32_to_cpu(curr_td->size_ioc_sts) & DTD_PACKET_SIZE) >> DTD_LENGTH_BIT_POS); + + if (TRACE_VERBOSE) TRACE_MSG4(pcd->TAG, "buffer_length: %d alloc_length: %d Len: %d (%x)" , + rx_urb->buffer_length, rx_urb->alloc_length, length, + le32_to_cpu(curr_td->size_ioc_sts)); + return length; + } + TRACE_MSG1(pcd->TAG, "NO RCV URB (%x)" , le32_to_cpu(curr_td->size_ioc_sts)); + return 0; +} + +/*! arc_udc_init + */ +void arc_udc_release (void) +{ + kfree(ep_qh_base); + ep_qh_base = NULL; + if (udc_controller) { + if (udc_controller->ep_dtd) LKFREE(udc_controller->ep_dtd); + LKFREE(udc_controller); + udc_controller = NULL; + } +} + +/*! arc_udc_init + */ +static struct arcotg_udc *arc_udc_init (struct otg_dev *otg_dev) +{ + struct device *device = otg_dev_get_drvdata(otg_dev); + struct platform_device *pdev = to_platform_device(device); + + struct fsl_usb2_platform_data *pdata = (struct fsl_usb2_platform_data*)pdev->dev.platform_data; + + struct otg_instance *otg = otg_dev->otg_instance; + struct pcd_instance *pcd = otg_dev->pcd_instance; + struct arcotg_udc *udc = NULL; + int timeout; + + + /* Setting up the udc structure */ + THROW_UNLESS((udc = (struct arcotg_udc *) CKMALLOC(sizeof(struct arcotg_udc))), error); + + /* Allocate queue */ + THROW_UNLESS((udc->ep_qh = (struct ep_queue_head *) KMALLOC_ALIGN( USB_MAX_PIPES * sizeof(struct ep_queue_head), + GFP_KERNEL | GFP_DMA, 2 * 1024, (void **)&ep_qh_base)), error); + THROW_UNLESS(ep_qh_base, error); + THROW_UNLESS((udc->ep_dtd = (struct ep_td_struct *) CKMALLOC(USB_MAX_PIPES * sizeof(struct ep_td_struct))), error); + + /* Stop, reset and wait for the UDC to reset */ + UOG_USBCMD &= ~USB_CMD_RUN_STOP; + UOG_USBCMD |= USB_CMD_CTRL_RESET; + + timeout = 10000000; // XXX This needs to be fixed, should not need to resort to timeout + while ((UOG_USBCMD & USB_CMD_CTRL_RESET) && --timeout) { continue; } + if (timeout == 0) { + printk(KERN_DEBUG "%s: TIMEOUT\n", __FUNCTION__); + return NULL; + } + + /* Setup UDC mode and disable lock out mode*/ + UOG_USBMODE |= USB_MODE_CTRL_MODE_DEVICE | USB_MODE_SETUP_LOCK_OFF; + + UOG_EPLISTADDR = virt_to_phys(udc->ep_qh); + UOG_EPLISTADDR &= USB_EP_LIST_ADDRESS_MASK; + + /* Setup transceiver type, N.B. this must be done in one assignment */ + UOG_PORTSC1 = (UOG_PORTSC1 & ~PORTSCX_PHY_TYPE_SEL) | pdata->xcvr_type; + + #if !defined(CONFIG_OTG_HIGH_SPEED) + UOG_PORTSC1 |= (0x01000000); + #endif + fsl_platform_set_vbus_power(pdata, 0); + + CATCH(error) { + if (ep_qh_base) kfree(ep_qh_base); + if (udc) { + if (udc->ep_dtd) LKFREE(udc->ep_dtd); + LKFREE(udc); + } + udc = NULL; + } + return udc; +} + +/*! arc_udc_run + */ +static int arc_udc_run (void) +{ + /* enable the interrupt sources we want */ + UOG_USBINTR |= USB_INTR_INT_EN | USB_INTR_ERR_INT_EN | USB_INTR_PTC_DETECT_EN | + USB_INTR_RESET_EN | USB_INTR_DEVICE_SUSPEND | USB_INTR_SYS_ERR_EN; + + /* Set to peripheral mode */ + UOG_USBMODE |= USB_MODE_CTRL_MODE_DEVICE; + + /* set interrupt threshold to zero, run udc */ + UOG_USBCMD &= ~USB_CMD_ITC; + UOG_USBCMD |= USB_CMD_ITC_NO_THRESHOLD | USB_CMD_RUN_STOP; + return 0; +} + +/*! arc_udc_stop + */ +void arc_udc_stop (void) +{ + /* Disable all interrupts */ + UOG_USBINTR = 0; + UOG_USBCMD &= ~USB_CMD_RUN_STOP; +} + +/*! arc_pcd_start + */ +void arc_pcd_start (struct pcd_instance *pcd) +{ + u32 id = (u32) UOG_ID; + TRACE_MSG1(pcd->TAG, "Initialize UDC and start id: %08x", id); + arc_udc_run (); +} + +/*! arc_pcd_stop + */ +void arc_pcd_stop(struct pcd_instance *pcd) +{ + TRACE_MSG0(pcd->TAG, "Stop"); + arc_udc_stop (); +} + +/*! arc_pcd_disable + */ +void arc_pcd_disable(struct pcd_instance *pcd) +{ + TRACE_MSG0(pcd->TAG, "Disabled"); +} + +/*! arc_pcd_framenum() - get current framenum + */ +static u16 arc_pcd_framenum (struct pcd_instance *pcd) +{ + u16 frame = (UOG_FRINDEX & USB_FRINDEX_MASKS); + frame &= ~(0x7); + frame >>= 3; + return (int)( frame ); +} + +/* arc_pcd_ticks - get current ticks + */ +#define MXC_GPT_GPTCNT (IO_ADDRESS(GPT1_BASE_ADDR + 0x24)) +otg_tick_t arc_pcd_ticks (struct pcd_instance *pcd) +{ + unsigned long ticks = 0; + ticks = __raw_readl(MXC_GPT_GPTCNT); + return (otg_tick_t) ticks; +} + +/* arc_pcd_elapsed - return micro-seconds between two tick values + */ +otg_tick_t arc_pcd_elapsed(otg_tick_t *t1, otg_tick_t *t2) +{ + otg_tick_t ticks = (((*t1 > *t2) ? (*t1 - *t2) : (*t2 - *t1))); + return (otg_tick_t) ticks / 17; // 185 vs 189 +} + +/*****************************************************************************************/ +/*! arc_add_buffer_to_dtd + * + * C.f. 39.16.5.3 - case 1: Link list is empty + */ +static void arc_add_buffer_to_dtd (struct pcd_instance *pcd, struct usbd_endpoint_instance *endpoint, + int dir, int len, int offset) +{ + struct otg_instance *otg = pcd->otg; + struct usbd_urb *urb = endpoint->active_urb; + struct arc_private_struct *privdata = endpoint->privdata; + + u8 hs = pcd->bus->high_speed; + u8 physicalEndpoint = endpoint->physicalEndpoint[hs]; + u8 bEndpointAddress = endpoint->bEndpointAddress[hs]; + u8 epnum = bEndpointAddress & 0x3f; + u16 wMaxPacketSize = endpoint->wMaxPacketSize[hs]; + struct ep_queue_head *dQH = &udc_controller->ep_qh[2 * epnum + dir]; + struct ep_td_struct *dtd = &(udc_controller->ep_dtd[2 * epnum + dir]); + + u32 mask = 0; + int timeout1 = 0; + int timeout2 = 0; + + u32 endptstat = -1; + u32 endptprime = -1; + u32 endptcomplete = -1; + + TRACE_MSG6(pcd->TAG, "[%2d] USBCMD: %08x ENDPTPRIME: %08x COMPLETE: %08x STATUS: %08x %s", + 2*epnum+dir, UOG_USBCMD, UOG_ENDPTPRIME, UOG_ENDPTCOMPLETE, + (u32)dQH->size_ioc_int_sts, (dir == ARC_DIR_OUT) ? "OUT" : "IN"); + + if (urb && urb->buffer) { + + TRACE_MSG4(pcd->TAG, "buffer: %x length: %d alloc: %d dir: %d ", + urb->buffer, urb->actual_length, urb->buffer_length, dir); + + /* flush cache for IN */ + if ((dir == ARC_DIR_IN) && urb->actual_length) + consistent_sync(urb->buffer, urb->actual_length, DMA_TO_DEVICE); + + /* invalidate cache for OUT */ + else if ((dir == ARC_DIR_OUT) && urb->buffer_length) + consistent_sync(urb->buffer, urb->alloc_length, DMA_FROM_DEVICE); + } + + /* Set size and interrupt on each dtd, Clear reserved field, + * set pointers and flush from cache, and save in cur_dqh for dtd_releases() + */ + memset(dtd, 0, sizeof(struct ep_td_struct)); + dtd->size_ioc_sts = cpu_to_le32(((len << DTD_LENGTH_BIT_POS) | DTD_IOC | DTD_STATUS_ACTIVE)); + dtd->size_ioc_sts &= cpu_to_le32(~DTD_RESERVED_FIELDS); + dtd->buff_ptr0 = cpu_to_le32(endpoint->active_urb ? (u32) (virt_to_phys (endpoint->active_urb->buffer + offset)) : 0); + dtd->next_td_ptr = cpu_to_le32(DTD_NEXT_TERMINATE); + dtd->next_td_virt = NULL; + consistent_sync(dtd, sizeof(struct ep_td_struct), DMA_TO_DEVICE); + privdata->cur_dqh = dQH; + + /* Case 1 - Step 1 - Write dQH next pointer and dQH terminate bit to 0 as single DWord */ + dQH->next_dtd_ptr = cpu_to_le32( virt_to_phys((void *)dtd) & EP_QUEUE_HEAD_NEXT_POINTER_MASK); + + /* Case 1 - Step 2 - Clear active and halt bit */ + dQH->size_ioc_int_sts &= le32_to_cpu(~(EP_QUEUE_HEAD_STATUS_ACTIVE | EP_QUEUE_HEAD_STATUS_HALT)); + + consistent_sync(dQH, sizeof(struct ep_queue_head), DMA_TO_DEVICE); + + /* Case 1 - Step 3 - Prime endpoint by writing ENDPTPRIME */ + mask = (dir == ARC_DIR_OUT) ? (1 << epnum) : (1 << (epnum + 16)); + + /* Verify that endpoint PRIME is not set, wait if necessary. */ + for (timeout1 = 0; (UOG_ENDPTPRIME & mask) && (timeout1 ++ < 100); udelay(1)); + + /* ep0 needs extra tests */ + UNLESS(epnum) { + /* C.f. 39.16.3.2.2 Data Phase */ + UOG_ENDPTPRIME |= mask; + for (timeout2 = 0; timeout2++ < 100; ) { + endptprime = UOG_ENDPTPRIME; // order may be important + endptstat = UOG_ENDPTSTAT; // we check stat after prime + BREAK_IF(endptstat & mask); + BREAK_UNLESS(endptprime & mask); + } + if (!(endptstat & mask) && !(endptprime & mask)) { + TRACE_MSG2(pcd->TAG, "[%2d] ENDPTSETUPSTAT: %04x PREMATURE FAILUURE", 2*epnum+dir, UOG_ENDPTSETUPSTAT); + } + TRACE_MSG6(pcd->TAG, "[%2d] ENDPTPRIME %08x ENPTSTAT: %08x mask: %08x timeout: %d:%d SET", + 2*epnum+dir, UOG_ENDPTPRIME, UOG_ENDPTSTAT, mask, timeout1, timeout2);; + } + /* epn general case */ + else { + /* Hit PRIME bit until STATUS bit is set. */ + UOG_ENDPTPRIME |= mask; + //for (timeout2 = 0; !(UOG_ENDPTSTAT & mask) && (timeout2++ < 100); /* UOG_ENDPTPRIME |= mask */); + + for (timeout2 = 0; timeout2++ < 100; ) { + endptprime = UOG_ENDPTPRIME; // order may be important + endptstat = UOG_ENDPTSTAT; // we check stat after prime + endptcomplete = UOG_ENDPTCOMPLETE; + BREAK_IF(endptstat & mask); + BREAK_IF(endptcomplete & mask); + UNLESS(endptprime & mask) { + TRACE_MSG8(pcd->TAG, "[%2d] ENDPTPRIME %08x:%08x ENPTSTAT: %08x:%08x COMPLETE: %08x " + "mask: %08x timeout: %x NOT SET", + 2*epnum+dir, endptprime, UOG_ENDPTPRIME, endptstat, UOG_ENDPTSTAT, + UOG_ENDPTCOMPLETE, + mask, (timeout1 << 8) | timeout2);; + udelay(1); + endptstat = UOG_ENDPTSTAT; // we check stat after prime + BREAK_IF(endptstat & mask); + UOG_ENDPTPRIME |= mask; + } + } + TRACE_MSG8(pcd->TAG, "[%2d] ENDPTPRIME %08x:%08x ENPTSTAT: %08x:%08x COMPLETE: %08x " + "mask: %08x timeout: %x SET", + 2*epnum+dir, endptprime, UOG_ENDPTPRIME, endptstat, UOG_ENDPTSTAT, + UOG_ENDPTCOMPLETE, + mask, (timeout1 << 8) | timeout2);; + } + +} + +/* arc_dtd_releases + */ +static void arc_dtd_releases (struct pcd_instance *pcd, struct usbd_endpoint_instance *endpoint, int dir) +{ + struct arc_private_struct *privdata = endpoint->privdata; + consistent_sync(privdata->cur_dtd, sizeof(struct ep_td_struct), (dir == ARC_DIR_OUT) ? DMA_FROM_DEVICE : DMA_TO_DEVICE); + consistent_sync(privdata->cur_dqh, sizeof(struct ep_queue_head), (dir == ARC_DIR_OUT) ? DMA_FROM_DEVICE : DMA_TO_DEVICE); +} + + +/* arc_pcd_start_endpoint_in - start transmit + */ +void arc_pcd_start_endpoint_in (struct pcd_instance *pcd, struct usbd_endpoint_instance *endpoint) +{ + struct otg_instance *otg = pcd->otg; + struct usbd_urb *tx_urb; + u8 hs = pcd->bus->high_speed; + u8 bEndpointAddress = endpoint->bEndpointAddress[hs]; + u8 epnum = bEndpointAddress & 0x3f; + + UNLESS(epnum) { + TRACE_MSG1(pcd->TAG, "[ 0] ENDPTSETUPSTAT: %04x", UOG_ENDPTSETUPSTAT); + RETURN_IF (UOG_ENDPTSETUPSTAT & 0x1); + } + if ((tx_urb = endpoint->tx_urb)) { + endpoint->last = pcd_tx_sendzlp(endpoint) ? 0 : tx_urb->actual_length; + TRACE_MSG4(pcd->TAG, "[%2d] urb length: %d sent: %d %s", + epnum, tx_urb->actual_length, endpoint->sent, endpoint->last ? "" : "ZLP"); + arc_add_buffer_to_dtd (pcd, endpoint, ARC_DIR_IN, endpoint->last, endpoint->sent); + } +} + +/* arc_pcd_start_endpoint_out - start receive + */ +void arc_pcd_start_endpoint_out (struct pcd_instance *pcd,struct usbd_endpoint_instance *endpoint) +{ + //struct usbd_urb *rcv_urb; + //u8 hs = pcd->bus->high_speed; + //u8 bEndpointAddress = endpoint->bEndpointAddress[hs]; + //u8 epnum = bEndpointAddress & 0x3f; + + struct usbd_urb *rcv_urb; + u8 hs; + u8 bEndpointAddress; + u8 epnum; + + UNLESS (pcd) printk(KERN_INFO"%s: pcd: %p\n", __FUNCTION__, pcd); + UNLESS (pcd) printk(KERN_INFO"%s: pcd->bus: %p\n", __FUNCTION__, pcd->bus); + hs = pcd->bus->high_speed; + + UNLESS (endpoint) printk(KERN_INFO"%s: endpoint: %p\n", __FUNCTION__, endpoint); + bEndpointAddress = endpoint->bEndpointAddress[hs]; + epnum = bEndpointAddress & 0x3f; + + UNLESS(epnum) { + //otg_led(LED2, 0); + TRACE_MSG1(pcd->TAG, "[ 0] ENDPTSETUPSTAT: %04x", UOG_ENDPTSETUPSTAT); + RETURN_IF (UOG_ENDPTSETUPSTAT & 0x1); + //otg_led(LED2, 1); + } + if ((rcv_urb = pcd_rcv_next_irq(endpoint))) { + TRACE_MSG3(pcd->TAG, "[%2d] urb length: %d actual: %d length: %d", epnum, + rcv_urb->actual_length, rcv_urb->buffer_length); + arc_add_buffer_to_dtd (pcd, endpoint, ARC_DIR_OUT, rcv_urb->buffer_length, 0); + } +} + +/*! arc_pcd_setup_ep - setup endpoint + * @param pcd - + * @param ep - + * @param endpoint + * @return none + */ +void arc_pcd_setup_ep (struct pcd_instance *pcd, unsigned int ep, struct usbd_endpoint_instance *endpoint) +{ + struct usbd_bus_instance *bus = pcd->bus; + struct ep_queue_head *dQH = NULL; + + u8 hs = pcd->bus->high_speed; + u8 bEndpointAddress = endpoint->bEndpointAddress[hs]; + u8 bmAttributes = endpoint->bmAttributes[hs]; + u8 epnum = bEndpointAddress & 0x3f; + u16 wMaxPacketSize = endpoint->wMaxPacketSize[hs]; + u8 dir = (bEndpointAddress & 0x80) ? 1 : 0; + u8 type = bmAttributes & 0x3; + + int timeout; + unsigned int tmp = 0; + + struct arc_private_struct *privdata; + + dQH = &udc_controller->ep_qh[2 * epnum + dir]; + UNLESS(endpoint->privdata) { + endpoint->privdata = (struct arc_private_struct *) CKMALLOC (sizeof(struct arc_private_struct)); + } + RETURN_UNLESS(privdata = endpoint->privdata); + + /* Set the packet size */ + switch (type) { + case 0: /* Control */ + tmp = (wMaxPacketSize << EP_QUEUE_HEAD_MAX_PKT_LEN_POS) | EP_QUEUE_HEAD_IOS; + break; + + case 2: /* Bulk */ + case 3: /* Interrupt */ + tmp = wMaxPacketSize << EP_QUEUE_HEAD_MAX_PKT_LEN_POS; + tmp |= EP_QUEUE_HEAD_ZLT_SEL; + break; + } + dQH->max_pkt_length = cpu_to_le32(tmp); + + consistent_sync(dQH, sizeof(struct ep_queue_head), DMA_TO_DEVICE); + + /* Set the control register of endpoint */ + tmp = dir ? + (EPCTRL_TX_ENABLE | ((u32)(type) << EPCTRL_TX_EP_TYPE_SHIFT) | (epnum ? EPCTRL_TX_DATA_TOGGLE_RST : 0)) : + (EPCTRL_RX_ENABLE | ((u32)(type) << EPCTRL_RX_EP_TYPE_SHIFT) | (epnum ? EPCTRL_RX_DATA_TOGGLE_RST : 0)); + + USBOTG_REG32(0x1c0 + epnum*4) |= tmp; + + /* wait for correct writing */ + timeout = 10000000; // XXX This needs to be fixed, should not need to resort to timeout + while ( ( !(USBOTG_REG32(0x1c0 + epnum*4) & (tmp & (EPCTRL_TX_ENABLE | EPCTRL_RX_ENABLE)) ) ) && --timeout) { continue; } + if (timeout == 0) + printk(KERN_INFO "%s: TIMEOUT\n", __FUNCTION__); + + TRACE_MSG4(pcd->TAG, "[%2x] Control register for size: %d is %x (%x)", + 2*epnum+dir, wMaxPacketSize, tmp, USBOTG_REG32(0x1c0 + epnum*4)); + + if (!epnum) { + dir = 1; + dQH = &udc_controller->ep_qh[2 * epnum + dir]; + tmp = (wMaxPacketSize << EP_QUEUE_HEAD_MAX_PKT_LEN_POS) | EP_QUEUE_HEAD_IOS; + dQH->max_pkt_length = le32_to_cpu(tmp); + consistent_sync(dQH, sizeof(struct ep_queue_head), DMA_FROM_DEVICE); + tmp = USBOTG_REG32(0x1c0 + epnum*4); + tmp |= EPCTRL_TX_ENABLE; + tmp |= ((unsigned int)(type) << EPCTRL_TX_EP_TYPE_SHIFT); + USBOTG_REG32(0x1c0 + epnum*4) = tmp; + + // XXX This needs to be fixed, should not need to resort to timeout, wait for correct writing + timeout = 10000000; + while ( ( !(USBOTG_REG32(0x1c0 + epnum*4) & (tmp & (EPCTRL_TX_ENABLE | EPCTRL_RX_ENABLE)) ) ) && --timeout) + continue; + if (timeout == 0) printk(KERN_INFO "%s: TIMEOUT\n", __FUNCTION__); + } + privdata->cur_dtd = &(udc_controller->ep_dtd[2 * epnum + dir]); + privdata->cur_dqh = &udc_controller->ep_qh[2 * epnum + dir]; +} + +/* arc_pcd_disable_ep - + */ +void arc_pcd_disable_ep(struct pcd_instance *pcd, unsigned int ep, struct usbd_endpoint_instance *endpoint) +{ + TRACE_MSG1(pcd->TAG, "endpoint->privdata: %p", endpoint->privdata); + RETURN_UNLESS (endpoint->privdata); + LKFREE(endpoint->privdata); + endpoint->privdata = NULL; +} + + +/*! arc_pcd_cancel_irq + */ +void arc_pcd_cancel_irq (struct pcd_instance *pcd, struct usbd_urb *urb) +{ + struct usbd_endpoint_instance *endpoint = urb->endpoint; + + u8 hs = pcd->bus->high_speed; + u8 bEndpointAddress = endpoint->bEndpointAddress[hs]; + u8 epnum = bEndpointAddress & 0x3f; + u8 dir = (bEndpointAddress & 0x80) ? 1 : 0; + + u32 mask = (dir == ARC_DIR_OUT) ? (1 << epnum) : (1 << (epnum + 16)); + + TRACE_MSG5(pcd->TAG, "[%2d] USBCMD: %08x ENDPTPRIME: %08x COMPLETE: %08x mask: %08x FLUSH", + 2*epnum+dir, UOG_USBCMD, UOG_ENDPTPRIME, UOG_ENDPTCOMPLETE, mask); + + + UOG_ENDPTFLUSH = mask; + + TRACE_MSG5(pcd->TAG, "[%2d] USBCMD: %08x ENDPTPRIME: %08x COMPLETE: %08x mask: %08x FLUSH", + 2*epnum+dir, UOG_USBCMD, UOG_ENDPTPRIME, UOG_ENDPTCOMPLETE, mask); + + TRACE_MSG5(pcd->TAG, "[%2d] USBCMD: %08x ENDPTPRIME: %08x COMPLETE: %08x mask: %08x FLUSH", + 2*epnum+dir, UOG_USBCMD, UOG_ENDPTPRIME, UOG_ENDPTCOMPLETE, mask); + +} + + +extern void fsl_platform_test_mode_select(struct fsl_usb2_platform_data *pdata, int on); +/* arc_pcd_device_feature - device feature + */ +int arc_pcd_device_feature(struct pcd_instance *pcd, int selector, int flag) +{ + u32 tmp = UOG_PORTSC1 | (selector << 16); + UOG_PORTSC1 = tmp; + return 0; +} + +/* + * arc_endpoint_halted() - is endpoint halted + */ +static int +arc_endpoint_halted (struct pcd_instance *pcd, struct usbd_endpoint_instance *endpoint) +{ + u8 hs = pcd->bus->high_speed; + u8 bEndpointAddress = endpoint->bEndpointAddress[hs]; + u8 epnum = bEndpointAddress & 0x3f; + u8 dir = (bEndpointAddress & 0x80) ? 1 : 0; + u32 tmp = USBOTG_REG32(0x1c0 + epnum*4); + switch (dir) { + case USB_DIR_IN: + TRACE_MSG3(pcd->TAG, "epn: %d dir: %d ENDPTCTRLn: %04x USB_DIR_IN", epnum, dir, tmp); + return BOOLEAN( tmp & EPCTRL_TX_EP_STALL ); + + case USB_DIR_OUT: + TRACE_MSG3(pcd->TAG, "epn: %d dir: %d ENDPTCTRLn: %04x USB_DIR_OUT", epnum, dir, tmp); + return BOOLEAN( tmp & EPCTRL_RX_EP_STALL ); + } + return 0; +} + +/* arc_halt_endpoint - halt endpoint + */ +int arc_halt_endpoint(struct pcd_instance *pcd, struct usbd_endpoint_instance *endpoint, int flag) +{ + u8 hs = pcd->bus->high_speed; + u8 bEndpointAddress = endpoint->bEndpointAddress[hs]; + u8 epnum = bEndpointAddress & 0x3f; + u8 dir = (bEndpointAddress & 0x80) ? 1 : 0; + u32 tmp = USBOTG_REG32(0x1c0 + epnum*4); + u32 mask = (dir == USB_DIR_IN) ? EPCTRL_TX_EP_STALL : EPCTRL_RX_EP_STALL; + + tmp = flag ? (tmp | mask) : (tmp & ~mask); + USBOTG_REG32(0x1c0 + epnum*4) = tmp; + + TRACE_MSG5(pcd->TAG, "epn: %d dir: %d flag: %d ENDPTCTRLn: %04x USB_DIR_%s STALLING", + epnum, dir, flag, tmp, (dir == USB_DIR_IN) ? "IN" : "OUT"); + return 0; +} + + +#if HAVE_PLATFORM_ARC_REMOTE_WAKEUP +extern void fsl_platform_perform_remote_wakeup(struct fsl_usb2_platform_data *pdata); + +/*! arc_remote_wakeup + */ +static int arc_remote_wakeup(struct pcd_instance *pcd) +{ + struct usbd_bus_instance *bus = pcd->bus; + struct otg_dev *otg_dev = pcd->privdata; + struct device *device = otg_dev_get_drvdata(otg_dev); + struct platform_device *pdev = to_platform_device(device); + struct fsl_usb2_platform_data *pdata = (struct fsl_usb2_platform_data*)pdev->dev.platform_data; + + printk(KERN_INFO"%s:\n", __FUNCTION__); + fsl_platform_perform_remote_wakeup(pdata); + return 0; +} +#endif + +/*! arc_vbus_status() - enable + * This is called to return status of the PCD and USBD stack. + * Start or stop the UDC. + */ +static int +arc_vbus_status (struct pcd_instance *pcd) +{ + u32 vbus = UOG_PORTSC1 & PORTSCX_CURRENT_CONNECT_STATUS; + TRACE_MSG2(pcd->TAG, "UOG_PORTSC1: %x, vbus: %x", UOG_PORTSC1, vbus); + return BOOLEAN(vbus); +} + +/*! arc_softcon + * @param pcd - pcd pointer + * @param flag - + * + * Enable or disable pullup. + */ +int arc_softcon(struct pcd_instance *pcd, int flag) +{ + TRACE_MSG1(pcd->TAG, "ARC PULLUP %s", flag ? "SET": "RESET"); + UOG_USBCMD = flag ? (UOG_USBCMD | USB_CMD_RUN_STOP) : (UOG_USBCMD & ~USB_CMD_RUN_STOP); + return 0; +} + + +/* ********************************************************************************************* */ + +struct usbd_pcd_ops usbd_pcd_ops = { + .bmAttributes = + #ifdef CONFIG_OTG_SELF_POWERED + USB_BMATTRIBUTE_SELF_POWERED | + #endif /* CONFIG_OTG_SELF_POWERED */ + #ifdef CONFIG_OTG_REMOTE_WAKEUP + USB_BMATTRIBUTE_REMOTE_WAKEUP | + #endif /* CONFIG_OTG_REMOTE_WAKEUP */ + //USB_OTG_HNP_SUPPORTED | USB_OTG_SRP_SUPPORTED | + USB_BMATTRIBUTE_RESERVED, + #ifndef CONFIG_OTG_SELF_POWERED + .bMaxPower = CONFIG_OTG_BMAXPOWER, + #else /* CONFIG_OTG_SELF_POWERED */ + .bMaxPower = 1, + #endif /* CONFIG_OTG_SELF_POWERED */ + .max_endpoints = 0, + .high_speed_capable = TRUE, + .ep0_packetsize = 64, + .name = UDC_NAME, + .start = arc_pcd_start, + .stop = arc_pcd_stop, + .disable = arc_pcd_disable, + .disable_ep = arc_pcd_disable_ep, + .start_endpoint_in = arc_pcd_start_endpoint_in, + .start_endpoint_out = arc_pcd_start_endpoint_out, + .request_endpoints = pcd_request_endpoints, + .setup_ep = arc_pcd_setup_ep, + .cancel_in_irq = arc_pcd_cancel_irq, + .cancel_out_irq = arc_pcd_cancel_irq, + .device_feature = arc_pcd_device_feature, + .halt_endpoint = arc_halt_endpoint, + .endpoint_halted = arc_endpoint_halted, + .remote_wakeup = arc_remote_wakeup, + .vbus_status = arc_vbus_status, + .softcon = arc_softcon, + .ticks = arc_pcd_ticks, + .elapsed = arc_pcd_elapsed, + .framenum = arc_pcd_framenum, +}; + +/* ********************************************************************************************* */ +/*! arc_ep0_irq + */ +static irqreturn_t arc_ep0_irq(struct pcd_instance *pcd) +{ + struct usbd_endpoint_instance *endpoint = pcd->bus->endpoint_array + 0; + struct usbd_urb *urb = endpoint->active_urb; + + /* SETUP - Device request received */ + if (UOG_ENDPTSETUPSTAT & 0x1) { + struct usbd_device_request request; + TRACE_MSG2(pcd->TAG, "EP0 SETUP active: %p %x", urb, urb ? urb->flags : 0); + + /* Complete any outstanding transfers */ + if (urb && (urb->flags & USBD_URB_IN)) pcd_tx_complete_irq(endpoint, 0); + if (urb && (urb->flags & USBD_URB_OUT)) pcd_rcv_complete_irq(endpoint, 0, 0); + + /* Read setup - reset UOG_ENDPTSETUPSTAT */ + arc_read_setup_buffer(pcd, (u8 *) &request); + + if (pcd_recv_setup_irq(pcd, &request)) { + /* endpoint requested not handled - need to stall */ + TRACE_MSG0(pcd->TAG, "REQUEST NOT HANDLED - STALLED"); + USBOTG_REG32(0x1c0 + 0*4) |= EPCTRL_TX_EP_STALL | EPCTRL_RX_EP_STALL; + return IRQ_HANDLED; + } + /* Finish if no data phase */ + UNLESS (request.wLength) { + TRACE_MSG0(pcd->TAG, "REQUEST HANDLED OK - NO DATA PHASE"); + arc_add_buffer_to_dtd (pcd, endpoint, ARC_DIR_IN, 0, 0); + } + return IRQ_HANDLED; + } + /* TX/IN Complete */ + if (UOG_ENDPTCOMPLETE & 0x10000) { + TRACE_MSG2(pcd->TAG, "EP0 IN COMPLETE active: %p %x", urb, urb ? urb->flags : 0); + + /* set address here if previous request was SET_ADDRESS */ + if (pcd->new_address) { + UOG_DEVICEADDR = pcd->new_address << USB_DEVICE_ADDRESS_BIT_POS; + pcd->new_address = 0; + } + + /* sync and clear complete bit */ + arc_dtd_releases (pcd, endpoint, ARC_DIR_IN); + UOG_ENDPTCOMPLETE = 0x10000; + + /* finish urb, reset zlp flag if it's there, we always send zlp */ + if (pcd_tx_complete_irq(endpoint, 0)) { + TRACE_MSG2(pcd->TAG, "EP0 IN COMPLETE ZLP check: %p %x", urb, urb ? urb->flags : 0); + if (pcd_tx_sendzlp(endpoint)) { + pcd_tx_complete_irq(endpoint, 0); + TRACE_MSG2(pcd->TAG, "EP0 IN COMPLETE ZLP active: %p %x", urb, urb ? urb->flags : 0); + } + } + arc_add_buffer_to_dtd (pcd, endpoint, ARC_DIR_OUT, 0, 0); + return IRQ_HANDLED; + } + /* RX/OUT Complete */ + if (UOG_ENDPTCOMPLETE & 0x1) { + struct arc_private_struct *privdata = endpoint->privdata; + struct ep_td_struct *curr_td = privdata->cur_dtd; + int length; + + /* sync and clear complete bit */ + arc_dtd_releases (pcd, endpoint, ARC_DIR_OUT); + length = ((le32_to_cpu(curr_td->size_ioc_sts) & DTD_PACKET_SIZE) >> DTD_LENGTH_BIT_POS); + UOG_ENDPTCOMPLETE = 0x1; + + TRACE_MSG3(pcd->TAG, "EP0 OUT COMPLETE active: %p %x length: %d", urb, urb ? urb->flags : 0, length); + UNLESS (urb && (urb->flags & USBD_URB_OUT)) { + TRACE_MSG0(pcd->TAG, "EP0 OUT COMPLETE - ZLP received"); + return IRQ_HANDLED; + } + length = arc_read_rcv_buffer (pcd, endpoint); + pcd_rcv_complete_irq(endpoint, length, 0); + arc_add_buffer_to_dtd (pcd, endpoint, ARC_DIR_IN, 0, 0); + return IRQ_HANDLED; + } + return IRQ_NONE; +} + +/*! arc_epn_irq + */ +static irqreturn_t arc_epn_irq(struct pcd_instance *pcd) +{ + u32 endptcomplete; + /* N.B. need to loop until nothing left to do, endpoints can complete while + * working on other endpoints. + */ + while ((endptcomplete = (UOG_ENDPTCOMPLETE & 0xfffefffe))) { + + /* We have endptcomplete bits, immediately reset so that subsequent + * completions will be seen when we loop. Note that we mask ep0 status. + */ + UOG_ENDPTCOMPLETE = endptcomplete; + + /* Loop while non-zero - N.B. clz requires pre-test as it is undefined on zero */ + while (endptcomplete) { + int bit = 31 - __builtin_clz(endptcomplete); /* highest non-zero bit */ + int ep = bit & 0xf; + int dir = (bit > 15) ? ARC_DIR_IN : ARC_DIR_OUT; + struct usbd_endpoint_instance *endpoint = pcd->bus->endpoint_array + (2 * ep + dir); + struct arc_private_struct *privdata = endpoint->privdata; + struct ep_td_struct *cur_dtd = privdata->cur_dtd; + int errors = le32_to_cpu(cur_dtd->size_ioc_sts) & DTD_ERROR_MASK; + int remain_len; + TRACE_MSG6(pcd->TAG, "[%2d:%2d] mask: %08x endptcomplete: %08x %s %s", bit, ep, + (1 << bit), endptcomplete, (ARC_DIR_IN) ? "IN" : "OUT", ep ? "EPN" : "EP0"); + switch (dir) { + case ARC_DIR_IN: /* Transmitting */ + arc_dtd_releases (pcd, endpoint, ARC_DIR_IN); + if ((pcd_tx_complete_irq(endpoint, 0))) + arc_pcd_start_endpoint_in(pcd, endpoint); + break; + + case ARC_DIR_OUT: /* Receiving */ + remain_len = arc_read_rcv_buffer (pcd, endpoint); + arc_dtd_releases (pcd, endpoint, ARC_DIR_OUT); + if ( errors & DTD_STATUS_HALTED ) { + TRACE_MSG4(pcd->TAG, "[%2d:%2d] Current dtd is halted size_ioc_sts: %08x errors: %02x", + bit, ep, le32_to_cpu(cur_dtd->size_ioc_sts), errors); + cur_dtd->size_ioc_sts &= cpu_to_le32(errors); + consistent_sync(cur_dtd, sizeof(struct ep_td_struct), DMA_TO_DEVICE); + } + + if ((pcd_rcv_complete_irq(endpoint, remain_len, 0))) { + + UNLESS(pcd && pcd->bus && endpoint) { + printk(KERN_INFO"%s: NULL pcd: %p bus: %p endpoint: %p\n", + __FUNCTION__, pcd, pcd ? pcd->bus : NULL, endpoint); + break; + } + arc_pcd_start_endpoint_out(pcd, endpoint); + } + break; + } + endptcomplete &= ~(1 << bit); + } + } + return IRQ_HANDLED; +} + +/*! arc_pcd_isr + */ +static irqreturn_t arc_pcd_isr(struct otg_dev *otg_dev, void *data, u32 mask) +{ + u32 irq_src; + struct otg_instance *otg = otg_dev ? otg_dev->otg_instance : NULL; + struct pcd_instance *pcd = otg_dev ? otg_dev->pcd_instance : NULL; + struct usbd_bus_instance *bus = pcd->bus; + int timeout; + irqreturn_t status = IRQ_NONE; + + otg_led(LED2, 1); + otg->interrupts++; + if (TRACE_VERBOSE) { + TRACE_MSG0(pcd->TAG, "--"); + TRACE_MSG5(pcd->TAG, "UOG_USBINTR: %08x UOG_ENDPTSTAT: %08x " + "UOG_ENDPTSETUPSTAT: %04x, UOG_ENDPTCOMPLETE: %08x, UOG_PORTSC1: %08x", + UOG_USBINTR, UOG_ENDPTSTAT, UOG_ENDPTSETUPSTAT, UOG_ENDPTCOMPLETE, UOG_PORTSC1); + } + + /* verify controller is running */ + RETURN_IRQ_NONE_UNLESS(UOG_USBCMD & USB_CMD_RUN_STOP); + + /* get interrupt source and reset in USBSTS register */ + irq_src = UOG_USBSTS & UOG_USBINTR; + UOG_USBSTS &= irq_src; + /* USB */ + if (irq_src & USB_STS_INT) { + if ( (UOG_ENDPTSETUPSTAT & 0x1) || (UOG_ENDPTCOMPLETE & 0x1) || (UOG_ENDPTCOMPLETE & 0x10000)) + status = arc_ep0_irq (pcd); + if ( (UOG_ENDPTCOMPLETE & 0xfffefffe) ) + status = arc_epn_irq (pcd); + } + /* SOF */ + if (irq_src & USB_STS_SOF) { + // TRACE_MSG0(pcd->TAG, "SOF interrupt"); + status = IRQ_HANDLED; + } + /* port changed */ + if (irq_src & USB_STS_PORT_CHANGE) { + u32 speed; + TRACE_MSG0(pcd->TAG, "PORT CHANGE interrupt"); + if (!(UOG_PORTSC1 & PORTSCX_PORT_RESET)) { + speed = (UOG_PORTSC1 & PORTSCX_PORT_SPEED_MASK); + switch (speed) { + case PORTSCX_PORT_SPEED_HIGH: + TRACE_MSG0(pcd->TAG, "High Speed"); + bus->high_speed = TRUE; + break; + case PORTSCX_PORT_SPEED_FULL: + TRACE_MSG0(pcd->TAG, "Full Speed"); + bus->high_speed = FALSE; + break; + case PORTSCX_PORT_SPEED_LOW: + TRACE_MSG0(pcd->TAG, "Low Speed"); + break; + default: + TRACE_MSG0(pcd->TAG, "Unknown Speed"); + break; + } + status = IRQ_HANDLED; + } + if ((UOG_PORTSC1 & PORTSCX_CURRENT_CONNECT_STATUS)) { + TRACE_MSG0(pcd->TAG, "Cable is attached"); + otg_event(otg, VBUS_VLD | B_SESS_VLD, otg->pcd->TAG, "ARC VBUS VALID"); + status = IRQ_HANDLED; + } + if (!(UOG_PORTSC1 & PORTSCX_CURRENT_CONNECT_STATUS)) { + TRACE_MSG0(pcd->TAG, "Cable is detached"); + otg_event(otg, VBUS_VLD_ | B_SESS_VLD_ | A_SESS_VLD_, otg->pcd->TAG, "ARC VBUS INVALID"); + status = IRQ_HANDLED; + } + } + /* reset */ + if (irq_src & USB_STS_RESET) { + u32 temp; + TRACE_MSG0(pcd->TAG, "RESET interrupt"); + + /* Reset address, endpoint status and complete */ + UOG_DEVICEADDR &= ~USB_DEVICE_ADDRESS_MASK; + UOG_ENDPTSTAT = UOG_ENDPTSTAT; + UOG_ENDPTCOMPLETE = UOG_ENDPTCOMPLETE; + + timeout = 10000000; // XXX + /* Wait until all endptprime bits cleared */ + while (UOG_ENDPTPRIME && --timeout) { continue; } + if (timeout == 0) printk(KERN_ERR "%s: TIMEOUT\n", __FUNCTION__); + + UOG_ENDPTFLUSH = 0xFFFFFFFF; + + if (UOG_PORTSC1 & PORTSCX_PORT_RESET) { + TRACE_MSG0(pcd->TAG, "Bus reset"); + } + else { + TRACE_MSG0(pcd->TAG, "Controller reset"); + arc_udc_run (); + } + pcd_bus_event_handler_irq (pcd->bus, DEVICE_RESET, 0); + pcd_bus_event_handler_irq (pcd->bus, DEVICE_ADDRESS_ASSIGNED, 0); + status = IRQ_HANDLED; + } + /* SUSPEND / RESUME */ + if (irq_src & USB_STS_SUSPEND) { + if (UOG_PORTSC1 & PORTSCX_PORT_SUSPEND) { + TRACE_MSG0(pcd->TAG, "SUSPEND interrupt"); + pcd_check_device_feature(pcd->bus, USB_DEVICE_REMOTE_WAKEUP, 1); + pcd_bus_event_handler_irq (pcd->bus, DEVICE_BUS_INACTIVE, 0); + } + else { + TRACE_MSG0(pcd->TAG, "RESUME interrupt"); + pcd_bus_event_handler_irq (pcd->bus, DEVICE_BUS_ACTIVITY, 0); + pcd_check_device_feature(pcd->bus, USB_DEVICE_REMOTE_WAKEUP, 0); + } + status = IRQ_HANDLED; + } + if (irq_src & (USB_STS_ERR | USB_STS_SYS_ERR)) { + TRACE_MSG0(pcd->TAG, "Error in interrupt"); + status = IRQ_HANDLED; + } + if (TRACE_VERBOSE) TRACE_MSG6(pcd->TAG, "UOG_USBINTR: %08x UOG_ENDPTSTAT: %08x " + "UOG_ENDPTSETUPSTAT: %04x, UOG_ENDPTCOMPLETE: %08x, UOG_PORTSC1: %08x %s", + UOG_USBINTR, UOG_ENDPTSTAT, UOG_ENDPTSETUPSTAT, UOG_ENDPTCOMPLETE, UOG_PORTSC1, + (status == IRQ_HANDLED) ? "IRQ_HANDLED" : "IRQ_NONE"); + + otg_led(LED2, 0); + return status; +} + +/*! default initialization + */ +int pcd_mod_init (struct otg_instance *otg); +void pcd_mod_exit (struct otg_instance *otg); + +/*! arc_pcd_remove() - called to remove hardware + * @param otg_dev - otg device + */ +static void +arc_pcd_remove(struct otg_dev *otg_dev) +{ + struct otg_instance *otg = otg_dev->otg_instance; + struct device *device = otg_dev_get_drvdata(otg_dev); + struct platform_device *pdev = to_platform_device(device); + struct fsl_usb2_platform_data *pdata = (struct fsl_usb2_platform_data*)pdev->dev.platform_data; + + TRACE_MSG0(otg->pcd->TAG, "--"); + arc_udc_release (); + udc_controller = NULL; + pdata->platform_uninit(pdata); +//printk(KERN_INFO"%s: jumpin116 start--------\n ", __FUNCTION__); + pcd_mod_exit(otg); +//printk(KERN_INFO"%s: jumpin116 end--------- \n", __FUNCTION__); +} + +/*! arc_pcd_probe() - called to probe hardware + * @param otg_dev - otg device + * + * This function should do minimal, one time only hardware recognition, + * resource reservation and minimal setup. Typically to get to known + * disabled state. It should not start the hardware. + * + */ +static int arc_pcd_probe(struct otg_dev *otg_dev) +{ + struct device *device = otg_dev_get_drvdata(otg_dev); + struct otg_instance *otg = otg_dev->otg_instance; + struct platform_device *pdev = to_platform_device(device); + struct fsl_usb2_platform_data *pdata = (struct fsl_usb2_platform_data*)pdev->dev.platform_data; + + usbd_pcd_ops.max_endpoints = UDC_MAX_ENDPOINTS; + + RETURN_ENODEV_IF(strcmp(pdev->name, "arc_udc")); + UNLESS(pdata && pdata->platform_init) { + printk(KERN_INFO"%s: NO TRANSCEIVER CONFIGURED\n", __FUNCTION__); + return -ENODEV; + } + RETURN_ZERO_IF (pdata->platform_init(pdev)); + + //fsl_platform_set_vbus_power(pdata, 0); + RETURN_ENODEV_IF(pcd_mod_init(otg)); + udc_controller = (struct arcotg_udc *) arc_udc_init (otg_dev); + return 0; +} + +/*! arc_pcd_suspend + */ +static void arc_pcd_suspend(struct otg_dev *otg_dev, u32 state) +{ + /* NOTHING */ +} + +/*! arc_pcd_resume + */ +static void arc_pcd_resume(struct otg_dev *otg_dev) +{ + /* NOTHING */ +} + +/* ********************************************************************************************* */ +static struct otg_dev_driver arc_pcd_driver = { + .name = "arc_udc", + .id = OTG_DRIVER_PCD, + .probe = arc_pcd_probe, + .remove = arc_pcd_remove, + .suspend = arc_pcd_suspend, + .resume = arc_pcd_resume, + .irqs = (1 << 1), /* use irq at location #0 in udc_resources */ + .isr = arc_pcd_isr, + .ops = &pcd_ops, +}; + +/*! arc_pcd_module_init() - module init + */ +int arc_pcd_module_init (struct otg_device_driver *otg_device_driver) +{ + return otg_dev_register_driver(otg_device_driver, &arc_pcd_driver); +} + +/*! arc_pcd_module_exit() - module exit + */ +void arc_pcd_module_exit (struct otg_device_driver *otg_device_driver) +{ + otg_dev_unregister_driver (otg_device_driver, &arc_pcd_driver); +} diff --git a/drivers/otg/hardware/arc-tcd.c b/drivers/otg/hardware/arc-tcd.c new file mode 100644 index 000000000000..64196b55fa27 --- /dev/null +++ b/drivers/otg/hardware/arc-tcd.c @@ -0,0 +1,260 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/hardware/arc-tcd.c -- USB Transceiver Controller driver + * @(#) balden@belcarra.com/seth2.rillanon.org|otg/platform/arc/arc-tcd.c|20070614183949|64278 + * + * Copyright (c) 2006-2007 Belcarra Technologies 2005 Corp + * + * By: + * Shahrad Payandeh Lynne <sp@lbelcarra.com>, + * Stuart Lynne <sl@lbelcarra.com>, + * + */ +/*! + * @file otg/hardware/arc-tcd.c + * @brief Belcarra Freescale ARC Device driver. + * + * This interfaces to the generic Freescale platform transceiver driver. + * + * + * @ingroup ARC + * @ingroup TCD + * @ingroup OTGDEV + * @ingroup LINUXOS + */ + +#include <otg/otg-compat.h> + +#include <otg/usbp-chap9.h> +#include <otg/usbp-bus.h> +#include <otg/otg-trace.h> +#include <otg/otg-api.h> +#include <otg/otg-dev.h> +#include <otg/otg-hcd.h> +#include <otg/otg-tcd.h> +#include <otg/otg-ocd.h> +#include <otg/otg-pcd.h> +#include <asm/arch/arc_otg.h> +#include "arc-hardware.h" + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,15) +#include <linux/platform_device.h> +#endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,15) */ +#include <linux/fsl_devices.h> +#include <linux/usb/fsl_xcvr.h> + +extern void fsl_platform_set_vbus_power(struct fsl_usb2_platform_data *pdata, int on); + +/* ********************************************************************************************* */ +/*! arc_tcd_init() - used to enable + * @param otg - otg_instance pointer + * @param flag - + * + * Enable MX31 OTG interrupts. + * + */ +void arc_tcd_init(struct otg_instance *otg, u8 flag) +{ + struct otg_dev *otg_dev = otg->privdata; + struct tcd_instance *tcd = otg->tcd; + + switch (flag) { + case SET: + break; + case RESET: + break; + } + otg_event(otg, OCD_OK, otg->tcd->TAG, "MX31 TCD OK"); +} + +/*! arc_tcd_en() - used to enable + * @param otg - otg_instance pointer + * @param flag - + * + * Sample OTG physical inputs and pass to OTG state machine. + * + */ +void arc_tcd_en(struct otg_instance *otg, u8 flag) +{ + struct otg_dev *otg_dev = otg->privdata; + struct tcd_instance *tcd = otg->tcd; + + // XXX + u32 vbus = UOG_PORTSC1 & PORTSCX_CURRENT_CONNECT_STATUS; + TRACE_MSG2(tcd->TAG, "UOG_PORTSC1: %x, vbus: %x", UOG_PORTSC1, vbus); + + switch (flag) { + case PULSE: + case SET: + switch (vbus) { + case 0: + otg_event(otg, VBUS_VLD_ | B_SESS_VLD_ | A_SESS_VLD_, otg->tcd->TAG, "MX31 VBUS INVALID"); + break; + case 1: + otg_event(otg, VBUS_VLD | B_SESS_VLD, otg->tcd->TAG, "MX31 TCD EN"); + break; + default: + break; + } + case RESET: + break; + default: + break; + + } +} + + +/*! arc_dp_pullup_func - used to enable or disable peripheral connecting to bus + * + * C.f. 5.1.6, 5.1.7, 5.2.4 and 5.2.5 + * + * host peripheral + * d+ pull-up clr set + * d+ pull-down set clr + * + * d- pull-up clr clr + * d- pull-down set set + * + */ + + +/*! arc_dp_pullup_func + * @param otg - otg_instance pointer + * @param flag - + * + * Enable or disable pullup. + */ +void arc_dp_pullup_func(struct otg_instance *otg, u8 flag) +{ + struct otg_dev *otg_dev = otg->privdata; + + switch (flag) { + case SET: + TRACE_MSG0(otg->tcd->TAG, "MX31 PULLUP SET"); + UOG_USBCMD |= USB_CMD_RUN_STOP; + break; + case RESET: + TRACE_MSG0(otg->tcd->TAG, "MX31 PULLUP RESET"); + UOG_USBCMD &= ~USB_CMD_RUN_STOP; + break; + } +} + +struct tcd_ops tcd_ops = { + .tcd_init_func = arc_tcd_init, + .tcd_en_func = arc_tcd_en, + .dp_pullup_func = arc_dp_pullup_func, +}; + +/* ********************************************************************************************* */ +/* ********************************************************************************************* */ + +int arc_transceiver_callback(struct otg_instance *otg, arc_belcarra_transceiver_event_t event) +{ + struct otg_dev *otg_dev = otg->privdata; + struct device *device = otg_dev_get_drvdata(otg_dev); + struct pcd_instance *pcd_instance = otg_dev->pcd_instance; + + switch (event) { + case arc_vbus_valid: + TRACE_MSG1(otg->tcd->TAG, "VBUS VALID event: %d", event); + otg_event(otg, VBUS_VLD | B_SESS_VLD, otg->tcd->TAG, "MX31 VBUS VALID"); + break; + case arc_vbus_invalid: + TRACE_MSG1(otg->tcd->TAG, "VBUS INVALID event: %d", event); + otg_event(otg, VBUS_VLD_ | B_SESS_VLD_ | A_SESS_VLD_, otg->tcd->TAG, "MX31 VBUS INVALID"); + break; + } + return 0; +} + +/*! + * arc_tcd_remove() - called to remove hardware + * @param otg_dev - otg device + */ +static void +arc_tcd_remove(struct otg_dev *otg_dev) +{ + struct device *device = otg_dev_get_drvdata(otg_dev); + struct platform_device *pdev = to_platform_device(device); + + struct fsl_usb2_platform_data *pdata = (struct fsl_usb2_platform_data*)pdev->dev.platform_data; + + if (pdata->platform_uninit) + pdata->platform_uninit(pdata); +} + +/*! + * arc_tcd_probe() - called to probe hardware + * @param otg_dev - otg device + * + * This function should do minimal, one time only hardware recognition, + * resource reservation and minimal setup. Typically to get to known + * disabled state. It should not start the hardware. + * + */ +static int +arc_tcd_probe(struct otg_dev *otg_dev) +{ + struct device *device = otg_dev_get_drvdata(otg_dev); + struct platform_device *pdev = to_platform_device(device); + + struct fsl_usb2_platform_data *pdata = (struct fsl_usb2_platform_data*)pdev->dev.platform_data; + + struct otg_instance *otg = otg_dev->otg_instance; + struct tcd_instance *tcd = otg->tcd; + + RETURN_ENODEV_IF(strcmp(pdev->name, "arc_udc")); + RETURN_ZERO_IF (pdata->platform_init(pdev)); + + fsl_platform_set_vbus_power(pdata, 0); + + return 0; +} + + +/* ********************************************************************************************* */ +/* ********************************************************************************************* */ + +/* arc tcd otg_dev_driver structure + */ +static struct otg_dev_driver arc_tcd_driver = { + .name = "arc_udc", + .id = OTG_DRIVER_TCD, + + .probe = arc_tcd_probe, + .remove = arc_tcd_remove, + + .ops = &tcd_ops, +}; + +/* ********************************************************************************************* */ + +/*! arc_tcd_module_init() - module init + */ +int arc_tcd_module_init (struct otg_device_driver *otg_device_driver) +{ + printk(KERN_INFO"%s:\n", __FUNCTION__); + return otg_dev_register_driver(otg_device_driver, &arc_tcd_driver); +} + +/*! + * arc_tcd_module_exit() - module exit + */ +void arc_tcd_module_exit (struct otg_device_driver *otg_device_driver) +{ + printk(KERN_INFO"%s:\n", __FUNCTION__); + otg_dev_unregister_driver (otg_device_driver, &arc_tcd_driver); +} diff --git a/drivers/otg/hardware/arc.h b/drivers/otg/hardware/arc.h new file mode 100644 index 000000000000..070109135e76 --- /dev/null +++ b/drivers/otg/hardware/arc.h @@ -0,0 +1,53 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/hardware/arc.h - MX31 Device driver + * @(#) sl@belcarra.com/whiskey.enposte.net|otg/platform/arc/arc.h|20070801221240|58512 + * + * Copyright (c) 2006-2007 Belcarra Technologies 2005 Corp + * + * By: + * Stuart Lynne <sl@belcarra.com> + */ +/*! + * @defgroup ARC Freescale HS (ARC) + * @ingroup Hardware + */ +/*! + * @file otg/hardware/arc.h + * @brief Belcarra Freescale ARC Device driver. + * + * @ingroup ARC + * @ingroup OTGDEV + * @ingroup LINUXOS + */ + + + +struct mx31_dtd_list_struct { + struct list_head td_queue; + struct ep_td_struct *td; + int primed; + int epnum; + int dir; +}; + +struct arc_private_struct { + struct ep_queue_head *cur_dqh; + struct ep_td_struct *cur_dtd; +}; + +struct arcotg_udc { + struct ep_queue_head *ep_qh; /* Endpoints Queue-Head */ + struct ep_td_struct *ep_dtd; +}; diff --git a/drivers/otg/hardware/i2c-l26.c b/drivers/otg/hardware/i2c-l26.c new file mode 100644 index 000000000000..6959136a6735 --- /dev/null +++ b/drivers/otg/hardware/i2c-l26.c @@ -0,0 +1,295 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/hardware/i2c-l26.c -- Linux 2.6 I2C access + * @(#) balden@belcarra.com/seth2.rillanon.org|otg/platform/otglib/i2c-l26.c|20070614183950|36713 + * + * Copyright (c) 2004-2005 Belcarra Technologies Corp + * Copyright (c) 2005-2007 Belcarra Technologies 2005 Corp + * + * By: + * Stuart Lynne <sl@lbelcarra.com>, + * Bruce Balden <balden@belcarra.com> + * + */ +/*! + * @file otg/hardware/i2c-l26.c + * @brief Linux I2C I/O via generic i2c device. + * + * Writes are queued and performed in a bottom half handler. + * + * @ingroup ISP1301TCD + * @ingroup LINUXOS + */ + +#include <otg/otg-compat.h> + +#include <asm/irq.h> +#include <asm/system.h> +#include <asm/io.h> +#include <linux/i2c.h> + +#include <otg/pcd-include.h> +#include <linux/pci.h> +#include "isp1301-hardware.h" +#include "isp1301.h" + +/* ********************************************************************************************* */ + +/* + * N.B. i2c functions must not be called from interrupt handlers + */ + +static struct file *i2c_file; +static struct i2c_client *i2c_client; +static int initstate_i2c; +static int initstate_region; +#define MAX_I2C 16 + +/*! i2c_configure + * Attempt to find and open generic i2c device + * @param name - i2c device name + * @param addr - address used to configure i2c + * @return - non-zero for failure + */ +int i2c_configure(char *name, int addr) +{ + char filename[20]; + struct i2c_adapter *ad; + int tmp; + + RETURN_ZERO_IF(initstate_i2c); + +#if 1 + for (tmp=0 ; tmp<MAX_I2C; tmp++){ + ad = i2c_get_adapter(tmp); + if (ad){ +// printk(KERN_INFO"SHP - for tmp = %d name = %s\n", tmp, ad->name); + if (!strncmp(ad->name, name, strlen(name))) + break; + }// valid driver + } + if (tmp == MAX_I2C) { // Nothing found + printk(KERN_ERR"%s: cannot find I2C driver", __FUNCTION__); + return -ENODEV; + } +// printk(KERN_ERR"%s: kmalloc(sizeof(*i2c_client) ", __FUNCTION__); + i2c_client = kmalloc(sizeof(*i2c_client), GFP_KERNEL); + i2c_client->adapter = (struct i2c_adapter *) ad; + // printk(KERN_INFO"i2c_client name = %s\n", i2c_client->adapter->name); +#endif + +#if 0 + /*find the I2C driver we need + */ + for (tmp = 0; tmp < MAX_I2C; tmp++) { + + sprintf(filename, "/dev/i2c/%d", tmp); + + printk(KERN_INFO"%s: %s\n", __FUNCTION__, filename); + + UNLESS (IS_ERR(i2c_file = filp_open(filename, O_RDWR, 0))) { + + //printk(KERN_INFO"%s: %s found\n", __FUNCTION__, filename); + + /*found some driver */ + i2c_client = (struct i2c_client *)i2c_file->private_data; + + printk(KERN_INFO"%s: \"%s\" found \"%s\"\n", __FUNCTION__, name, i2c_client->adapter->name); + if (strlen(i2c_client->adapter->name) >= 8) { + if (!strncmp(i2c_client->adapter->name, name, strlen(name))) + break; /*we found our driver! */ + } + i2c_client = NULL; + filp_close(i2c_file, NULL); + } + printk(KERN_INFO"%s: %s %d\n", __FUNCTION__, filename, i2c_file); + } + if (tmp == MAX_I2C) { // Nothing found + printk(KERN_ERR"%s: cannot find I2C driver", __FUNCTION__); + return -ENODEV; + } +#endif + + i2c_client->addr = addr; + initstate_i2c = 1; + return 0; +} + + + +/*! i2c_close + * Close i2c device fd. + */ +void i2c_close(void) +{ +#if 1 + printk(KERN_ERR"%s: Free i2c_client \n", __FUNCTION__); + kfree(i2c_client); +#endif +#if 0 + if (initstate_i2c) + filp_close(i2c_file, NULL); +#endif + initstate_i2c = 0; +} + +/*! i2c_readb + * Read byte from i2c device + * @param subaddr - address to read value + * @return read byte value + */ +u8 i2c_readb(u8 subaddr) +{ + u8 buf = 0; + i2c_master_send(i2c_client, &subaddr, 1); + i2c_master_recv(i2c_client, &buf, 1); + //TRACE_MSG2(TCD, "addr: %02x buf: %02x", subaddr, buf); + return buf; +} + +/*! i2c_readw - + * Read word from i2c device + * @param subaddr - address to fetch value + * @return fetched word value + */ +u16 i2c_readw(u8 subaddr) +{ + u16 buf = 0; + i2c_master_send(i2c_client, &subaddr, 1); + i2c_master_recv(i2c_client, (u8 *)&buf, 2); + //TRACE_MSG2(TCD, "addr: %02x buf: %04x", subaddr, buf); + return buf; +} + +/*! i2c_readl - + * Read long from i2c device + * @param subaddr - address to fetch value + * @return fetched value + */ +u32 i2c_readl(u8 subaddr) +{ + u32 buf = 0; + i2c_master_send(i2c_client, &subaddr, 1); + i2c_master_recv(i2c_client, (u8 *)&buf, 4); + //TRACE_MSG2(TCD, "addr: %02x buf: %08x", subaddr, buf); + return buf; +} + + + +/* ********************************************************************************************* */ +/*! @var struct otg_task i2c_io_task + * @brief - an otg_task intance to process i2c_io operations + */ +struct otg_task *i2c_io_task; + +#define I2C_MAX_WRITE 500 + +int i2c_Write_Queued; +u8 i2c_Write_Data[I2C_MAX_WRITE]; +u8 i2c_Write_Addr[I2C_MAX_WRITE]; + +/*! i2c_writeb_direct() - internal + * Writw byte to i2c device + * @param subaddr + * @param buf + */ +void i2c_writeb_direct(u8 subaddr, u8 buf) +{ + char tmpbuf[2]; + + tmpbuf[0] = subaddr; /*register number */ + tmpbuf[1] = buf; /*register data */ + i2c_master_send(i2c_client, &tmpbuf[0], 2); +} + +/*! + * i2c_io_bh() - bottom half handler to use i2c_write on queued data + * i2c_write operations are queued so that they can be done in this bottom + * half handler. + * + * XXX the memcpy's could be eliminated with head/tail pointers. + * + * @param data + */ +void *i2c_io_bh(otg_task_arg_t data) +{ + while (i2c_Write_Queued > 0) { + u8 write = i2c_Write_Data[0]; + u8 port = i2c_Write_Addr[0]; + unsigned long flags; // atomic update of counter and saved values + local_irq_save (flags); + //printk(KERN_INFO"%s: i2c_Write_Queued: %d\n", __FUNCTION__, i2c_Write_Queued); + i2c_Write_Queued--; + memcpy(i2c_Write_Data, i2c_Write_Data + 1, sizeof(i2c_Write_Data) - 1); + memcpy(i2c_Write_Addr, i2c_Write_Addr + 1, sizeof(i2c_Write_Addr) - 4); + local_irq_restore (flags); + + i2c_writeb_direct(port, write); // perform write + + //TRACE_MSG3(TCD,"port: %02x data: %02x result: %02x", port, write, i2c_readb(port & 0xfe)); + } + return NULL; +} + + +/*! + * i2c_writeb() - queue write operation + * @param port + * @param data byte to write + */ +void i2c_writeb(u8 port, u8 data) +{ + unsigned long flags; // atomic update of counter and saved values + RETURN_IF(i2c_Write_Queued < 0); // check if terminating + if ((i2c_Write_Queued == I2C_MAX_WRITE)) { + + printk(KERN_ERR"TOO MANY QUEUED CANNOT WRITE port: %02x data: %02x active: %d\n", port, data, i2c_Write_Queued); + //TRACE_MSG3(TCD, "TOO MANY QUEUED CANNOT WRITE port: %02x data: %02x active: %d", port, data, i2c_Write_Queued); + return; + } + local_irq_save (flags); + i2c_Write_Addr[i2c_Write_Queued] = port; + i2c_Write_Data[i2c_Write_Queued] = data; + i2c_Write_Queued++; + //printk(KERN_INFO"%s: i2c_Write_Queued: %d\n", __FUNCTION__, i2c_Write_Queued); + local_irq_restore (flags); + //TRACE_MSG3(TCD, "port: %02x data: %02x active: %d", port, data, i2c_Write_Queued); + //SCHEDULE_WORK(i2c_io_work_struct); + if (i2c_io_task) otg_up_work(i2c_io_task); +} + + +int i2c_mod_init(struct otg_instance *otg) +{ + //printk(KERN_INFO"%s:\n", __FUNCTION__); + //PREPARE_WORK_ITEM(i2c_io_work_struct, &i2c_io_bh, NULL); + TRACE_MSG0(otg->tcd->TAG, "INIT"); + RETURN_EINVAL_UNLESS((i2c_io_task = otg_task_init2("otgi2c", i2c_io_bh, NULL, otg->tcd->TAG))); + //i2c_io_task->debug = TRUE; + otg_task_start(i2c_io_task); + return 0; +} + +void i2c_mod_exit(struct otg_instance *otg) +{ + otg_task_exit(i2c_io_task); + i2c_io_task = NULL; + + TRACE_MSG0(otg->tcd->TAG, "EXIT"); + //while (PENDING_WORK_ITEM(i2c_io_work_struct) /*|| PENDING_WORK_ITEM(i2c_xcvr_work_struct)*/ ) { + // printk(KERN_ERR"%s: waiting for bh\n", __FUNCTION__); + // schedule_timeout(10 * HZ); + //} +} diff --git a/drivers/otg/hardware/imx31ads-l26.c b/drivers/otg/hardware/imx31ads-l26.c new file mode 100644 index 000000000000..bee89f11194e --- /dev/null +++ b/drivers/otg/hardware/imx31ads-l26.c @@ -0,0 +1,184 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/hardware/mx31-l26.c - iMX31ADS EVB OTG Peripheral and OTG Controller Drivers Module Initialization + * @(#) balden@belcarra.com/seth2.rillanon.org|otg/platform/imx31ads/imx31ads-l26.c|20070909224442|14233 + * + * Copyright (c) 2004-2005 Belcarra Technologies Corp + * Copyright (c) 2005-2007 Belcarra Technologies 2005 Corp + * + * By: + * Stuart Lynne <sl@belcarra.com>, + * Bruce Balden <balden@belcarra.com> + */ +/*! + * @defgroup MX31 High Speed + * @ingroup Hardware + */ +/*! + * @file otg/hardware/imx31ads-l26.c + * @brief MX31 USB Host Controller Driver + * + * Linux MX31 OTG PCD/OCD/TCD Driver Initialization + * + * This file selects and initializes all of the low level hardware drivers + * for the MX31 High Speed. + * + * Notes. + * + * + * @ingroup MX31 + * @ingroup MXC + * @ingroup LINUXOS + */ + +#include <otg/pcd-include.h> +#include <otg/otg-dev.h> +#include <linux/module.h> + +#include <linux/pci.h> +#include <asm/arch/gpio.h> + +//#include "mxc-lnx.h" +//#include "mxc-hardware.h" + +MOD_AUTHOR ("sl@belcarra.com"); +MOD_DESCRIPTION ("Belcarra MX31"); +EMBED_LICENSE(); + + +MOD_PARM_STR(serial_number_str, "Serial Number String", NULL); + + +/* ************************************************************************************* */ + +extern int arc_dev_module_init(struct otg_device_driver *otg_device_driver, + int (*device_probe)(struct device *), + int (*device_remove)(struct device *) + ); + +extern void arc_dev_module_exit(struct otg_device_driver *); + +extern int arc_pcd_module_init (struct otg_device_driver *); +extern void arc_pcd_module_exit (struct otg_device_driver *); + +//extern int arc_tcd_module_init (struct otg_device_driver *); +//extern void arc_tcd_module_exit (struct otg_device_driver *); + +//extern int arc_ocd_module_init (struct otg_device_driver *otg_device_driver); +//extern void arc_ocd_module_exit (struct otg_device_driver *otg_device_driver); + +static struct otg_device_driver mx31_otg_device_driver = { + .name = "arc_udc", +}; + + + +/*! arc_dev_probe - called to initialize platform + * @param device - device + * + * This is used to call the dev level probe functions with + * the additional otg_device_driver structure. + */ +static int +arc_dev_probe(struct device *device) +{ + printk(KERN_INFO"%s: l26\n", __FUNCTION__); + RETURN_ZERO_UNLESS (mx31_otg_device_driver.probe); + return mx31_otg_device_driver.probe(device, &mx31_otg_device_driver); +} + +/*! arc_dev_remove- called to remote device + * @param device - device + * + * This is used to call the dev level probe functions with + * the additional otg_device_driver structure. + */ +static int +arc_dev_remove(struct device *device) +{ + int rc; + printk(KERN_INFO"%s: l26\n", __FUNCTION__); + RETURN_ZERO_UNLESS (mx31_otg_device_driver.remove); + rc = mx31_otg_device_driver.remove(device, &mx31_otg_device_driver); + return rc; +} + + +/*! + * mx31_modexit() - This is used as module exit, and as cleanup if modinit fails. + */ +static void mx31_modexit (void) +{ + /* unload the dev driver, this will stop otg and destroy + * the otg_dev and otg instances etc. + */ + printk(KERN_INFO"%s:\n", __FUNCTION__); + arc_dev_module_exit(&mx31_otg_device_driver); + + /* cleanup the rest of the sub-drivers + */ + arc_pcd_module_exit(&mx31_otg_device_driver); + //arc_tcd_module_exit(&mx31_otg_device_driver); + //arc_ocd_module_exit(&mx31_otg_device_driver); +} + +/*! + * mx31_modinit() - linux module initialization + * + * This needs to initialize the hcd, pcd and tcd drivers. This includes tcd and possibly hcd + * for some architectures. + * + */ +static int mx31_modinit (void) +{ + int ocd = -1, tcd = -1, pcd = -1, dev = -1; + + /* initialize all of the sub-drivers except for dev + */ + //ocd = arc_ocd_module_init(&mx31_otg_device_driver); + //tcd = arc_tcd_module_init(&mx31_otg_device_driver); + pcd = arc_pcd_module_init(&mx31_otg_device_driver); + + /* ensure everything is ok until now */ + //THROW_IF(ocd || tcd || pcd, error); + THROW_IF(pcd, error); + + /* serial number needs to be set prior to initializing the dev + * driver + */ + mx31_otg_device_driver.serial_number = MODPARM(serial_number_str); + + /* initialize the dev driver, this will get all sub-drivers + * started via their probe functions, create otg and otg_dev + * instances and finally start otg state machine. + */ + THROW_IF((dev = arc_dev_module_init(&mx31_otg_device_driver, arc_dev_probe, arc_dev_remove)), error); + + return 0; + + CATCH(error) { + + printk(KERN_INFO"%s: FAILED\n", __FUNCTION__); + + UNLESS (dev) arc_dev_module_exit(&mx31_otg_device_driver); + //UNLESS (tcd) arc_tcd_module_exit(&mx31_otg_device_driver); + UNLESS (pcd) arc_pcd_module_exit(&mx31_otg_device_driver); + //UNLESS (ocd) arc_ocd_module_exit(&mx31_otg_device_driver); + + return -EINVAL; + } +} + +module_init (mx31_modinit); +module_exit (mx31_modexit); diff --git a/drivers/otg/hardware/isp1301-hardware.h b/drivers/otg/hardware/isp1301-hardware.h new file mode 100644 index 000000000000..5e15e02b093a --- /dev/null +++ b/drivers/otg/hardware/isp1301-hardware.h @@ -0,0 +1,227 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/hardware/isp1301-hardware.h -- ISP1301 hardware specific defines + * @(#) sl@belcarra.com/whiskey.enposte.net|otg/platform/isp1301/isp1301-hardware.h|20061123215517|03742 + * + * Copyright (c) 2004-2005 Belcarra + * Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + * + * By: + * Stuart Lynne <sl@lbelcarra.com>, + * Bruce Balden <balden@belcarra.com> + * + */ +/*! + * @file otg/hardware/isp1301-hardware.h + * @brief Public Structures And Defines For ISP1301 Hardware. + * + * The Philips ISP1301 is an OTG Transceiver. There a additional + * compatible parts such as the Maxim MAX3301E + * + * @ingroup ISP1301TCD + */ + + +/*! + * @name ADR/PSW - C.f. 8.9.1 + * + * The i2c address is either 0x2c or 0x2d depending on the level of the + * ADR/PSW pin after device reset. Note that this pin can be programmed as + * an output via the Mode Control 2 register, bit PSW_OE to enable an + * @{ + */ + +#define ISP1301_I2C_ADDR_LOW 0x2c +#define ISP1301_I2C_ADDR_HIGH 0x2d +/*! @} */ + +/*! + * @name ISP1301 Registers C.f. 11.1 + * @{ + */ + +#define ISP1301_VENDOR_ID 0x00 +#define ISP1301_PRODUCT_ID 0x02 +#define ISP1301_VERSION_ID 0x14 + +#define ISP1301_VENDOR_ID_LOW 0x00 +#define ISP1301_PRODUCT_ID_LOW 0x02 +#define ISP1301_VERSION_ID_LOW 0x14 +#define ISP1301_VENDOR_ID_HIGH 0x01 +#define ISP1301_PRODUCT_ID_HIGH 0x03 +#define ISP1301_VERSION_ID_HIGH 0x15 + +/* these are all single byte registers + */ +#define ISP1301_MODE_CONTROL_1 0x04 +#define ISP1301_MODE_CONTROL_1_SET 0x04 +#define ISP1301_MODE_CONTROL_1_CLR 0x05 + +#define ISP1301_MODE_CONTROL_2 0x12 +#define ISP1301_MODE_CONTROL_2_SET 0x12 +#define ISP1301_MODE_CONTROL_2_CLR 0x13 + +#define ISP1301_OTG_CONTROL 0x06 +#define ISP1301_OTG_CONTROL_SET 0x06 +#define ISP1301_OTG_CONTROL_CLR 0x07 + +#define ISP1301_OTG_STATUS 0x10 + +#define ISP1301_INTERRUPT_SOURCE 0x08 + +#define ISP1301_INTERRUPT_LATCH 0x0a +#define ISP1301_INTERRUPT_LATCH_SET 0x0a +#define ISP1301_INTERRUPT_LATCH_CLR 0x0b + +#define ISP1301_INTERRUPT_ENABLE_LOW 0x0c +#define ISP1301_INTERRUPT_ENABLE_LOW_SET 0x0c +#define ISP1301_INTERRUPT_ENABLE_LOW_CLR 0x0d + +#define ISP1301_INTERRUPT_ENABLE_HIGH 0x0e +#define ISP1301_INTERRUPT_ENABLE_HIGH_SET 0x0e +#define ISP1301_INTERRUPT_ENABLE_HIGH_CLR 0x0f +/*! @} */ + + +/*! + * @name Mode Control 1 register - C.f. Table 17 and Table 18 + * @{ + */ +#define ISP1301_UART_EN (1 << 6) +#define ISP1301_OE_INT_EN (1 << 5) +#define ISP1301_BDIS_ACON_EN (1 << 4) +#define ISP1301_TRANSP_EN (1 << 3) +#define ISP1301_DAT_SE0 (1 << 2) +#define ISP1301_SUSPEND_REG (1 << 1) +#define ISP1301_SPEED_REG (1 << 0) +/*! @} */ + + +/*! + * @name Mode Control 2 register - C.f. Table 19 and Table 20 + * @{ + */ +#define ISP1301_EN2V7 (1 << 7) +#define ISP1301_PSW_OE (1 << 6) +#define ISP1301_AUDIO_EN (1 << 5) +#define ISP1301_TRANSP_BDIR (3 << 3) +#define ISP1301_BI_DI (1 << 2) +#define ISP1301_SPD_SUSP_CTRL (1 << 1) +#define ISP1301_GLOBAL_PWR_ON (1 << 0) +/*! @} */ + + +/*! + * @name OTG Control register - C.f. Table 21 and Table 22 + * @{ + */ +#define ISP1301_VBUS_CHRG (1 << 7) +#define ISP1301_VBUS_DISCHRG (1 << 6) +#define ISP1301_VBUS_DRV (1 << 5) +#define ISP1301_ID_PULLDOWN (1 << 4) +#define ISP1301_DM_PULLDOWN (1 << 3) +#define ISP1301_DP_PULLDOWN (1 << 2) +#define ISP1301_DM_PULLUP (1 << 1) +#define ISP1301_DP_PULLUP (1 << 0) + +#define ISP1301_VBUS_RESET (ISP1301_VBUS_CHRG | ISP1301_VBUS_DISCHRG | ISP1301_VBUS_DRV) + +/*! @} */ + + +/*! + * @name OTG Status register - C.f. Table 23 and Table 24 + * @{ + */ +#define ISP1301_B_SESS_VLD (1 << 7) +#define ISP1301_B_SESS_END (1 << 6) +/*! @} */ + + +/*! + * @name Interrupt Source Register + * Interrupt Source register - C.f. Table 25 and Table 26 + * Interrupt Latch register - C.f. Table 27 and Table 28 + * Interrupt Enable Low register - C.f. Table 29 and Table 30 + * Interrupt Enable High register - C.f. Table 31 and Table 32 + * @{ + */ +#define ISP1301_CR_INT (1 << 7) +#define ISP1301_BDIS_ACON (1 << 6) +#define ISP1301_ID_FLOAT (1 << 5) +#define ISP1301_DM_HI (1 << 4) +#define ISP1301_ID_GND (1 << 3) +#define ISP1301_DP_HI (1 << 2) +#define ISP1301_SESS_VLD (1 << 1) +#define ISP1301_VBUS_VLD (1 << 0) + +//#define ISP1301_SE1 (ISP1301_DM_HI | ISP1301_DP_HI) + +/*! @} */ + +/*! + * @name Maxim MAX3301E + * + * This part is compatible with the ISP1301 with minor differences: + * + * Mode Control 1 register is called Control Register 1. It is almost + * identical except for: + * + * bit isp1301 max3301e + * 3 transp_en not used + * @{ + */ +#define MAX3301E_CONTROL_REGISTER_1 0x04 +#define MAX3301E_CONTROL_REGISTER_1_SET 0x04 +#define MAX3301E_CONTROL_REGISTER_1_CLR 0x05 +/*! @} */ + +/*! + * @name Mode Control 2 + * Mode Control 2 is called Special Function Register 1. Mostly the same + * except for: + * + * bit isp1301 max3301e + * 7 en2v7 gp_en + * 6 psw_oe sess_end + * 5 audio_en int_source + * @{ + */ +#define MAX3301E_SPECIAL_FUNCTION_1 0x12 +#define MAX3301E_SPECIAL_FUNCTION_1_SET 0x12 +#define MAX3301E_SPECIAL_FUNCTION_1_CLR 0x13 +#define MAX3301E_GP_EN (1 << 7) +#define MAX3301E_SESS_END (1 << 6) +#define MAX3301E_INT_SOURCE (1 << 5) +/*! @} */ + +/*! + * @name OTG Status + * The OTG Status is not present, but B_SESS_END seems to be available + * in the Mode Control 2 register bit 6. + * + * An additional register is available for MAX3301E specific features. + * + * Special Function Register 2 + * @{ + */ + +#define MAX3301E_SPECIAL_FUNCTION_2 0x16 +#define MAX3301E_SPECIAL_FUNCTION_2_SET 0x16 +#define MAX3301E_SPECIAL_FUNCTION_2_CLR 0x17 +#define MAX3301E_SDWN (1 << 0) +#define MAX3301E_IRQ_MODE (1 << 1) +#define MAX3301E_XCVR_INPUT_DISC (1 << 2) +#define MAX3301E_REQ_SET (1 << 3) +/*! @} */ diff --git a/drivers/otg/hardware/isp1301-procfs.c b/drivers/otg/hardware/isp1301-procfs.c new file mode 100644 index 000000000000..726e1d7bf782 --- /dev/null +++ b/drivers/otg/hardware/isp1301-procfs.c @@ -0,0 +1,468 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/hardware/isp1301-procfs.c - USB Device Core Layer + * @(#) sl@belcarra.com/whiskey.enposte.net|otg/platform/isp1301/isp1301-procfs.c|20070612233038|36770 + * + * Copyright (c) 2004-2005 Belcarra Technologies Corp + * Copyright (c) 2005-2007 Belcarra Technologies 2005 Corp + * + * By: + * Stuart Lynne <sl@belcarra.com>, + * Bruce Balden <balden@belcarra.com> + * + * + */ +/*! + * @file otg/hardware/isp1301-procfs.c + * @brief Implement /proc/isp1301 to dump ISP1301 registers. + * + * + * @ingroup ISP1301TCD + */ + +#include <otg/otg-compat.h> +#if defined(CONFIG_OTG_LNX) + +#include <otg/pcd-include.h> +#include "isp1301-hardware.h" +#include "isp1301.h" + +#ifdef CONFIG_ARCH_MX2ADS +#include <asm/arch/mx2.h> +#define MX2_OTG_XCVR_DEVAD 0x18 +#define MX2_SEQ_OP_REG 0x19 +#define MX2_SEQ_RD_STARTAD 0x1a +#define MX2_I2C_OP_CTRL_REG 0x1b +#define MX2_SCLK_TO_SCL_HPER 0x1e +#define MX2_I2C_INTERRUPT_AND_CTRL 0x1f + +#define OTG_BASE_ADDR 0x10024000 +//#define OTG_I2C_BASE (OTG_BASE_ADDR+0x100) + +#endif /* CONFIG_ARCH_MX2ADS */ + +#if defined(CONFIG_OTG_ISP1301_PROCFS) || defined(_OTG_DOXYGEN) +/* Proc Filesystem *************************************************************************** */ + +extern struct isp1301_private isp1301_private; + +#define MAX_HISTORY 6 +/*! @fn struct reg_list + * @brief - struct definition for register + */ +struct reg_list { + u8 reg; + u8 size; + char *name; + u32 values[MAX_HISTORY]; +}; + +#define REG(r, s) {r, s, #r, } + +/*! @name register list tables + * + * @{ + */ + +struct reg_list isp1301_prod_list[] = { + REG(ISP1301_VENDOR_ID, 2), + REG(ISP1301_PRODUCT_ID, 2), + REG(ISP1301_VERSION_ID, 2), + { 0, 1, NULL,}, +}; +struct reg_list isp1301_reg_list[] = { + REG(ISP1301_OTG_CONTROL_SET, 1), + REG(ISP1301_INTERRUPT_SOURCE, 1), + REG(ISP1301_INTERRUPT_LATCH_SET, 1), + REG(ISP1301_INTERRUPT_ENABLE_LOW_SET, 1), + REG(ISP1301_INTERRUPT_ENABLE_HIGH_SET, 1), + REG(ISP1301_MODE_CONTROL_1_SET, 1), + { 0, 1, NULL,}, +}; +struct reg_list isp1301_spec_list[] = { + REG(ISP1301_MODE_CONTROL_2_SET, 1), + REG(ISP1301_OTG_STATUS, 1), + { 0, 1, NULL,}, +}; +struct reg_list max3301e_spec_list[] = { + REG(MAX3301E_SPECIAL_FUNCTION_1_SET, 1), + REG(MAX3301E_SPECIAL_FUNCTION_2_SET, 1), + { 0, 1, NULL,}, +}; +#ifdef CONFIG_ARCH_MX2ADS +struct reg_list mx21_spec_list[] = { + REG(MX2_OTG_XCVR_DEVAD, 1), + REG(MX2_SEQ_OP_REG, 1), + REG(MX2_SEQ_RD_STARTAD, 1), + REG(MX2_I2C_OP_CTRL_REG, 1), + REG(MX2_SCLK_TO_SCL_HPER, 1), + REG(MX2_I2C_INTERRUPT_AND_CTRL, 1), + { 0, 1, NULL,}, +}; +/* @} */ +#endif /* CONFIG_ARCH_MX2ADS */ +/*! isp1301_update - + * @param list - isp1301 registers table + */ +void isp1301_update(struct reg_list *list) +{ + for (; list && list->name; list++) { + //TRACE_MSG1(TCD, "list: %s", list->name); + memmove(list->values + 1, list->values, sizeof(list->values) - sizeof(u32)); + switch(list->size) { + case 1: + list->values[0] = i2c_readb(list->reg); + break; + case 2: + list->values[0] = i2c_readw(list->reg); + break; + case 4: + list->values[0] = i2c_readl(list->reg); + break; + } + } +} + +#ifdef CONFIG_ARCH_MX2ADS +/*! mx2_rb - read a byte value from I2C device port + * @param port - device port value + * @return - read byte value + */ +static u8 __inline__ mx2_rb(u32 port) +{ + return *(volatile u8 *) (MX2_IO_ADDRESS(port + OTG_I2C_BASE)); +} + +/*! mx21_update - update an reg_list + * @param list - pointer to reg_list to update + */ +void mx21_update(struct reg_list *list) +{ + for (; list && list->name; list++) { + memmove(list->values + 1, list->values, sizeof(list->values) - sizeof(u32)); + list->values[0] = mx2_rb(list->reg); + } +} + +#endif /* CONFIG_ARCH_MX2ADS */ +/*! + * isp1301_update_all - update all reg_list + */ +void isp1301_update_all(void) +{ + isp1301_update(isp1301_reg_list); + switch (isp1301_private.transceiver_map->transceiver_type) { + case isp1301: + isp1301_update(isp1301_spec_list); + break; + case max3301e: + isp1301_update(max3301e_spec_list); + break; + default: + break; + } +#ifdef CONFIG_ARCH_MX2ADS + mx21_update(mx21_spec_list); +#endif /* CONFIG_ARCH_MX2ADS */ + +} + + + +/*! + * dohexdigit - translate a value to hex notation + * @param cp - buffer to save translated value + * @param val - value to translate + * + */ +static void dohexdigit (char *cp, unsigned char val) +{ + if (val < 0xa) + *cp = val + '0'; + else if ((val >= 0x0a) && (val <= 0x0f)) + *cp = val - 0x0a + 'a'; +} + +/*! + * dohexval - translate a value to hex notation + * @param cp - buffer to save translated value + * @param val - value to translate + * + */ +static void dohexval (char *cp, unsigned char val) +{ + dohexdigit (cp++, val >> 4); + dohexdigit (cp++, val & 0xf); +} + +/*! isp_1301_dump - dump a reg_list instance information + * @param buf - place to store information + * @param name - + * @param fmt + * @param reg + * @return + */ +int isp1301_dump(char *buf, char *name, char *fmt, struct reg_list *reg) +{ + int len = 0, i; + len += sprintf (buf + len, "%-20s %-34s [%03x]: ", name, reg->name, reg->reg); + len += sprintf (buf + len, fmt, reg->values[0]); + for (i = 1; i < MAX_HISTORY; i++) + if (reg->values[i - 1] == reg->values[i]) + len += sprintf (buf + len, " "); + else + len += sprintf (buf + len, fmt, reg->values[i]); + len += sprintf (buf + len, "\n"); + return len; +} +/*! isp1301_dump_list - dump reg_list table's inforamtion + * @param buf - + * @param name + * @param list + * @return information buffer length + */ +int isp1301_dump_list(char * buf, char *name, struct reg_list *list) +{ + int len = 0; + for (; list && list->name; list++) + switch(list->size) { + case 1: len += isp1301_dump(buf + len, name, " %02x", list); break; + case 2: len += isp1301_dump(buf + len, name, " %04x", list); break; + case 4: len += isp1301_dump(buf + len, name, " %08x", list); break; + } + return len; +} + +char *isp1301_otg_control[8] = { + "DP_PULLUP", + "DM_PULLUP", + "DP_PULLDOWN", + "DM_PULLDOWN", + "ID_PULLDOWN", + "VBUS_DRV", + "VBUS_DISCHRG", + "VBUS_CHRG", +}; +char *isp1301_interrupt_source[8] = { + "VBUS_VLD", + "SESS_VLD", + "DP_HI", + "ID_GND", + "DM_HI", + "ID_FLOAT", + "BDIS_ACON", + "CR_INT", +}; + +char *isp1301_mode_control_1[8] = { + "SPEED_REG", + "SUSPEND_REG", + "DAT_SE0", + "TRANSP_EN", + "BDIS_ACON_EN", + "OE_INT_EN", + "UART_EN", + NULL, +}; + +char *isp1301_mode_control_2[8] = { + "GLOBAL_PWR_ON", + "SPD_SUSP_CTRL", + "BI_DI", + "TRANSP_BDIR_0", + "TRANSP_BDIR_1", + "AUDIO_EN", + "PSW_OE", + "EN2V7", +}; + +char *isp1301_otg_status[8] = { + NULL, NULL, NULL, NULL, NULL, NULL, + "B_SESS_END", + "B_SESS_VLD", +}; + +/*! isp1301_detailed - dump detail information for reg port + * @param buf + * @param name + * @param reg + * @param detail + * @return + */ +int isp1301_detailed(char *buf, char *name, u8 reg, char **detail) +{ + u8 val; + int i; + int len = 0; + + val = i2c_readb(reg); + for (i = 7; i >= 0; i--) { + + if (3 == (i % 4)) + len += sprintf (buf + len, "\n%-20s [%02d] ", name, reg); + + if (detail[i]) + len += sprintf (buf + len, "%14s%s ", detail[i], (val & (1 << i)) ? " " : "/"); + else + len += sprintf (buf + len, "%14s%s ", "", " "); + } + len += sprintf (buf + len, "\n", name); + + return len; +} +/*! isp1301_dump_all - + * @param buf + */ +int isp1301_dump_all(char *buf) +{ + int len = 0; + len += isp1301_dump_list(buf + len, "ISP1301 Standard", isp1301_reg_list); + switch (isp1301_private.transceiver_map->transceiver_type) { + case isp1301: + len += isp1301_dump_list(buf + len, "ISP1301 Extra", isp1301_spec_list); + break; + case max3301e: + len += isp1301_dump_list(buf + len, "MAX3301E Extra", max3301e_spec_list); + break; + default: + break; + } +#ifdef CONFIG_ARCH_MX2ADS + len += isp1301_dump_list(buf + len, "MX21 ADS Extra", mx21_spec_list); +#endif /* CONFIG_ARCH_MX2ADS */ + return len; +} +/*! isp 1301_dump_detail + * @param buf + * @return buf length + */ +int isp1301_dump_detail(char *buf) +{ + int len = 0; + + len += isp1301_detailed(buf + len, "MODE CONTROL 1", ISP1301_MODE_CONTROL_1, isp1301_mode_control_1); + len += isp1301_detailed(buf + len, "MODE CONTROL 2", ISP1301_MODE_CONTROL_2, isp1301_mode_control_2); + len += isp1301_detailed(buf + len, "INTERRUPT ENABLE", ISP1301_INTERRUPT_ENABLE_HIGH, isp1301_interrupt_source); + + len += isp1301_detailed(buf + len, "OTG CONTROL", ISP1301_OTG_CONTROL_SET, isp1301_otg_control); + len += isp1301_detailed(buf + len, "INTERRUPT SOURCE", ISP1301_INTERRUPT_SOURCE, isp1301_interrupt_source); + len += isp1301_detailed(buf + len, "OTG STATUS", ISP1301_OTG_STATUS, isp1301_otg_status); + return len; +} + + +/*! + * isp1301_device_proc_read - implement proc file system read. + * + * Standard proc file system read function. + * + * We let upper layers iterate for us, *pos will indicate which device to return + * statistics for. + * @param file - file pointer + * @param buf - buffer pointer + * @param count - + * @param pos - + * @return read length + */ +static ssize_t isp1301_device_proc_read_functions (struct file *file, char *buf, size_t count, loff_t * pos) +{ + unsigned long page; + int len = 0; + int index; + int i; + u32 r; + + int config_size; + + // get a page, max 4095 bytes of data... + //RETURN_EINVAL_UNLESS ((page = get_free_page (GFP_KERNEL))); + RETURN_EINVAL_UNLESS ((page = GET_KERNEL_PAGE())); + + len = 0; + index = (*pos)++; + + //printk(KERN_INFO"%s: index: %d\n", __FUNCTION__, index); + switch(index) { + case 0: + len += sprintf ((char *) page + len, "ISP1301 Transceiver Registers\n"); + + TRACE_MSG0(REMOVE_TCD, "UPDATING"); + isp1301_update_all(); + TRACE_MSG0(REMOVE_TCD, "UPDATE FINISHED"); + len += sprintf ((char *) page + len , "Vendor: %04x Product: %04x Revision: %04x %s\n", + isp1301_private.vendor, isp1301_private.product, + isp1301_private.revision, isp1301_private.transceiver_map->name + ); + + len += isp1301_dump_all((char *) page + len); + len += sprintf ((char *) page + len, "\n"); + + break; + + case 1: + len += sprintf ((char *) page + len, "\n--\n"); + len += isp1301_dump_detail((char *) page + len); + len += sprintf ((char *) page + len, "\n"); + break; + + default: + break; + + } + + //printk(KERN_INFO"%s: len: %d\n", __FUNCTION__, len); + + if (len > count) + len = -EINVAL; + + else if ((len > 0) && copy_to_user (buf, (char *) page, len)) + len = -EFAULT; + + //printk(KERN_INFO"%s: len: %d\n", __FUNCTION__, len); + free_page (page); + return len; +} + +static struct file_operations isp1301_device_proc_operations_functions = { + read:isp1301_device_proc_read_functions, +}; + +/* Module init ******************************************************************************* */ +/*! isp1301_procfs_init - create a proc entry for isp1301 procfs + */ +int isp1301_procfs_init (void) +{ + struct proc_dir_entry *p; + + // create proc filesystem entries + if ((p = create_proc_entry ("isp1301", 0, 0)) == NULL) + return -ENOMEM; + + p->proc_fops = &isp1301_device_proc_operations_functions; + + isp1301_update_all(); + + return 0; +} +void isp1301_procfs_exit (void) +{ + remove_proc_entry ("isp1301", NULL); +} +#else +int isp1301_procfs_init (void) { return 0; +} +void isp1301_procfs_exit (void) { } +#endif + +#endif /* defined(CONFIG_OTG_LNX) */ diff --git a/drivers/otg/hardware/isp1301.c b/drivers/otg/hardware/isp1301.c new file mode 100644 index 000000000000..1994ac849bb2 --- /dev/null +++ b/drivers/otg/hardware/isp1301.c @@ -0,0 +1,1159 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/hardware/isp1301.c -- USB Transceiver Controller driver + * @(#) sl@belcarra.com/whiskey.enposte.net|otg/platform/isp1301/isp1301.c|20070612233038|28967 + * + * Copyright (c) 2004-2005 Belcarra Technologies Corp + * Copyright (c) 2005-2007 Belcarra Technologies 2005 Corp + * + * By: + * Stuart Lynne <sl@lbelcarra.com>, + * Bruce Balden <balden@belcarra.com> + * + */ +/*! + * @file otg/hardware/isp1301.c + * @brief ISP1301 OTG Transceiver Driver. + * + * This is the generic ISP1301 TCD core support. + * + * Notes: + * + * 1. The ISP1301 can control the speed and suspend directly, would it be + * appropriate to allow state machine to control this. + * + * 2. The ISP1301 has auto connect feature, can this be used without change + * to state machine. + * + * 3. The ISP1301 can control the ADR/PSW pin to enable / disable external + * charge pump. + * + * @ingroup ISP1301TCD + */ + +#include <otg/otg-compat.h> + +#if defined(CONFIG_OTG_ISP1301) || defined(_OTG_DOXYGEN) + +//#include <asm/irq.h> +//#include <asm/system.h> +//#include <asm/io.h> +//#include <linux/i2c.h> + +#include <otg/usbp-chap9.h> +#include <otg/usbp-bus.h> +#include <otg/otg-trace.h> +#include <otg/otg-api.h> +//#include <otg/otg-task.h> +#include <otg/otg-hcd.h> +#include <otg/otg-tcd.h> +#include <otg/otg-ocd.h> +#include <otg/otg-pcd.h> + +//#include <linux/i2c.h> + +#include "isp1301-hardware.h" +#include "isp1301.h" + + +//#ifdef CONFIG_OMAP_H2 +//#include <asm/arch/gpio.h> +//#include <asm/arch/mux.h> +//#endif /* CONFIG_OMAP_H2 */ + +/*! @var struct otg_transceiver_map isp1301_transceiver_map + */ +struct otg_transceiver_map isp1301_transceiver_map[] = { + { isp1301, 0x4cc, 0x1301, 0x00, "Phillips ISP1301", }, + { max3301e, 0x6a0b, 0x0133, 0x00, "Maxim MAX3301E", }, + { 0, 0, 0, 0, "Unknown Transceiver", }, +}; + +struct isp1301_private isp1301_private; +struct i2c_client *omap_i2c_client; +//static struct file *i2c_file; +#define MAX_I2C 16 + + + +/* ********************************************************************************************* */ +#define TRACE_I2CB(t,r) TRACE_MSG3(t, "%-40s[%02x] %02x", #r, r, i2c_readb(r)) +#define TRACE_I2CW(t,r) TRACE_MSG3(t, "%-40s[%02x] %04x", #r, r, i2c_readw(r)) +#define TRACE_GPIO(t,b,r) TRACE_MSG2(t, "%-40s %04x", #r, readw(b + r)) +#define TRACE_REGL(t,r) TRACE_MSG2(t, "%-40s %08x", #r, readl(r)) + + + +/*! isp1301_int_src - update interrupt source test mask + * + * This sets the current mask and updates the interrupt registers to match. + * @param otg - otg_instance pointer + * @param int_src - + */ +void isp1301_int_src(struct otg_instance *otg, u8 int_src) +{ + TRACE_MSG1(otg->tcd->TAG, "setting int_src %02x", int_src); + isp1301_private.int_src = int_src; + i2c_writeb(ISP1301_INTERRUPT_ENABLE_LOW_CLR, ~int_src); + i2c_writeb(ISP1301_INTERRUPT_ENABLE_HIGH_CLR, ~int_src); + i2c_writeb(ISP1301_INTERRUPT_ENABLE_LOW_SET, int_src); + i2c_writeb(ISP1301_INTERRUPT_ENABLE_HIGH_SET, int_src); + i2c_writeb(ISP1301_INTERRUPT_LATCH_CLR, int_src); +} + +/*! isp1301_int_src_set - add to the interrupt source mask + * @param otg - otg_instance pointer + * @param int_src - interrupt maks to add + */ +void isp1301_int_src_set(struct otg_instance *otg, u8 int_src) +{ + isp1301_int_src(otg, int_src |= isp1301_private.int_src); +} + +/*! isp1301_int_src_clr - remove from the interrup source mask + * @param otg - otg_instance pointer + * @param int_src - interrupt mask to remove + */ +void isp1301_int_src_clr(struct otg_instance *otg, u8 int_src) +{ + isp1301_int_src(otg, int_src = isp1301_private.int_src & ~int_src); +} + +/* ********************************************************************************************* */ + +/*!create a type for struct struct isp1301_test + * @brief typedef struct isp1301_test isp1301_test_t +*/ +typedef struct isp1301_test { + u32 input; + char *name; + u8 mask; +} isp1301_test_t; + +#define OV(i, m) {i, #i, m} + +/*! struct isp1301_test isp1301_int_src_tests */ + +struct isp1301_test isp1301_int_src_tests[9] = { + + OV(VBUS_VLD, ISP1301_VBUS_VLD), + OV(A_SESS_VLD, ISP1301_SESS_VLD), + OV(DP_HIGH, ISP1301_DP_HI), + OV(ID_GND, ISP1301_ID_GND), + OV(DM_HIGH, ISP1301_DM_HI), + OV(ID_FLOAT, ISP1301_ID_FLOAT), + OV(BDIS_ACON, ISP1301_BDIS_ACON), + OV(CR_INT_DET, ISP1301_CR_INT), + {0, NULL, 0}, +}; + +/*! struct isp1301_test isp1301_otg_status-tests */ +struct isp1301_test isp1301_otg_status_tests[3] = { + + OV(B_SESS_END, ISP1301_B_SESS_END), + OV(B_SESS_VLD, ISP1301_B_SESS_VLD), + {0, NULL, 0}, +}; + +/*! isp1301_event() - set input bit based on current settings and interrupt mask + * @param otg - otg_instance pointer + * @param tests - + * @param val + * @param testmask + * @param msg + * @return inputs mask value + */ +otg_current_t isp1301_event(struct otg_instance *otg, struct isp1301_test *tests, int val, int testmask, char *msg) +{ + int i; + otg_current_t inputs = 0; + TRACE_MSG3(otg->tcd->TAG, "val: %02x mask: %02x %s", val, testmask, msg); + for (i = 0; tests[i].mask; i++) { + otg_current_t input = tests[i].input; + u8 mask = tests[i].mask; + CONTINUE_UNLESS(mask & testmask); + inputs |= (val & mask) ? input : _NOT(input); + TRACE_MSG6(otg->tcd->TAG, "%s%s %02x %08x inputs: %08x %08x", + tests[i].name, (val & mask) ? "" : "/", tests[i].mask, tests[i].input, + (u32) (inputs >> 32), (u32)(inputs & 0xffffffff) + ); + } + return inputs; +} + + + +int isp1301_bh_first; +int isp1301_debounce; +otg_tick_t isp1301_ticks; + +void isp1301_info(char *msg, u8 value) +{ + #if 0 + otg_tick_t new_ticks = otg_tmr_ticks(); + otg_tick_t ticks = otg_tmr_elapsed(&new_ticks, &isp1301_ticks); + + if (ticks < 10000) + TRACE_MSG3(PCD, "ISP1301 %d uS %s: %02x", (u32)(ticks & 0xffffffff), msg, value); + else if (ticks < 10000000) + TRACE_MSG3(PCD, "ISP1301 %d mS %s: %02x", (u32)((ticks >> 10) & 0xffffffff), msg, value); + else + TRACE_MSG3(PCD, "ISP1301 %d S %s: %02x", (u32)((ticks >> 20) & 0xffffffff), msg, value); + + isp1301_ticks = new_ticks; + #endif +} + + +void isp1301_trace(u8 int_src, u8 int_changed, char *msg) +{ + + #if 0 + otg_tick_t new_ticks = otg_tmr_ticks(); + otg_tick_t ticks = otg_tmr_elapsed(&new_ticks, &isp1301_ticks); + + if (ticks < 10000) + TRACE_MSG4(PCD, "ISP1301 %d uS %s: src: %02x chng: %02x", + (u32)(ticks & 0xffffffff), msg, int_src, int_changed); + else if (ticks < 10000000) + TRACE_MSG4(PCD, "ISP1301 %d mS %s src: %02x chng: %02x", + (u32)((ticks >> 10) & 0xffffffff), msg, int_src, int_changed); + else + TRACE_MSG4(PCD, "ISP1301 %d S %s src: %02x chng: %02x ", + (u32)((ticks >> 20) & 0xffffffff), msg, int_src, int_changed); + + isp1301_ticks = new_ticks; + #endif +} + +#if 0 +/*! isp1301_bh + */ +void *isp1301_bh(void *data) +{ + struct otg_instance *otg = (struct otg_instance *) data; + + u8 otg_status = 0, special_function_22 = 0; + u8 int_lat, int_src1 = 0, int_src_prev = 0; + #if defined(CONFIG_MX2_TO11) + u8 int_src2 = 0; + #endif /* defined(CONFIG_MX2_TO11) */ + static u8 otg_status_saved, special_function_22_saved; + static u8 int_src_saved; + + int count = 0; + + printk(KERN_INFO"%s:\n", __FUNCTION__); + //printk(KERN_INFO"%s:\n", __FUNCTION__); + TRACE_MSG0(otg->tcd->TAG, "--"); + + /*if isp1301_private is NOT filled before then return */ + RETURN_NULL_UNLESS (isp1301_private.ready); + + do { + otg_current_t inputs = 0; + + printk(KERN_INFO"%s: loop\n", __FUNCTION__); + + /* read the interrupt latch and and clear latch and update otg + */ + if ((int_lat = i2c_readb(ISP1301_INTERRUPT_LATCH_SET))) { + //TRACE_MSG1(TCD, "CLEARING INT LAT: %02x", int_lat); + isp1301_info("INT LAT", int_lat); + i2c_writeb(ISP1301_INTERRUPT_LATCH_CLR, int_lat); + } + + isp1301_info("INT_LAT", int_lat); + + /* If int_lat shows a change or we are forcing an update, read interrupt source. + * Note that we mask DP_HI and DM_HI when LOC_CONN is set, we don't want them + * when USB Bus is in use. + * + * Mask with bits we are currently interested in. + * + * Ensure that we have two matching reads to ensure that value is stable. + * Also ensure that interrupt source is non-zero. + */ + + int_src1 = i2c_readb(ISP1301_INTERRUPT_SOURCE); + + printk(KERN_INFO"%s: int_src: %02x %02x", __FUNCTION__, int_src_saved, int_src1); + + BREAK_IF(int_src1 == int_src_prev); + int_src_prev = int_src1; + + isp1301_trace(int_src1, int_src_saved ^ int_src1, "INT_SRC_1"); + + #if defined(CONFIG_MX2_TO11) + do { + int_src2 = i2c_readb(ISP1301_INTERRUPT_SOURCE); // re-read interrupt source + BREAK_IF(int_src1 && (int_src1 & (ISP1301_ID_GND | ISP1301_ID_FLOAT)) && + (int_src1 == int_src2)); // finished if non-zero and same + isp1301_trace(int_src2, int_src_saved ^ int_src2, "INT_SRC_2"); + int_src1 = int_src2; // save most recent read value + } while (1); + #endif /* defined(CONFIG_MX2_TO11) */ + + + + #if 0 + while ( (int_src1 != (int_src2 = i2c_readb(ISP1301_INTERRUPT_SOURCE) & isp1301_private.int_src))) { + isp1301_trace(int_src2, int_src_saved ^ int_src2, "INT_SRC_2"); + int_src1 = int_src2; + } + #endif + + if (isp1301_bh_first || int_src_saved ^ (int_src1 & isp1301_private.int_src)) { + int_src_saved = int_src1; + inputs |= isp1301_event(otg, isp1301_int_src_tests, int_src1, + isp1301_private.int_src, "ISP1301 INT SRC"); + if (int_src1 & ISP1301_VBUS_VLD) { + TRACE_MSG0(otg->tcd->TAG, "VBUS_VLD, setting B_SESS_VLD"); + inputs |= B_SESS_VLD; + } + else if (!(int_src1 & ISP1301_SESS_VLD)) { + TRACE_MSG0(otg->tcd->TAG, "A_SESS_VLD/, setting B_SESS_VLD/"); + inputs |= B_SESS_VLD_; + } + } + + #if 1 + /* further work if B-Device + */ + if ( !(int_src1 & ISP1301_ID_GND) || !(int_src_saved & ISP1301_ID_GND) ) { + /* read the otg_status register and update otg state machine + * + * XXX this needs to be conditional on Transceiver type. The + * MAX3301E for example supports sess_end in a separate register. + */ + switch (isp1301_private.transceiver_map->transceiver_type) { + case isp1301: + otg_status = i2c_readb(ISP1301_OTG_STATUS); + isp1301_trace(otg_status, int_src_saved ^ int_src1, "OTG_STATUS"); + TRACE_MSG3(otg->tcd->TAG, "otg_status_saved: %02x otg_status: %02x chng: %02x", + otg_status_saved, otg_status, otg_status_saved ^ otg_status); + + if (isp1301_bh_first || (otg_status_saved ^ otg_status)) { + inputs |= isp1301_event(otg, isp1301_otg_status_tests, otg_status, 0xff, + "ISP1301 OTG STATUS"); + otg_status_saved = otg_status; + } + + #if 0 + isp1301_info("OTG STATUS", otg_status); + if (isp1301_bh_first) + otg_status_saved = ~otg_status; + + otg_status_saved = isp1301_update_otg_status( otg, otg_status, + otg_status_saved ^ otg_status); + #endif + break; + + // XXX + case max3301e: + special_function_22 = i2c_readb(MAX3301E_SPECIAL_FUNCTION_2_SET); + break; + case unknown_transceiver: + break; + } + } + #endif + if (inputs) { + u32 reset = (u32)(inputs >> 32); + u32 set = (u32) (inputs & 0xffffffff); + TRACE_MSG3(otg->tcd->TAG, "ISP1301: RESET: %08x SET: %08x %s", reset, set, "QUEUE"); + otg_event(otg, inputs, otg->tcd->TAG, "ISP1301"); + } + isp1301_bh_first = 0; + + } while ((int_lat & isp1301_private.int_src) && (count++ < 6)); + TRACE_MSG1(otg->tcd->TAG, "count: %d", count); + + otg->tcd->vbus = BOOLEAN(int_src_saved & ISP1301_VBUS_VLD); + otg->tcd->id = BOOLEAN(int_src_saved & ISP1301_ID_GND); + printk(KERN_INFO"%s: finis\n", __FUNCTION__); + return NULL; +} + +#else +/*! isp1301_bh - + * @param data - otg_instance pointer + */ + +void *isp1301_bh(void *data) +{ + u8 int_lat, int_src1 = 0, otg_status = 0; + otg_current_t inputs = 0; + + struct otg_instance *otg = (struct otg_instance *) data; + + TRACE_MSG0(otg->tcd->TAG, "--"); + + int_lat = i2c_readb(ISP1301_INTERRUPT_LATCH_SET); + i2c_writeb(ISP1301_INTERRUPT_LATCH_CLR, int_lat); + + int_src1 = i2c_readb(ISP1301_INTERRUPT_SOURCE); +// printk(KERN_INFO"%s: interrupt source: %x latch: %x\n", __FUNCTION__, int_src1, int_lat); + inputs |= isp1301_event(otg, isp1301_int_src_tests, int_src1, + isp1301_private.int_src, "ISP1301 INT SRC"); + if (int_src1 & ISP1301_VBUS_VLD) { + TRACE_MSG0(otg->tcd->TAG, "VBUS_VLD, setting B_SESS_VLD"); + inputs |= B_SESS_VLD; + } + else if (!(int_src1 & ISP1301_SESS_VLD)) { + TRACE_MSG0(otg->tcd->TAG, "A_SESS_VLD/, setting B_SESS_VLD/"); + inputs |= B_SESS_VLD_; + } + + //Extra work for otg + otg_status = i2c_readb(ISP1301_OTG_STATUS); + inputs |= isp1301_event(otg, isp1301_otg_status_tests, otg_status, 0xff, "ISP1301 OTG STATUS"); + //End of extra work for otg + + + if (inputs) { + //u32 reset = (u32)(inputs >> 32); + //u32 set = (u32) (inputs & 0xffffffff); + TRACE_MSG2(otg->tcd->TAG, "inputs: %x otg->task: %x\n", inputs, otg->task); + if (otg->task) + otg_event(otg, inputs, otg->tcd->TAG, "ISP1301"); + } + otg->tcd->vbus = BOOLEAN(int_src1 & ISP1301_VBUS_VLD); + otg->tcd->id = BOOLEAN(int_src1 & ISP1301_ID_GND); + return NULL; +} +#endif + +/*! isp1301_work - process isp1301 task work + * @param data - otg_instance pointer + */ +void *isp1301_work(otg_task_arg_t data) +{ + struct otg_instance *otg = (struct otg_instance *) data; + TRACE_MSG0(otg->tcd->TAG, "ISP1301 WORK AWAKE"); + return isp1301_private.work_proc(data); +} + +/*! isp1301_bh_wakeup - wakeup the isp1301 bottom half + * @param otg - otg_instance pointer + * @param first + */ +void isp1301_bh_wakeup(struct otg_instance *otg, int first) +{ + TRACE_MSG1(otg->tcd->TAG, "ISP1301 WAKEUP%s", first ? " FIRST" : ""); + isp1301_bh_first |= first; + otg_up_work(isp1301_private.task); +} + + +/* ********************************************************************************************** */ +#if 0 +/*! isp1301_vbus - Do we have Vbus (cable attached?) + * Return non-zero if Vbus is detected. + * + */ +int isp1301_vbus (struct otg_instance *otg) +{ + return isp1301_private.int_src & ISP1301_VBUS_VLD ? 1 : 0; +} + +/*! isp1301_id - Do we have Vbus (cable attached?) + * Return non-zero if Vbus is detected. + * + */ +int isp1301_id (struct otg_instance *otg) +{ + return isp1301_private.int_src & ISP1301_ID_GND ? 1 : 0; +} +#endif + +/* ********************************************************************************************* */ +/*! isp1301_tcd_en() - used to enable + * @param otg - otg_instance pointer + * @param flag - + * + */ +void isp1301_tcd_init(struct otg_instance *otg, u8 flag) +{ + switch (flag) { + case SET: + TRACE_MSG0(otg->tcd->TAG, "SET"); + break; + case PULSE: + TRACE_MSG0(otg->tcd->TAG, "PULSE"); + break; + case RESET: + TRACE_MSG0(otg->tcd->TAG, "RESET"); + break; + } + //printk(KERN_INFO"%s:\n", __FUNCTION__); + isp1301_bh_wakeup(otg, TRUE); + otg_event(otg, OCD_OK, otg->tcd->TAG, "ISP1301 OK"); +} + +/*! isp1301_tcd_en() - used to enable + * @param otg - otg_instance pointer + * @param flag - + * + */ +void isp1301_tcd_en(struct otg_instance *otg, u8 flag) +{ + switch (flag) { + case SET: + TRACE_MSG0(otg->tcd->TAG, "SET"); + break; + case PULSE: + TRACE_MSG0(otg->tcd->TAG, "PULSE"); + break; + case RESET: + TRACE_MSG0(otg->tcd->TAG, "RESET"); + break; + } + //printk(KERN_INFO"%s:\n", __FUNCTION__); + isp1301_bh_wakeup(otg, TRUE); +} + + +/*! isp1301_chrg_vbus - used to enable or disable B-device Vbus charging + * @param otg - otg_instance pointer + * @param flag - + */ +void isp1301_chrg_vbus(struct otg_instance *otg, u8 flag) +{ + //TRACE_MSG0(otg->tcd->TAG, "--"); + switch (flag) { + case SET: + TRACE_MSG0(otg->tcd->TAG, "CHRG_VBUS_SET"); + i2c_writeb(ISP1301_OTG_CONTROL_SET, ISP1301_VBUS_CHRG); + break; + case RESET: + TRACE_MSG0(otg->tcd->TAG, "CHRG_VBUS_RESET"); + i2c_writeb(ISP1301_OTG_CONTROL_CLR, ISP1301_VBUS_RESET); + break; + case PULSE: + break; + } +} + +/*! isp1301_drv_vbus - used to enable or disable A-device driving Vbus + * @param otg - otg_instance pointer + * @param flag - + */ +void isp1301_drv_vbus(struct otg_instance *otg, u8 flag) +{ + //TRACE_MSG0(otg->tcd->TAG, "--"); + switch (flag) { + case SET: + TRACE_MSG0(otg->tcd->TAG, "DRV_VBUS_SET"); + i2c_writeb(ISP1301_OTG_CONTROL_SET, ISP1301_VBUS_DRV); + break; + case RESET: + TRACE_MSG0(otg->tcd->TAG, "DRV_VBUS_RESET"); + i2c_writeb(ISP1301_OTG_CONTROL_CLR, ISP1301_VBUS_RESET); + break; + } +} + +/*! isp1301_dischrg_vbus - used to enable Vbus discharge + * @param otg - otg_instance pointer + * @param flag - + */ +void isp1301_dischrg_vbus(struct otg_instance *otg, u8 flag) +{ + //TRACE_MSG0(otg->tcd->TAG, "--"); + switch (flag) { + case SET: + TRACE_MSG0(otg->tcd->TAG, "OUTPUT: TCD_DISCHRG_VBUS_SET"); + i2c_writeb(ISP1301_OTG_CONTROL_SET, ISP1301_VBUS_DISCHRG); + break; + case RESET: + TRACE_MSG0(otg->tcd->TAG, "OUTPUT: TCD_DISCHRG_VBUS_RESET"); + i2c_writeb(ISP1301_OTG_CONTROL_CLR, ISP1301_VBUS_RESET); + break; + } +} + +/*! isp1301_mx21_vbus_drain - used to enable Vbus discharge + * @param otg - otg_instance pointer + * @param flag - + */ +void isp1301_mx21_vbus_drain_func(struct otg_instance *otg, u8 flag) +{ + //TRACE_MSG0(otg->tcd->TAG, "--"); + switch (flag) { + case SET: + TRACE_MSG0(otg->tcd->TAG, "OUTPUT: TCD_DISCHRG_VBUS_SET"); + i2c_writeb(ISP1301_OTG_CONTROL_SET, ISP1301_VBUS_DISCHRG); + break; + case RESET: + TRACE_MSG0(otg->tcd->TAG, "OUTPUT: TCD_DISCHRG_VBUS_RESET"); + i2c_writeb(ISP1301_OTG_CONTROL_CLR, ISP1301_VBUS_RESET); + break; + } +} + +/*! isp1301_dp_pullup_func - used to enable or disable peripheral connecting to bus + * + * C.f. 5.1.6, 5.1.7, 5.2.4 and 5.2.5 + * + * host peripheral + * d+ pull-up clr set + * d+ pull-down set clr + * + * d- pull-up clr clr + * d- pull-down set set + * + */ +/*! isp1301_dp_pullup_func + * @param otg - otg_instance pointer + * @param flag - + */ +void isp1301_dp_pullup_func(struct otg_instance *otg, u8 flag) +{ + //TRACE_MSG0(otg->tcd->TAG, "--"); + switch (flag) { + case SET: + isp1301_private.flags |= ISP1301_LOC_CONN; + TRACE_MSG0(otg->tcd->TAG, "ISP1301_LOC_CONN SET - Set DP PULLUP"); + i2c_writeb(ISP1301_OTG_CONTROL_SET, ISP1301_DP_PULLUP); + break; + + case RESET: + isp1301_private.flags &= ~ISP1301_LOC_CONN; + TRACE_MSG0(otg->tcd->TAG, "ISP1301_LOC_CONN RESET - Clr DP PULLUP"); + i2c_writeb(ISP1301_OTG_CONTROL_CLR, ISP1301_DP_PULLUP); + break; + } +} + +/*! isp1301_dm_pullup_func - used to enable or disable peripheral connecting to bus + * @param otg - otg_instance pointer + * @param flag - + */ +void isp1301_dm_pullup_func(struct otg_instance *otg, u8 flag) +{ + //TRACE_MSG0(otg->tcd->TAG, "--"); + switch (flag) { + case SET: + isp1301_private.flags |= ISP1301_LOC_CONN; + TRACE_MSG0(otg->tcd->TAG, "ISP1301_LOC_CONN SET - Set DM PULLUP"); + i2c_writeb(ISP1301_OTG_CONTROL_SET, ISP1301_DM_PULLUP); + break; + + case RESET: + isp1301_private.flags &= ~ISP1301_LOC_CONN; + TRACE_MSG0(otg->tcd->TAG, "ISP1301_LOC_CONN RESET - Clr DM PULLUP"); + i2c_writeb(ISP1301_OTG_CONTROL_CLR, ISP1301_DM_PULLUP); + break; + } +} + +/*! isp1301_dp_pulldown_func - used to enable or disable peripheral connecting to bus + * + * C.f. 5.1.6, 5.1.7, 5.2.4 and 5.2.5 + * + * host peripheral + * d+ pull-up clr set + * d+ pull-down set clr + * + * d- pull-up clr clr + * d- pull-down set set + * + */ +/*! isp1301_dp_pulldown_func - + * @param otg - otg_instance pointer + * @param flag - + */ +void isp1301_dp_pulldown_func(struct otg_instance *otg, u8 flag) +{ + //TRACE_MSG0(otg->tcd->TAG, "--"); + switch (flag) { + case SET: + isp1301_private.flags |= ISP1301_LOC_CONN; + TRACE_MSG0(otg->tcd->TAG, "ISP1301_LOC_CONN SET - Set DP PULLUP"); + i2c_writeb(ISP1301_OTG_CONTROL_SET, ISP1301_DP_PULLDOWN); + break; + + case RESET: + isp1301_private.flags &= ~ISP1301_LOC_CONN; + TRACE_MSG0(otg->tcd->TAG, "ISP1301_LOC_CONN RESET - Clr DP PULLUP"); + i2c_writeb(ISP1301_OTG_CONTROL_CLR, ISP1301_DP_PULLDOWN); + break; + } +} + +/*! isp1301_dm_pulldown_func - used to enable or disable peripheral connecting to bus + * @param otg - otg_instance pointer + * @param flag - + */ +void isp1301_dm_pulldown_func(struct otg_instance *otg, u8 flag) +{ + //TRACE_MSG0(otg->tcd->TAG, "--"); + switch (flag) { + case SET: + isp1301_private.flags |= ISP1301_LOC_CONN; + TRACE_MSG0(otg->tcd->TAG, "ISP1301_LOC_CONN SET - Set DM PULLUP"); + i2c_writeb(ISP1301_OTG_CONTROL_SET, ISP1301_DM_PULLDOWN); + break; + + case RESET: + isp1301_private.flags &= ~ISP1301_LOC_CONN; + TRACE_MSG0(otg->tcd->TAG, "ISP1301_LOC_CONN RESET - Clr DM PULLUP"); + i2c_writeb(ISP1301_OTG_CONTROL_CLR, ISP1301_DM_PULLDOWN); + break; + } +} + +/*! isp1301_peripheral_host_func - used to enable or disable peripheral connecting to bus + * + * A-Device D+ pulldown D- pulldown + * idle set set + * host set set + * peripheral reset set + * + * B-Device + * idle set set + * host set set + * peripheral reset set + */ +void isp1301_peripheral_host_func(struct otg_instance *otg, u8 flag) +{ + //TRACE_MSG0(otg->tcd->TAG, "--"); + switch (flag) { + case SET: // peripheral + TRACE_MSG0(otg->tcd->TAG, "SET - CLR DP PULLDOWN"); + i2c_writeb(ISP1301_OTG_CONTROL_CLR, ISP1301_DP_PULLDOWN); + break; + + case RESET: // host + TRACE_MSG0(otg->tcd->TAG, "RESET - SET DM PULLDOWN"); + i2c_writeb(ISP1301_OTG_CONTROL_SET, ISP1301_DP_PULLDOWN); + break; + } +} + +/*! isp1301_dm_det_func - used to enable or disable D- detect + * @param otg - otg_instance pointer + * @param flag - + */ +void isp1301_dm_det_func(struct otg_instance *otg, u8 flag) +{ + //TRACE_MSG0(otg->tcd->TAG, "--"); + //printk(KERN_INFO"%s:\n", __FUNCTION__); + switch (flag) { + case SET: + TRACE_MSG0(otg->tcd->TAG, "setting DM_HI detect"); + isp1301_int_src_set(otg, ISP1301_DM_HI); + isp1301_bh_wakeup(otg, TRUE); + isp1301_debounce = 0; + break; + + case RESET: + TRACE_MSG0(otg->tcd->TAG, "reseting DM_HI detect"); + isp1301_int_src_clr(otg, ISP1301_DM_HI); + isp1301_bh_wakeup(otg, TRUE); + isp1301_debounce = 1; + break; + } +} + +/*! isp1301_dp_det_func - used to enable or disable D+ detect + * @param otg - otg_instance pointer + * @param flag - + */ +void isp1301_dp_det_func(struct otg_instance *otg, u8 flag) +{ + //TRACE_MSG0(otg->tcd->TAG, "--"); + //printk(KERN_INFO"%s:\n", __FUNCTION__); + switch (flag) { + case SET: + TRACE_MSG0(otg->tcd->TAG, "setting DP_HI detect"); + isp1301_int_src_set(otg, ISP1301_DP_HI); + isp1301_bh_wakeup(otg, TRUE); + break; + + case RESET: + TRACE_MSG0(otg->tcd->TAG, "reseting DP_HI detect"); + isp1301_int_src_clr(otg, ISP1301_DP_HI); + isp1301_bh_wakeup(otg, TRUE); + break; + } +} + +/*! isp1301_cr_det_func - used to enable or disable D+ detect + * @param otg - otg_instance pointer + * @param flag - + */ +void isp1301_cr_det_func(struct otg_instance *otg, u8 flag) +{ + //TRACE_MSG0(otg->tcd->TAG, "--"); + //printk(KERN_INFO"%s:\n", __FUNCTION__); + switch (flag) { + case SET: + TRACE_MSG0(otg->tcd->TAG, "setting CR_INT detect"); + isp1301_int_src_set(otg, ISP1301_CR_INT); + isp1301_bh_wakeup(otg, TRUE); + break; + + case RESET: + TRACE_MSG0(otg->tcd->TAG, "reseting CR_INT detect"); + isp1301_int_src_clr(otg, ISP1301_CR_INT); + isp1301_bh_wakeup(otg, TRUE); + break; + } +} + +/*! isp1301_bdis_acon_func - used to enable or disable auto a-connect + * @param otg - otg_instance pointer + * @param flag - + */ +void isp1301_bdis_acon_func(struct otg_instance *otg, u8 flag) +{ + //TRACE_MSG0(otg->tcd->TAG, "--"); + switch (flag) { + case SET: + TRACE_MSG0(otg->tcd->TAG, "setting BDIS ACON"); + isp1301_int_src_set(otg, ISP1301_BDIS_ACON); + i2c_writeb(ISP1301_MODE_CONTROL_1_SET, ISP1301_BDIS_ACON_EN); + break; + + case RESET: + TRACE_MSG0(otg->tcd->TAG, "reseting BDIS ACON"); + i2c_writeb(ISP1301_MODE_CONTROL_1_CLR, ISP1301_BDIS_ACON_EN); + isp1301_int_src_clr(otg, ISP1301_BDIS_ACON); + break; + } +} + +/*! isp1301_id_pulldown_func - used to enable or disable ID pulldown + * @param otg - otg_instance pointer + * @param flag - + */ +void isp1301_id_pulldown_func(struct otg_instance *otg, u8 flag) +{ + //TRACE_MSG0(otg->tcd->TAG, "--"); + switch (flag) { + case SET: + TRACE_MSG0(otg->tcd->TAG, "setting ID PULLDOWN"); + i2c_writeb(ISP1301_OTG_CONTROL_SET, ISP1301_ID_PULLDOWN); + break; + + case RESET: + TRACE_MSG0(otg->tcd->TAG, "reseting ID PULLDOWN"); + i2c_writeb(ISP1301_OTG_CONTROL_CLR, ISP1301_ID_PULLDOWN); + break; + } +} + +/*! isp1301_audio_func - used to enable or disable Carkit Interrupt + * @param otg - otg_instance pointer + * @param flag - + */ +void isp1301_audio_func(struct otg_instance *otg, u8 flag) +{ + //TRACE_MSG0(otg->tcd->TAG, "--"); + switch (flag) { + case SET: + TRACE_MSG0(otg->tcd->TAG, "SET AUDIO_EN"); + //isp1301_int_src_set(otg, ISP1301_CR_INT); + i2c_writeb(ISP1301_MODE_CONTROL_2_SET, ISP1301_AUDIO_EN); + break; + + case RESET: + TRACE_MSG0(otg->tcd->TAG, "RESET AUDIO_EN"); + i2c_writeb(ISP1301_MODE_CONTROL_2_CLR, ISP1301_AUDIO_EN); + //isp1301_int_src_clr(otg, ISP1301_CR_INT); + break; + } +} + +/*! isp1301_uart_func - used to enable or disable transparent uart mode + * @param otg - otg_instance pointer + * @param flag - + */ +void isp1301_uart_func(struct otg_instance *otg, u8 flag) +{ + //TRACE_MSG0(otg->tcd->TAG, "--"); + switch (flag) { + case SET: + TRACE_MSG0(otg->tcd->TAG, "setting UART_EN"); + i2c_writeb(ISP1301_MODE_CONTROL_1_SET, ISP1301_UART_EN); + /* XXX enable uart */ + break; + + case RESET: + TRACE_MSG0(otg->tcd->TAG, "reseting UART_EN"); + i2c_writeb(ISP1301_MODE_CONTROL_1_CLR, ISP1301_UART_EN); + /* XXX disable uart */ + break; + } +} + +/*! isp1301_mono_func - used to enable or disable mono audio connection + * @param otg - otg_instance pointer + * @param flag - + */ +void isp1301_mono_func(struct otg_instance *otg, u8 flag) +{ + //TRACE_MSG0(otg->tcd->TAG, "--"); + switch (flag) { + case SET: + TRACE_MSG0(otg->tcd->TAG, "setting MONO"); + /* XXX enable mono output */ + break; + + case RESET: + TRACE_MSG0(otg->tcd->TAG, "reseting MONO"); + /* XXX disable mono output */ + break; + } +} + + +/* ********************************************************************************************* */ +#ifdef CONFIG_OTG_ISP1301_PROCFS +extern int isp1301_procfs_init (void); +extern void isp1301_procfs_exit (void); +#endif /* CONFIG_OTG_ISP1301_PROCFS */ + +/*! isp1301_mod_init + * @param otg - otg_instance pointer + * @param work_proc - task process function + */ +int isp1301_mod_init(struct otg_instance *otg, otg_task_proc_t work_proc) +{ + /* Setup work item, use isp1301_bh directly if platform is not + * providing a bottom half wrapper. + */ + TRACE_MSG0(otg->tcd->TAG, "1. setup work item"); + + isp1301_private.work_proc = work_proc ? work_proc : &isp1301_bh; + + RETURN_EINVAL_UNLESS(( isp1301_private.task = otg_task_init2("isp1301", isp1301_work, otg, otg->tcd->TAG))); + otg->task = isp1301_private.task; +// isp1301_private.task->debug = TRUE; + otg_task_start(isp1301_private.task); + return 0; +} + +/*! isp1301_mod_exit + */ +void isp1301_mod_exit(struct otg_instance *otg) +{ +#ifdef CONFIG_OTG_ISP1301_PROCFS + isp1301_procfs_exit (); +#endif /* CONFIG_OTG_ISP1301_PROCFS */ + otg_task_exit(isp1301_private.task); +} + +/* ********************************************************************************************* */ +/*! isp1301_configure - configure the ISP1301 for this host + * @param otg - otg instance + * @param tx_mode - the type of connection between the host USB and the ISP1301 + * @param spd_ctrl - suspend control method + */ +void isp1301_configure(struct otg_instance *otg, isp1301_tx_mode_t tx_mode, isp1301_spd_ctrl_t spd_ctrl) +{ + //u16 vendor = 0, product = 0, revision = 0; + //u16 vendor = 0, product = 0, revision = 0; + //u8 mode1, mode2, control; + u8 mode1, mode2; + + struct otg_transceiver_map *map = isp1301_transceiver_map; + + +#if defined(CONFIG_OTG_ISP1301_MX2ADS) || defined(CONFIG_OTG_ISP1301_MX2ADS_MODULE) + u32 vp; + TRACE_MSG0(otg->tcd->TAG, "1. Read Transceiver ID's with long read"); + vp = i2c_readl(ISP1301_VENDOR_ID); + isp1301_private.vendor = vp & 0xffff; + isp1301_private.product = vp >> 16; + isp1301_private.revision = i2c_readw(ISP1301_VERSION_ID); + mode1 = i2c_readb(ISP1301_MODE_CONTROL_1_SET); + mode2 = i2c_readb(ISP1301_MODE_CONTROL_2_SET); + + TRACE_MSG5(otg->tcd->TAG, "2. OTG Transceiver: vendor: %04x product: %04x revision: %04x mode1: %02x mode2: %02x", + isp1301_private.vendor, isp1301_private.product, isp1301_private.revision, mode1, mode2); + +#else /* defined(CONFIG_OTG_ISP1301_MX2ADS) */ + + isp1301_private.vendor = i2c_readw(ISP1301_VENDOR_ID); + isp1301_private.product = i2c_readw(ISP1301_PRODUCT_ID); + isp1301_private.revision = i2c_readw(ISP1301_VERSION_ID); + mode1 = i2c_readb(ISP1301_MODE_CONTROL_1_SET); + mode2 = i2c_readb(ISP1301_MODE_CONTROL_2_SET); + + TRACE_MSG5(otg->tcd->TAG, "2. OTG Transceiver: vendor: %04x product: %04x revision: %04x mode1: %02x mode2: %02x", + isp1301_private.vendor, isp1301_private.product, isp1301_private.revision, mode1, mode2); + +#endif /* defined(CONFIG_OTG_ISP1301_MX2ADS) */ + + + printk(KERN_INFO"%s: vendor: %04x product: %04x revision: %04x mode1: %02x mode2: %02x\n", __FUNCTION__, + isp1301_private.vendor, isp1301_private.product, isp1301_private.revision, mode1, mode2 + ); + + for ( ; map && map->transceiver_type != unknown_transceiver; map++) { + CONTINUE_UNLESS ((isp1301_private.vendor == map->vendor) && (isp1301_private.product == map->product)); + TRACE_MSG1(otg->tcd->TAG, "Found Transceiver: %s", map->name); + isp1301_private.transceiver_map = map; + break; + } + UNLESS (isp1301_private.transceiver_map) + isp1301_private.transceiver_map = isp1301_transceiver_map; + + TRACE_MSG0(otg->tcd->TAG, "3. Disable All Transceiver Control Register 1 "); + i2c_writeb(ISP1301_OTG_CONTROL_CLR, 0xff); // clear + + TRACE_MSG0(otg->tcd->TAG, "4. Enable D-/D+ Pulldowns in Transceiver Control Register 1 "); + + i2c_writeb(ISP1301_OTG_CONTROL_SET, ISP1301_DM_PULLDOWN | ISP1301_DP_PULLDOWN); + i2c_writeb(ISP1301_OTG_CONTROL_CLR, (u8) ISP1301_DM_PULLUP | ISP1301_DP_PULLUP); + + + TRACE_MSG0(otg->tcd->TAG, "5. Clear latch and enable interrupts"); + i2c_writeb(ISP1301_INTERRUPT_LATCH_CLR, 0xff); + //i2c_writeb(ISP1301_INTERRUPT_ENABLE_LOW_CLR, 0xeb); + //i2c_writeb(ISP1301_INTERRUPT_ENABLE_HIGH_CLR, 0xeb); + isp1301_int_src_set(otg, 0xeb); + + /* The PSW_OE enables the ADR/PSW pin for output driving either high + * or low depending on the address. + * + * ADR ADR_REG Address PSW_OE=0 PSW_OE=1 + * + * low 0 0x2c LOW HIGH + * + * high 1 0x2d HIGH LOW + * + * + * The i2c address by tying the ADR/PSW pin either high or low. + * Setting the PSW_OE drives the ADR/PSW into the opposite of the + * default wiring. + * + * On the MX21ADS the ADR/PSW pin is wired high which enables the + * charge pump. So enabling the PSW_OE is required to disable Vbus + * generation on the Charge Pump. + * + * The MAX3355E will only enable Vbus when this signal is high AND + * the ID signal is low. So it may be safe to leave enabled when + * operating as a B-device (ID floating.) + */ + + //i2c_writeb(ISP1301_MODE_CONTROL_2_SET, ISP1301_PSW_OE); // PSW_OE - OFFVBUS low + + + TRACE_MSG1(otg->tcd->TAG, "6. tx_mode: %02x", tx_mode); + /* + * DAT_SE0 + * 0 - VP_VM mode + * 1 - DAT_SE0 mode + */ + switch (tx_mode) { + case vp_vm_unidirectional: + case vp_vm_bidirectional: + TRACE_MSG0(otg->tcd->TAG, "6. tx_mode: VP_VM"); + i2c_writeb(ISP1301_MODE_CONTROL_1_CLR, ISP1301_DAT_SE0); + break; + + case dat_se0_unidirectional: + case dat_se0_bidirectional: + TRACE_MSG0(otg->tcd->TAG, "6. tx_mode: DAT_SE0"); + i2c_writeb(ISP1301_MODE_CONTROL_1_SET, ISP1301_DAT_SE0); + break; + } + /* + * BI_DI + * 0 - unidirectional + * 1 - bidirectional + */ + switch (tx_mode) { + case vp_vm_unidirectional: + case dat_se0_unidirectional: + TRACE_MSG0(otg->tcd->TAG, "6. tx_mode: unidirectional"); + i2c_writeb(ISP1301_MODE_CONTROL_2_CLR, ISP1301_BI_DI); + break; + case vp_vm_bidirectional: + case dat_se0_bidirectional: + TRACE_MSG0(otg->tcd->TAG, "6. tx_mode: bidirectional"); + i2c_writeb(ISP1301_MODE_CONTROL_2_SET, ISP1301_BI_DI); + break; + } + TRACE_MSG0(otg->tcd->TAG, "6. tx_mode done"); + + TRACE_MSG1(otg->tcd->TAG, "7. spd_ctrl: %02x", spd_ctrl); + switch (spd_ctrl) { + case spd_susp_pins: + i2c_writeb(ISP1301_MODE_CONTROL_2_CLR, ISP1301_SPD_SUSP_CTRL); + break; + case spd_susp_reg: + i2c_writeb(ISP1301_MODE_CONTROL_1_SET, ISP1301_SPEED_REG); + i2c_writeb(ISP1301_MODE_CONTROL_2_SET, ISP1301_SPD_SUSP_CTRL); + break; + } + + //i2c_writeb(ISP1301_MODE_CONTROL_2_SET, ISP1301_PSW_OE); // PSW_OE - OFFVBUS low + + TRACE_MSG0(otg->tcd->TAG, "7. spd_ctrl done"); + + TRACE_MSG1(otg->tcd->TAG, "8. transceiver_type: %02x", isp1301_private.transceiver_map); + + TRACE_MSG1(otg->tcd->TAG, "8. transceiver_type: %02x", isp1301_private.transceiver_map->transceiver_type); + switch (isp1301_private.transceiver_map->transceiver_type) { + case isp1301: + break; + case max3301e: + i2c_writeb(MAX3301E_SPECIAL_FUNCTION_1_SET, MAX3301E_SESS_END); + break; + case unknown_transceiver: + break; + } + + TRACE_MSG0(otg->tcd->TAG, "8. transceiver_type done"); + + TRACE_MSG0(otg->tcd->TAG, "9. enable interrupts"); + isp1301_int_src(otg, ISP1301_ID_FLOAT | ISP1301_DM_HI | ISP1301_ID_GND | ISP1301_DP_HI | ISP1301_SESS_VLD | ISP1301_VBUS_VLD); + +#ifdef CONFIG_OTG_ISP1301_PROCFS + TRACE_MSG0(otg->tcd->TAG, "3."); + isp1301_procfs_init (); +#endif /* CONFIG_OTG_ISP1301_PROCFS */ + + /* Set the ready flag in isp1301_private to show that the structure is ready for use */ + isp1301_private.ready = TRUE; + +#if 0 //For debugging + + printk(KERN_INFO"%s: VENDOR_ID %04x\n", __FUNCTION__, i2c_readb(ISP1301_VENDOR_ID));; + printk(KERN_INFO"%s: PRODUCT_ID %04x\n", __FUNCTION__, i2c_readb(ISP1301_PRODUCT_ID));; + printk(KERN_INFO"%s: VERSION_ID %04x\n", __FUNCTION__, i2c_readb(ISP1301_VERSION_ID));; + + printk(KERN_INFO"%s: OTG_CONTROL_SET %02x\n", __FUNCTION__, i2c_readb(ISP1301_OTG_CONTROL_SET));; + printk(KERN_INFO"%s: INTERRUPT_SOURCE %02x\n", __FUNCTION__, i2c_readb(ISP1301_INTERRUPT_SOURCE));; + printk(KERN_INFO"%s: INTERRUPT_LATCH_SET %02x\n", __FUNCTION__, i2c_readb(ISP1301_INTERRUPT_LATCH_SET));; + printk(KERN_INFO"%s: INTERRUPT_ENABLE_LOW %02x\n", __FUNCTION__, i2c_readb(ISP1301_INTERRUPT_ENABLE_LOW));; + printk(KERN_INFO"%s: INTERRUPT_ENABLE_HIGH %02x\n", __FUNCTION__, i2c_readb(ISP1301_INTERRUPT_ENABLE_HIGH));; + printk(KERN_INFO"%s: MOD_CONTROL_1 %02x\n", __FUNCTION__, i2c_readb(ISP1301_MODE_CONTROL_1));; + printk(KERN_INFO"%s: MOD_CONTROL_2 %02x\n", __FUNCTION__, i2c_readb(ISP1301_MODE_CONTROL_2));; +#endif +} + +void isp1301_exit(void) +{ + i2c_writeb_direct(ISP1301_OTG_CONTROL_SET, ISP1301_DP_PULLDOWN | ISP1301_DM_PULLDOWN); + i2c_writeb_direct(ISP1301_OTG_CONTROL_CLR, ISP1301_DP_PULLUP | ISP1301_DM_PULLUP); + +} + +#endif /* defined(CONFIG_OTG_ISP1301) */ diff --git a/drivers/otg/hardware/isp1301.h b/drivers/otg/hardware/isp1301.h new file mode 100644 index 000000000000..3beebb2786d8 --- /dev/null +++ b/drivers/otg/hardware/isp1301.h @@ -0,0 +1,239 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/hardware/isp1301.h -- USB Transceiver Controller driver + * @(#) sl@belcarra.com/whiskey.enposte.net|otg/platform/isp1301/isp1301.h|20061218212925|39953 + * + * Copyright (c) 2004-2005 Belcarra + * Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + * + * By: + * Stuart Lynne <sl@lbelcarra.com>, + * Bruce Balden <balden@belcarra.com> + * + */ +/*! + * @defgroup ISP1301TCD Philips ISP1301 + * @ingroup TCD + */ +/*! + * @file otg/hardware/isp1301.h + * @brief Private structures and defines for ISP1301 Transciever Controller Driver. + * + * This file contains the private defines and structures for the ISP1301 Transceiver + * Driver. + * + * @ingroup ISP1301TCD + */ + +/*! + * @name ISP1301 ISP1301 configuration + * @{ + */ + +#ifdef CONFIG_OMAP_H2 +#define ISP1301_GPIO (2) +#define TCD_ISP1301_I2C_ADDR ISP1301_I2C_ADDR_HIGH; +#endif + +#define ISP1301_NAME "isp1301" + +#define ISP1301_LOC_CONN 0x01 + +#define ISP1301_INT_SRC 0xeb + +/*! + * @enum otg_transceiver + * Define ISP1301 compatible transceivers + */ +typedef enum otg_transceiver { + unknown_transceiver, /*!< unknown */ + isp1301, /*!< Philips ISP1301 */ + max3301e /*!< Maxim MAX3301e */ +} otg_transceiver_t; + +/*! + * @struct otg_transceiver_map + * How to determine transceiver model and manufacturer + */ +struct otg_transceiver_map { + otg_transceiver_t transceiver_type; /*!< transceiver type */ + u16 vendor; /*!< vendor number */ + u16 product; /*!< product number */ + u16 revision; /*!< revision number */ + char *name; /*!< name */ +}; + +/*! + * @struct isp1301_private + * Information required for operation + */ +struct isp1301_private { + struct otg_task *task; + otg_task_proc_t work_proc; + //void (*work_proc) (void *); + + u16 vendor; /*!< vendor number found */ + u16 product; /*!< product number found */ + u16 revision; /*!< revision number found */ + u32 flags; /*!< flags */ + u8 int_src; /*!< current interrupt source */ + struct otg_transceiver_map *transceiver_map; /*! pointer to transceiver map for this transceiver */ + BOOL ready; /*!< it is set when the structure is ready and filled */ +}; + +/*! + * @var typedef enum isp1301_tx_mode isp1301_tx_mode_t + * This defines the various ways that the ISP1301 can be + * wired into the host USB. + * + * + * VP_VM unidirectional mode + * + * VP_VM bidirectional mode + * + * + * DAT_SE0 unidirectional mode + * + * DAT_SE0 bidirectional mode + * + * + * + * + * C.f. Figure 23 OTG controller with DAT_SE0 SIE interface + * 3-Wire - DAT_SE0 Bi-Directional - Single ended (Dat, SE0) + * __ + * TXEN ---- OE ---> OE (9) + * + * RXD/TXD <---- TXDAT/RCVDAT ---> DAT (14 DAT/VP) + * + * SE0 <---- TXSE0/RCVSE0 ---> SE0 (13 SE0/VM) + * ______ + * GPIO <---- OTGIRQ --- INT_N (5) Active low + * + * + * C.f. Figure 24 OTG controller with VP_VM SIE interface + * 4-Wire - VP_VM Bi-directional + * + * __ + * TXEN ---- OE ---> OE (9) + * + * VP <---- TXVP/RCVVP ---> VP (14 DAT/VP) + * + * VM <---- TXVM/RCVVM ---> VM (10) + * + * RCV <---- RCV --- RCV (12) + * ______ + * GPIO <---- OTGIRQ --- INT_N (5) Active low + * + * + * + * + * 6-Wire VP_VM Uni-directional + * __ + * TXEN ---- OE ---> OE (9) + * + * TXD ---- TXDAT ---> VP (14 DAT/VP) + * + * SE0 ---- TXSE0 ---> VM (13 SE0/VM) + * + * RCV <---- RCV --- RCV (12) + * + * RCVVP <---- RCVVP --- VP (11) + * + * RCVVM <---- RCVVM --- VM (10) + * ______ + * GPIO <---- OTGIRQ --- INT_N (5) Active low + * + * + * + */ +typedef enum isp1301_tx_mode { + dat_se0_bidirectional, /*!< 3-Wire Single-Ended (DAT, SEO) / Single-Ended (DAT, SE0) */ + vp_vm_bidirectional, /*!< 4-Wire Differential (TxDp,TxDM) / Single-Ended (DAT, SE0)*/ + vp_vm_unidirectional, /*!< 6-Wire Differential (TxDp,TxDM) / Differential (RxDp, RxDM, RxD )*/ + dat_se0_unidirectional, /*!< 6-Wire Single-Ended (DAT, SE0) / Differential (RxDp, RxDM, RxD ) */ +} isp1301_tx_mode_t; + +/*! + * @var typedef enum isp1301_spd_ctrl isp1301_spd_ctrl_t + * This defines how the speed and suspend pins are controlled + * for this host. + */ +typedef enum isp1301_spd_ctrl { + spd_susp_pins, /*!< controlled by SPEED and SUSPEND pins */ + spd_susp_reg, /*!< controled by SPEED_REG and SUSPEND_REG in Mode Control 1 register */ +} isp1301_spd_ctrl_t; + + +extern struct tcd_ops tcd_ops; +extern struct isp1301_private isp1301_private; +extern struct tcd_instance *tcd_instance; + +/* isp1301.c */ +extern int isp1301_mod_init(struct otg_instance *, otg_task_proc_t ); +//extern int isp1301_mod_init__(struct otg_instance *); +extern void isp1301_mod_exit(struct otg_instance *); +extern void isp1301_tcd_init(struct otg_instance *, u8 ); +extern void isp1301_tcd_en(struct otg_instance *, u8 ); +extern void isp1301_chrg_vbus(struct otg_instance *, u8 ); +extern void isp1301_drv_vbus(struct otg_instance *, u8 ); +extern void isp1301_dischrg_vbus(struct otg_instance *, u8 ); +extern void isp1301_dp_pullup_func(struct otg_instance *, u8 ); +extern void isp1301_dm_pullup_func(struct otg_instance *, u8 ); +extern void isp1301_dp_pulldown_func(struct otg_instance *, u8 ); +extern void isp1301_dm_pulldown_func(struct otg_instance *, u8 ); +extern void isp1301_peripheral_host_func(struct otg_instance *, u8 ); +extern void isp1301_dm_det_func(struct otg_instance *, u8 ); +extern void isp1301_dp_det_func(struct otg_instance *, u8 ); +extern void isp1301_cr_det_func(struct otg_instance *, u8 ); +extern void isp1301_bdis_acon_func(struct otg_instance *, u8 ); +extern void isp1301_mx21_vbus_drain_func(struct otg_instance *, u8 ); +extern void isp1301_id_pulldown_func(struct otg_instance *, u8 ); +extern void isp1301_audio_func(struct otg_instance *, u8 ); +extern void isp1301_uart_func(struct otg_instance *, u8 ); +extern void isp1301_mono_func(struct otg_instance *, u8 ); + +extern void isp1301_otg_timer(struct otg_instance *, u8 ); +extern void *isp1301_bh(void *); +extern int isp1301_id (struct otg_instance *); +extern int isp1301_vbus (struct otg_instance *); +extern void isp1301_configure(struct otg_instance *, isp1301_tx_mode_t, isp1301_spd_ctrl_t ); + +extern void isp1301_bh_wakeup(struct otg_instance *, int); + +/* thread */ +//extern int isp1301_thread_init (void (bh_proc)(void *)); +//extern void isp1301_thread_exit (wait_queue_head_t *); +//extern void isp1301_thread_wakeup(int enabled, int first); + + + +/* i2c-xxx.c */ +int i2c_configure(char *name, int addr); +extern void i2c_close(void); +extern u8 i2c_readb(u8 ); +extern u16 i2c_readw(u8 ); +extern u32 i2c_readl(u8 ); +extern void i2c_writeb(u8 , u8 ); +extern void i2c_writeb_direct(u8 , u8 ); +//extern void i2c_write(u8 , u8 ); + + +/* ********************************************************************************************* */ +#define TRACE_I2CB(t,r) TRACE_MSG3(t, "%-40s[%02x] %02x", #r, r, i2c_readb(r)) +#define TRACE_I2CW(t,r) TRACE_MSG3(t, "%-40s[%02x] %04x", #r, r, i2c_readw(r)) +#define TRACE_GPIO(t,b,r) TRACE_MSG2(t, "%-40s %04x", #r, readw(b + r)) +#define TRACE_REGL(t,r) TRACE_MSG2(t, "%-40s %08x", #r, readl(r)) + +/* @} */ diff --git a/drivers/otg/hardware/l26-ocd-dev.c b/drivers/otg/hardware/l26-ocd-dev.c new file mode 100644 index 000000000000..22eecbd7832b --- /dev/null +++ b/drivers/otg/hardware/l26-ocd-dev.c @@ -0,0 +1,118 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/hardware/l26-ocd-dev.c -- Generic Linux 2.6 timer Dev driver wrapper + * @(#) sl@belcarra.com/whiskey.enposte.net|otg/platform/otglib/l26-ocd-dev.c|20070612232808|61728 + * + * Copyright (c) 2005-2007 Belcarra Technologies 2005 Corp + * + * By: + * Stuart Lynne <sl@belcarra.com> + * + */ +/*! + * @file otg/hardware/l26-ocd-dev.c + * @brief Generic L26 OCD Driver. + * + * OTG-DEV wrapper for l26-ocd.c + * + * @ingroup LINUXOS + * @ingroup OTGDEV + */ + + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> + +#include <linux/usb.h> +#include <linux/delay.h> + +#include <otg/otg-compat.h> + + +#include <linux/ioport.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/smp_lock.h> +#include <linux/init.h> +#include <linux/timer.h> +#include <linux/list.h> +#include <linux/interrupt.h> + +/* Other includes*/ +#include <linux/poll.h> + +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/system.h> +#include <asm/unaligned.h> +#include <asm/dma.h> + +#include <otg/otg-trace.h> +#include <otg/otg-api.h> +#include <otg/otg-dev.h> +#include <otg/otg-ocd.h> + +extern struct ocd_ops l26_ocd_ops; + +/* ********************************************************************************************* */ + + +#ifdef CONFIG_HIGH_RES_TIMERS +void l26_ocd_remove(struct otg_dev *otg_dev); +int l26_ocd_probe(struct otg_dev *otg_dev); + +static void +l26_ocd_dev_remove(struct otg_dev *otg_dev) +{ + l26_ocd_remove(otg_dev); +} + +static int +l26_ocd_dev_probe(struct otg_dev *otg_dev) +{ + return l26_ocd_probe(otg_dev); +} + +#endif /* CONFIG_HIGH_RES_TIMERS */ + +/* ********************************************************************************************* */ +static struct otg_dev_driver arc_ocd_driver = { + .name = "l26-ocd-dev", + .id = OTG_DRIVER_OCD, + #ifdef CONFIG_HIGH_RES_TIMERS + .probe = l26_ocd_dev_probe, + .remove = l26_ocd_dev_remove, + #endif /* CONFIG_HIGH_RES_TIMERS */ + .ops = &l26_ocd_ops, +}; + +/* ********************************************************************************************* */ + +/*! l26_ocd_dev_module_init() - module init + */ +int l26_ocd_dev_module_init (struct otg_device_driver *otg_device_driver) +{ + printk(KERN_INFO"%s:\n", __FUNCTION__); + return otg_dev_register_driver(otg_device_driver, &arc_ocd_driver); +} + +/*! + * l26_ocd_dev_module_exit() - module exit + */ +void l26_ocd_dev_module_exit (struct otg_device_driver *otg_device_driver) +{ + printk(KERN_INFO"%s:\n", __FUNCTION__); + otg_dev_unregister_driver (otg_device_driver, &arc_ocd_driver); +} diff --git a/drivers/otg/hardware/l26-ocd.c b/drivers/otg/hardware/l26-ocd.c new file mode 100644 index 000000000000..fdfd9b1b2259 --- /dev/null +++ b/drivers/otg/hardware/l26-ocd.c @@ -0,0 +1,256 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/hardware/l26-ocd.c -- Generic Linux 2.6 Timer + * @(#) sl@belcarra.com/whiskey.enposte.net|otg/platform/otglib/l26-ocd.c|20070612232808|58041 + * + * Copyright (c) 2005-2007 Belcarra Technologies 2005 Corp + * + * By: + * Stuart Lynne <sl@belcarra.com> + * + */ +/*! + * @file otg/hardware/l26-ocd.c + * @brief Generic L26 OCD Driver. + * + * Provides ticks via gettimeofday() and optional a OTG timer + * using high resolution timer. + * + * CONFIG_HIGH_RES_TIMERS=y + * + * Optionally provides accurate OTG timer using Linux High + * Resolution Timer. + * + * CONFIG_HIGH_RES_TIMERS=n + * + * Start timer is a dummy if High Resolution Timers not + * enabled. + * + * @ingroup LINUXOS + */ + + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> + +#include <linux/usb.h> +#include <linux/delay.h> + +#include <otg/otg-compat.h> + + +#include <linux/ioport.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/smp_lock.h> +#include <linux/init.h> +#include <linux/timer.h> +#include <linux/list.h> +#include <linux/interrupt.h> + +/* Other includes*/ +#include <linux/pci.h> +#include <linux/poll.h> + +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/system.h> +#include <asm/unaligned.h> +#include <asm/dma.h> +//#include <asm/hrtime.h> + +#include <otg/otg-trace.h> +#include <otg/otg-dev.h> +#include <otg/otg-api.h> +#include <otg/otg-ocd.h> + +/* ********************************************************************************************* */ + +#ifdef CONFIG_HIGH_RES_TIMERS + +struct l26_hr_timer { + struct timer_list timer_list; + BOOL active; + u32 usec_set; + u32 jiffy_per_sec; +}; + +/*! + * l26_hrt_callback() - called to queue an a timer event to otg queue + * @param arg - pointer of otg_instance type + */ + +void l26_hrt_callback (unsigned long arg) +{ + struct ocd_instance *ocd = (struct ocd_instance *) arg; + struct otg_instance *otg = ocd->otg; + struct l26_hr_timer *hr_timer = ocd->privdata; + + RETURN_UNLESS(hr_timer->active); + hr_timer->active = FALSE; + //TRACE_MSG3 (ocd->TAG, "usec: %d expires: %8u arch_cycle_expires: %8u", + // hr_timer->usec_set, hr_timer.expires, hr_timer.arch_cycle_expires); + + otg_queue_event(otg, TMOUT, ocd->TAG, "HRT TMOUT"); +} + + +#if 0 +/*! + * l26_hrt_start_timer() - start a timer for otg state machine + * Set or reset timer to interrupt in number of uS (micro-seconds). + * + * XXX There may be a floor or minimum that can be effectively set. + * XXX We have seen an occasional problem with US(25) for discharge for example. + * + * @param otg + * @param usec + * @return 0 on success + */ +int l26_hrt_start_timer(struct otg_instance *otg, int usec) +{ + struct ocd_instance *ocd = otg->ocd; + struct l26_hr_timer *hr_timer = ocd->privdata; + + TRACE_MSG1(ocd->TAG, "usec: %d", usec); + + hr_timer->usec_set = usec; + //TRACE_MSG1 (otg->ocd->TAG, "usec: %d", usec); + + hr_timer->active = FALSE; + TRACE_MSG1(ocd->TAG, "resetting active: %d", hr_timer->active); + + del_timer(&hr_timer->timer_list); + RETURN_ZERO_UNLESS(usec); + + hr_timer->active = TRUE; + TRACE_MSG1(ocd->TAG, "setting active: %d", hr_timer->active); + + if (hr_timer->usec_set >= 1000000) { + hr_timer->timer_list.expires = jiffies + ((hr_timer->usec_set/1000000)*hr_timer->jiffy_per_sec); + hr_timer->timer_list.arch_cycle_expires = get_arch_cycles(jiffies); + TRACE_MSG4 (otg->ocd->TAG, "usec: %u jiffies: %8u expires: %8u arch_cycle_expires: %8u LONG", + usec, jiffies, hr_timer->timer_list.expires, hr_timer->timer_list.arch_cycle_expires); + } + else { + hr_timer->timer_list.expires = jiffies; + hr_timer->timer_list.arch_cycle_expires = get_arch_cycles(jiffies); + + if (hr_timer->usec_set < 100) { + TRACE_MSG1(otg->ocd->TAG, "usec: %d set to minimum 100", hr_timer->usec_set); + hr_timer->usec_set = 100; + } + hr_timer->timer_list.arch_cycle_expires += nsec_to_arch_cycle(hr_timer->usec_set * 1000); + + //TRACE_MSG2(ocd->TAG, "arch_cycle_expires: %d arch_cycles_per_jiffy: %d", + // hr_timer->timer_list.arch_cycle_expires, hr_timer->timer_list.arch_cycles_per_jiffy); + + while (hr_timer->timer_list.arch_cycle_expires >= arch_cycles_per_jiffy) { + hr_timer->timer_list.expires++; + hr_timer->timer_list.arch_cycle_expires -= arch_cycles_per_jiffy; + } + TRACE_MSG4 (ocd->TAG, "usec: %u jiffies: %8u expires: %8u arch_cycle_expires: %8u SHORT", + usec, jiffies, hr_timer->timer_list.expires, hr_timer->timer_list.arch_cycle_expires); + } + + add_timer(&hr_timer->timer_list); + return 0; +} +#endif +#endif /* CONFIG_HIGH_RES_TIMERS */ +/* ********************************************************************************************* */ +/*! + * l26_ocd_start_timer() - start a timer for otg state machine + * Set or reset timer to interrupt in number of uS (micro-seconds). + * + * @param otg + * @param usec + */ +int l26_ocd_start_timer(struct otg_instance *otg, int usec) +{ + otg_event(otg, TMOUT, otg->ocd->TAG, "l26 Timer - FAKE TMOUT"); + return 0; +} + +/* l26_ocd_ticks - get current ticks + */ +otg_tick_t l26_ocd_ticks (void) +{ + struct timeval tv; + do_gettimeofday(&tv); + #if 0 + return tv.tv_sec * 1000000 + tv.tv_usec; + #else + return tv.tv_sec << 20 | (tv.tv_usec & 0xfffff); + #endif +} + +/* l26_ocd_elapsed - return micro-seconds between two tick values + */ +otg_tick_t l26_ocd_elapsed(otg_tick_t *t1, otg_tick_t *t2) +{ + #if 0 + return (((t1 > t2) ? (t1 - t2) : (t2 - *t1))); + #else + otg_tick_t n1 = (*t1 >> 20) * 1000000 + (*t1 & 0xfffff); + otg_tick_t n2 = (*t1 >> 20) * 1000000 + (*t2 & 0xfffff); + return (((n1 > n2) ? (n1 - n2) : (n2 - n1))); + #endif +} + + +struct ocd_ops l26_ocd_ops = { + #ifdef xxxx_CONFIG_HIGH_RES_TIMERS + .start_timer = l26_hrt_start_timer, + #else /* CONFIG_HIGH_RES_TIMERS */ + .start_timer = l26_ocd_start_timer, + #endif /* CONFIG_HIGH_RES_TIMERS */ + .ticks = l26_ocd_ticks, + .elapsed = l26_ocd_elapsed, +}; + +/* ********************************************************************************************* */ + +#ifdef CONFIG_HIGH_RES_TIMERS +void +l26_ocd_remove(struct otg_dev *otg_dev) +{ + struct ocd_instance *ocd = otg_dev->ocd_instance; + RETURN_UNLESS (ocd->privdata); + LKFREE(ocd->privdata); + ocd->privdata = NULL; +} + +int +l26_ocd_probe(struct otg_dev *otg_dev) +{ + struct ocd_instance *ocd = otg_dev->ocd_instance; + struct l26_timer *hr_timer = NULL; + RETURN_EINVAL_UNLESS((ocd->privdata = CKMALLOC(sizeof(struct l26_hr_timer)))); + #if 0 + init_timer (hr_timer); + hr_timer->timer_list.expires = jiffies + 10; + hr_timer->timer_list.function = l26_hrt_callback; + hr_timer->timer_list.data = (unsigned long) ocd_instance; + hr_timer->timer_list.arch_cycle_expires = get_arch_cycles(jiffies); + hr_timer->timer_list.arch_cycle_expires += nsec_to_arch_cycle(100 * 1000 * 1000); + while (hr_timer->timer_list.arch_cycle_expires >= arch_cycles_per_jiffy) { + hr_timer->timer_list.expires++; + hr_timer->timer_list.arch_cycle_expires -= arch_cycles_per_jiffy; + } + #endif + return 0; +} +#endif /* CONFIG_HIGH_RES_TIMERS */ diff --git a/drivers/otg/hardware/mxc-gpio.c b/drivers/otg/hardware/mxc-gpio.c new file mode 100644 index 000000000000..93fbaef850b5 --- /dev/null +++ b/drivers/otg/hardware/mxc-gpio.c @@ -0,0 +1,276 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/hardware/mxc-gpio.c - Freescale USBOTG common gpio and iomux setting + * @(#) sl@belcarra.com/whiskey.enposte.net|otg/platform/mxc/mxc-gpio.c|20070612233038|29528 + * + * Copyright (c) 2004-2005 Belcarra Technologies Corp + * Copyright (c) 2005-2007 Belcarra Technologies 2005 Corp + * + * By: + * Stuart Lynne <sl@belcarra.com>, + * Bruce Balden <balden@belcarra.com> + */ +/*! + * @file otg/hardware/mxc-gpio.c + * @brief Freescale USB Host Controller Driver + * + * @ingroup FSOTG + */ + +#include <otg/pcd-include.h> + +#ifdef CONFIG_OTG_ZASEVB_ATLAS_CONNECTIVITY +#include <asm/arch/gpio.h> +#include "mxc-hardware.h" +#include "isp1301.h" +#include "isp1301-hardware.h" +#endif + +#if defined(CONFIG_OTG_ZASEVB_ISP1301) +#include <asm/arch/gpio.h> +#include "mxc-hardware.h" +#include "isp1301.h" +#include "isp1301-hardware.h" +#endif + +#if defined(CONFIG_MACH_ARGONPLUSEVB) || defined(CONFIG_ARCH_ARGONPLUSEVB) || defined(CONFIG_MACH_ARGONPLUSODYSSEY) || defined(CONFIG_ARCH_ARGONLV) +#define ZGPIO_PORT 0 +#define ZGPIO_PIN 2 +#endif/* defined(CONFIG_MACH_ARGONPLUSEVB)*/ + +#if defined(CONFIG_MACH_SCMA11EVB) || defined(CONFIG_ARCH_SCMA11EVB) +#define ZGPIO_PORT 2 +#define ZGPIO_PIN 12 +#endif /* CONFIG_MACH_SCMA11EVB */ + +#ifdef CONFIG_ARCH_ZEUS +#define ZGPIO_PORT 1 +#define ZGPIO_PIN 30 +#endif /* CONFIG_ARCH_ZEUS */ + + +#if defined(CONFIG_OTG_ZASEVB_ISP1301) || defined(_OTG_DOXYGEN) + +BOOL zasevb_int_disabled = FALSE; + +/* ********************************************************************************************* */ +/*! + * zasevb_gpio_int_hndlr() - gpio interrupt handler + * @param irq + * @param dev_id + * @param regs + * @return interrupt handler status + * This disables the gpio interrup and schedules the isp1301 bottom half handler. + * + */ +static irqreturn_t zasevb_gpio_int_hndlr (int irq, void *dev_id, struct pt_regs *regs) +{ + gpio_config_int_en(ZGPIO_PORT, ZGPIO_PIN, FALSE); + zasevb_int_disabled = TRUE; + + TRACE_MSG0(TCD, "ZASEVB GPIO INTERRUPT: SCHEDULE WORK"); + printk(KERN_INFO"%s:\n", __FUNCTION__); + isp1301_bh_wakeup(FALSE); + + return IRQ_HANDLED; +} + +/*! + * zasevb_isp1301_bh()- call isp1301 bottom half handler + * @param arg + * This is a wrapper to the isp1301 bottom half handler, it + * re-enables the gpio interrupt after processing complete. +*/ +void *zasevb_isp1301_bh(void *arg) +{ + TRACE_MSG0(TCD, "ZASEVB GPIO INTERRUPT: ISP1301_BH"); + isp1301_bh(arg); + TRACE_MSG0(TCD, "ZASEVB GPIO INTERRUPT: REENABLE"); + if (zasevb_int_disabled) { + zasevb_int_disabled = FALSE; + gpio_config_int_en(ZGPIO_PORT, ZGPIO_PIN, TRUE); + } + return NULL; +} + + + + +/*! + * This function set iomux settings depends on platform and usb mode. + * + * @param otg otg instance + * @param usb_mode setting usb mode. + * + */ + +int mxc_iomux_gpio_isp1301_set (struct otg_instance *otg, int usb_mode) +{ + + int gpio = 1; + + printk (KERN_INFO"MXC gpio setting for isp1301\n"); + + isp1301_mod_init(otg, &zasevb_isp1301_bh); + + TRACE_MSG0(TCD, "5. IOMUX and GPIO Interrupt Configuration"); + + #ifdef CONFIG_MACH_SCMA11EVB + iomux_config_mux(AP_GPIO_AP_C12, OUTPUTCONFIG_DEFAULT, INPUTCONFIG_NONE); // XXX INPUTCONFIG_DEFAULT? + #endif /* CONFIG_MACH_SCMA11EVB */ + + #ifdef CONFIG_MACH_ARGONPLUSEVB + iomux_config_mux(PIN_GPIO2, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + #endif /* CONFIG_MACH_ARGONPLUSEVB */ + + #ifdef CONFIG_MACH_ZEUSEVB + iomux_config_mux(SPI2_SS1_PIN, MUX0_OUT, GPIO_MUX1_IN); + #endif /* CONFIG_MACH_ZEUSEVB */ + + //Settung interrupt for ISP1301 + gpio_config(ZGPIO_PORT, ZGPIO_PIN, false, GPIO_INT_FALL_EDGE); + gpio = gpio_request_irq(ZGPIO_PORT, ZGPIO_PIN, GPIO_HIGH_PRIO, zasevb_gpio_int_hndlr, + SA_SHIRQ, "ISP1301", (void *) &ocd_ops); + THROW_IF(gpio, error); + gpio_config_int_en(ZGPIO_PORT, ZGPIO_PIN, TRUE); // XXX this might not be needed + + + #if defined(CONFIG_ARCH_SCMA11) + iomux_config_mux(SP_USB_TXOE_B, OUTPUTCONFIG_FUNC1, INPUTCONFIG_FUNC1); + iomux_config_mux(SP_USB_DAT_VP, OUTPUTCONFIG_FUNC1, INPUTCONFIG_FUNC1); + iomux_config_mux(SP_USB_SE0_VM, OUTPUTCONFIG_FUNC1, INPUTCONFIG_FUNC1); + iomux_config_mux(SP_USB_RXD, OUTPUTCONFIG_FUNC1, INPUTCONFIG_FUNC1); + #if 0 + switch(hwmode) { + case XCVR_SE0_D_NEW: + case XCVR_D_SE0_NEW: + iomux_config_mux(AP_GPIO_AP_C16,OUTPUTCONFIG_FUNC3, INPUTCONFIG_FUNC3); + iomux_config_mux(AP_GPIO_AP_C17,OUTPUTCONFIG_FUNC3, INPUTCONFIG_FUNC3); + printk(KERN_INFO"%s: EXTRA IOMUX\n", __FUNCTION__); + break; + + case XCVR_D_D: + case XCVR_SE0_SE0: + printk(KERN_INFO"%s: NO EXTRA IOMUX\n", __FUNCTION__); + break; + } + #endif + #endif /* CONFIG_ARCH_SCMA11 */ + + #if defined(CONFIG_ARCH_ARGONPLUS) || defined(CONFIG_ARCH_ARGONLV) + iomux_config_mux(PIN_USB_XRXD, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + iomux_config_mux(PIN_USB_VMOUT, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + iomux_config_mux(PIN_USB_VPOUT, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + iomux_config_mux(PIN_USB_VPIN, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + iomux_config_mux(PIN_USB_TXENB, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + iomux_config_mux(PIN_USB_VMIN, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + #endif /* CONFIG_ARCH_ARGONPLUS */ + + + #ifdef CONFIG_ARCH_ZEUS + iomux_config_mux(USB_DAT_VP_PIN, MUX0_OUT, MUX0_IN); // iomux_com_base + 0x00 + iomux_config_mux(USB_SE0_VM_PIN, MUX0_OUT, MUX0_IN); // iomux_com_base + 0x01 + iomux_config_mux(USB_TXOE_B_PIN, MUX0_OUT, MUX0_IN); // iomux_com_base + 0x09 + iomux_config_mux(USB_RXD_PIN, MUX0_OUT, MUX0_IN); // iomux_com_base + 0x3c + #endif + + + CATCH(error) { + printk(KERN_INFO"%s: failed\n", __FUNCTION__); + UNLESS (gpio) gpio_free_irq (ZGPIO_PORT, ZGPIO_PIN, GPIO_HIGH_PRIO); + return -EINVAL; + } + + return 0; +} + +/*! mxc_iomux_gpio_isp1301_reset - reset isp1301 gpio irq setting + */ +int mxc_iomux_gpio_isp1301_reset (void) +{ + gpio_free_irq (ZGPIO_PORT, ZGPIO_PIN, GPIO_HIGH_PRIO); + return 0; +} + +#endif //CONFIG_OTG_ZASEVB_ISP1301 + + + +#ifdef CONFIG_OTG_ZASEVB_ATLAS_CONNECTIVITY +/*! mxc_iomux_gpio_atlas_set - set atlas gpio settings + * @param usb_mode + * @return 0 + */ +int mxc_iomux_gpio_atlas_set (int usb_mode) +{ + + printk (KERN_INFO"MXC gpio setting for Atlas\n"); + #if defined(CONFIG_ARCH_SCMA11) + printk(KERN_INFO"IOMUX setting for SCMA11\n"); + iomux_config_mux(SP_USB_TXOE_B, OUTPUTCONFIG_FUNC1, INPUTCONFIG_FUNC1); + iomux_config_mux(SP_USB_DAT_VP, OUTPUTCONFIG_FUNC1, INPUTCONFIG_FUNC1); + iomux_config_mux(SP_USB_SE0_VM, OUTPUTCONFIG_FUNC1, INPUTCONFIG_FUNC1); + iomux_config_mux(SP_USB_RXD, OUTPUTCONFIG_FUNC1, INPUTCONFIG_FUNC1); + #endif + + #if defined(CONFIG_MACH_ARGONPLUSEVB) || defined(CONFIG_ARCH_ARGONPLUSEVB) || defined(CONFIG_MACH_ARGONPLUSODYSSEY) || defined(CONFIG_ARCH_ARGONLV) + printk(KERN_INFO"IOMUX setting for Argon+\n"); + iomux_config_mux(PIN_USB_XRXD, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + iomux_config_mux(PIN_USB_VMOUT, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + iomux_config_mux(PIN_USB_VPOUT, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + iomux_config_mux(PIN_USB_VPIN, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + iomux_config_mux(PIN_USB_TXENB, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + iomux_config_mux(PIN_USB_VMIN, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + #endif /* CONFIG_ARCH_ARGONPLUS */ + + + return 0; +} + +/*! mxc_iomux_gpio_atlas_reset - reset atlas gpio settings + * @param usb_mode + * @return 0 + */ +int mxc_iomux_gpio_atlas_reset (void) +{ + + #if defined(CONFIG_ARCH_SCMA11) + iomux_config_mux(SP_USB_TXOE_B, OUTPUTCONFIG_FUNC2, INPUTCONFIG_FUNC2); + iomux_config_mux(SP_USB_DAT_VP, OUTPUTCONFIG_FUNC2, INPUTCONFIG_FUNC2); + iomux_config_mux(SP_USB_SE0_VM, OUTPUTCONFIG_FUNC2, INPUTCONFIG_FUNC2); + iomux_config_mux(SP_USB_RXD, OUTPUTCONFIG_FUNC2, INPUTCONFIG_FUNC2); + #endif + + #if defined(CONFIG_MACH_ARGONPLUSEVB) || defined(CONFIG_ARCH_ARGONPLUSEVB) + iomux_config_mux(PIN_USB_XRXD, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + iomux_config_mux(PIN_USB_VMOUT, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + iomux_config_mux(PIN_USB_VPOUT, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + iomux_config_mux(PIN_USB_VPIN, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + iomux_config_mux(PIN_USB_TXENB, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + iomux_config_mux(PIN_USB_VMIN, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + #endif /* CONFIG_ARCH_ARGONPLUS */ + + + return 0; +} +#endif //CONFIG_OTG_ZASEVB_ATLAS_CONNECTIVITY + +#if defined(CONFIG_OTG_ZASEVB_ISP1301) +OTG_EXPORT_SYMBOL(mxc_iomux_gpio_isp1301_set); +OTG_EXPORT_SYMBOL(mxc_iomux_gpio_isp1301_reset); +#endif +#ifdef CONFIG_OTG_ZASEVB_ATLAS_CONNECTIVITY +OTG_EXPORT_SYMBOL(mxc_iomux_gpio_atlas_set); +OTG_EXPORT_SYMBOL(mxc_iomux_gpio_atlas_reset); +#endif diff --git a/drivers/otg/hardware/mxc-gptcr.c b/drivers/otg/hardware/mxc-gptcr.c new file mode 100644 index 000000000000..8de271e9b0ae --- /dev/null +++ b/drivers/otg/hardware/mxc-gptcr.c @@ -0,0 +1,286 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/hardware/mxc-gptcr.c -- Freescale GPT timer + * @(#) sl@belcarra.com/whiskey.enposte.net|otg/platform/mxc/mxc-gptcr.c|20070612233038|11611 + * + * Copyright (c) 2005 Belcarra Technologies Corp + * Copyright (c) 2005-2007 Belcarra Technologies 2005 Corp + * + * By: + * Stuart Lynne <sl@lbelcarra.com>, + * Bruce Balden <balden@belcarra.com> + * + */ +/*! + * @file otg/hardware/mxc-gptcr.c + * @brief Freecale GPT Timer implementation. + * + * The GPT2 timer is used for the OTG timer. + * + * The GPT3 timer is used as a free running counter for a source of ticks + * for use with the trace facility. + * + * @ingroup FSOTG + * + */ + + +#include <otg/pcd-include.h> + +#if defined (CONFIG_OTG_GPTR) || defined(_OTG_DOXYGEN) + +#include <linux/pci.h> +#include <asm/arch/gpio.h> +#include <asm/arch/board.h> +#include <asm/system.h> +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/preempt.h> + + +#include "mxc-lnx.h" +#include "mxc-hardware.h" + + + + +/* ********************************************************************************************* */ + + +static otg_tick_t mxc_gptcr_divisor = 0; +static otg_tick_t mxc_gptcr_multiplier = 0; +static int dev_id = 1; +static bool mxc_shared_int = 0; + +/*! + * mxc_gptcr_ticks() - compute ticks from usecs + * @param usecs + * @return ticks + */ +otg_tick_t inline mxc_gptcr_ticks(u32 usecs) +{ + return (((otg_tick_t)usecs) * ((otg_tick_t)mxc_gptcr_divisor)) / ((otg_tick_t)mxc_gptcr_multiplier); +} + +/*! + * mxc_gptcr_usecs() - compute usecs from ticks + * @param usecs + * @return usecs + */ +otg_tick_t inline mxc_gptcr_usecs(u32 usecs) +{ + return (((otg_tick_t)usecs) * ((otg_tick_t)mxc_gptcr_multiplier)) / ((otg_tick_t)mxc_gptcr_divisor); +} + +/*! + * mxc_gptcr_trace_ticks() - get current ticks + * GPT3 is setup as free running clock at 266 Mhz - 1/266 = .0037 + */ +otg_tick_t mxc_gptcr_trace_ticks (void) +{ + return (otg_tick_t) *_reg_GPT_GPTCNT; +} + +/*! + * mxc_gptcr_trace_elapsed() - return micro-seconds between two tick values + * + * GPT3 is setup as free running clock at 266 Mhz - 1/266 = .003759 + * + * MPLL = 266, FCLK = 266, BCLK = 88 + * + */ +otg_tick_t mxc_gptcr_trace_elapsed(otg_tick_t *t1, otg_tick_t *t2) +{ + otg_tick_t ticks = (*t1 > *t2) ? (*t1 - *t2) : (*t2 - *t1); +// ticks = (ticks * mxc_gptcr_multiplier) / mxc_gptcr_divisor; // .3776 + if (TICKS_PER_USEC != 0) + ticks /= TICKS_PER_USEC; + else + ticks = 0; + return ticks; +} + +/* ********************************************************************************************* */ +//u32 mxc_gptcr_timeout; +//u32 mxc_gptcr_ticks_start; + +u32 mxc_gptcr_usec_set; +u32 mxc_gptcr_ticks_set; +u32 mxc_gptcr_match_set; +u32 mxc_gptcr_active; + +/*! + * mxc_gptcr_start_timer() - start a timer for otg state machine + * Set or reset timer to interrupt in number of uS (micro-seconds). + * + * XXX There may be a floor or minimum that can be effectively set. + * XXX We have seen an occasional problem with US(25) for discharge for example. + * + * @param otg + * @param usec + */ +int mxc_gptcr_start_timer(struct otg_instance *otg, int usec) +{ + u32 ticks; + u32 match; + unsigned long flags; + local_irq_save (flags); + + //TRACE_MSG2(OCD, "usec: %d CNT: %08x", usec, *_reg_GPT_GPTCNT); + + if (usec && (usec < 100)) { + usec = 100; + TRACE_MSG2(OCD, "usec: %d CNT: %08x", usec, *_reg_GPT_GPTCNT); + } + + /* + * Disable Channel 3 compare. + */ + *_reg_GPT_GPTCR &= ~(0x7 << 26); + + mxc_gptcr_usec_set = usec; + if (usec) { + + mxc_gptcr_ticks_set = ticks = (u32 ) mxc_gptcr_ticks(usec); + mxc_gptcr_match_set = 0; + mxc_gptcr_active = 0; + + + /* + * Compute and set match register + */ + mxc_gptcr_match_set = match = *_reg_GPT_GPTCNT + ticks; + *_reg_GPT_GPTOCR3 = match; + mxc_gptcr_active = 1; + + TRACE_MSG6(OCD, "cnt: %08x match: %08x GPTCNT: %08x GPTCR: %08x GPTSR: %08x GPTOCR3: %08x\n", + mxc_gptcr_ticks_set, mxc_gptcr_match_set, + *_reg_GPT_GPTCNT, *_reg_GPT_GPTCR, *_reg_GPT_GPTSR, *_reg_GPT_GPTOCR3); + /* + * Enable interrupt + */ + *_reg_GPT_GPTIR |= (0x01 << 2); + + } + + local_irq_restore (flags); + return 0; +} + +/*! + * mxc_gptcr_timer_int_hndlr() - timer interrupt + * @param irq + * @param dev_id + * @param regs + */ +irqreturn_t mxc_gptcr_timer_int_hndlr (int irq, void *dev_id, struct pt_regs *regs) +{ + u32 gptsr = *_reg_GPT_GPTSR; + + + *_reg_GPT_GPTIR &= ~(0x04); + + if (gptsr & (0x01 << 2)){ + TRACE_MSG7(OCD, "cnt: %08x match: %08x gptsr: %08x GPTCNT: %08x GPTCR: %08x GPTSR: %08x GPTOCR3: %08x\n", + mxc_gptcr_ticks_set, mxc_gptcr_match_set, gptsr, + *_reg_GPT_GPTCNT, *_reg_GPT_GPTCR, *_reg_GPT_GPTSR, *_reg_GPT_GPTOCR3); + + *_reg_GPT_GPTCR &= ~(0x7 << 26); + *_reg_GPT_GPTOCR3 = 0; + TRACE_MSG0(OCD, "OCM3"); + TRACE_MSG1(OCD, "active: %x", mxc_gptcr_active); + if (mxc_gptcr_active) { + TRACE_MSG0(OCD, "calling otg_event"); + mxc_gptcr_active = 0; + otg_queue_event(ocd_instance->otg, TMOUT, OCD, "SCMA11 TMOUT"); + } + else { + TRACE_MSG0(OCD, "skipping otg_event"); + } + *_reg_GPT_GPTSR |= 0x4; + return IRQ_HANDLED; + } + + if (!mxc_shared_int) + *_reg_GPT_GPTIR |= 0x4; + + return IRQ_NONE; +} + + +/* ********************************************************************************************* */ +extern void fs_ocd_init(struct otg_instance *otg, u8 flag); + + + +/*! + * mxc_gptcr_ocd_mod_init() - initial tcd setup + * Allocate interrupts and setup hardware. + * @param divisor + * @param multiplier + */ +int mxc_gptcr_mod_init (int divisor, int multiplier) +{ + int timer; + + if (*_reg_GPT_GPTCR & 0x1) + mxc_shared_int = 1; + else + mxc_shared_int = 0; + + if (mxc_shared_int){ + printk (KERN_INFO"Using shared interrupt for timer 3 \n"); + timer = request_irq (INT_GPT, mxc_gptcr_timer_int_hndlr, SA_INTERRUPT | SA_SHIRQ, + UDC_NAME " OTG TIMER", (int *) &dev_id); + } + else{ + printk (KERN_INFO"OTG TIMER is the only ISR for timer 3 \n"); + timer = request_irq (INT_GPT, mxc_gptcr_timer_int_hndlr, SA_INTERRUPT | SA_SHIRQ, + UDC_NAME " OTG TIMER", (int *) &dev_id); + /* disable and reset GPT + */ + *_reg_GPT_GPTCR &= ~0x00000001; + *_reg_GPT_GPTCR |= (1<<15); + while ((*_reg_GPT_GPTCR & (1<<15)) != 0) ; + *_reg_GPT_GPTPR = 0; + *_reg_GPT_GPTCR = (0x1 << 9) | (0x2 << 6); + *_reg_GPT_GPTCR |= 0x1; + } + + TRACE_MSG0(OCD, "1. Interrupts requested"); + + THROW_IF(timer, error); + + mxc_gptcr_divisor = divisor; + mxc_gptcr_multiplier = multiplier; + + return 0; + + CATCH(error) { + return -EINVAL; + } + return 0; +} + +/*! + * mxc_gptcr_mod_exit() - de-initialize + */ +void mxc_gptcr_mod_exit (void) +{ + mxc_gptcr_start_timer(NULL, 0); + free_irq (INT_GPT, (int *) &dev_id); +} + + +#endif //CONFIG_OTG_GPTR diff --git a/drivers/otg/hardware/mxc-hardware.h b/drivers/otg/hardware/mxc-hardware.h new file mode 100644 index 000000000000..dd923371068c --- /dev/null +++ b/drivers/otg/hardware/mxc-hardware.h @@ -0,0 +1,816 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/hardware/mxc-hardware.h + * @(#) sl@belcarra.com/whiskey.enposte.net|otg/platform/mxc/mxc-hardware.h|20061218212926|59377 + * + * Copyright (c) 2004-2005 Belcarra + * Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + * + * By: + * Stuart Lynne <sl@belcarra.com>, + * Bruce Balden <balden@belcarra.com> + * + */ + +/*! + * @defgroup FSOTG Freescale MXC USBOTG Support + * @ingroup Hardware + */ +/*! + * @file otg/hardware/mxc-hardware.h + * @brief Hardware defines for Freescale USBOTG Hardware + * + * This supports the Freescale MXC implementation of the Trans Dimension + * USBOTG. + * + * This is used on: + * + * - i.MX21 + * - SCM-A11 + * - Argon+ + * - Zeus + * + * @ingroup FSOTG + */ + + +/*! + * @name OTG_SYS_CTRL + * C.f. 23.8 USB Control Register + */ + /*! @{ */ + +//#define OTG_SYS_CTRL (OTG_SYS_BASE+0x00) + +#define SYS_CTRL_I2C_WU_INT_STAT (1 << 27) +#define SYS_CTRL_OTG_WU_INT_STAT (1 << 26) +#define SYS_CTRL_HOST_WU_INT_STAT (1 << 25) +#define SYS_CTRL_FNT_WU_INT_STAT (1 << 24) + +#define SYS_CTRL_I2C_WU_INT_EN (1 << 19) +#define SYS_CTRL_OTG_WU_INT_EN (1 << 18) +#define SYS_CTRL_HOST_WU_INT_EN (1 << 17) +#define SYS_CTRL_FNT_WU_INT_EN (1 << 16) + +#define SYS_CTRL_OTG_BYP_VAL (0x3 << 11) +#define SYS_CTRL_HOST1_BYP_VAL (0x3 << 9) + +#define SYS_CTRL_OTG_PWR_MASK (1 << 6) +#define SYS_CTRL_HOST1_PWR_MASK (1 << 6) +#define SYS_CTRL_HOST2_PWR_MASK (1 << 4) +#define SYS_CTRL_USB_BYP (1 << 2) +#define SYS_CTRL_HOST1_TXEN_OE (1 << 1) +/*! @} */ + +/*! + * @name C.f. 23.9 USBOTG Module Registers + */ + /*! @{ */ + +#define OTG_CORE_HWMODE (OTG_CORE_BASE+0x00) // 32bit core hardware mode reg + +#define XCVR_D_D 0x00 +//#define XCVR_SE0_D_OLD 0x01 // XXX should be 0x02 +//#define XCVR_D_SE0_OLD 0x02 // XXX should be 0x01 +#define XCVR_SE0_D_NEW 0x02 // XXX should be 0x02 +#define XCVR_D_SE0_NEW 0x01 // XXX should be 0x01 +#define XCVR_SE0_SE0 0x03 + +#define MODULE_ANASDBEN (1 << 14) +#define MODULE_OTGXCVR (0x3 << 6) +#define MODULE_HOSTXCVR (0x3 << 4) +#define MODULE_CRECFG (0x3) +#define MODULE_CRECFG_HHNP (0x0) +#define MODULE_CRECFG_HOST (0x1) +#define MODULE_CRECFG_FUNC (0x2) +#define MODULE_CRECFG_SHNP (0x3) + +#define OTG_CORE_CINT_STAT (OTG_CORE_BASE+0x04) // 32bit core int status reg + +#define MODULE_FCINTDSPEN (1 << 6) + +#define MODULE_ASHNPINT (1 << 5) +#define MODULE_ASFCINT (1 << 4) +#define MODULE_ASHCINT (1 << 3) +#define MODULE_HNPINT (1 << 2) +#define MODULE_FCINT (1 << 1) +#define MODULE_HCINT (1) + +#define OTG_CORE_CINT_STEN (OTG_CORE_BASE+0x08) // 32bit core int enable reg +#define OTG_CORE_CINT_STEN_CLR (OTG_CORE_BASE+0x3c) // 32bit core int enable clear reg + +#define MODULE_ASHNPINT_EN (1 << 5) +#define MODULE_ASFCINT_EN (1 << 4) +#define MODULE_ASHCINT_EN (1 << 3) +#define MODULE_HNPINT_EN (1 << 2) +#define MODULE_FCINT_EN (1 << 1) +#define MODULE_HCINT_EN (1) + +#define OTG_CORE_CLK_CTRL (OTG_CORE_BASE+0x0C) // 32bit core clock control reg + +#define MODULE_FUNC_CLK (1 << 2) +#define MODULE_HOST_CLK (1 << 1) +#define MODULE_MAIN_CLK (1) + +#define OTG_CORE_RST_CTRL (OTG_CORE_BASE+0x10) // 32bit core reset control reg + +#define MODULE_RSTI2C (1 << 15) +#define MODULE_RSTCTRL (1 << 5) +#define MODULE_RSTFC (1 << 4) +#define MODULE_RSTFSIE (1 << 3) +#define MODULE_RSTRH (1 << 2) +#define MODULE_RSTHSIE (1 << 1) +#define MODULE_RSTHC (1) + +#define OTG_CORE_FRM_INTVL (OTG_CORE_BASE+0x14) // 32bit core frame interval reg + +#define MODULE_RESET_FRAME (1 << 15) + +#define OTG_CORE_FRM_REMAIN (OTG_CORE_BASE+0x18) // 32bit core frame remaining reg + +#define OTG_CORE_HNP_CSTAT (OTG_CORE_BASE+0x1C) // 32bit core HNP current state reg + +#define MODULE_HNPDAT (1 << 30) +#define MODULE_VBUSBSE (1 << 29) +#define MODULE_VBUSABSV (1 << 28) +#define MODULE_VBUSGTAVV (1 << 27) + +#define MODULE_ARMTHNPE (1 << 25) +#define MODULE_BHNPEN (1 << 24) + +#define MODULE_SLAVE (1 << 22) +#define MODULE_MASTER (1 << 21) +#define MODULE_BGEN (1 << 20) +#define MODULE_CMPEN (1 << 19) +#define MODULE_ISBDEV (1 << 18) +#define MODULE_ISADEV (1 << 17) + +#define MODULE_SWVBUSPUL (1 << 15) + +#define MODULE_SWAUTORST (1 << 12) +#define MODULE_SWPUDP (1 << 11) + +#define MODULE_SWPDDM (1 << 9) +#define MODULE_HNPSTATE (0x1f << 8) +#define MODULE_CLRERROR (1 << 3) +#define MODULE_ADROPBUS (1 << 2) +#define MODULE_ABBUSREQ (1 << 1) + +#define HNP_A_IDLE (0x10 << 8) +#define HNP_A_MASTER (0x11 << 8) +#define HNP_A_SLAVE (0x12 << 8) +#define HNP_A_WAIT_VPULSE (0x13 << 8) + +#define HNP_A_WAIT_DPULSE (0x14 << 8) +#define HNP_A_WAIT_CONN_A (0x15 << 8) +#define HNP_A_WAIT_CONN_B (0x16 << 8) +#define HNP_A_WAIT_VRISE (0x17 << 8) +#define HNP_A_SUSPEND (0x18 << 8) +#define HNP_A_WAIT_VFALL (0x19 << 8) +#define HNP_A_VBUS_ERR (0x1a << 8) +#define HNP_CONN_DEBOUNCE (0x1b << 8) +#define HNP_A_WAIT_ABREQ (0x1c << 8) +#define HNP_B_IDLE (0x00 << 8) +#define HNP_B_MASTER (0x01 << 8) +#define HNP_B_SLAVE (0x02 << 8) +#define HNP_B_SRP_INIT_V (0x03 << 8) +#define HNP_B_SRP_INIT_D (0x04 << 8) +#define HNP_B_PRE_WAIT_CONN_AB_SHORT_DB (0x05 << 8) +#define HNP_B_WAIT_CONN_A (0x06 << 8) +#define HNP_B_PRE_SLAVE (0x0a << 8) + + +#define OTG_CORE_HNP_TIMER1 (OTG_CORE_BASE+0x20) // 32bit core HNP timer 1 reg +#define OTG_CORE_HNP_TIMER2 (OTG_CORE_BASE+0x24) // 32bit core HNP timer 2 reg +#define OTG_CORE_HNP_T3PCR (OTG_CORE_BASE+0x28) // 32bit core HNP timer 3 pulse ctrl + +#define HNP_DATAPULSE (1 << 5) +#define HNP_VBUSPULSE (1 << 4) + +#define OTG_CORE_HINT_STAT (OTG_CORE_BASE+0x2C) // 32bit core HNP int status reg + +#define HNP_I2COTGINT (1 << 15) +#define HNP_AWAITBTO (1 << 8) // Hardware HNP Only +#define HNP_AIDLEBDTO (1 << 7) // Hardware HNP Only +#define HNP_SRPSUCFAIL (1 << 6) // Hardware HNP Only +#define HNP_SRPINT (1 << 5) +#define HNP_VBUSERROR (1 << 4) // Hardware HNP Only +#define HNP_ABSEVAILD (1 << 3) +#define HNP_ABUSVALID (1 << 2) +#define HNP_MASSLVCHG (1 << 1) // Hardware HNP Only +#define HNP_IDCHANGE (1) + +#define OTG_CORE_HINT_STEN (OTG_CORE_BASE+0x30) // 32bit core HNP int enable reg + +#define HNP_I2COTGINT_EN (1 << 15) +#define HNP_AWAITBTO_EN (1 << 8) +#define HNP_AIDLEBDTO_EN (1 << 7) +#define HNP_SRPSUCFAIL_EN (1 << 6) +#define HNP_SRPINT_EN (1 << 5) +#define HNP_VBUSERROR_EN (1 << 4) +#define HNP_ABSEVAILD_EN (1 << 3) +#define HNP_ABUSVALID_EN (1 << 2) +#define HNP_MASSLVCHG_EN (1 << 1) +#define HNP_IDCHANGE_EN (1) + + +#define OTG_CORE_CPUEPSEL_STAT (OTG_CORE_BASE+0x34) +#define OTG_CORE_INTERRUPT_STEN (OTG_CORE_BASE+0x3c) + +/*! @} */ + + +/*! + * @name C.f. 23.11.11 Host Registers + */ + /*! @{ */ + + +#define OTG_HOST_CONTROL (OTG_HOST_BASE+0x00) // 32bit host controller config reg + +#define HOST_CONTROL_HCRESET (1 << 31) +#define HOST_CONTROL_RMTWUEN (1 << 4) + +#define HOST_CONTROL_HCUSBSTE_RESET (0 << 2) +#define HOST_CONTROL_HCUSBSTE_RESUME (1 << 2) +#define HOST_CONTROL_HCUSBSTE_OPERATIONAL (2 << 2) +#define HOST_CONTROL_HCUSBSTE_SUSPEND (3 << 2) + +#define HOST_CONTROL_CTLBLKSR_11 (0) +#define HOST_CONTROL_CTLBLKSR_21 (1) +#define HOST_CONTROL_CTLBLKSR_41 (2) +#define HOST_CONTROL_CTLBLKSR_81 (3) + + +#define OTG_HOST_SINT_STAT (OTG_HOST_BASE+0x08) // 32bit host system int status reg + +#define HOST_PSCINT (1 << 6) // Port Status Change +#define HOST_FMOFINT (1 << 5) // Frame Number Overflow +#define HOST_HERRINT (1 << 4) // Host Error +#define HOST_RESDETINT (1 << 3) // Resume Detected +#define HOST_SOFINT (1 << 2) // Start of Frame +#define HOST_DONEINT (1 << 1) // Done Register +#define HOST_SORINT (1) // Schedule Overrun + +#define OTG_HOST_SINT_STEN (OTG_HOST_BASE+0x0C) // 32bit host system int enable reg + +#define HOST_PSCINT_EN (1 << 6) // Port Status Change +#define HOST_FMOFINT_EN (1 << 5) // Frame Number Overflow +#define HOST_HERRINT_EN (1 << 4) // Host Error +#define HOST_RESDETINT_EN (1 << 3) // Resume Detected +#define HOST_SOFINT_EN (1 << 2) // Start of Frame +#define HOST_DONEINT_EN (1 << 1) // Done Register +#define HOST_SORINT_EN (1) // Schedule Overrun + +#define OTG_HOST_XINT_STAT (OTG_HOST_BASE+0x18) // 32bit host X buf int status reg +#define OTG_HOST_YINT_STAT (OTG_HOST_BASE+0x1C) // 32bit host Y buf int status reg + +#define OTG_HOST_XYINT_STEN (OTG_HOST_BASE+0x20) // 32bit host XY buf int enable reg +#define OTG_HOST_XFILL_STAT (OTG_HOST_BASE+0x28) // 32bit host X filled status reg +#define OTG_HOST_YFILL_STAT (OTG_HOST_BASE+0x2C) // 32bit host Y filled status reg + +#define OTG_HOST_ETD_EN (OTG_HOST_BASE+0x40) // 32bit host ETD enables reg +#define OTG_HOST_DIR_ROUTE (OTG_HOST_BASE+0x48) // 32bit host direct routing reg +#define OTG_HOST_IINT (OTG_HOST_BASE+0x4C) // 32bit host immediate interrupt reg + +#define OTG_HOST_EP_DSTAT (OTG_HOST_BASE+0x50) // 32bit host endpoints done status +#define OTG_HOST_ETD_DONE (OTG_HOST_BASE+0x54) // 32bit host ETD done reg + +#define OTG_HOST_FRM_NUM (OTG_HOST_BASE+0x60) // 32bit host frame number reg +#define OTG_HOST_LSP_THRESH (OTG_HOST_BASE+0x64) // 32bit host low speed threshold reg + +#define OTG_HOST_ROOTHUB_DESCA (OTG_HOST_BASE+0x68) // 32bit host root hub descriptor A +#define OTG_HOST_ROOTHUB_DESCB (OTG_HOST_BASE+0x6C) // 32bit host root hub descriptor B +/* @} */ + +/*! + * @name C.f. 23.11.29 + */ +/*! @{ */ +#define OTG_HOST_ROOTHUB_STATUS (OTG_HOST_BASE+0x70) // 32bit host root hub status reg + +#define ROOTHUB_STATUS_CLRMTWUE (1 << 31) // Clear Remote Wakeup Enable +#define ROOTHUB_STATUS_OVRCURCHG (1 << 17) // Over Current Indicator Change +#define ROOTHUB_STATUS_LOCPWRSC (1 << 16) // Local Power Status Change +#define ROOTHUB_STATUS_DEVCONWUE (1 << 15) // Device Connect Wakeup Enable +#define ROOTHUB_STATUS_OVRCURI (1 << 1) // Over Current Indicator +#define ROOTHUB_STATUS_LOCPWRS (1) // Local Power Status +/*! @} */ + + +/*! + * @name C.f. 23.11.30 + */ +/*! @{ */ +#define OTG_HOST_PORT_STATUS_1 (OTG_HOST_BASE+0x74) // 32bit host port 1 status bits +#define OTG_HOST_PORT_STATUS_2 (OTG_HOST_BASE+0x78) // 32bit host port 2 status bits +#define OTG_HOST_PORT_STATUS_3 (OTG_HOST_BASE+0x7c) // 32bit host port 3 status bits + +#define PORT_STATUS_PRTRSTSC (1 << 20) // Port Reset Status Change +#define PORT_STATUS_OVRCURIC (1 << 19) // Port Over Current Indicator Change +#define PORT_STATUS_PRTSTATSC (1 << 18) // Port Suspend Status Change +#define PORT_STATUS_PRTENBLSC (1 << 17) // Port Enable Status Change +#define PORT_STATUS_CONNECTSC (1 << 16) // Connect Status Change + +#define PORT_STATUS_LSDEVCON (1 << 9) // Low Speed Device Attached +#define PORT_STATUS_PRTPWRST (1 << 8) // Port Power Status + +#define PORT_STATUS_PRTRSTST (1 << 4) // Port Reset Status +#define PORT_STATUS_PRTOVRCURI (1 << 3) // Port Over Current Indicator +#define PORT_STATUS_PRTSUSPST (1 << 2) // Port Suspend Status +#define PORT_STATUS_PRTENABST (1 << 1) // Port Enable Status +#define PORT_STATUS_CURCONST (1) // Current Connect Status + + +/* flags are the same for the interrupt, control and bulk transfer descriptors */ +// R1: sec 23.11.10.2,3 pg 23-44,47 +#define ETD_GET_COMPCODE(flags) ((flags >> 12) & 0xf) +// R1: sec 23.11.10 Table 23-23 pg 23-40 +#define ETD_CC_NOERROR 0x0 +#define ETD_CC_CRCERR 0x1 +#define ETD_CC_BITSTUFFERR 0x2 +#define ETD_CC_DATATOGERR 0x3 +#define ETD_CC_STALL 0x4 +#define ETD_CC_DEVNOTRESPONDING 0x5 +#define ETD_CC_PIDFAILURE 0x6 +// Reserved 0x7 +#define ETD_CC_DATAOVERRUN 0x8 +#define ETD_CC_DATAUNDERRUN 0x9 +#define ETD_CC_ACK 0xA +#define ETD_CC_NAK 0xB +#define ETD_CC_BUFFEROVERRUN 0xC +#define ETD_CC_BUFFERUNDERRUN 0xD +#define ETD_CC_SCHEDOVERRUN 0xE +#define ETD_CC_NOTACCESSED 0xF + +// R1: sec 23.11.10.2,3 pg 23-44,47 (contd) +#define ETD_GET_ERRORCNT(flags) (((flags) >> 8) & 0xf) +#define ETD_GET_DATATOGL(flags) (((flags) >> 6) & 0x3) +#define ETD_SET_DATATOGL(v) (((v) & 0x3) << 6) +#define ETD_GET_DELAYINT(flags) (((flags) >> 3) & 0x7) +#define ETD_SET_DELAYINT(v) (((v) & 0x7) << 3) +#define ETD_GET_BUFROUND(flags) (((flags) >> 2) & 0x1) +#define ETD_SET_BUFROUND(v) (((v) & 0x1) << 2) +#define ETD_GET_DIRPID(flags) ((flags) & 0x3) +#define ETD_SET_DIRPID(v) ((v) & 0x3) +#define ETD_FLIP_DIRPID(flags) ((flags) ^ 0x3) /* xor with 0x3 flips 2<->1 (IN<->OUT) */ +#define ETD_DIRPID_SETUP 0x0 // OUT +#define ETD_DIRPID_OUT 0x1 +#define ETD_DIRPID_IN 0x2 +#define ETD_DIRPID_RESERVED 0x3 + +// For non-isoc td.w3.val - +#define ETD_GET_BUFSIZE(w3) (((w3) >> 21) & 0xfff) +#define ETD_SET_BUFSIZE(v) (((v) & 0xfff) << 21) +#define ETD_GET_TOTBYECNT(w3) ((w3) & 0xfffff) +#define ETD_SET_TOTBYECNT(v) ((v) & 0xfffff) + +/* isoc flags shares the COMPCODE and DELAYINT bitfields with the other TDs, + but the remaining bits are different. */ +#define ETD_GET_AUTOISO(flags) (((flags) >> 11) & 0x1) +#define ETD_SET_AUTOISO(v) (((v) & 0x1) << 11) +#define ETD_GET_FRAMECNT(flags) (((flags) >> 8) & 0x1) +#define ETD_SET_FRAMECNT(v) (((v) & 0x1) << 8) +// For isoc td.w3.val - +/* pkt1 and pkt0 use the COMPCODE bitfield, and add a PKTLEN field. */ +#define ETD_GET_PKTLEN(pkt) ((pkt) & 0x3ff) +#define ETD_SET_PKTLEN(v) ((v) & 0x3ff) + + +// ETD endpoint descriptor (etd->epd) bitfield access +// R1: sec 23.11.10.1 pg 23-41 (word0) +#define ETD_GET_SNDNAK(epd) (((epd) >> 30) & 0x1) +#define ETD_SET_SNDNAK(v) (((v) & 0x1) << 30) +#define ETD_GET_TOGCRY(epd) (((epd) >> 28) & 0x1) +#define ETD_SET_TOGCRY(v) (((v) & 0x1) << 28) +#define ETD_GET_HALTED(epd) (((epd) >> 27) & 0x1) +#define ETD_SET_HALTED(v) (((v) & 0x1) << 27) +#define ETD_GET_MAXPKTSIZ(epd) (((epd) >> 16) & 0x3ff) +#define ETD_SET_MAXPKTSIZ(v) (((v) & 0x3ff) << 16) +#define ETD_GET_FORMAT(epd) (((epd) >> 14) & 0x3) +#define ETD_SET_FORMAT(v) (((v) & 0x3) << 14) +#define ETD_FORMAT_CONTROL 0x0 +#define ETD_FORMAT_ISOC 0x1 +#define ETD_FORMAT_BULK 0x2 +#define ETD_FORMAT_INTERRUPT 0x3 +#define ETD_GET_SPEED(epd) (((epd) >> 13) & 0x1) +#define ETD_SET_SPEED(v) (((v) & 0x1) << 13) +#define ETD_SPEED_FULL 0x0 +#define ETD_SPEED_LOW 0x1 +#define ETD_GET_DIRECT(epd) (((epd) >> 11) & 0x3) +#define ETD_SET_DIRECT(v) (((v) & 0x3) << 11) +/* xor with 3 flips 2<->1 */ +#define ETD_FLIP_DIRECT(epd) ((epd) ^ (0x3 << 11)) +#define ETD_DIRECT_TD00 0x0 +#define ETD_DIRECT_OUT 0x1 +#define ETD_DIRECT_IN 0x2 +#define ETD_DIRECT_TD11 0x3 +#define ETD_GET_ENDPNT(epd) (((epd) >> 7) & 0xf) +#define ETD_SET_ENDPNT(v) (((v) & 0xf) << 7) +#define ETD_GET_ADDRESS(epd) ((epd) & 0x7f) +#define ETD_SET_ADDRESS(v) ((v) & 0x7f) + +#if 0 +// etd_urb_state[] values +#define ETD_URB_COMPLETED 0 +#define ETD_URB_SETUP_STATUS 1 +#define ETD_URB_SETUP_DATA 2 +#define ETD_URB_SETUP_START 3 +#define ETD_URB_BULK_START 4 +#define ETD_URB_BULKWZLP 5 +#define ETD_URB_BULKWZLP_START 6 +#define ETD_URB_INTERRUPT_START 7 +#define ETD_URB_ISOC_START 8 +#endif +/*! @} */ + + +/*! + * @name C.f. 23.12.8 Function Registers + */ +/*! @{ */ + +#define OTG_FUNC_CMD_STAT (OTG_FUNC_BASE+0x00) // 32bit func command status reg + +#define COMMAND_SOFTRESET (1 << 7) +#define COMMAND_BADISOAP (1 << 3) +#define COMMAND_SUPDET (1 << 2) +#define COMMAND_RSMINPROG (1 << 1) +#define COMMAND_RESETDET (1) + + +#define OTG_FUNC_DEV_ADDR (OTG_FUNC_BASE+0x04) // 32bit func device address reg + +#define OTG_FUNC_SINT_STAT (OTG_FUNC_BASE+0x08) // 32bit func system int status reg + +#define SYSTEM_DONEREGINTDS (1 << 5) +#define SYSTEM_SOFDETINT (1 << 4) +#define SYSTEM_DONEREGINT (1 << 3) +#define SYSTEM_SUSPDETINT (1 << 2) +#define SYSTEM_RSMFININT (1 << 1) +#define SYSTEM_RESETINT (1) + +#define OTG_FUNC_SINT_STEN (OTG_FUNC_BASE+0x0C) // 32bit func system int enable reg + +#define OTG_FUNC_SINT_STEN_CLR (OTG_FUNC_BASE+0x10C) // 32bit func system int enable clear reg + +#define SYSTEM_DONEREGINTDS_EN (1 << 5) +#define SYSTEM_SOFDETINT_EN (1 << 4) +#define SYSTEM_DONEREGINT_EN (1 << 3) +#define SYSTEM_SUSPDETINT_EN (1 << 2) +#define SYSTEM_RSMFININT_EN (1 << 1) +#define SYSTEM_RESETINT_EN (1) + + +#define OTG_FUNC_XINT_STAT (OTG_FUNC_BASE+0x10) // 32bit func X buf int status reg +#define OTG_FUNC_YINT_STAT (OTG_FUNC_BASE+0x14) // 32bit func Y buf int status reg + +#define OTG_FUNC_XYINT_STEN (OTG_FUNC_BASE+0x18) // 32bit func XY buf int enable reg +#define OTG_FUNC_XYINT_STEN_CLR (OTG_FUNC_BASE+0x118) // 32bit func XY buf int enable clear reg + +#define OTG_FUNC_XFILL_STAT (OTG_FUNC_BASE+0x1C) // 32bit func X filled status reg +#define OTG_FUNC_YFILL_STAT (OTG_FUNC_BASE+0x20) // 32bit func Y filled status reg + +#define OTG_FUNC_EP_EN (OTG_FUNC_BASE+0x24) // 32bit func endpoints enable reg +#define OTG_FUNC_EP_EN_CLR (OTG_FUNC_BASE+0x124) // 32bit func endpoints enable clear reg + +#define OTG_FUNC_EP_RDY (OTG_FUNC_BASE+0x28) // 32bit func endpoints ready reg +#define OTG_FUNC_EP_RDY_CLR (OTG_FUNC_BASE+0x3C) // 32bit func endpoints ready clear reg + +#define OTG_FUNC_IINT (OTG_FUNC_BASE+0x2C) // 32bit func immediate interrupt reg +#define OTG_FUNC_IINT_CLR (OTG_FUNC_BASE+0x12C) // 32bit func immediate interrupt clear reg + +#define OTG_FUNC_EP_DSTAT (OTG_FUNC_BASE+0x30) // 32bit func endpoints done status + +#define OTG_FUNC_EP_DEN (OTG_FUNC_BASE+0x34) // 32bit func endpoints done enable +#define OTG_FUNC_EP_DEN_CLR (OTG_FUNC_BASE+0x134) // 32bit func endpoints done clear enable + +#define OTG_FUNC_EP_TOGGLE (OTG_FUNC_BASE+0x38) // 32bit func endpoints toggle bits +#define OTG_FUNC_FRM_NUM (OTG_FUNC_BASE+0x3C) // 32bit func frame number reg + + + + + + +#define EP0_STALL (1 << 31) +#define EP0_SETUP (1 << 30) +#define EP0_OVERRUN (1 << 29) +#define EP0_AUTOISO (1 << 27) + +#define EP_FORMAT_CONTROL 0x0 +#define EP_FORMAT_ISOC 0x1 +#define EP_FORMAT_BULK 0x2 +#define EP_FORMAT_INTERRUPT 0x3 + +/* generic endpoint register manipulations + */ +#define EP_OUT 0x1 +#define EP_IN 0x2 +#define EP_BOTH 0x3 + +#define ETD_MASK(n) (1 << n) +#define ep_num_both(n) (EP_BOTH << n) +#define ep_num_dir(n, dir) ((dir ? EP_IN : EP_OUT) << (n*2)) +#define ep_num_out(n) ep_num_dir(n, USB_DIR_OUT) +#define ep_num_in(n) ep_num_dir(n, USB_DIR_IN) + + +/*! @} */ + +/*! + * @name C.f. 23.13.3 DMA Registers + */ +/*! @{ */ +#define OTG_DMA_REV_NUM (OTG_DMA_BASE+0x000) // 32bit dma revision number reg +#define OTG_DMA_DINT_STAT (OTG_DMA_BASE+0x004) // 32bit dma int status reg +#define OTG_DMA_DINT_STEN (OTG_DMA_BASE+0x008) // 32bit dma int enable reg +#define OTG_DMA_ETD_ERR (OTG_DMA_BASE+0x00C) // 32bit dma ETD error status reg +#define OTG_DMA_EP_ERR (OTG_DMA_BASE+0x010) // 32bit dma EP error status reg +#define OTG_DMA_ETD_EN (OTG_DMA_BASE+0x020) // 32bit dma ETD DMA enable reg +#define OTG_DMA_EP_EN (OTG_DMA_BASE+0x024) // 32bit dma EP DMA enable reg +#define OTG_DMA_ETD_ENXREQ (OTG_DMA_BASE+0x028) // 32bit dma ETD DMA enable Xtrig req +#define OTG_DMA_EP_ENXREQ (OTG_DMA_BASE+0x02C) // 32bit dma EP DMA enable Ytrig req +#define OTG_DMA_ETD_ENXYREQ (OTG_DMA_BASE+0x030) // 32bit dma ETD DMA enble XYtrig req +#define OTG_DMA_EP_ENXYREQ (OTG_DMA_BASE+0x034) // 32bit dma EP DMA enable XYtrig req +#define OTG_DMA_ETD_BURST4 (OTG_DMA_BASE+0x038) // 32bit dma ETD DMA enble burst4 reg +#define OTG_DMA_EP_BURST4 (OTG_DMA_BASE+0x03C) // 32bit dma EP DMA enable burst4 reg + +#define OTG_DMA_MISC_CTRL (OTG_DMA_BASE+0x040) // 32bit dma EP misc control reg +#define OTG_DMA_MISC_ARBMODE (1 << 1) + + +#define OTG_DMA_ETD_CH_CLR (OTG_DMA_BASE+0x048) // 32bit dma ETD clear channel reg +#define OTG_DMA_EP_CH_CLR (OTG_DMA_BASE+0x04c) // 32bit dma EP clear channel reg + +#if 0 +#define OTG_DMA_ETD_CH_CLR (OTG_DMA_BASE+0x044) // 32bit dma ETD clear channel reg +#define OTG_DMA_ETD_ERR (OTG_DMA_BASE+0x00C) // 32bit dma ETD error status reg +#define OTG_DMA_ETD_EN (OTG_DMA_BASE+0x020) // 32bit dma ETD DMA enable reg +#define OTG_DMA_ETD_ENXREQ (OTG_DMA_BASE+0x028) // 32bit dma ETD DMA enable Xtrig req +#define OTG_DMA_ETD_ENXYREQ (OTG_DMA_BASE+0x030) // 32bit dma ETD DMA enble XYtrig req +#define OTG_DMA_ETD_BURST4 (OTG_DMA_BASE+0x038) // 32bit dma ETD DMA enble burst4 reg + + + +#define OTG_DMA_EP_CH_CLR (OTG_DMA_BASE+0x048) // 32bit dma EP clear channel reg +#define OTG_DMA_EP_ERR (OTG_DMA_BASE+0x010) // 32bit dma EP error status reg +#define OTG_DMA_EP_EN (OTG_DMA_BASE+0x024) // 32bit dma EP DMA enable reg +#define OTG_DMA_EP_ENXREQ (OTG_DMA_BASE+0x02C) // 32bit dma EP DMA enable Ytrig req +#define OTG_DMA_EP_ENXYREQ (OTG_DMA_BASE+0x034) // 32bit dma EP DMA enable XYtrig req +#define OTG_DMA_ETD_BURST4 (OTG_DMA_BASE+0x038) // 32bit dma ETD DMA enable burst4 reg +#define OTG_DMA_EP_BURST4 (OTG_DMA_BASE+0x03C) // 32bit dma EP DMA enable burst4 reg + +#endif + + +#define dma_num_dir(n, dir) (n * 2 + (dir ? 1 : 0)) +#define dma_num_out(n) dma_num_dir(n, USB_DIR_OUT) +#define dma_num_in(n) dma_num_dir(n, USB_DIR_IN) + +#define OTG_DMA_ETD_MSA(x) (OTG_DMA_BASE+0x100+x*4) +#define OTG_DMA_EPN_MSA(x) (OTG_DMA_BASE+0x180+x*4) +#define OTG_DMA_ETDN_BPTR(x) (OTG_DMA_BASE+0x280+x*4) +#define OTG_DMA_EPN_BPTR(x) (OTG_DMA_BASE+0x284+x*4) + +/*! @} */ + +/* ********************************************************************************************** */ +/*! + * Warning: the MX21 memory mapped region appears to require 32-bit access only. + * As of this writing, only the ETD region has been tested, but it does show the + * following behavior: + * + * Step 1: *(volatile u32 *)(void*)0xe40243f8 == 00000000 + * Step 2: *(volatile u16 *)(void*)0xe40243f8 = 0x0084; + * Step 3: *(volatile u32 *)(void*)0xe40243f8 == 00840084 + * Step 4: *(volatile u8 *)(void*)(0xe40243f8+3) = 0x01; + * Step 5: *(volatile u32 *)(void*)0xe40243f8 == 01010101 + * + * Any access to less than 32 bits is replicated as many times as required + * to make 32 bits, and the surrounding 32-bit region is changed. + * + * Just to add insult to injury, gcc 3.2.3 wants to store constants + * that are small enough using byte instructions. E.g. + * + * mm->host.System_Interrupt_Enables = (SIEN_PSCIEN | SIEN_FMOFIEN | SIEN_HERRIEN | SIEN_RESDETIEN | + * SIEN_DONEIEN | SIEN_SORIEN); + * + * becomes: + * + * ldrb r2, [r3, #140] + * mov r2, #123 + * strb r2, [r3, #140] + * ldrb r2, [r3, #141] + * strb r1, [r3, #141] + * ldrb r2, [r3, #142] + * strb r1, [r3, #142] + * ldrb r2, [r3, #143] + * strb r1, [r3, #143] + * + * which, given the 32-bit access restrictions above, + * is not going to work very well. Changing the original assignment to: + * + * *&(mm->host.System_Interrupt_Enables) = (SIEN_PSCIEN | SIEN_FMOFIEN | SIEN_HERRIEN | SIEN_RESDETIEN | + * SIEN_DONEIEN | SIEN_SORIEN); + * + * which should be identical, from a language definition point of view, + * produces: + * + * mov r2, #123 + * str r2, [r3, #140] + * + */ + + +/*! + * @name TransferDescriptors + * + * Note that the MX21 needs to run in little-endian mode for OTG, but + * since the memory can only be accessed in 32-bit quantities, anything + * smaller will be built in regular memory, then transfered one word at + * a time to the memory-mapped registers, and that word access will flip + * the order of bytes. u16 quantities survive this flipping because they + * get "pre-flipped" in regular memory. + * + * + * An Endpoint Transfer Descriptor (ETD) consists of 4x32bit words. + * The first word is the Endpoint Descriptor, the last three are + * the transfer descriptor. Transfer descriptors come in 3 formats, + * CONTROL/BULK, INTERRUPT, and ISOCRONOUS. Becasue of the memory + * access restrictions to less than 32 bits described above, only + * the words are mapped directly, The smaller fields are assembled + * into 32-bit words before being placed into the live ETD. + * + */ + /*! @{ */ + +/*! + * create a type for transfer_descriptor_w1 + * @brief typedef struct transfer_descriptor_w1 transfer_descriptor_w1 + *All three types of TD have the same format for w1. + */ +typedef struct transfer_descriptor_w1 { +#if defined(LITTLE_ENDIAN) + u16 x; + u16 y; +#else + u16 y; + u16 x; +#endif +} __attribute__ ((packed)) volatile transfer_descriptor_w1; + +/*! + * create a type for control_bulk_transfer_descriptor_w2 + * @brief typedef struct control_bulk_transfer_descriptor_w2 control_bulk_transfer_descriptor_w2 + * There are 3 different formats for w2. + * C.f. R1: sec 23.11.10.2 pg 23-43 + */ +typedef struct control_bulk_transfer_descriptor_w2 { +#if defined(LITTLE_ENDIAN) + u8 rtrydelay; + u8 reserved; + u16 flags; +#else + u16 flags; + u8 reserved; + u8 rtrydelay; +#endif +} __attribute__ ((packed)) volatile control_bulk_transfer_descriptor_w2; + +/*! + *create an alias for interrupt_transfer_descriptor_w2 + * @brief typedef struct interrupt_transfer_descriptor_w2 interrupt_transfer_descriptor_w2 + * C.f. R1: sec 23.11.10.3 pg 23-46 + * C.f. R1: sec 23.11.10.3 pg 23-46 + */ +typedef struct interrupt_transfer_descriptor_w2 { +#if defined(LITTLE_ENDIAN) + u8 polinterv; + u8 relpolpos; + u16 flags; +#else + u16 flags; + u8 relpolpos; + u8 polinterv; +#endif +} __attribute__ ((packed)) volatile interrupt_transfer_descriptor_w2; + +/*! + * create a type for isoc_transfer_descriptor_w2 + * @brief typedef struct isoc_transfer_descriptor_w2 isoc_transfer_descriptor * C.f. R1: sec 23.11.10.4 pg 23-48 + */ +typedef struct isoc_transfer_descriptor_w2 { +#if defined(LITTLE_ENDIAN) + u16 startfrm; + u16 flags; +#else + u16 flags; + u16 startfrm; +#endif +} __attribute__ ((packed)) volatile isoc_transfer_descriptor_w2; + + + +/*! + * create a type for isoc_transfer_descriptor_w3 + * @brief typedef struct isoc_transfer_descriptor_w3 isoc_transfer_descriptor + * + * There are 2 different formats for w3. + * All but isoc use a packet size and 20-bit bytecount, accessed through w3.val. + */ +typedef struct isoc_transfer_descriptor_w3 { +#if defined(LITTLE_ENDIAN) + u16 pkt0; + u16 pkt1; +#else + u16 pkt1; + u16 pkt0; +#endif +} __attribute__ ((packed)) volatile isoc_transfer_descriptor_w3; + +/*! + * @name transfer_descriptor + */ +/*! @{ */ +/*! create an alias for transfer_descriptor + * @brief typedef struct transfer_descriptor transfer_descriptor + */ +typedef struct transfer_descriptor { + union { + u32 val; + transfer_descriptor_w1 bufsrtad; + } volatile w1; + union { + u32 val; + control_bulk_transfer_descriptor_w2 cb; + interrupt_transfer_descriptor_w2 intr; + isoc_transfer_descriptor_w2 isoc; + } volatile w2; + union { + u32 val; + isoc_transfer_descriptor_w3 isoc; + } volatile w3; +} __attribute__ ((packed)) volatile transfer_descriptor; + +/*! @} */ + +/*! + * @name endpoint_transfer_descriptor + * C.f. R1: sec 23.11.10 pg 23-41 + */ + /*! @{ */ +/*! create a type for endpoint_transfer_descriptor + * @brief typedef struct endpoint_transfer_descriptor endpoint_transfer_descriptor + */ +typedef struct endpoint_transfer_descriptor { + u32 epd; + transfer_descriptor td; +} __attribute__ ((packed)) volatile endpoint_transfer_descriptor; // ETD + +/*! @} */ +/*! + * fs_host_port_stat() - get host start port address for ports 1-3 + * @param n + * @returns port address + */ +static u32 inline fs_host_port_stat(int n) +{ + switch (n) { + default: + case 1: + return OTG_HOST_PORT_STATUS_1; + case 2: + return OTG_HOST_PORT_STATUS_2; + case 3: + return OTG_HOST_PORT_STATUS_3; + } +} + +/*! @name clock switch functions */ +/* ********************************************************************************************** */ + +void fs_func_clock_on(void); +void fs_host_clock_on(void); +void fs_func_clock_off(void); +void fs_host_clock_off(void); +void fs_main_clock_on(void); +void fs_main_clock_off(void); +void fs_set_transceiver_mode(int mode); + + + + + +/*! @} */ diff --git a/drivers/otg/hardware/mxc-hcd.c b/drivers/otg/hardware/mxc-hcd.c new file mode 100644 index 000000000000..9f7647de5d5f --- /dev/null +++ b/drivers/otg/hardware/mxc-hcd.c @@ -0,0 +1,1481 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/hardware/mxc-hcd.c - Freescale USBOTG aware Host Controller Driver (HCD) + * @(#) tt/root@belcarra.com/debian286.bbb|otg/platform/mxc/mxc-hcd.c|20070918011422|08412 + * + * Copyright (c) 2004-2005 Belcarra Technologies Corp + * Copyright (c) 2005-2007 Belcarra Technologies 2005 Corp + * + * By: + * Stuart Lynne <sl@belcarra.com>, + * Bruce Balden <balden@belcarra.com> + * Tony Tang <tt@belcarra.com> + */ +/*! + * @file otg/hardware/mxc-hcd.c + * @brief Freescale USB Host Controller Driver + * + * This is a complete and self contained Linux 2.6 USB Host Driver. + * + * It also conforms to the requirements for use as an OTG HCD driver + * in the Belcarra OTG Stack. + * + * The hardware is an integrated design, so it also works in conjunction + * with the mxc OCD and PCD drivers to share the hardware under the direction + * of the OTG State Machine. + * + * The root hub is managed locally, specifically we clear all relevant status + * changes etc. The MXC USB interrupt is level triggered so this is required + * to stop interrupts. + * + * The actual OTG port status changes are propagated to the upper layers + * through a virtual root hub which will allow the root hub support in the + * upper layers to operate properly. + * + * @ingroup FSOTG + * @ingroup HCD + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> + +#include <linux/usb.h> +#include <linux/delay.h> + +#include <otg/otg-compat.h> + +#include <core/hcd.h> + +#include <otg/usbp-hub.h> +#include <otg/otg-trace.h> +#include <otg/otg-api.h> +#include <otg/otg-utils.h> +#include <otg/otg-tcd.h> + +#include <otg/otg-hcd.h> +#include <asm/arch/gpio.h> +#include "mxc-lnx.h" +#include "mxc-hardware.h" +#include <asm/memory.h> + +#include "mxc-hcd.h" + +/* ********************************************************************************************* */ +/* ********************************************************************************************* */ +void mxc_hcd_giveback_req_irq(struct mxc_hcd *mxc_hcd, struct mxc_req *mxc_req, int status); + +/* ********************************************************************************************* */ +/* ********************************************************************************************* */ + +// For debugging... +static char *dirpid_name[4] = { "SETUP", "OUT", "IN", "WRONG-PID"}; +// +char *etd_urb_state_name[] = { + "COMPLETED", "SETUP_STATUS", "SETUP_DATA", "SETUP_PKT", + "BULK_START", "BULK_WZLP", "BULK_WZLP_START", "INTERRUPT_START", + "ISOC", "WRONG-STATE", +}; + +static char *format_name[4] = { "CONTROL", "ISO", "BULK", "INT", }; + +/*! + * @struct cc_name + * @brief Map CC values to names + */ +static char *cc_name[] = { + "no error", "CRC error", "bitstuff error", "data toggle error", + "stall", "device not responding", "PID failure" "reserved", + "data overrun", "data underrun", "ACK", "NAK", "buffer overrun", + "buffer underrun", "schedule overrun" "not accessed" +}; + + +/*! + * comp_code_to_status() - map condition cod3 to errno value + * @param cc condition code + * @return errno value + */ +static int comp_code_to_status(int cc) +{ + if (cc) TRACE_MSG2(HCD,"NON-ZERO CC: %d %s", cc,cc_name[cc]); + switch (cc) { + case ETD_CC_NOERROR: return 0; + case ETD_CC_CRCERR: return -EILSEQ; + case ETD_CC_BITSTUFFERR: return -EPROTO; + case ETD_CC_DATATOGERR: return -EPROTO; // I guess, nothing else looks better. + case ETD_CC_STALL: return -EPIPE; + case ETD_CC_DEVNOTRESPONDING: return -ETIMEDOUT; + case ETD_CC_PIDFAILURE: return -EPROTO; + case ETD_CC_DATAOVERRUN: return -EOVERFLOW; + case ETD_CC_DATAUNDERRUN: return -EREMOTEIO; + case ETD_CC_ACK: return -EPROTO; // For lack of anything better + case ETD_CC_NAK: return -EPROTO; // For lack of anything better + case ETD_CC_BUFFEROVERRUN: return -ECOMM; + case ETD_CC_BUFFERUNDERRUN: return -ENOSR; + case ETD_CC_SCHEDOVERRUN: return -ENOSPC; + case ETD_CC_NOTACCESSED: return -EPROTO; // For lack of anything better + default: return -EINVAL; + } +} +/* ********************************************************************************************* */ +/*! ETD Management + */ + +/*! + * rel_etd_irq() - release etd + * @param mxc_hcd + * @param etdn + * {get,rel}_etd maintain a free list of ETDs by saving the index of the next free ETD in the + * etd->xbufsrtad field. The list is terminated by 0x0000ffff. List manipulation needs to be + * protected by irq_lock, since ETDs are released in the interrupt handler, but allocated at + * regular priority. + */ +void rel_etd_irq(struct mxc_hcd *mxc_hcd, int etdn) +{ + TRACE_MSG1(HCD,"*****************etdn:%d",etdn); + fs_wl(etd_word(etdn, 0), 0); + fs_wl(etd_word(etdn, 1), 0); + fs_wl(etd_word(etdn, 2), 0); + fs_wl(etd_word(etdn, 3), 0); + mxc_hcd->active[etdn] = NULL; + //mxc_hcd->active_count--; +} + +/*! + * get_etd_irq() - get etd + * @param mxc_hcd + * @param mxc_req + * @return int + * {get,rel}_etd maintain a free list of ETDs by saving the index of the next free ETD in the + * etd->xbufsrtad field. The list is terminated by 0x0000ffff. List manipulation needs to be + * protected by irq_lock, since ETDs are released in the interrupt handler, but allocated at + * regular priority. + */ +static int get_etd_irq(struct mxc_hcd *mxc_hcd, struct mxc_req *mxc_req) +{ + int etdn; + for (etdn = 0; etdn < NUM_ETDS; etdn++) + UNLESS (mxc_hcd->active[etdn]) { + mxc_hcd->active[etdn] = mxc_req; + //mxc_hcd->active_count++; + return etdn; + } + TRACE_MSG0(HCD,"error"); + return -ENOMEM; +} + +/* ********************************************************************************************* */ +/* ********************************************************************************************* */ +/*! Data Buffer Management + */ + +/*! + * @name DataBuffs + * + * There is no predetermined size for data buffers, so + * this structure was chosen as an arbitrary, but generally + * adequate size. + */ +/*! @{ */ + +/*! + * get_data_buff() - get data buff + * Get the next available free data_buff, return NULL if none available. + */ +static __inline__ fs_data_buff *get_data_buff(struct mxc_hcd *mxc_hcd) +{ + unsigned long flags; + fs_data_buff *db = NULL; + u16 ndx; + local_irq_save(flags); + if (0xffff != (ndx = mxc_hcd->free_buffs)) { + mxc_hcd->free_buffs = mxc_hcd->buff_list[ndx]; + db = ndx + (fs_data_buff *)OTG_DATA_BASE; + } + local_irq_restore(flags); + return db; +} + +/*! + * rel_data_buff() - release data buff + * Release db to the pool of available data_buffs. + */ +fs_data_buff *rel_data_buff(struct mxc_hcd *mxc_hcd, fs_data_buff *db) +{ + unsigned long flags; + u16 ndx; + RETURN_NULL_UNLESS (db); + local_irq_save(flags); + ndx = db - (fs_data_buff *)OTG_DATA_BASE; + mxc_hcd->buff_list[ndx] = mxc_hcd->free_buffs; + mxc_hcd->free_buffs = ndx; + local_irq_restore(flags); + return NULL; +} + +/*! + * data_buff_boff() - get data buffer offset given address + */ +static __inline__ u16 data_buff_boff(fs_data_buff *db) +{ + return((u16)(((void *) db) - ((void *) OTG_DATA_BASE))); +} + +/*! + * data_buff_addr() - get data buffer address given offset + * Return the address of the fs_data_buffer that is boff bytes from the start of data buffer memory. + */ +static __inline__ fs_data_buff *data_buff_addr(u16 boff) +{ + return(boff + ((void *) OTG_DATA_BASE)); +} +/*! @} */ + +/* ********************************************************************************************* */ +/* ********************************************************************************************* */ + +/*! Data Tranfer Handling + */ + +u8 dmabuf[128]; + +/*! + * mxc_hcd_start_etd_irq() - start an etd + * @param etdn + * @param len + * @param dma + * @param out + * + * This will start a request by enabling the specified etd. + * + * XXX I don't know why this doesn't work correctly unless DMA is enabled. + * But enabling DMA with NULL data address is not a greate idea... so + * we check and give a dummy buffer for it to use. + * + */ +static void mxc_hcd_start_etd_irq(int etdn, int len, u32 dma, int out) +{ + TRACE_MSG4(HCD, "etdn: %d len: %d dma: %x out: %d", etdn, len, dma, out); + + fs_wl(OTG_DMA_ETD_MSA(etdn), dma ? dma : (int)virt_to_phys(dmabuf)); + + /* check for DMA alignment - downgrade if neccessary */ + if (dma & 0xf) { + fs_andl(OTG_DMA_ETD_BURST4, ~ETD_MASK(etdn)); + TRACE_MSG2(HCD, "RESET ETD BURST4[%2d]: %08x", ETD_MASK(etdn), fs_rl(OTG_DMA_ETD_BURST4)); + } + else { + fs_orl(OTG_DMA_ETD_BURST4, ETD_MASK(etdn)); + TRACE_MSG2(HCD, "SET ETD BURST4[%2d]: %08x", ETD_MASK(etdn), fs_rl(OTG_DMA_ETD_BURST4)); + } + + + if (len || out) fs_wl(OTG_DMA_ETD_EN, ETD_MASK(etdn)); + + fs_orl(OTG_HOST_IINT, ETD_MASK(etdn)); // Don't wait until SOF, interrupt ASAP. + fs_orl(OTG_HOST_ETD_DONE, ETD_MASK(etdn)); // Interrupt on ETD done (forget DMA interrupt). + fs_wl(OTG_HOST_ETD_EN, ETD_MASK(etdn)); +} + + +/* ********************************************************************************************* */ +/* ********************************************************************************************* */ + + +/*! mxc_hcd_start_req_irq - start a request + * @param mxc_hcd + * @param mxc_req + * + * @return non-zero if no resources available + * + * This function will attempt to find the resources to perform + * a request and will start it if the required resources are + * available. + * + * A non-zero return value signals a failure to find resources. + * + * Requests that are not possible to start for other reasons are + * not signalled back to the caller, the urb is simply given + * back to the hcd driver. + * + * XXX it is possible that too many receive urbs could tie up all + * existing etds. It would be possible to dequeue a receive urb + * temporarily and then restart. + */ +static int mxc_hcd_start_req_irq(struct mxc_hcd *mxc_hcd, struct mxc_req *mxc_req) +{ + struct urb *urb = mxc_req->urb; + struct usb_device *udev = urb->dev; + /*struct hcd_dev *hdev = (struct hcd_dev *) udev->hcpriv; */ + + int address = usb_pipedevice(urb->pipe) ? + (usb_pipedevice(urb->pipe) % MXC_MAX_USB_ADDRESS + 1) : 0; + int is_out = usb_pipein(urb->pipe) ? 0 : 1; + int endpoint = usb_pipeendpoint(urb->pipe); + u32 dma = mxc_req->transfer_dma; + int len = urb->transfer_buffer_length; + + u32 other; + + int pid; + int toggle; + + transfer_descriptor td; + int fmt; + int dir; + + endpoint_transfer_descriptor *sdp = NULL; + + TRACE_MSG2(HCD, "mxc_req: %x urb: %x", mxc_req, urb); +#if 0 + if (is_out) { + int i; + u8 *cp = urb->transfer_buffer; + TRACE_MSG1(HCD, "NEXT TX: length: %d", urb->actual_length); + for (i = 0; i < urb->actual_length; i+= 8) + TRACE_MSG8(HCD, "SENT %02x %02x %02x %02x %02x %02x %02x %02x", + cp[i + 0], cp[i + 1], cp[i + 2], cp[i + 3], + cp[i + 4], cp[i + 5], cp[i + 6], cp[i + 7]); + + } + if (usb_pipetype(urb->pipe) == PIPE_CONTROL) { + u8 *cp = (u8 *) urb->setup_packet; + TRACE_MSG8(HCD, "SETUP %02x %02x %02x %02x %02x %02x %02x %02x", + cp[0], cp[1], cp[2], cp[3], cp[4], cp[5], cp[6], cp[7]); + } +#endif + + /* verify that urb status is ok */ + + if (urb->status != -EINPROGRESS) { + TRACE_MSG1(HCD, "bad status for urb: %08x status: %d, urb rejected.", urb->status); + list_del(&mxc_req->queue); + mxc_hcd_giveback_req_irq(mxc_hcd, mxc_req, -EPIPE); + return 0; + } + + /* verify that this device endpoint is inactive */ + + if (mxc_hcd->ep[address][EPNUM(endpoint, is_out)]) { + TRACE_MSG3(HCD, "endpoint active for urb: %08x status: %d endpoint: %d out: %d.", urb->status, endpoint, is_out); + return 0; + } + + /* verify we have hardware resources */ + + RETURN_ENOMEM_IF(mxc_hcd->active_count > NUM_ETDS); + + /* 1. Allocate resources: + * a. X,Y buffers + * b. ETD (& matching DMA) + * 2. Set the registers + */ + mxc_req->etdn = get_etd_irq(mxc_hcd, mxc_req); + mxc_req->x = get_data_buff(mxc_hcd); + mxc_req->y = get_data_buff(mxc_hcd); + mxc_req->epnum = EPNUM(endpoint, is_out); + + UNLESS (mxc_req->x && mxc_req->y && (mxc_req->etdn != -1)) { + printk(KERN_INFO"%s: NO RESOURCES\n", __FUNCTION__); + TRACE_MSG0(HCD, "NO RESOURCES"); + if (mxc_req->x) mxc_req->x = rel_data_buff(mxc_hcd, mxc_req->x); + if (mxc_req->y) mxc_req->y = rel_data_buff(mxc_hcd, mxc_req->y); + mxc_req->x = mxc_req->y = NULL; + mxc_req->etdn = -1; + return -ENOMEM; // no sense in trying anymore... + } + + mxc_hcd->active_count++; + + /* set hcd_dev->ep[] so we can find specific request from hcd_dev, indexed by EPNUM(endpoint,is_out) + * set mxc_hcd->active[] so we can find requests from hardware POV, indexed by etdn + */ + mxc_hcd->ep[address][mxc_req->epnum] = mxc_req; + memset((void *)&td, 0, sizeof(td)); + td.w1.bufsrtad.y = data_buff_boff(mxc_req->y); + td.w1.bufsrtad.x = data_buff_boff(mxc_req->x); + + /* we have request, we have urb, we have resources */ + + TRACE_MSG5(HCD,"urb: %08lx ep: %d %s len: %d etdn: %d", + (u32)(void*)urb, endpoint, (is_out?"OUT":"IN"), urb->actual_length, mxc_req->etdn); + +#if 0 + if (usb_endpoint_halted(urb->dev, endpoint, is_out)) { + TRACE_MSG4(HCD,"urb for halted endpoint urb: %08lx dev: %d ep: %d dir: %c", + urb, dev_addr, endpoint, (is_out?'O':'I')); + urb->actual_length = 0; + mxc_hcd_giveback_urb(urb,-EPIPE); + return -EINVAL; + } +#endif + + /* Check pid, setup pid and out flags */ + switch ( (usb_pipetype(urb->pipe) == PIPE_CONTROL) ? USB_PID_SETUP : ((is_out ? USB_PID_OUT : USB_PID_IN))) { + default: + case USB_PID_SETUP: + pid = ETD_DIRPID_SETUP; + is_out = 1; + break; + case USB_PID_OUT: + pid = ETD_DIRPID_OUT; + break; + case USB_PID_IN: + pid = ETD_DIRPID_IN; + break; + } + + TRACE_MSG7(HCD, "len: %d endpoint: %02x pid: %d format: %d dev_addr: %d %s %s", + len, endpoint, pid, usb_pipetype(urb->pipe), usb_pipedevice(urb->pipe), + dirpid_name[pid], format_name[usb_pipetype(urb->pipe)]); + + switch (usb_pipetype(urb->pipe)) { + default: + case PIPE_CONTROL: + TRACE_MSG0(HCD, "PIPE_CONTROL"); + // XXX send set feature otg enable IFF set configuration and + // to first device and previously otg descriptor was seen. + // overload toggle with data phase direction (setup is always toggle 0) + // toggle == TRUE --> OUT (HOST2DEVICE --> OUT) + + toggle = ((((struct usb_ctrlrequest *) urb->setup_packet)->bRequestType & USB_ENDPOINT_DIR_MASK) == + USB_DIR_OUT); + + //other = (u32) (void *) urb->setup_packet; + + td.w3.val = (ETD_SET_BUFSIZE(sizeof(fs_data_buff)-1) | ETD_SET_TOTBYECNT(len)); + + fmt = ETD_FORMAT_CONTROL; + dir = ETD_DIRECT_TD00; + + /* build setup data/status phase ETD for use after completion of 8 byte setup packet + */ + dma = mxc_req->setup_dma; + sdp = &mxc_req->sdp_etd; + memset((void*)sdp, 0, sizeof(endpoint_transfer_descriptor)); + + /* toggle is constant 0 for setup packet, and 1 for data and status phases, + * so toggle was overloaded to handle data phase direction. If len == 0, there + * is no data phase, so direction is IN. + */ + sdp->td.w1.val = td.w1.val; + sdp->td.w2.cb.flags = ETD_SET_DATATOGL((0x2|1)) | // toggle is always 1, for data or status phases. + ETD_SET_DELAYINT(0) | // when done, interrupt ASAP (wait for 0 frames) + ETD_SET_BUFROUND(1) | // Allow short recv xfers + ETD_SET_DIRPID(((toggle && len > 0)? ETD_DIRPID_OUT:ETD_DIRPID_IN)); + sdp->td.w2.cb.reserved = 0; + sdp->td.w2.cb.rtrydelay = 1; // Number of frames to wait before retry on failure. + sdp->td.w3.val = ETD_SET_BUFSIZE(sizeof(fs_data_buff)-1) | ETD_SET_TOTBYECNT(len); + + /* build SETUP ETD for initial 8 byte setup packet + */ + len = 8; + td.w2.cb.flags = ETD_SET_DATATOGL((0x2|0)) | // starting toggle is here, not TOGCRY, and is always 0. + ETD_SET_DELAYINT(0) | // when done, interrupt ASAP (wait for 0 frames) + ETD_SET_BUFROUND(1) | // Allow short recv xfers + ETD_SET_DIRPID(pid); + td.w2.cb.reserved = 0; + td.w2.cb.rtrydelay = 1; // Number of frames to wait before retry on failure. + td.w3.val = (ETD_SET_BUFSIZE(sizeof(fs_data_buff)-1) | ETD_SET_TOTBYECNT(len)); + + mxc_req->etd_urb_state = ETD_URB_SETUP_START; + + //TRACE_MSG3(HCD,"SETUP pkt, len: %d : %08x : %08x",len,*((u32*)(void*)data),*(1+(u32*)(void*)data)); + + /* Complete the setup data phase ETD. + */ + sdp->epd = ETD_SET_MAXPKTSIZ ( usb_maxpacket(urb->dev, urb->pipe, usb_pipeout(urb->pipe))) | + ETD_SET_TOGCRY(0) | + ETD_SET_FORMAT(ETD_FORMAT_BULK) | + ETD_SET_SPEED(((((urb->pipe) >> 26) & 1)? ETD_SPEED_LOW : ETD_SPEED_FULL)) | + ETD_SET_DIRECT(ETD_DIRECT_TD00) | + ETD_SET_ENDPNT(endpoint) | + ETD_SET_ADDRESS(usb_pipedevice(urb->pipe)); + + break; + + case PIPE_BULK: + TRACE_MSG0(HCD, "PIPE_BULK"); + toggle = usb_gettoggle(urb->dev, endpoint, is_out); + other = (urb->transfer_flags & URB_ZERO_PACKET); + td.w3.val = (ETD_SET_BUFSIZE(sizeof(fs_data_buff)-1) | ETD_SET_TOTBYECNT(len)); + fmt = ETD_FORMAT_BULK; + dir = ETD_DIRECT_TD00; + + TRACE_MSG1(HCD,"BULK, starting toggle %x",toggle); + td.w2.cb.flags = ETD_SET_DATATOGL((0x2|toggle)) | // starting toggle is here, not TOGCRY + ETD_SET_DELAYINT(0) | // when done, interrupt ASAP (wait for 0 frames) + ETD_SET_BUFROUND(1) | // Allow short recv xfers + ETD_SET_DIRPID(pid); + td.w2.cb.reserved = 0; + td.w2.cb.rtrydelay = 0; // Number of frames to wait before retry on failure. + + /* Set the state to indicate if a trailing ZLP is required. + */ + mxc_req->etd_urb_state = (other && is_out && !(len % usb_maxpacket(urb->dev, urb->pipe, usb_pipeout(urb->pipe)))) ? + ETD_URB_BULKWZLP_START : ETD_URB_BULK_START; + break; + + case PIPE_INTERRUPT: + TRACE_MSG0(HCD, "PIPE_INTERRUPT"); + toggle = usb_gettoggle(urb->dev, endpoint, is_out); + other = urb->interval; + td.w3.val = (ETD_SET_BUFSIZE(sizeof(fs_data_buff)-1) | ETD_SET_TOTBYECNT(len)); + fmt = ETD_FORMAT_INTERRUPT; + dir = ETD_DIRECT_TD00; + + TRACE_MSG1(HCD,"INTERRUPT, starting toggle %x",toggle); + td.w2.intr.flags = ETD_SET_DATATOGL((0x2|toggle)) | // starting toggle is here, not TOGCRY + ETD_SET_DELAYINT(0) | // when done, interrupt ASAP (wait for 0 frames) + ETD_SET_BUFROUND(1) | // Allow short recv xfers + ETD_SET_DIRPID(pid); + + td.w2.intr.relpolpos = (fs_rl(OTG_HOST_FRM_NUM) + 1) & 0xff; // Start next frame + + td.w2.intr.polinterv = other & 0xff; + mxc_req->etd_urb_state = ETD_URB_INTERRUPT_START; + break; + + case PIPE_ISOCHRONOUS: + TRACE_MSG0(HCD, "PIPE_ISOCHRONOUS"); + toggle = 0; + other = urb->interval; + td.w3.val = (ETD_SET_BUFSIZE(sizeof(fs_data_buff)-1) | ETD_SET_TOTBYECNT(len)); + + fmt = ETD_FORMAT_ISOC; + dir = is_out ? ETD_DIRECT_OUT : ETD_DIRECT_IN; + td.w2.isoc.startfrm = (fs_rl(OTG_HOST_FRM_NUM) + 1) & 0xffff; // next frame + if (len > 1023) { + // Two frames needed, send 1023 in the first, remainder in the second. + td.w2.isoc.flags = ETD_SET_DELAYINT(0) | ETD_SET_AUTOISO(0) | ETD_SET_FRAMECNT(1); + td.w3.isoc.pkt0 = ETD_SET_PKTLEN(1023); + td.w3.isoc.pkt1 = ETD_SET_PKTLEN(len-1023); + } + else { + // Only one frame needed, send it all at once. + td.w2.isoc.flags = ETD_SET_DELAYINT(0) | ETD_SET_AUTOISO(0) | ETD_SET_FRAMECNT(0); + td.w3.isoc.pkt0 = ETD_SET_PKTLEN(len); + td.w3.isoc.pkt1 = 0; + } + mxc_req->etd_urb_state = ETD_URB_ISOC_START; + break; + } + + /* copy etd into hardware registers */ + + fs_wl(etd_word(mxc_req->etdn, 0), + ETD_SET_MAXPKTSIZ ( usb_maxpacket(urb->dev, urb->pipe, usb_pipeout(urb->pipe))) | + ETD_SET_TOGCRY(0) | + ETD_SET_FORMAT(fmt) | + ETD_SET_SPEED(((((urb->pipe) >> 26) & 1)? ETD_SPEED_LOW : ETD_SPEED_FULL)) | + ETD_SET_DIRECT(dir) | + ETD_SET_ENDPNT(endpoint) | + ETD_SET_ADDRESS(usb_pipedevice(urb->pipe))); + + fs_wl(etd_word(mxc_req->etdn, 1), td.w1.val); + fs_wl(etd_word(mxc_req->etdn, 2), td.w2.val); + fs_wl(etd_word(mxc_req->etdn, 3), td.w3.val); + + TRACE_MSG5(HCD, "ETD[%02d] %08x %08x %08x %08x", mxc_req->etdn, + fs_rl(etd_word(mxc_req->etdn, 0)), fs_rl(etd_word(mxc_req->etdn, 1)), + fs_rl(etd_word(mxc_req->etdn, 2)), fs_rl(etd_word(mxc_req->etdn, 3))); + + /* housekeeping before actual start */ + + urb->actual_length = 0; + list_del(&mxc_req->queue); + + mxc_hcd_start_etd_irq(mxc_req->etdn, len, dma, is_out); + + return 0; // allow scheduler to continue +} + +/*! mxc_hcd_schedule_irq - + * @param mxc_hcd + * + * This function scans inactive list for work and will start + * anything it can find assuming no conflicts and that there + * are resources available. + * + * This is called from the enqueue function and the hcd interrupt handler. + * + */ +void mxc_hcd_schedule_irq(struct mxc_hcd *mxc_hcd) +{ + struct mxc_req *mxc_req; + struct mxc_req *mxc_req_save; + + /* iterate across list of active urbs, normally this will be a very + * short list.... stop if non-zero which means there are no more + * resources. + * + * N.B. use _safe version of macro because we are deleting entries. + */ + list_for_each_entry_safe(mxc_req, mxc_req_save, &mxc_hcd->inactive, queue) { + BREAK_IF(mxc_hcd_start_req_irq(mxc_hcd, mxc_req)); + } +} + +/* ********************************************************************************************* */ +/* ********************************************************************************************* */ + +/*! + * mxc_hcd_finish_req_irq() - called to complete transfer + * @param mxc_hcd + * @param mxc_req + * @param urb + * @param killed + * + * This function will finish a request and give the urb back to the hcd driver. + * + * Control Transfers may be restarted to do the status phase done and/or + * send a ZLP. + * + * Bulk transfers may be restarted to send a ZLP. + * + */ +void mxc_hcd_finish_req_irq(struct mxc_hcd *mxc_hcd, struct mxc_req *mxc_req, struct urb *urb, int killed) +{ + int cc = 0; + int next_toggle = 0; + int format = 0; + u32 remaining = 0; + fs_data_buff *x; + fs_data_buff *y; + endpoint_transfer_descriptor res; + + struct usb_device *dev = urb->dev; + struct usb_bus *bus = dev->bus; + struct usb_device *udev = urb->dev; +// struct hcd_dev *hdev = (struct hcd_dev *) udev->hcpriv; + int is_out = usb_pipeout(urb->pipe); + int address = usb_pipedevice(urb->pipe) ? + (usb_pipedevice(urb->pipe) % MXC_MAX_USB_ADDRESS + 1) : 0; + int endpoint = usb_pipeendpoint(urb->pipe); + TRACE_MSG6(HCD, "urb: %x mxc_req: %x etdn: %d state: %d %s %s", + urb, mxc_req, mxc_req->etdn, mxc_req->etd_urb_state, + etd_urb_state_name[mxc_req->etd_urb_state], (killed?"KILLED":"")); + + /* Disable/Abort DMA. R1: sec 23.13.16 pg 23-103 + * (supposed to auto-clear on DMA completion, but this shouldn't hurt). + */ + fs_wl(OTG_DMA_ETD_CH_CLR, ETD_MASK(mxc_req->etdn)); + + if (fs_rl(OTG_HOST_ETD_EN) & ETD_MASK(mxc_req->etdn)) + fs_andl(OTG_HOST_ETD_EN, ~ETD_MASK(mxc_req->etdn)); // This register IS write 0 to disable. Really. + + if ( fs_rl(OTG_HOST_ETD_DONE) & ETD_MASK(mxc_req->etdn)) + fs_andl(OTG_HOST_ETD_DONE, ~ETD_MASK(mxc_req->etdn)); // This register IS write 0 to disable. Really. + + // Hardware should have disabled ETD, but it doesn't hurt to check. + if (fs_rl(OTG_HOST_ETD_EN) & ETD_MASK(mxc_req->etdn)) + fs_wl(OTG_HOST_ETD_EN, ETD_MASK(mxc_req->etdn)); // This register IS write 1 to clear. Really. + + if (fs_rl(OTG_HOST_XINT_STAT) & ETD_MASK(mxc_req->etdn)) + fs_wl(OTG_HOST_XINT_STAT, ETD_MASK(mxc_req->etdn)); // This register IS write 1 to clear. Really. + + if (fs_rl(OTG_HOST_YINT_STAT) & ETD_MASK(mxc_req->etdn)) + fs_wl(OTG_HOST_YINT_STAT, ETD_MASK(mxc_req->etdn)); // This register should be write 1 to clear. + + + if (fs_rl(OTG_HOST_XYINT_STEN) & ETD_MASK(mxc_req->etdn)) + fs_andl(OTG_HOST_XYINT_STEN, ~ETD_MASK(mxc_req->etdn)); // This register should be write 0 to disable. + + if (fs_rl(OTG_HOST_XFILL_STAT) & ETD_MASK(mxc_req->etdn)) + fs_wl(OTG_HOST_XFILL_STAT, ETD_MASK(mxc_req->etdn)); // This register IS write 1 to clear. Really. + + if (fs_rl(OTG_HOST_YFILL_STAT) & ETD_MASK(mxc_req->etdn)) + fs_wl(OTG_HOST_YFILL_STAT, ETD_MASK(mxc_req->etdn)); // This register should be write 1 to clear. + + // Extract results + res.epd = fs_rl(etd_word(mxc_req->etdn, 0)); + res.td.w1.val = fs_rl(etd_word(mxc_req->etdn, 1)); + res.td.w2.val = fs_rl(etd_word(mxc_req->etdn, 2)); + res.td.w3.val = fs_rl(etd_word(mxc_req->etdn, 3)); + + fs_wl(OTG_HOST_EP_DSTAT, ETD_MASK(mxc_req->etdn)); + + //TRACE_MSG1(HCD,"OTG_HOST_ETD_DONE: %08x", fs_rl(OTG_HOST_ETD_DONE)); + + //if ((ETD_URB_BULK_START == mxc_req->etd_urb_state || (ETD_URB_BULKWZLP_START == mxc_req->etd_urb_state)) && + // (ETD_GET_DIRPID(res.td.w2.cb.flags) != ETD_DIRPID_IN) + // ) + TRACE_MSG4(HCD,"RES: epd: %08x w1: %08x w2: %08x w3: %08x", res.epd,res.td.w1.val,res.td.w2.val,res.td.w3.val); + + if (ETD_GET_HALTED(res.epd)) + TRACE_MSG0(HCD,"endpoint HALTED"); + + format = ETD_GET_FORMAT(res.epd); + + switch (mxc_req->etd_urb_state) { + case ETD_URB_SETUP_START: + /* Just finished the setup packet. + * Go to data or status phase of setup packet while we still have the ETD and data buffers allocated. + */ + if (!killed && ETD_CC_NOERROR == (cc = ETD_GET_COMPCODE(res.td.w2.cb.flags))) { + + endpoint_transfer_descriptor *sdp = &mxc_req->sdp_etd; + int len = ETD_GET_TOTBYECNT(sdp->td.w3.val); + void *data = mxc_req->urb->transfer_buffer; + u32 dma = mxc_req->transfer_dma; + mxc_req->etd_urb_state = ((len > 0) ? ETD_URB_SETUP_DATA : ETD_URB_SETUP_STATUS); + + // Copy into the active ETD and start it. + fs_wl(etd_word(mxc_req->etdn, 0), sdp->epd); + fs_wl(etd_word(mxc_req->etdn, 1), sdp->td.w1.val); + fs_wl(etd_word(mxc_req->etdn, 2), sdp->td.w2.val); + fs_wl(etd_word(mxc_req->etdn, 3), sdp->td.w3.val); + // watch out for ETD_DIRPID_SETUP + + mxc_hcd_start_etd_irq(mxc_req->etdn, len, dma, + (ETD_GET_DIRPID(sdp->td.w2.cb.flags) != ETD_DIRPID_IN)); + + TRACE_MSG1(HCD,"SETUP %s Phase started",((len > 0) ? "DATA" : "STATUS")); + return; + } + + /* Something is wrong, finish the URB as is. */ + remaining = ETD_GET_TOTBYECNT(fs_rl(etd_word(mxc_req->etdn, 3))); + format = PIPE_CONTROL; + break; + + case ETD_URB_SETUP_DATA: + /* Go to status phase of setup packet while we still have the ETD and data buffers allocated. + * Save the data phase length for after status. + */ + if (!killed && ETD_CC_NOERROR == (cc = ETD_GET_COMPCODE(res.td.w2.cb.flags))) { + endpoint_transfer_descriptor *sdp = &mxc_req->sdp_etd; + mxc_req->remaining = ETD_GET_TOTBYECNT(res.td.w3.val); + mxc_req->etd_urb_state = ETD_URB_SETUP_STATUS; + + /* Rebuild data phase sdp for status phase with opposite direction, and 0 length. + */ + + sdp->td.w2.cb.flags = ETD_FLIP_DIRPID(sdp->td.w2.cb.flags); + sdp->td.w3.val = (ETD_SET_BUFSIZE(sizeof(fs_data_buff)-1) | ETD_SET_TOTBYECNT(0)); + + /* Copy into the active ETD and start it. + */ + fs_wl(etd_word(mxc_req->etdn, 0), sdp->epd); + fs_wl(etd_word(mxc_req->etdn, 1), sdp->td.w1.val); + fs_wl(etd_word(mxc_req->etdn, 2), sdp->td.w2.val); + fs_wl(etd_word(mxc_req->etdn, 3), sdp->td.w3.val); + mxc_hcd_start_etd_irq(mxc_req->etdn, 0, 0, + (ETD_GET_DIRPID(sdp->td.w2.cb.flags) != ETD_DIRPID_IN)); + TRACE_MSG0(HCD,"SETUP STATUS Phase started"); + return; + } + + /* Something is wrong, finish the URB as is. */ + remaining = ETD_GET_TOTBYECNT(res.td.w3.val); + format = PIPE_CONTROL; + break; + + case ETD_URB_SETUP_STATUS: + cc = ETD_GET_COMPCODE(res.td.w2.cb.flags); + remaining = mxc_req->remaining; + mxc_req->remaining = 0; + format = PIPE_CONTROL; + break; + + case ETD_URB_BULKWZLP_START: + /* Need a trailing ZLP. + */ + next_toggle = ETD_GET_TOGCRY(res.epd); + if (!killed && ETD_CC_NOERROR == ETD_GET_COMPCODE(res.td.w2.cb.flags)) { + // Since TOTBYECNT should be 0, re-enabling the same ETD ought to give a ZLP. + mxc_req->etd_urb_state = ETD_URB_BULKWZLP; + // Save the data phase length for after ZLP. + // mxc_hcd->sdp_data[mxc_req->etdn] = (void *) ETD_GET_TOTBYECNT(res.td.w3.val); + // FIXME - verify toggle OK + + mxc_hcd_start_etd_irq(mxc_req->etdn, 0, 0, TRUE/*always OUT*/); + + TRACE_MSG0(HCD,"ZLP started"); + return; + } + // Something is wrong, finish the URB as is. + remaining = ETD_GET_TOTBYECNT(res.td.w3.val); + format = PIPE_BULK; + break; + + case ETD_URB_BULKWZLP: + /* Trailing ZLP now finished. */ + cc = ETD_GET_COMPCODE(res.td.w2.cb.flags); + next_toggle = ETD_GET_TOGCRY(res.epd); + /* remaining must be zero */ + remaining = 0; + format = PIPE_BULK; + break; + + case ETD_URB_BULK_START: + cc = ETD_GET_COMPCODE(res.td.w2.cb.flags); + next_toggle = ETD_GET_TOGCRY(res.epd); + remaining = ETD_GET_TOTBYECNT(res.td.w3.val); + format = PIPE_BULK; + break; + + case ETD_URB_INTERRUPT_START: + cc = ETD_GET_COMPCODE(res.td.w2.intr.flags); + remaining = ETD_GET_TOTBYECNT(res.td.w3.val); + next_toggle = usb_gettoggle(urb->dev,usb_pipeendpoint(urb->pipe),usb_pipeout(urb->pipe)) ? 0 : 1; + format = PIPE_INTERRUPT; + break; + + case ETD_URB_ISOC_START: + remaining = ETD_GET_PKTLEN(res.td.w3.isoc.pkt0); + cc = ETD_GET_COMPCODE(res.td.w3.isoc.pkt0); + if (ETD_GET_FRAMECNT(res.td.w2.isoc.flags)) { + // There were two packets in this transfer + remaining += ETD_GET_PKTLEN(res.td.w3.isoc.pkt1); + } + format = PIPE_ISOCHRONOUS; + } + + mxc_req->etd_urb_state = ETD_URB_COMPLETED; + + /* Convert completion code to HW independent value. */ + + if (killed) { + cc = -ENOENT; + remaining = 0; + } + else + cc = comp_code_to_status(cc); + + /* Return ETD and X,Y buffers to free list. */ + + y = data_buff_addr(res.td.w1.bufsrtad.y); + x = data_buff_addr(res.td.w1.bufsrtad.x); + y = rel_data_buff(mxc_hcd, y); + x = rel_data_buff(mxc_hcd, x); + + /* clear ep and active entries */ + + mxc_hcd->ep[address][mxc_req->epnum] = NULL; + rel_etd_irq(mxc_hcd, mxc_req->etdn); + mxc_hcd->active_count--; + + /* Forward results to the layers above. */ + + UNLESS (urb) { + printk(KERN_INFO"%s: active_urb[%d] NULL", __FUNCTION__, mxc_req->etdn); + TRACE_MSG1(HCD,"active_urb[%d] NULL", mxc_req->etdn); + return; + } + + TRACE_MSG5(HCD,"id: %d status: %d tlen: %d rem: %d buffer: %p", + mxc_req->etdn, cc, urb->transfer_buffer_length, remaining, urb->transfer_buffer); +#if 0 + if (urb->transfer_buffer) { + int i; + u8 *cp = urb->transfer_buffer; + urb->actual_length = urb->transfer_buffer_length - remaining; + + TRACE_MSG1(HCD, "NEXT TX: length: %d", urb->actual_length); + + for (i = 0; i < urb->actual_length; i+= 8) + + TRACE_MSG8(HCD, "RECV %02x %02x %02x %02x %02x %02x %02x %02x", + cp[i + 0], cp[i + 1], cp[i + 2], cp[i + 3], + cp[i + 4], cp[i + 5], cp[i + 6], cp[i + 7] + ); + } +#endif + + if (-ENOENT == cc) { + // Cancelled. + urb->actual_length = 0; + } + else { + switch (format) { + case PIPE_CONTROL: + urb->actual_length = urb->transfer_buffer_length - remaining; + + if (urb->setup_packet) { + u8 *cp = urb->setup_packet; + TRACE_MSG8(HCD, "SETUP %02x %02x %02x %02x %02x %02x %02x %02x", + cp[0], cp[1], cp[2], cp[3], cp[4], cp[5], cp[6], cp[7]); + } + + + // XXX Should this be moved? + // + // XXX We need to figure out a new way to ensure first device. Perhaps active_count == 1. + // + //if ((dev == mxc_hcd->first_dev) && !cc && urb->setup_packet) + { + + struct usb_ctrlrequest *request = (struct usb_ctrlrequest*) urb->setup_packet; + switch (request->bRequest) { + case USB_REQ_SET_ADDRESS: + otg_queue_event(hcd_instance->otg, ADDRESSED, HCD, "HCD COMPLETE ADDRESSED"); + break; + case USB_REQ_SET_CONFIGURATION: + otg_queue_event(hcd_instance->otg, CONFIGURED, HCD, "HCD COMPLETE CONFIGURED"); + break; + // XXX check for GET_CONFIGURATION and otg descriptor here + } + } + break; + + case PIPE_BULK: + case PIPE_INTERRUPT: + if (0 == cc) { + // QQSV needed for BULK, what about interrupt? + TRACE_MSG4(HCD,"dev: %d ep: %d dir: %d TOGGLE: %d",urb->dev->devnum, + usb_pipeendpoint(urb->pipe),usb_pipeout(urb->pipe),next_toggle); + usb_settoggle(urb->dev,usb_pipeendpoint(urb->pipe),usb_pipeout(urb->pipe),next_toggle); + } + urb->actual_length = urb->transfer_buffer_length - remaining; + //hcd_trace_mem(HCD,__FUNCTION__,"BULK/INTERRUPT urb",urb->actual_length,urb->transfer_buffer); + break; + + case PIPE_ISOCHRONOUS: + urb->actual_length = urb->transfer_buffer_length - remaining; // QQSV + break; + + default: // Paranoia. + urb->actual_length = 0; + break; + } + } + + /* give the urb back to the hcd driver, put the request back into the unused queue */ + mxc_hcd_giveback_req_irq(mxc_hcd, mxc_req, cc); + //list_add_tail(&mxc_req->queue, &mxc_hcd->unused); + return; +} + +/* ********************************************************************************************* */ +/* ********************************************************************************************* */ + +/*! MXC Root Hub Support + * + * We track all changes to the MXC root hub and push the OTG port changes through + * to the upper layers IFF we are in a_host or b_host states. + * + * This requires that we implement some of the root hub state machine to get ports + * connected etc even if we are not currently pushing up and conversly emulate + * the port connect changes and reset/enable for the root hub implementation in + * the usb core layer. + * + */ +char *port_feature_name[] = { + "PORT_CONNECTION", "PORT_ENABLE", "PORT_SUSPEND", "FEATURE 0x03", + "PORT_RESET", "FEATURE 0x05", "FEATURE 0x06", "FEATURE 0x07", + "PORT_POWER", "PORT_LOW_SPEED", "FEATURE 0x0a", "FEATURE 0x0b", + "FEATURE 0x0c", "FEATURE 0x0d", "FEATURE 0x0e", "FEATURE 0x0f", + "C_PORT_CONNECTION", "C_PORT_ENABLE", "C_PORT_SUSPEND", "C_PORT_OVER_CURRENT", + "C_PORT_RESET", "PORT_TEST", +}; + +/*! + * mxc_hcd_hw_rh_port_feature() - get rh port feature + * @param mxc_hcd + * @param wValue is feature selector + * @param wIndex is port number (1-N) + * @param set_flag + */ +void mxc_hcd_hw_rh_port_feature(struct mxc_hcd *mxc_hcd, u16 wValue, u16 wIndex, int set_flag) +{ + char buf[64]; + sprintf(buf, "port %d feature %s %s", wIndex, port_feature_name[wValue], set_flag ? "SET" : "RESET"); + + if (wIndex == 1) + TRACE_MSG5(HCD, "-> port: %d %s (%d) %s port_stat: %8x", wIndex, port_feature_name[wValue], + wValue, set_flag ? "SET" : "RESET", + fs_rl(fs_host_port_stat(wIndex))); + + RETURN_UNLESS(mxc_hcd->otg_port_enabled); + if (set_flag) { + // SET feature + switch (wValue) { + case PORT_SUSPEND: + otg_queue_event(hcd_instance->otg, BUS_SUSPENDED, HCD, "HUB_PORT_SUSPEND (mx21 hw)"); + fs_wl(fs_host_port_stat(wIndex), 1 << wValue); + break; + case PORT_RESET: + case PORT_POWER: + fs_wl(fs_host_port_stat(wIndex), 1 << wValue); + break; + default: + // Error, but ignore. + TRACE_MSG2(HCD,"SET port %d invalid feature %d", wIndex, wValue); + break; + } + } + else { + // CLEAR feature (valid features from USB2.0 11.24.2.2 pg 423). + switch (wValue) { + case PORT_SUSPEND: // Cause a Host initiated resume, or no-op if already active. + wValue++; // yes, really. this clears PORT_SUSPEND + fs_wl(fs_host_port_stat(wIndex), 1 << wValue); + otg_queue_event(hcd_instance->otg, BUS_SUSPENDED_, HCD, "HUB_PORT_SUSPEND/ (mx21 hw)"); + break; + + case PORT_POWER: // Put port in powered-off state. + wValue++; // yes, really. this clears PORT_POWER + fs_wl(fs_host_port_stat(wIndex), 1 << wValue); + break; + + case PORT_ENABLE: // Disable port. + //wValue--; + break; + default: + break; + } + switch (wValue) { + case PORT_POWER: // Put port in powered-off state. + case PORT_SUSPEND: // Cause a Host initiated resume, or no-op if already active. + case PORT_ENABLE: // Disable port. + case PORT_INDICATOR: // ind_selector gives which indicator to clear + case C_PORT_SUSPEND: // clear the PORT_SUSPEND change bit + case C_PORT_CONNECTION: // clear the PORT_CONNECTION change bit + case C_PORT_RESET: // clear the PORT_RESET change bit + case C_PORT_ENABLE: // clear the PORT_ENABLE change bit + case C_PORT_OVER_CURRENT: // clear the PORT_OVERCURRENT change bit + fs_wl(fs_host_port_stat(wIndex), 1 << wValue); + break; + default: + // Error, but ignore. + TRACE_MSG2(HCD,"CLEAR port %d invalid feature %d", wIndex, wValue); + break; + } + } + + if (wIndex == 1) + TRACE_MSG4(HCD, "<- port: %d %s %s port_stat: %8x", wIndex, port_feature_name[wValue], set_flag ? "SET" : "RESET", + fs_rl(fs_host_port_stat(wIndex))); +} + +/*! + * MXC_PORT_CHANGED() - send otg event information to state machine + * @param name + * @param cs + * @param changed + * @param status + * @param set + * @param reset + */ +void MXC_PORT_CHANGED(char *name, u32 cs, u32 changed, u32 status, otg_current_t set, otg_current_t reset) +{ + RETURN_UNLESS(cs & changed); + TRACE_MSG2(HCD, "%s%s", name, (cs & (1 << status)) ? "" : "/"); + if (cs & (1 << status) && set) + otg_queue_event(hcd_instance->otg, set, HCD, name); + if (!(cs & (1 << status)) && reset) + otg_queue_event(hcd_instance->otg, reset, HCD, name); +} + + +/*! + * mxc_hcd_update_shadow_rh() - Update shadow _status_ info (change info already updated). + * @param mxc_hcd + * @param port_num - 1-N based port number + * @param cs + * + * Process change status for OTG port. Track changes into virtual hub change + * status so that virtual root hub implementation can see them when we want + * them to see them. + * + * N.B. The port reset processing is done here so that the OTG state machine + * can oversee things. We emulate it for the upper layers when we want them + * to see the actual connection. + * + */ +static void mxc_hcd_update_shadow_rh(struct mxc_hcd *mxc_hcd, int port_num, u32 cs) +{ + TRACE_MSG2(HCD,"port: %d cs: %08x", port_num, cs); + + /* PORT_CONNECTION (0) bit has changed (either direction). + * PORT_STATUS_CONNECTSC (16) - PORT_CONNECTION (0) changed + * + * When set will initiate a PORT_RESET, which should result in PORT_ENABLE. + * + * When reset this needs to clear virtual hub connection if otg port. + */ + MXC_PORT_CHANGED("PORT_CONNECTION", cs, PORT_STATUS_CONNECTSC, PORT_CONNECTION, + HUB_PORT_CONNECT, HUB_PORT_CONNECT_ ); + + if (cs & (1 << C_PORT_CONNECTION)) { + if (cs & (1 << PORT_CONNECTION)) { + if (cs & (1 << PORT_LOW_SPEED)) TRACE_MSG1(HCD,"port: %d low speed device", port_num); + + // initiate reset, which should enable port + mxc_hcd_hw_rh_port_feature(mxc_hcd, PORT_RESET, port_num, TRUE); + } + else { + + mxc_hcd->virt_port_status[port_num - 1] &= ~(1 << PORT_CONNECTION); + mxc_hcd->virt_port_status[port_num - 1] &= ~(1 << PORT_ENABLE); + mxc_hcd->virt_port_status[port_num - 1] |= (1 << C_PORT_CONNECTION); + //mxc_hcd->virt_port_status[port_num - 1] |= (1 << C_PORT_ENABLE); + mxc_hcd->virt_hub_port_change_status |= (1 << port_num); + + TRACE_MSG2(HCD,"port: %d disconnection virt_port_status: %08x", + port_num, mxc_hcd->virt_port_status[port_num - 1]); + } + } + + + /* PORT_RESET (4) status change, end of reset, PORT_ENABLE may (should) be on. + * + * This should be because of PORT_RESET started due to PORT_CONNECT and + * should have resulted in PORT_ENABLE. + * + * If PORT_ENABLE is true then we can set PORT_CONNECT in virtual hub if otg port. + * + */ + MXC_PORT_CHANGED("PORT_RESET", cs, PORT_STATUS_PRTRSTSC, PORT_RESET, BUS_RESET, BUS_RESET_); + if (cs & (1 << C_PORT_RESET)) { + TRACE_MSG1(HCD,"port: %d PORT_RESET complete (s.b. enabled)", port_num); + if (cs & (1 << PORT_ENABLE)) { + TRACE_MSG1(HCD,"port: %d enabled", port_num); + + /* If OTG Port set Port Connection in virtual root hub, this starts + * the sequeuence of events required to get virtual + * root hub to emulate a connection. + */ + mxc_hcd->virt_port_status[port_num - 1] |= (1 << PORT_CONNECTION); + mxc_hcd->virt_port_status[port_num - 1] |= (1 << C_PORT_CONNECTION); + mxc_hcd->virt_hub_port_change_status |= (1 << port_num); + + TRACE_MSG2(HCD,"port: %d reset virt_port_status: %08x", + port_num, mxc_hcd->virt_port_status[port_num - 1]); + } + else + TRACE_MSG1(HCD,"port: %d NOT enabled!!!", port_num); + + } + + + /* PORT_ENABLE (1) has been __cleared__ by some HW event + * PORT_STATUS_PRTENBLSC (17) - PORT_ENABLE (1) changed + * + * This will be set only on port error. + * + * This needs to clear virtual hub connection if otg port. + */ + MXC_PORT_CHANGED("PORT_ENABLE", cs, PORT_STATUS_PRTENBLSC, PORT_ENABLE, (u64) 0, (u64) 0); + if (cs & (1 << C_PORT_ENABLE)) { + TRACE_MSG1(HCD,"port: %d disabled (by HW)", port_num); + if ((cs & (1 << PORT_ENABLE))) { + + } + else { + + /* Reset Port Connection in virtual root hub + */ + TRACE_MSG2(HCD,"port: %d lost connection virt_port_status: %08x", + port_num, mxc_hcd->virt_port_status[port_num - 1]); + + mxc_hcd->virt_port_status[port_num - 1] &= ~(1 << PORT_CONNECTION); + mxc_hcd->virt_port_status[port_num - 1] &= ~(1 << PORT_ENABLE); + //mxc_hcd->virt_port_status[port_num - 1] |= (1 << C_PORT_CONNECTION); + mxc_hcd->virt_port_status[port_num - 1] |= (1 << C_PORT_ENABLE); + mxc_hcd->virt_hub_port_change_status |= (1 << port_num); + + TRACE_MSG2(HCD,"port: %d enable lost virt_port_status: %08x", + port_num, mxc_hcd->virt_port_status[port_num - 1]); + } + } + + /* PORT_OVER_CURRENT (3) bit has changed (either direction). + * PORT_STATUS_OVRCURIC (19) - PORT_OVER_CURRENT (3) changed + * + * XXX This will need to clear virtual hub. + */ + MXC_PORT_CHANGED("PORT_OVER_CURRENT", cs, PORT_STATUS_OVRCURIC, PORT_OVER_CURRENT, (u64) 0, (u64) 0); + if (cs & (1 << C_PORT_OVER_CURRENT)) { + if (cs & (1 << PORT_OVER_CURRENT)) { + TRACE_MSG1(HCD,"port: %d over current ON", port_num); + } + else { + TRACE_MSG1(HCD,"port: %d over current OFF", port_num); + } + } + + /* PORT_SUSPEND (2) Resume sequence has completed.... + * PORT_STATUS_PRTSTATSC (18) - PORT_SUSPEND (2) changed + */ + MXC_PORT_CHANGED("PORT_SUSPEND", cs, PORT_STATUS_PRTSTATSC, PORT_SUSPEND, + BUS_SUSPENDED, BUS_SUSPENDED_); + + if (cs & (1 << C_PORT_SUSPEND)) { + // FIXME - figure this out when we get to suspend/resume.... + } + + //TRACE_MSG5(HCD, "port: %d shadow port_change_status: ((%08x & ~%04x) | %04x) hpcs: %04x", + // port_num, mxc_hcd->real_port_change_status[port_num - 1], + // mxc_hcd->real_hub_port_change_status); +} + +/* ********************************************************************************************** */ + +/*! mxc_hcd_rh_portstatus_bh() + * + * Track changes in portstatus. This is run as a bottom-half handler when + * changes in the port status registers are detected in the interrupt + * handler. + * + */ +static void mxc_hcd_rh_portstatus_bh(void *data) +{ + struct mxc_hcd *mxc_hcd = (struct mxc_hcd *) data; + u8 change_data; + struct urb *int_urb; + + u32 real_hub_port_change_status; + u32 cs; + unsigned long flags; + + int i; + + TRACE_MSG3(HCD, "port_change_status: %08x %08x %08x", mxc_hcd->real_port_change_status[0], + mxc_hcd->real_port_change_status[1], mxc_hcd->real_port_change_status[2]); + + /* check for changes in real port status */ + + RETURN_UNLESS (cs = mxc_hcd->real_port_change_status[0] | fs_rl(fs_host_port_stat(1))); // OTG Port + + /* clear real port status and update the OTG port to the upper layers */ + + mxc_hcd->real_port_change_status[0] = 0; + mxc_hcd_update_shadow_rh(mxc_hcd, 1, cs); +} + +/*! mxc_hcd_rh_int_hndler() - handle port status interrupt + * @param mxc_hcd + * + * This finds, saves and clears port status changes, then schedules + * the bottom-half handler to process them. + * + * XXX Current MXC hardware reports apparantly spurious changes to port status + * for the non-OTG ports. These must be properly cleared. + * + */ +void mxc_hcd_rh_int_hndlr(struct mxc_hcd *mxc_hcd) +{ + u32 roothub_status = fs_rl(OTG_HOST_ROOTHUB_STATUS); + int port_num; + + TRACE_MSG1(HCD, "HOST_PSCINT ROOTHUB_STATUS: %08x", roothub_status); + + if (roothub_status & ROOTHUB_STATUS_OVRCURCHG) { + fs_wl(OTG_HOST_ROOTHUB_STATUS, ROOTHUB_STATUS_OVRCURCHG); + roothub_status = fs_rl(OTG_HOST_ROOTHUB_STATUS); + TRACE_MSG1(HCD, "HOST_PSCINT ROOTHUB_STATUS: %08x CLEARED", roothub_status); + } + + //printk(KERN_INFO"%s: cs: %08x %08x %08x\n", __FUNCTION__, fs_rl(fs_host_port_stat(1)), + // fs_rl(fs_host_port_stat(2)), fs_rl(fs_host_port_stat(3))); + + /* Clear the interrupt by clearing the port(s) in question. + * Reflect any changes in the shadow values. + */ + for (port_num = 1; port_num <= mxc_hcd->bNbrPorts; port_num++) { + + u32 cs = fs_rl(fs_host_port_stat(port_num)); + u32 cm = cs & 0xFFFF0000; + + CONTINUE_UNLESS (cm); + if (port_num == 1) + TRACE_MSG3(HCD, "port: %d cs: %08x cm: %08x", port_num, cs, cm); + + if (port_num == 1) { + mxc_hcd->real_hub_port_change_status |= (1 << port_num); + mxc_hcd->real_port_change_status[port_num - 1] |= cm; + } + if ( (fs_rl(OTG_HOST_ROOTHUB_STATUS) & 0x2) && + (cs & 0x8) ) //Over current in port and hub occurs + { + printk (KERN_INFO"USB port overcurrent is happened !?\n"); + } + + /* clear port status */ + if (cm) { + //printk(KERN_INFO"%s: clearing[%d] %08x\n", __FUNCTION__, port_num, cm); + fs_wl(fs_host_port_stat(port_num), cm); + cs = fs_rl(fs_host_port_stat(port_num)); + cm = cs & 0xFFFF0000; + //printk(KERN_INFO"%s: port[%d] cs: %08x cm: %08x CLEARED\n", __FUNCTION__, port_num, cs, cm); + TRACE_MSG3(HCD, "port: %d cs: %08x cm: %08x CLEARED", port_num, cs, cm); + } + } + + TRACE_MSG3(HCD, "port_change_status: %08x %08x %08x", mxc_hcd->real_port_change_status[0], + mxc_hcd->real_port_change_status[1], mxc_hcd->real_port_change_status[2]); + + /* Tell the RH bottom-half to scan for changes in shadow data. (schedule bottom half handler) + */ + PREPARE_WORK_ITEM(mxc_hcd->rh_bh, mxc_hcd_rh_portstatus_bh, mxc_hcd); + SCHEDULE_WORK(mxc_hcd->rh_bh); +} + +/* ********************************************************************************************* */ +/* ********************************************************************************************* */ +/*! MXC Host Interrupt Handler + * + * Handle a host controller interrupt. This can be for either a transfer complete + * or root hub / port change etc. + */ + +static int num_host_interrupts = 0; +#define MAX_HOST_INTERRUPTS 0 +#define MAX_PSC_INTERRUPTS 0 + +/*! + * mxc_hcd_hw_int_hndlr() - interrupt handler for hcd controller + * @param irq + * @param dev_id + * @param regs + */ + + + + +irqreturn_t mxc_hcd_hw_int_hndlr(int irq, void *dev_id, struct pt_regs *regs) +{ + struct mxc_hcd *mxc_hcd = hcd_instance->privdata; // XXX this should come from dev_id + + u32 host_sint; // C.f. 23.11.11 Host Interrupt Register + + int loop_count = 0; + struct usb_hcd *hcd = (struct usb_hcd *) mxc_hcd; + int start = hcd->state; + + + /* XXX - what is this.... */ + if (OTG_USBDMA == irq) { + // HOST DMA error interrupt + u32 err_stat = fs_rl(OTG_DMA_ETD_ERR); + TRACE_MSG1(HCD,"host DMA interrupt, error status %08x",err_stat); + if (0 != err_stat) { + // Disable the matching DMA channels and clear the interrupt + fs_wl(OTG_DMA_ETD_CH_CLR, err_stat); + fs_wl(OTG_DMA_ETD_ERR, err_stat); + } + // local_irq_restore(flags); + return IRQ_HANDLED; + } + + /* FIXME - should check mxc_hcd->mm->otg.OTG_Module_Interrupt_Status & (0x1 << 3) | 0x1; + * for Host (async + regular) Interrupt - enable clock on async. + */ +#if 1 + if (unlikely(start == HC_STATE_HALT || + !test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags))) + return IRQ_NONE; +#endif + while ((host_sint = fs_rl(OTG_HOST_SINT_STAT) & mxc_hcd->int_mask)) { + + num_host_interrupts++; + if ((loop_count++ > 100) || (MAX_HOST_INTERRUPTS && (num_host_interrupts > MAX_HOST_INTERRUPTS))) { + printk(KERN_INFO"%s: DISABLED\n", __FUNCTION__); + fs_wl(OTG_HOST_SINT_STEN, 0); + fs_andl(OTG_CORE_CINT_STEN, ~(/*(0x1 << 3) |*/ 0x1)); + return IRQ_HANDLED; + } + + TRACE_MSG1(HCD, "host_sint: %08x", host_sint); + + while (HOST_DONEINT & host_sint) { + u32 etds_done; + + BREAK_IF(loop_count++ > 100); + + TRACE_MSG1(HCD,"HOST_DONEINT: %08x", fs_rl(OTG_HOST_EP_DSTAT)); + fs_wl(OTG_HOST_SINT_STAT, HOST_DONEINT); + host_sint = fs_rl(OTG_HOST_SINT_STAT) & mxc_hcd->int_mask; + + /* while DSTAT is non-zero call finish urb on highest bit set + */ + while ((etds_done = fs_rl(OTG_HOST_EP_DSTAT))) { + int etdn = fls(etds_done) - 1; + struct mxc_req *mxc_req; + + mxc_req = mxc_hcd->active[etdn]; + + BREAK_IF(loop_count++ > 100); + + UNLESS(mxc_req) { + printk(KERN_INFO"%s: ERROR NO REQUEST %d etds_done: %08x etdn: %d\n", + __FUNCTION__, num_host_interrupts, etds_done, fls(etds_done) - 1); + fs_wl(OTG_HOST_EP_DSTAT, ETD_MASK(etdn)); + continue; + } + mxc_hcd_finish_req_irq(mxc_hcd, mxc_req, mxc_req->urb, FALSE); + } + /* assuming something was done, we may be able to start + * something if there is anything to start... + */ + mxc_hcd_schedule_irq(mxc_hcd); + } + + host_sint = fs_rl(OTG_HOST_SINT_STAT) & mxc_hcd->int_mask; + + /* Start Of Frame. + * Note: this interrupt seems to happen even if disabled when there is a frame number overflow. + */ + if (host_sint & HOST_SOFINT) { + + mxc_hcd->sof_count += 1; + + TRACE_MSG2(HCD, "HOST_SOFINT: %d sof_count: %d", fs_rl(OTG_HOST_FRM_NUM), mxc_hcd->sof_count); + + if (0 == (mxc_hcd->sof_count & (4096 - 1))) { + TRACE_MSG1(HCD, "SOF %08lx", mxc_hcd->sof_count); + } + } + + /* Frame Number Overflow. + */ + if (host_sint & HOST_FMOFINT) + TRACE_MSG0(HCD,"Frame Number Overflow"); + + /* Port Status Change. + */ + if (host_sint & HOST_PSCINT) + mxc_hcd_rh_int_hndlr(mxc_hcd); + + /* Overrun + */ + if (host_sint & (HOST_SORINT | HOST_HERRINT)) { + if (host_sint & HOST_SORINT) + TRACE_MSG0(HCD,"Scheduling Overrun"); + if (host_sint & HOST_HERRINT) + TRACE_MSG0(HCD,"Host Scheduling Error"); + } + /* Resume Detected. + */ + if (host_sint & HOST_RESDETINT) + TRACE_MSG0(HCD,"Resume Detected"); + + /* Clear by writing back the ones we've checked for since the last read. + */ + fs_wl(OTG_HOST_SINT_STAT, host_sint & mxc_hcd->int_mask); + } +#if 1 + set_bit(HCD_FLAG_SAW_IRQ, &hcd->flags); + + if (unlikely(hcd->state == HC_STATE_HALT)) + usb_hc_died (hcd); +#endif + + + return IRQ_HANDLED; +} diff --git a/drivers/otg/hardware/mxc-hcd.h b/drivers/otg/hardware/mxc-hcd.h new file mode 100644 index 000000000000..396bc8f18439 --- /dev/null +++ b/drivers/otg/hardware/mxc-hcd.h @@ -0,0 +1,294 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/hardware/mxc-hcd.h - Freescale USBOTG aware Host Controller Driver (HCD) + * @(#) balden@belcarra.com/seth2.rillanon.org|otg/platform/mxc/mxc-hcd.h|20070614183949|39353 + * + * Copyright (c) 2004-2005 Belcarra Technologies + * Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + * + * By: + * Stuart Lynne <sl@belcarra.com>, + * Bruce Balden <balden@belcarra.com> + * Tony Tang <tt@belcarra.com> + */ +/*! + * @file otg/hardware/mxc-hcd.h + * @brief Freescale USB Host Controller Driver + * + * This is a complete and self contained Linux 2.6 USB Host Driver. + * + * It also conforms to the requirements for use as an OTG HCD driver + * in the Belcarra OTG Stack. + * + * The hardware is an integrated design, so it also works in conjunction + * with the mxc OCD and PCD drivers to share the hardware under the direction + * of the OTG State Machine. + * + * + * @ingroup FSOTG + */ + +/* ********************************************************************************************* */ + +/*! Data Structures Overview + * + * Urbs point at: + * + * struct urb { + * struct usb_device *udev; + * } + * + * Udev points at hdev (stuct hcd_dev) via: + * + * struct usb_device { + * void *hcpriv; + * } + * + * Hdev is a simple structure for each "device" on the bus that the host has + * enumerated. It contains an array of void *ep[32] which contains pointers + * to active requests indexed using the endpoint number and direction. This + * allows us to find an active request using endpoint information provided + * by the hcd layer: + * + * struct hcd_dev { // usb_device.hcpriv points to this + * + * struct list_head dev_list; // on this hcd + * struct list_head urb_list; // pending on this dev + * void *ep[32]; // per-configuration hc/hcd state such as qh or ed + * } + * + * + * The host drivers are not allowed to use the urb->urb_list so we will keep + * our own list of inactive requests using the mxc_req structure. Note that + * we track the epnum (derived from endpoint information provided by the + * hcd layer) and etdn (the hardware slot number) for the request if it + * is schedule: + * + * struct mxc_req { + * struct urb *urb; + * struct list_head queue; + * + * int epnum; + * int etdn; + * }; + * + * + * + * The mxc_hcd structure contains: + * + * struct mxc_hcd { + * struct list_head unused; // unused requests + * struct list_head inactive; // in-active requests + * struct mxc_req *active[NUM_ETDS]; // active requests by ETD + * }; + * + * Requests are allocated on the fly when needed, but saved in the unused + * queue when finished. + * + * Requests that are pending are in the inactive queue. The scheduler will + * attempt to start any inactive requests when new requests are queued or + * old requests are finished. + * + * Requests can stay on the inactive queue for two reasons, first if there is already + * an active request for that device and endpoint (by verifying ep[] list is null) or + * if there are no available resources (x/y buffers or etd slots.) + * + * The mxc_hcd structure will have a list of NUM_ETD pointer to active + * requests. If the array is NULL then the ETD is not being used. The active + * array is indexed by the ETD (hardware) number that the request is + * scheduled on. + * + * + * The primary reason for the inactive queue is that while the TDI design + * handles all of the details about a transfer until it is complete, it can + * only handle a limited number (NUM_ETDS) of requests at a time. So if there + * are more requests to valid device/endpoint combinations than there are ETD's + * then some will have to wait. + * + * Generally the maximum number of ETD's that can be used for any device is 1 (for the + * control endpoint) plus the number of endpoints defined in the device's configuration + * descriptor. + * + * It may be necessary to de-activate previously started requests periodically + * if there is more work than can be handled. This will require three things: + * + * - setup of etd registers in mxc request + * - start mxc request based on etd in mxc request + * - code to de-activate by saving etd in mxc request and place at end of inactive list + * + * IFF we can safely stop the ETD then the above would safely get it restarted. + * + * It may be safest to stop ETD's from SOF interrupt when (presumably) there is + * no active tranfers. + * + */ + +/*! Driver Overview + * + * Data Structures + * ETD Management + * Transfer Request Management + * Data Buffer Management + * Data Tranfer Handling + * Real Root Hub Support + * MXC Host Interrupt Handler + * OTG Support Functions + * Linux 2.6 USB Device Support + * Linux 2.6 Device Driver Support + * Module init/exit + * + */ + +/* ********************************************************************************************* */ +/* ********************************************************************************************* */ +/*! Data Structures + */ + + +/*! + * @struct fs_data_buff + */ +typedef struct fs_data_buff { + u32 data[DATA_BUFF_SIZE/sizeof(u32)]; // Cannot safely write less than 32-bit quantities +} volatile fs_data_buff; + + +/*! + * @struct mxc_req + * + * This is used to queue urb's so that they can be performed when there + * are hardware resources available. + * + */ +struct mxc_req { + + struct urb *urb; // the urb for this request + struct list_head queue; // for inactive or unused queue + + int epnum; // array index for ep and active arrays + int etdn; // if scheduled this is the etd we are using + u8 etd_urb_state; // current state + + u32 remaining; + + endpoint_transfer_descriptor sdp_etd; // Setup data phase save area + fs_data_buff *x; + fs_data_buff *y; + + u32 setup_dma; + u32 transfer_dma; + + void *bounce_buffer; + void *bounce_addr; + void *transfer_buffer_save; +}; + + +/*! mxc_hcd + */ +#define MAX_HUB_PORTS 8 +#define MXC_MAX_USB_ADDRESS 8 +//#define OTG_PORT 2 +struct mxc_hcd { + struct usb_hcd hcd; + spinlock_t lock; + + struct device *dev; + + //u32 port1; + //u8 ctrl1; + //u8 ctrl2; + + //u8 otg_device_mask; + BOOL otg_port_enabled; + + u32 real_hub_port_change_status; + u32 real_port_change_status[MAX_HUB_PORTS]; + u32 virt_hub_port_change_status; + u32 virt_port_status[MAX_HUB_PORTS]; + + u32 change_data; + + struct OLD_WORK_STRUCT rh_bh; + + BOOL suspended; + + u32 sof_count; + u32 int_mask; + + //int free_etds; + + u16 free_buffs; + u16 buff_list[NUM_DATA_BUFFS]; + + u32 root_hub_desc_a; + u32 root_hub_desc_b; + u8 bNbrPorts; + u8 wHubCharacteristics; + u8 bPwrOn2PwrGood; + u8 DeviceRemovable; + u8 PortPwrCtrlMask; + + struct list_head unused; // unused requests + struct list_head inactive; // in-active requests + struct mxc_req *active[NUM_ETDS]; // active requests by ETD + + // Statistics + int active_count; // number of active requests + int allocated_count; // number of allocated request structs + u32 request_count; // total number of enqueued requests + + //struct usb_device *first_dev; + void *ep[MXC_MAX_USB_ADDRESS + 1][32]; +}; + +#define OUT(o,e)((o && e) ? 1 : 0) + +#define EPQ_EMPTY 0 +#define EPQ_RUNNING 1 +#define EPQ_WAITING 2 +#define EPQ_NOTUSED 3 + +struct usbp_hub_descriptor { + __u8 bDescLength; + __u8 bDescriptorType; + __u8 bNbrPorts; + __u16 wHubCharacteristics; + __u8 bPwrOn2PwrGood; + __u8 bHubContrCurrent; + /* add 1 bit for hub status change; round to bytes */ + __u8 DeviceRemovable; + __u8 PortPwrCtrlMask; +} __attribute__ ((packed)); + + + +static inline struct mxc_hcd *hcd_to_mxc(struct usb_hcd *hcd) +{ + return container_of(hcd, struct mxc_hcd, hcd); +} + +/* convert epnum and out flag into index into 32 entry array */ + +#define EPNUM(e,o) (((e & 0xf) << 1) + o) + +#define ETD_URB_COMPLETED 0 +#define ETD_URB_SETUP_STATUS 1 +#define ETD_URB_SETUP_DATA 2 +#define ETD_URB_SETUP_START 3 +#define ETD_URB_BULK_START 4 +#define ETD_URB_BULKWZLP 5 +#define ETD_URB_BULKWZLP_START 6 +#define ETD_URB_INTERRUPT_START 7 +#define ETD_URB_ISOC_START 8 diff --git a/drivers/otg/hardware/mxc-hrt.c b/drivers/otg/hardware/mxc-hrt.c new file mode 100644 index 000000000000..fc642ea397bc --- /dev/null +++ b/drivers/otg/hardware/mxc-hrt.c @@ -0,0 +1,174 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/hardware/mxc-hrt.c -- Freescale High Resolution timer + * @(#) balden@belcarra.com/seth2.rillanon.org|otg/platform/mxc/mxc-hrt.c|20070614183950|63070 + * + * Copyright (c) 2005 Belcarra Technologies Corp + * Copyright (c) 2005-2007 Belcarra Technologies 2005 Corp + * + * By: + * Stuart Lynne <sl@belcarra.com>, + * Bruce Balden <balden@belcarra.com> + * Shahrad Payandeh <sp@belcarra.com> + * + */ + +/*! + * @file otg/hardware/mxc-hrt.c + * @brief Freecale GPT Timer implementation. + * + * Use the Linux High Resolution Timers to implement OTG Timer. + * + * @ingroup FSOTG + * @ingroup LINUXOS + * @ingroup OCD + * + */ + +#include <otg/pcd-include.h> + +#if defined (CONFIG_OTG_HRT) || defined(_OTG_DOXYGEN) + +#include <linux/pci.h> +#include <linux/hrtimer.h> +#include <asm/arch/gpio.h> +#include <asm/system.h> +#include <asm/io.h> +#include <asm/irq.h> +#include "mxc-lnx.h" +#include "mxc-hardware.h" + +/* ********************************************************************************************* */ +static struct hrtimer g_mxc_hrt_timer; /*hrt timer */ +static struct otg_instance *g_otg; /* save the OTG instance */ +static otg_tick_t g_mxc_hrt_divisor = 0; +static otg_tick_t g_mxc_hrt_multiplier = 0; +static BOOL g_mxc_hr_active = FALSE; + +/*! + * mxc_hrt_callback() - called to queue an a timer event to otg queue + * @param arg - pointer of hrtimer type + */ +static int mxc_hrt_callback(struct hrtimer *arg) +{ + TRACE_MSG1(g_otg->ocd->TAG, "checking active: %d", g_mxc_hr_active); + if (!g_mxc_hr_active) { + return HRTIMER_NORESTART; + } + + g_mxc_hr_active = FALSE; + TRACE_MSG1(g_otg->ocd->TAG, "resetting active: %d", g_mxc_hr_active); + + otg_queue_event(g_otg, TMOUT, g_otg->ocd->TAG, "TMOUT"); + return HRTIMER_NORESTART; +} + +/*! + * mxc_hrt_start_timer() - start a timer for otg state machine + * Set or reset timer to interrupt in number of uS (micro-seconds). + * + * XXX There may be a floor or minimum that can be effectively set. + * XXX We have seen an occasional problem with US(25) for discharge for example. + * + * @param otg + * @param usec + * @return 0 on success + */ +int mxc_hrt_start_timer(struct otg_instance *otg, int usec) +{ + ktime_t expires; + + TRACE_MSG1(otg->ocd->TAG, "usec: %d", usec); + + g_mxc_hr_active = FALSE; + TRACE_MSG1(otg->ocd->TAG, "resetting active: %d", g_mxc_hr_active); + + hrtimer_cancel(&g_mxc_hrt_timer); + + if (usec == 0) { + goto cancel; + } + + g_mxc_hr_active = TRUE; + TRACE_MSG1(otg->ocd->TAG, "setting active: %d", g_mxc_hr_active); + + if (usec < 100) { + TRACE_MSG1(otg->ocd->TAG, "usec: %d set to minimum 100", usec); + usec = 100; + } + hrtimer_init(&g_mxc_hrt_timer, CLOCK_REALTIME, HRTIMER_REL); + g_mxc_hrt_timer.function = mxc_hrt_callback; + expires = ktime_set(usec / 1000000, (usec % 1000000) * 1000); + + hrtimer_start(&g_mxc_hrt_timer, expires, HRTIMER_REL); + + cancel: + return 0; +} + +/*! + * mxc_hrt_trace_ticks() - get current ticks + * GPT3 is setup as free running clock at 266 Mhz - 1/266 = .0037 + */ +otg_tick_t mxc_hrt_trace_ticks(void) +{ + struct timeval tv; + + do_gettimeofday(&tv); + return ((tv.tv_sec * USEC_PER_SEC) + tv.tv_usec); +} + +/*! + * mxc_hrt_trace_elapsed() - return micro-seconds between two tick values + * + * GPT3 is setup as free running clock at 266 Mhz - 1/266 = .003759 + * + * MPLL = 266, FCLK = 266, BCLK = 88 + * + */ +otg_tick_t mxc_hrt_trace_elapsed(otg_tick_t * t1, otg_tick_t * t2) +{ + otg_tick_t ticks = (*t1 > *t2) ? (*t1 - *t2) : (*t2 - *t1); + return ticks; +} + +/*! + * mxc_hrt_ocd_mod_init() - initial tcd setup + * Allocate interrupts and setup hardware. + * @param otg + * @param divisor + * @param multiplier + * @return 0 for finish, or other on error + */ +int mxc_hrt_mod_init(struct otg_instance *otg, int divisor, int multiplier) +{ + g_mxc_hrt_divisor = divisor; + g_mxc_hrt_multiplier = multiplier; + + hrtimer_init(&g_mxc_hrt_timer, CLOCK_REALTIME, HRTIMER_ABS); + g_mxc_hrt_timer.function = mxc_hrt_callback; + g_otg = otg; + + return 0; +} + +/*! + * mxc_hrt_mod_exit() - de-initialize + */ +void mxc_hrt_mod_exit(void) +{ + hrtimer_cancel(&g_mxc_hrt_timer); +} + +#endif //CONFIG_OTG_HRT diff --git a/drivers/otg/hardware/mxc-l26.c b/drivers/otg/hardware/mxc-l26.c new file mode 100644 index 000000000000..edf6872b53da --- /dev/null +++ b/drivers/otg/hardware/mxc-l26.c @@ -0,0 +1,1356 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/hardware/mxc-l26.c - Linux 2.6 Freescale USBOTG aware Host Controller Driver (HCD) + * @(#) tt/root@belcarra.com/debian286.bbb|otg/platform/mxc/mxc-l26.c|20070907214828|35671 + * + * Copyright (c) 2004-2005 Belcarra Technologies Corp + * Copyright (c) 2005-2007 Belcarra Technologies 2005 Corp + * + * By: + * Stuart Lynne <sl@belcarra.com> + * Bruce Balden <balden@belcarra.com> + * Tony Tang <tt@belcarra.com> + */ +/*! + * @file otg/hardware/mxc-l26.c + * @brief Freescale USB Host Controller Driver + * + * This is a complete and self contained Linux 2.6 USB Host Driver. + * + * It also conforms to the requirements for use as an OTG HCD driver + * in the Belcarra OTG Stack. + * + * The hardware is an integrated design, so it also works in conjunction + * with the mxc OCD and PCD drivers to share the hardware under the direction + * of the OTG State Machine. + * + * + * @ingroup FSOTG + * @ingroup LINUXOS + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> + +#include <linux/usb.h> +#include <linux/delay.h> + +#include <otg/otg-compat.h> + +#include <core/hcd.h> + +#include <otg/usbp-hub.h> +#include <otg/otg-trace.h> +#include <otg/otg-api.h> +#include <otg/otg-utils.h> +#include <otg/otg-tcd.h> + +#include <otg/otg-hcd.h> +#include <asm/arch/gpio.h> +#include "mxc-lnx.h" +#include "mxc-hardware.h" +#include <asm/memory.h> + +#include <linux/proc_fs.h> +#include <linux/seq_file.h> +#include <linux/platform_device.h> + +#include "mxc-hcd.h" + +/* ********************************************************************************************* */ +/* ********************************************************************************************* */ +extern struct usb_operations usb_hcd_operations; +extern void mxc_hcd_schedule_irq(struct mxc_hcd *mxc_hcd); +extern void mxc_hcd_finish_req_irq(struct mxc_hcd *mxc_hcd, struct mxc_req *mxc_req, struct urb *urb, int killed); +extern void mxc_host_clock_on(void); +extern void mxc_host_clock_off(void); +extern fs_data_buff *rel_data_buff(struct mxc_hcd *mxc_hcd, fs_data_buff *db); +extern void rel_etd_irq(struct mxc_hcd *mxc_hcd, int etdn); +extern void mxc_hcd_hw_rh_port_feature(struct mxc_hcd *mxc_hcd, u16 wValue, u16 wIndex, int set_flag); +extern char *port_feature_name[]; +int mxc_host_gpio (void); + +/* ********************************************************************************************* */ +/* ********************************************************************************************* */ + +/*! Transfer Request Management + */ + +/*! mxc_get_request - get unused mxc_request or allocate a new one + * @param mxc_hcd + * @return requested mxc_req + */ +struct mxc_req * mxc_get_request(struct mxc_hcd *mxc_hcd) +{ + struct mxc_req *mxc_req = NULL; + struct mxc_req *mxc_req_tmp = NULL; + unsigned long flags; + + /* attempt to find first entry in list */ + + local_irq_save(flags); + list_for_each_entry(mxc_req_tmp, &mxc_hcd->unused, queue) { + /* delete from list */ + mxc_req = mxc_req_tmp; + list_del(&mxc_req->queue); + break; + } + local_irq_restore(flags); + + /* otherwise attempt to allocate and initialize */ + + UNLESS(mxc_req) { + RETURN_NULL_UNLESS((mxc_req = CKMALLOC(sizeof(struct mxc_req)))); + INIT_LIST_HEAD(&mxc_req->queue); + local_irq_save(flags); + mxc_hcd->allocated_count++; + local_irq_restore(flags); + } + return mxc_req; +} + +/* ********************************************************************************************* */ +/* ********************************************************************************************* */ + +/*! mxc_hcd_urq_enqueue - enqueue an mxc request + * @param hcd + * @param ep + * @param urb + * @param mem_flags + * + * New Style L2.6.10 USB Core urb enqueue. + */ +int mxc_hcd_urb_enqueue(struct usb_hcd *hcd, struct usb_host_endpoint *ep, struct urb *urb, gfp_t mem_flags) +{ + struct mxc_hcd *mxc_hcd = hcd_to_mxc(hcd); + struct usb_device *udev = urb->dev; + //struct hcd_dev *hdev = (struct hcd_dev *) udev->hcpriv; + + //int dev_addr = usb_pipedevice(urb->pipe); + unsigned int pipe = urb->pipe; + int is_out = usb_pipein(pipe) ? 0 : 1; + int type = usb_pipetype(pipe); + int endpoint = usb_pipeendpoint(pipe); + unsigned long flags; + int address = usb_pipedevice(urb->pipe) ? (usb_pipedevice(urb->pipe) % MXC_MAX_USB_ADDRESS + 1) : 0; + + struct mxc_req *mxc_req = NULL; + + TRACE_MSG5(HCD, "hcd: %x urb: %x endpoint: %02x is_out: %d type: %0d", hcd, urb, endpoint, is_out, type); + + /* data address needs to be on a 32-bit boundary. */ + + RETURN_EPIPE_IF (0x3 & (u32)urb->transfer_buffer); + + /* get request */ + + RETURN_ENOMEM_UNLESS((mxc_req = mxc_get_request(mxc_hcd))); + + /* initialize request */ + + mxc_req->urb = urb; + mxc_req->etdn = -1; + mxc_req->etd_urb_state = EPQ_NOTUSED; + mxc_req->remaining = 0; + memset((void *)&mxc_req->sdp_etd, 0, sizeof(mxc_req->sdp_etd)); + mxc_req->setup_dma = mxc_req->transfer_dma = 0; + urb->hcpriv = mxc_req; + ep->hcpriv = (void *) pipe; + + /* XXX MXC DMA can only transfer full buffers, so we need to provide + * an intermediate buffer for it to use. + */ + mxc_req->bounce_buffer = NULL; + if (!is_out && (urb->transfer_buffer_length % 128)) { + + int length = ((urb->transfer_buffer_length + 128) / 128) * 128; + + TRACE_MSG2(HCD, "bounce buffer length: %d -> %d", urb->transfer_buffer_length, length); + + UNLESS((mxc_req->bounce_buffer = CKMALLOC(length + 32))) { + local_irq_save(flags); + list_add_tail(&mxc_req->queue, &mxc_hcd->unused); + local_irq_restore(flags); + spin_unlock(&urb->lock); + return -ENOMEM; + } + + mxc_req->transfer_buffer_save = urb->transfer_buffer; + mxc_req->bounce_addr = mxc_req->bounce_buffer; + urb->transfer_buffer = mxc_req->bounce_addr; + + //if (is_out) + // memcpy(mxc_req->bounce_addr, mxc_req->transfer_buffer_save, urb->transfer_buffer_length); + } + + /* map setup_packet and transfer_buffer */ + + if (urb->setup_packet) + mxc_req->setup_dma = dma_map_single ( mxc_hcd->hcd.self.controller, + urb->setup_packet, sizeof (struct usb_ctrlrequest), DMA_TO_DEVICE); + + if (urb->transfer_buffer && urb->transfer_buffer_length) + mxc_req->transfer_dma = dma_map_single ( mxc_hcd->hcd.self.controller, + urb->transfer_buffer, urb->transfer_buffer_length, + usb_pipein (urb->pipe) ? DMA_FROM_DEVICE : DMA_TO_DEVICE); + + + TRACE_MSG5(HCD, "setup_packet: %x %x transfer_buffer: %x %x %d", + urb->setup_packet, mxc_req->setup_dma, + urb->transfer_buffer, mxc_req->transfer_dma, + urb->transfer_buffer_length); + + /* in case of unlink-during-submit */ + + spin_lock(&urb->lock); + + if (urb->status != -EINPROGRESS) { + printk(KERN_INFO"%s: EINPROGRESS\n", __FUNCTION__); + TRACE_MSG2(HCD, "status != EINPROGRESS hcd: %x urb: %x", hcd, urb); + local_irq_save(flags); + list_add_tail(&mxc_req->queue, &mxc_hcd->unused); + local_irq_restore(flags); + spin_unlock(&urb->lock); + return -ENOMEM; + } + spin_unlock(&urb->lock); + + local_irq_save(flags); + mxc_hcd->request_count++; + list_add_tail(&mxc_req->queue, &mxc_hcd->inactive); + local_irq_restore(flags); + + /* if ep[EPNUM(endpoint,is_out)] is NULL then nothing is currently scheduled + * for this device to that endpoint + */ + UNLESS (mxc_hcd->ep[address][EPNUM(endpoint, is_out)]) { + TRACE_MSG2(HCD, "SCHEDULING hcd: %x urb: %x", hcd, urb); + local_irq_save(flags); + mxc_hcd_schedule_irq(mxc_hcd); + local_irq_restore(flags); + } + return 0; +} + +/* ********************************************************************************************* */ +/* ********************************************************************************************* */ + +/*! mxc_hcd_giveback_req - give an urb back to the hcd driver + * @param mxc_hcd + * @param mxc_req + * @param status + * + * This function will set the status of an urb IFF it is currently EINPROGRESS + * and then use usb_hcd_giveback_req() to pass control of the urb back to the + * hcd driver. + */ +void mxc_hcd_giveback_req_irq(struct mxc_hcd *mxc_hcd, struct mxc_req *mxc_req, int status) +{ + struct urb *urb = mxc_req->urb; + unsigned int pipe = urb->pipe; + int is_out = usb_pipeout(pipe); + + TRACE_MSG2(HCD, "urb: %x status: %x", mxc_req->urb, status); + + /* unmap setup_packet and transfer_buffer */ + + if (urb->setup_packet) + dma_unmap_single (mxc_hcd->hcd.self.controller, mxc_req->setup_dma, + sizeof (struct usb_ctrlrequest), DMA_TO_DEVICE); + + if (urb->transfer_buffer && urb->transfer_buffer_length) + dma_unmap_single (mxc_hcd->hcd.self.controller, + mxc_req->transfer_dma, urb->transfer_buffer_length, + usb_pipein (urb->pipe) ? DMA_FROM_DEVICE : DMA_TO_DEVICE); + + if (mxc_req->bounce_buffer) { + + if (!is_out) + memcpy(mxc_req->transfer_buffer_save, mxc_req->bounce_addr, urb->transfer_buffer_length); + + urb->transfer_buffer = mxc_req->transfer_buffer_save; + LKFREE(mxc_req->bounce_buffer); + mxc_req->bounce_buffer = NULL; + } + +#if 0 + if (urb->transfer_buffer) { + int i; + u8 *cp = urb->transfer_buffer; + + TRACE_MSG1(HCD, "NEXT TX: length: %d", urb->actual_length); + + for (i = 0; i < urb->actual_length; i+= 8) + + TRACE_MSG8(HCD, "BUF: %02x %02x %02x %02x %02x %02x %02x %02x", + cp[i + 0], cp[i + 1], cp[i + 2], cp[i + 3], + cp[i + 4], cp[i + 5], cp[i + 6], cp[i + 7] + ); + } +#endif + + + /* Call the upper layer completion routine. Decrement the ref count + * (release our interest in this urb). + */ + if (urb->status == -EINPROGRESS) urb->status = status; + + urb->hcpriv = NULL; + usb_hcd_giveback_urb(&mxc_hcd->hcd, mxc_req->urb, NULL /* XXX */); + list_add_tail(&mxc_req->queue, &mxc_hcd->unused); +} + + +/*! mxc_hcd_urb_dequeue - + * @param hcd + * @param urb + * @return error if not found + * + * Used by hcd driver to dequeue a previously enqueued urb that has not + * completed. + * + * This should find and stop the request and then use the giveback + * procedure to pass control of the urb back to the hcd layer. + */ +int mxc_hcd_urb_dequeue(struct usb_hcd *hcd, struct urb *urb) +{ + struct mxc_hcd *mxc_hcd = hcd_to_mxc(hcd); + struct mxc_req *mxc_req; + struct mxc_req *mxc_req_save; + + unsigned long flags; + int etdn; + + unsigned int pipe = urb->pipe; + int is_out = usb_pipeout(pipe); + int type = usb_pipetype(pipe); + int endpoint = usb_pipeendpoint(pipe); + + TRACE_MSG5(HCD, "hcd: %x urb: %x endpoint: %02x is_out: %d type: %0d", hcd, urb, endpoint, is_out, type); + + // XXX + // mxc_req = urb->hcpriv; + + local_irq_save(flags); + + /* search through inactive list */ + + list_for_each_entry_safe(mxc_req, mxc_req_save, &mxc_hcd->inactive, queue) { + CONTINUE_IF(mxc_req->urb != urb); + + TRACE_MSG1(HCD, "urb: %x found inactive", urb); + + /* found it - delete from active list, restore irq, giveback and return */ + + list_del(&mxc_req->queue); + mxc_hcd_giveback_req_irq(mxc_hcd, mxc_req, 0); // check if 0 is correct status to return + local_irq_restore(flags); + return 0; + } + + /* search through active array */ + + for (etdn = 0; etdn < NUM_ETDS; etdn++) { + mxc_req = mxc_hcd->active[etdn]; + CONTINUE_UNLESS(mxc_req); + CONTINUE_IF(mxc_req->urb != urb); + + /* found it - finish it (and giveback), restore irq and return */ + TRACE_MSG1(HCD, "urb: %x found active", urb); + + mxc_hcd_finish_req_irq(mxc_hcd, mxc_req, mxc_req->urb, TRUE); // XXX This should work - need to test + local_irq_restore(flags); + return 0; + } + + + /* didn't find it - restore irq, return error */ + + TRACE_MSG1(HCD, "urb: %x not found", urb); + local_irq_restore(flags); + + return -EINVAL; +} + +/* ********************************************************************************************* */ +/* ********************************************************************************************* */ + +/*! OTG Support Functions + */ + +/*! mxc_hcd_bh_init - MXC Hardware bottom half and OTG Init + * Called as a "bottom-half" to do usbcore registration once all the HW is usable. + * @param arg - hacd instance + */ +static void mxc_hcd_bh_init(void *arg) +{ + struct hcd_instance *hcd = (struct hcd_instance *) arg; + otg_event(hcd->otg, HCD_OK, HCD, "HCD_INIT SET HCD_OK"); + TRACE_MSG0(HCD,"finished"); +} + +static void mxc_hcd_bh_exit(void *arg) +{ + struct hcd_instance *hcd = (struct hcd_instance *) arg; + otg_event(hcd->otg, HCD_OK, HCD, "HCD_INIT RESET (EXIT) HCD_OK"); +} + + +/*! hcd_init_func - per host controller common initialization + * + * This is called to initialize / de-initialize the HCD, all except the last + * stage of registering the root hub, because that needs to wait until rh_hcd_en_func() + * + * We start work items to do this. + * @param otg - otg instance + * @param flag - initialize /de-initialize flag + * + */ +void hcd_init_func (struct otg_instance *otg, u8 flag) +{ + struct hcd_instance *hcd = otg->hcd; + struct mxc_hcd *mxc_hcd = hcd ? hcd->privdata : NULL; + + switch (flag) { + case SET: + // Schedule BH for mxc_hcd_bh_init... + TRACE_MSG0(HCD, "HCD_INIT: SET"); + //PREPARE_WORK_ITEM(hcd->bh, mxc_hcd_bh_init, hcd); + //SCHEDULE_WORK(hcd->bh); + mxc_hcd_bh_init((void *)hcd); + TRACE_MSG0(HCD, "mxc_hcd_bh_init() schedule finished"); + break; + + case RESET: + TRACE_MSG0(HCD, "HCD_INIT: RESET"); + //PREPARE_WORK_ITEM(hcd->bh, mxc_hcd_bh_exit, hcd); + //SCHEDULE_WORK(hcd->bh); + mxc_hcd_bh_exit((void *)hcd); + break; + } +} + +/*! + * mxc_hcd_en_func() - otg hcd enable output function + * @param otg + * @param flag + */ +void mxc_hcd_en_func(struct otg_instance *otg, u8 flag) +{ + struct hcd_instance *hcd = otg->hcd; + struct mxc_hcd *mxc_hcd = hcd ? hcd->privdata : NULL; + struct tcd_instance *tcd = otg->tcd; + int i; + u32 hwmode = fs_rl(OTG_CORE_HWMODE); + unsigned long flags; + + /* puts OTG capable port into a state where host is enabled */ + + local_irq_save(flags); + //fs_andl(OTG_CORE_HWMODE, 0xfffffff0); // clear + + switch (flag) { + case SET: + TRACE_MSG0(HCD, "SET"); + //if (hwmode & MODULE_CRECFG_HOST) { + // printk(KERN_INFO"%s: warning FUNC STILL SET\n", __FUNCTION__); + //} + //printk(KERN_INFO"%s: SET\n", __FUNCTION__); + fs_orl(OTG_CORE_HWMODE, MODULE_CRECFG_HOST); // set to software hnp + hcd_instance->active = TRUE; + if (!tcd->id) { + TRACE_MSG1(HCD, "FRM_INTRVL: %8x current", fs_rl(OTG_CORE_FRM_INTVL)); + //fs_wl(OTG_CORE_FRM_INTVL, 4000 | MODULE_RESET_FRAME); + TRACE_MSG1(HCD, "FRM_INTRVL: %8x reset", fs_rl(OTG_CORE_FRM_INTVL)); + + } + + break; + case RESET: + TRACE_MSG0(HCD, "RESET"); + hcd_instance->active = FALSE; + fs_andl(OTG_CORE_HWMODE, ~MODULE_CRECFG_HOST); // set to software hnp + break; + } + + local_irq_restore(flags); + TRACE_MSG1(HCD, "HWMODE: %08x", fs_rl(OTG_CORE_HWMODE)); +} + +u32 otg_core_frm_intvl; + +/*! + * mxc_hcd_rh_func() - otg hcd root hub output function + * @param otg + * @param flag + */ +void mxc_hcd_rh_func(struct otg_instance *otg, u8 flag) +{ + struct hcd_instance *hcd = otg->hcd; + struct tcd_instance *tcd = otg->tcd; + struct mxc_hcd *mxc_hcd = hcd ? hcd->privdata : NULL; + //printk(KERN_INFO"%s:\n", __FUNCTION__); + int i; + u32 mask; + unsigned long flags; + + RETURN_UNLESS (hcd && mxc_hcd); + //printk(KERN_INFO"%s:\n", __FUNCTION__); + + switch (flag) { + case SET: + //printk(KERN_INFO"%s: SET\n", __FUNCTION__); + + local_irq_save(flags); + + /* reset the host core + */ + fs_wl_clr(HCD, OTG_CORE_RST_CTRL, MODULE_RSTRH | MODULE_RSTHSIE | MODULE_RSTHC); + while (fs_rl(OTG_CORE_RST_CTRL)); + + mxc_host_clock_on(); + + + for (i = 0; i < NUM_DATA_BUFFS; i++) + (void) rel_data_buff(mxc_hcd, ((fs_data_buff *)OTG_DATA_BASE)+i); +#if 0 + for (i = 0; i < NUM_ETDS; i++) + rel_etd_irq(mxc_hcd,i); +#endif + fs_wl(OTG_HOST_CONTROL, HOST_CONTROL_HCRESET | HOST_CONTROL_RMTWUEN | + HOST_CONTROL_HCUSBSTE_RESET | HOST_CONTROL_CTLBLKSR_11); + + + TRACE_MSG0(HCD, "HW HCD_ENABLE_SET"); + fs_andl(OTG_CORE_HNP_CSTAT, ~(MODULE_MASTER | MODULE_SLAVE | MODULE_CMPEN | + MODULE_BGEN | MODULE_SWAUTORST | MODULE_ABBUSREQ)); + + fs_rl(OTG_CORE_HNP_CSTAT); + + fs_orl(OTG_CORE_HNP_CSTAT, MODULE_MASTER | MODULE_CMPEN | MODULE_BGEN | MODULE_ABBUSREQ); + fs_orl(OTG_CORE_HNP_CSTAT, MODULE_ARMTHNPE | MODULE_BHNPEN); // XXX + + fs_wl(OTG_HOST_CONTROL, HOST_CONTROL_HCUSBSTE_OPERATIONAL); + + //hcd_hw_enable_interrupts(bus_hcpriv); + + mxc_hcd->int_mask = (HOST_PSCINT_EN | HOST_FMOFINT_EN | HOST_HERRINT_EN | + HOST_RESDETINT_EN | /* HOST_SOFINT_EN | */ HOST_DONEINT_EN | HOST_SORINT_EN); + + TRACE_MSG1(HCD, "HW HCD_ENABLE_SET: mask: %02x", mxc_hcd->int_mask); + + #if 1 + // R1: sec 23.11.15 pg 23-54 + fs_rl(OTG_CORE_HNP_CSTAT); + fs_rl(OTG_HOST_CONTROL); + fs_rl(OTG_HOST_ROOTHUB_STATUS); + fs_rl(OTG_HOST_PORT_STATUS_1); + fs_rl(OTG_HOST_PORT_STATUS_2); + fs_rl(OTG_HOST_PORT_STATUS_3); + + fs_wl(OTG_HOST_SINT_STEN, mxc_hcd->int_mask); + fs_rl(OTG_HOST_SINT_STEN); + + mxc_hcd->otg_port_enabled = TRUE; + //printk(KERN_INFO"%s: is_b_host FALSE\n", __FUNCTION__); + + // XXX need to get ID_GND + + mxc_hcd->hcd.self.is_b_host = !tcd->id; + + if (!tcd->id) + fs_orl(OTG_CORE_HNP_CSTAT, MODULE_SWAUTORST); // XXX + + for (i = 1; i <= mxc_hcd->bNbrPorts; i++) { + mxc_hcd_hw_rh_port_feature(mxc_hcd, PORT_POWER, i, TRUE); + //mxc_hcd->hub_port_change_status |= (1 << i); + } + + #endif + local_irq_restore(flags); + + break; + + case RESET: + + mxc_hcd->otg_port_enabled = FALSE; // XXX check this + mxc_hcd->hcd.self.is_b_host = TRUE; + //printk(KERN_INFO"%s: is_b_host TRUE\n", __FUNCTION__); + + #if 0 + local_irq_save(flags); + + //printk(KERN_INFO"%s: RESET\n", __FUNCTION__); + TRACE_MSG0(HCD, "HW HCD_ENABLE_RESET"); + + //hcd_hw_disable_interrupts(bus_hcpriv); + // R1: sec 23.11.15 pg 23-54 + fs_wl(OTG_HOST_SINT_STAT, 0); + + fs_andl(OTG_HOST_CONTROL, ~HOST_CONTROL_HCUSBSTE_OPERATIONAL); + + // Shut down hardware if not already shut down.... + //hcd_hw_disable_interrupts(bus_hcpriv); + + mxc_host_clock_off(); + local_irq_restore(flags); + #endif + break; + } +} + +/*! + * mxc_loc_sof_func() - otg loc sof output function + * @param otg + * @param flag + */ +void mxc_loc_sof_func(struct otg_instance *otg, u8 flag) +{ + struct hcd_instance *hcd = otg->hcd; + struct mxc_hcd *mxc_hcd = hcd ? hcd->privdata : NULL; + //struct tcd_instance *tcd = otg->tcd; + int i; + u32 hwmode = fs_rl(OTG_CORE_HWMODE); + unsigned long flags; + + RETURN_UNLESS (hcd && mxc_hcd); + //printk(KERN_INFO"%s:\n", __FUNCTION__); + + switch (flag) { + case SET: + //printk(KERN_INFO"%s: SET\n", __FUNCTION__); + + local_irq_save(flags); + + #if 0 + /* reset the host core + */ + fs_wl_clr(HCD, OTG_CORE_RST_CTRL, MODULE_RSTRH | MODULE_RSTHSIE | MODULE_RSTHC); + while (fs_rl(OTG_CORE_RST_CTRL)); + + mxc_host_clock_on(); + + for (i = 0; i < NUM_DATA_BUFFS; i++) + (void) rel_data_buff(mxc_hcd, ((fs_data_buff *)OTG_DATA_BASE)+i); + + for (i = 0; i < NUM_ETDS; i++) + rel_etd_irq(mxc_hcd,i); + + fs_wl(OTG_HOST_CONTROL, HOST_CONTROL_HCRESET | HOST_CONTROL_RMTWUEN | + HOST_CONTROL_HCUSBSTE_RESET | HOST_CONTROL_CTLBLKSR_11); + + + TRACE_MSG0(HCD, "HW HCD_ENABLE_SET"); + fs_andl(OTG_CORE_HNP_CSTAT, ~(MODULE_MASTER | MODULE_SLAVE | MODULE_CMPEN | + MODULE_BGEN | MODULE_SWAUTORST | MODULE_ABBUSREQ)); + fs_rl(OTG_CORE_HNP_CSTAT); + + fs_orl(OTG_CORE_HNP_CSTAT, MODULE_MASTER | MODULE_CMPEN | MODULE_BGEN | MODULE_ABBUSREQ); + fs_orl(OTG_CORE_HNP_CSTAT, MODULE_ARMTHNPE | MODULE_BHNPEN); // XXX + + fs_wl(OTG_HOST_CONTROL, HOST_CONTROL_HCUSBSTE_OPERATIONAL); + + //hcd_hw_enable_interrupts(bus_hcpriv); + + mxc_hcd->int_mask = (HOST_PSCINT_EN | HOST_FMOFINT_EN | HOST_HERRINT_EN | + HOST_RESDETINT_EN | /* HOST_SOFINT_EN | */ HOST_DONEINT_EN | HOST_SORINT_EN); + + TRACE_MSG1(HCD, "HW HCD_ENABLE_SET: mask: %02x", mxc_hcd->int_mask); + + + // R1: sec 23.11.15 pg 23-54 + fs_rl(OTG_CORE_HNP_CSTAT); + fs_rl(OTG_HOST_CONTROL); + fs_rl(OTG_HOST_ROOTHUB_STATUS); + fs_rl(OTG_HOST_PORT_STATUS_1); + fs_rl(OTG_HOST_PORT_STATUS_2); + fs_rl(OTG_HOST_PORT_STATUS_3); + + fs_wl(OTG_HOST_SINT_STEN, mxc_hcd->int_mask); + fs_rl(OTG_HOST_SINT_STEN); + + mxc_hcd->otg_port_enabled = TRUE; + + for (i = 1; i <= mxc_hcd->bNbrPorts; i++) { + mxc_hcd_hw_rh_port_feature(mxc_hcd, PORT_POWER, i, TRUE); + //mxc_hcd->hub_port_change_status |= (1 << i); + } + + #endif + local_irq_restore(flags); + + break; + + case RESET: + local_irq_save(flags); + + //printk(KERN_INFO"%s: RESET\n", __FUNCTION__); + TRACE_MSG0(HCD, "HW HCD_ENABLE_RESET"); + + //hcd_hw_disable_interrupts(bus_hcpriv); + // R1: sec 23.11.15 pg 23-54 + fs_wl(OTG_HOST_SINT_STAT, 0); + + fs_andl(OTG_HOST_CONTROL, ~HOST_CONTROL_HCUSBSTE_OPERATIONAL); + + // Shut down hardware if not already shut down.... + //hcd_hw_disable_interrupts(bus_hcpriv); + + mxc_host_clock_off(); + local_irq_restore(flags); + break; + } + +} +/*! mxc_hcd_loc_suspend_func - + * @param otg + * @param on + */ + +void mxc_hcd_loc_suspend_func(struct otg_instance *otg, u8 on) +{ + struct hcd_instance *hcd = otg->hcd; + struct mxc_hcd *mxc_hcd = hcd ? hcd->privdata : NULL; + int i; + unsigned long flags; + + RETURN_UNLESS (hcd && mxc_hcd); + + switch (on) { + case SET: + TRACE_MSG0(HCD, "OUTPUT: RH LOC_SUSPEND SET"); + break; + + case RESET: + TRACE_MSG0(HCD, "OUTPUT: RH LOC_SUSPEND RESET"); + break; + } +} + +/* ********************************************************************************************* */ +/* ********************************************************************************************* */ + +/*! + * mxc_pcd_framenum() - get current framenum + * @param otg - otg instance + */ +static u16 +mxc_hcd_framenum (struct otg_instance *otg) +{ + //printk(KERN_INFO"%s: framenum: %04x\n", __FUNCTION__, fs_rl(OTG_HOST_FRM_NUM) ); + return fs_rl(OTG_HOST_FRM_NUM); +} + + + +#if !defined(OTG_C99) +#if defined(LINUX26) +int mxc_hcd_mod_init_l26(struct otg_instance *otg); +void mxc_hcd_mod_exit_l26(struct otg_instance *otg); +struct hcd_ops hcd_ops = { mxc_hcd_mod_init_l26, mxc_hcd_mod_exit_l26, }; +#else /* defined(LINUX26) */ +int mxc_hcd_mod_init_l24(struct otg_instance *otg); +void mxc_hcd_mod_exit_l24(struct otg_instance *otg); +struct hcd_ops hcd_ops = { mxc_hcd_mod_init_l24, mxc_hcd_mod_exit_l24, }; +#endif /* defined(LINUX26) */ + +/*! + * fs_hcd_global_init() - initialize global vars for non C99 systems + */ +void fs_hcd_global_init(void) +{ + //printk(KERN_INFO"%s:\n", __FUNCTION__); + + ZERO(hcd_ops); + hcd_ops.name = "MX21 HCD"; + hcd_ops.max_ports = 1; + hcd_ops.capabilities = 0; + // module + hcd_ops.mod_init = mxc_hcd_mod_init; // called for module init +#ifdef MODULE + hcd_ops.mod_exit = mxc_hcd_mod_exit; // called for module exit +#endif + // otg state machine + hcd_ops.hcd_init_func = hcd_init_func; // initialize when otg enabled + hcd_ops.hcd_en_func = mxc_hcd_en_func; // setup hardware as host + hcd_ops.hcd_rh_func = mxc_hcd_rh_func; // start root hub + hcd_ops.loc_suspend_func = mxc_hcd_loc_suspend_func; // enable port on hub + hcd_ops.loc_sof_func = mxc_loc_sof_func; // enable port on hub + hcd_ops.framenum = mxc_hcd_framenum; +}; + +/* ********************************************************************************************* */ +#else /* !defined(OTG_C99) */ +int mxc_hcd_mod_init_l26(struct otg_instance *otg); +void mxc_hcd_mod_exit_l26(struct otg_instance *otg); +struct hcd_ops hcd_ops = { + .mod_init = mxc_hcd_mod_init_l26, // called for module init +#ifdef MODULE + .mod_exit = mxc_hcd_mod_exit_l26, // called for module exit +#endif + + .name = "MX21 HCD", + .max_ports = 1, + .capabilities = 0, + // module + .hcd_init_func = hcd_init_func, // initialize when otg enabled + .hcd_en_func = mxc_hcd_en_func, // setup hardware as host + .hcd_rh_func = mxc_hcd_rh_func, // start root hub + .loc_suspend_func = mxc_hcd_loc_suspend_func, // enable port on hub + .loc_sof_func = mxc_loc_sof_func, // enable port on hub + .framenum = mxc_hcd_framenum, +}; +#endif /* !defined(OTG_C99) */ + + +/* ********************************************************************************************* */ +/* ********************************************************************************************* */ +/*! Linux 2.6 USB Device Support + * New style linux 2.6.10 USB Core support + */ + +/* + * From drivers/usb/host/hcd.c: + * disables the endpoint: cancels any pending urbs, then synchronizes with + * the hcd to make sure all endpoint state is gone from hardware. use for + * set_configuration, set_interface, driver removal, physical disconnect. + */ +/*! mxc_hcd_endpoint_disable - + * @param hcd - usb_hcd instance + * @param ep - endpoint + */ +void mxc_hcd_endpoint_disable(struct usb_hcd *hcd, struct usb_host_endpoint *ep) +{ + struct mxc_hcd *mxc_hcd = hcd_to_mxc(hcd); + struct mxc_req *mxc_req; + unsigned long flags; + int is_out = (ep->desc.bEndpointAddress & USB_DIR_IN) ? 0 : 1; + unsigned int pipe = (unsigned int) ep->hcpriv; + int address = usb_pipedevice(pipe) ? + (usb_pipedevice(pipe) % MXC_MAX_USB_ADDRESS + 1) : 0; + TRACE_MSG0(HCD, "--"); + local_irq_save(flags); + + if ((mxc_req = mxc_hcd->ep[address][EPNUM(ep->desc.bEndpointAddress, is_out)])) { + //printk(KERN_INFO"%s: disabled\n", __FUNCTION__); + rel_etd_irq(mxc_hcd, mxc_req->etdn); + mxc_hcd->ep[address][EPNUM(ep->desc.bEndpointAddress, is_out)] = NULL; + + // XXX giveback? + mxc_hcd_giveback_req_irq(mxc_hcd, mxc_req, 0); // check if 0 is correct status to return + } + + local_irq_restore(flags); +} + +int mxc_rh_frame(struct usb_hcd *hcd) +{ + struct mxc_hcd *mxc_hcd = hcd_to_mxc(hcd); + TRACE_MSG0(HCD, "--"); + return fs_rl(OTG_HOST_FRM_NUM); +} + +/*! mxc_rh_descriptor -get root hub descriptor + * @param mxc_hcd - mxc_hcd instance + * @param desc - root hub descriptor pointer + */ +static void +mxc_rh_descriptor ( struct mxc_hcd *mxc_hcd, struct usbp_hub_descriptor *desc) +{ + u16 temp = 0; + + desc->bDescLength = 9; + desc->bDescriptorType = 0x29; + + desc->bNbrPorts = mxc_hcd->bNbrPorts; + desc->wHubCharacteristics = mxc_hcd->wHubCharacteristics; + desc->bPwrOn2PwrGood = mxc_hcd->bPwrOn2PwrGood; + desc->bHubContrCurrent = 0; + desc->DeviceRemovable = mxc_hcd->DeviceRemovable; + desc->PortPwrCtrlMask = mxc_hcd->PortPwrCtrlMask; +} + + +/*! mxc_rh_status_data() - provide hub status data to usb core root hub + * + * This function is used by the linux 2.6 virtual root hub to poll for + * changes. + * @param hcd - usb_hcd instance + * @param buf - + * + */ +int mxc_rh_status_data(struct usb_hcd *hcd, char *buf) +{ + struct mxc_hcd *mxc_hcd = hcd_to_mxc(hcd); + + RETURN_ZERO_UNLESS(mxc_hcd->otg_port_enabled); + RETURN_ZERO_UNLESS(mxc_hcd->virt_hub_port_change_status); + + *buf = mxc_hcd->virt_hub_port_change_status; + mxc_hcd->virt_hub_port_change_status = 0; + + TRACE_MSG1(HCD, "changed buf: %02x", *buf); + return 1; +} +/*! mxc_rh_control - called to process control endpoint request + * + * @param hcd - + * @param typeReq + * @param wValue + * @param wIndex + * @param buf + * @param wLength + * @return + */ +int mxc_rh_control( struct usb_hcd *hcd, u16 typeReq, u16 wValue, u16 wIndex, char *buf, u16 wLength) +{ + struct mxc_hcd *mxc_hcd = hcd_to_mxc(hcd); + unsigned long flags; + u32 status; + + TRACE_MSG4(HCD, "typeReq: %04x wValue: %04x wIndex: %04x wLength: %04x", typeReq, wValue, wIndex, wLength); + + switch (typeReq) { + case ClearHubFeature: + case SetHubFeature: + TRACE_MSG0(HCD, "ClearHubFeature/SetHubFeature"); + //mxc_rh_hub_feature(mxc_hcd, wValue, wIndex, typeReq == SetHubFeature); + return 0; + + case SetPortFeature: { + u32 virt_port_status; + TRACE_MSG0(HCD, "SetPortFeature"); + RETURN_EPIPE_IF (wIndex > mxc_hcd->bNbrPorts || wLength); + virt_port_status = mxc_hcd->virt_port_status[wIndex - 1]; + /* Feature selector is wValue, wIndex is 1-origin + */ + // SET feature + switch (wValue) { + case PORT_RESET: + mxc_hcd->virt_port_status[wIndex - 1] |= (1 << C_PORT_RESET | (1 << wValue)); + mxc_hcd->virt_hub_port_change_status |= (1 << wIndex); + break; + case PORT_SUSPEND: + case PORT_POWER: + mxc_hcd->virt_port_status[wIndex - 1] |= (1 << wValue); + break; + } + TRACE_MSG3(HCD, "[%d]: %08x -> %08x", wIndex, virt_port_status, mxc_hcd->virt_port_status[wIndex -1]); + //printk(KERN_INFO"%s: SetPortFeature[%d]: wValue: %d %08x -> %08x %s\n", + // __FUNCTION__, wIndex, wValue, virt_port_status, mxc_hcd->virt_port_status[wIndex -1], + // port_feature_name[wValue]); + return 0; + } + case ClearPortFeature: { + u32 virt_port_status; + TRACE_MSG0(HCD, "ClearPortFeature"); + RETURN_EPIPE_IF ((wIndex > mxc_hcd->bNbrPorts) || wLength); + virt_port_status = mxc_hcd->virt_port_status[wIndex - 1]; + /* Feature selector is wValue, wIndex is 1-origin + */ + // CLEAR feature (valid features from USB2.0 11.24.2.2 pg 423). + switch (wValue) { + case PORT_ENABLE: // Disable port. + case PORT_SUSPEND: // Cause a Host initiated resume, or no-op if already active. + case PORT_POWER: // Put port in powered-off state. + case C_PORT_CONNECTION: // clear the PORT_CONNECTION change bit + case C_PORT_RESET: // clear the PORT_RESET change bit + case C_PORT_ENABLE: // clear the PORT_ENABLE change bit + case C_PORT_SUSPEND: // clear the PORT_SUSPEND change bit + case C_PORT_OVER_CURRENT: // clear the PORT_OVERCURRENT change bit + mxc_hcd->virt_port_status[wIndex - 1] &= ~(1 << wValue); + break; + } + TRACE_MSG3(HCD, "[%d]: %08x -> %08x", wIndex, virt_port_status, mxc_hcd->virt_port_status[wIndex -1]); + //printk(KERN_INFO"%s: ClearPortFeature[%d]: wValue: %d %08x -> %08x %s\n", + // __FUNCTION__, wIndex, wValue, virt_port_status, mxc_hcd->virt_port_status[wIndex -1], + // port_feature_name[wValue]); + return 0; + } + + case GetHubDescriptor: + //printk(KERN_INFO"%s: GetHubDescriptor\n", __FUNCTION__); + TRACE_MSG0(HCD, "GetHubDescriptor"); + mxc_rh_descriptor(mxc_hcd, (struct usbp_hub_descriptor *) buf); + return 0; + + case GetHubStatus: + //printk(KERN_INFO"%s: GetHubStatus\n", __FUNCTION__); + //*(__le32 *) buf = cpu_to_le32(0); // XXX this seems to be de rigeur for 2.6 root hubs... + *(__le32 *) buf = cpu_to_le32(fs_rl(OTG_HOST_ROOTHUB_STATUS) & 0xffff); + TRACE_MSG1(HCD, "GetHubStatus: %04x", *(__le32 *)buf); + return 0; + + case GetPortStatus: + //printk(KERN_INFO"%s: GetPortStatus\n", __FUNCTION__); + TRACE_MSG0(HCD, "GetPortStatus"); + RETURN_EPIPE_IF ((wIndex > mxc_hcd->bNbrPorts)); + /* use saved port change status and reset it + */ + status = mxc_hcd->virt_port_status[wIndex - 1]; // | fs_rl(fs_host_port_stat(wIndex)) + *(__le32 *) buf = cpu_to_le32( status); + + TRACE_MSG3(HCD, "GetPortStatus: port: %d port_change_status: %08x buf: %08x", + wIndex, status, *(__le32 *) buf); + + //printk(KERN_INFO"%s: GetPortStatus port: %d status: %08x\n", + // __FUNCTION__, wIndex, status); + + /* Mini-state machine to progress to PORT_CONNECT in virtual root hub port */ + if (status & (1 << PORT_RESET)) { + mxc_hcd->virt_port_status[wIndex - 1] &= ~(1 << PORT_RESET); + mxc_hcd->virt_port_status[wIndex - 1] |= + (1 << C_PORT_RESET) | + (1 << C_PORT_ENABLE) | + (1 << PORT_ENABLE) + ; + mxc_hcd->virt_hub_port_change_status |= (1 << wIndex); + //printk(KERN_INFO"%s: GetPortStatus port: %d status: %08x CLEARED PORT_RESET\n", + // __FUNCTION__, wIndex, mxc_hcd->virt_port_status[wIndex - 1]); + } + return 0; + + default: + //printk(KERN_INFO"%s: Default\n", __FUNCTION__); + /* "protocol stall" on error */ + return -EPIPE; + } +} + +/*! mxc_rh_suspend - called to suspend root hub + * @param hcd - usb_hcd instance + */ +int mxc_rh_suspend(struct usb_hcd *hcd) +{ + struct mxc_hcd *mxc_hcd = hcd_to_mxc(hcd); + TRACE_MSG0(HCD, "--"); + //printk(KERN_INFO"%s:\n", __FUNCTION__); + return 0; +} +/*! mxc_rh_resume - called to resum root hub + * @param hcd - usb_hcd instance + * @return 0 + */ +int mxc_rh_resume(struct usb_hcd *hcd) +{ + struct mxc_hcd *mxc_hcd = hcd_to_mxc(hcd); + TRACE_MSG0(HCD, "--"); + //printk(KERN_INFO"%s:\n", __FUNCTION__); + return 0; +} + +/*! mxc_hcd_stop - called to stop hcd + * @param hcd - usb_hcd instance + */ +static void +mxc_hcd_stop(struct usb_hcd *hcd) +{ + //printk(KERN_INFO"%s:\n", __FUNCTION__); + TRACE_MSG0(HCD, "--"); + del_timer_sync(&hcd->rh_timer); +} + +static int +mxc_hcd_start(struct usb_hcd *hcd) +{ + hcd->state = HC_STATE_RUNNING; + return 0; +} + +/*! struct hcd_driver mxc_hc_driver */ +static const struct hc_driver mxc_hc_driver = { + .description = "mxc-hcd", + .product_desc = "Freescale On-Chip USB Host Controller", + .hcd_priv_size = sizeof(struct mxc_hcd) - sizeof(struct usb_hcd), + + /* + * generic hardware linkage + */ + .flags = HCD_USB11, + + .start = mxc_hcd_start, + .stop = mxc_hcd_stop, + + /* + * managing i/o requests and associated device resources + */ + .urb_enqueue = mxc_hcd_urb_enqueue, + .urb_dequeue = mxc_hcd_urb_dequeue, + .endpoint_disable = mxc_hcd_endpoint_disable, + + /* + * periodic schedule support + */ + .get_frame_number = mxc_rh_frame, + + /* + * root hub support + */ + .hub_status_data = mxc_rh_status_data, + .hub_control = mxc_rh_control, + .bus_suspend = mxc_rh_suspend, + .bus_resume = mxc_rh_resume, +}; + +/* ********************************************************************************************* */ +/*! mxc_hcd_remove - called to remove hcd + * @param dev + */ +static int +mxc_hcd_remove(struct platform_device *dev) +{ + struct mxc_hcd *hcd = platform_get_drvdata(dev); + + usb_remove_hcd(&hcd->hcd); + + usb_put_hcd(&hcd->hcd); + + return 0; +} + +static void mxc_dev_release(struct platform_device *device) +{ + TRACE_MSG0(HCD, "--"); + //usb_hcd_release((struct usb_bus *) dev); +} + +/*! mxc_hcd_probe - called to initialize hcd + * @param dev - hcd device + */ +static int mxc_hcd_probe(struct platform_device *dev) +{ + struct platform_device *platform_device = NULL; + struct mxc_hcd *hcd; + + hcd = (struct mxc_hcd *) usb_create_hcd(&mxc_hc_driver, &dev->dev, dev->dev.bus_id); + if (!hcd) { + return -ENOMEM; + } + + INIT_LIST_HEAD(&hcd->unused); + INIT_LIST_HEAD(&hcd->inactive); + + platform_set_drvdata(dev, hcd); + + //usb_bus_init(&hcd->hcd.self); + //hcd->hcd.self.op = &usb_hcd_operations; + hcd->hcd.self.hcpriv = hcd; + hcd->hcd.self.is_b_host = TRUE; + hcd->hcd.self.otg_port = 1; + hcd->root_hub_desc_a = fs_rl(OTG_HOST_ROOTHUB_DESCA); + hcd->root_hub_desc_b = fs_rl(OTG_HOST_ROOTHUB_DESCB); + hcd->bNbrPorts = hcd->root_hub_desc_a & 0xff; + hcd->wHubCharacteristics = (hcd->root_hub_desc_a >> 8) & 0xff; + hcd->bPwrOn2PwrGood = (hcd->root_hub_desc_a >> 24) & 0xff; + hcd->DeviceRemovable = hcd->root_hub_desc_b & 0xff; + hcd->PortPwrCtrlMask = (hcd->root_hub_desc_b >> 16) & 0xff; + hcd->allocated_count = hcd->active_count = 0; + + TRACE_MSG2(HCD, "root_hub descA: %08x descB: %08x", hcd->root_hub_desc_a, hcd->root_hub_desc_b); + + TRACE_MSG5(HCD, "bNbrPorts: %02x wHubCharacteristics: %02x bPwrOn2PwrGood: %02x " + "DeviceRemovalbe: %02x PortPwrCtrlMask: %02x", + hcd->bNbrPorts, + hcd->wHubCharacteristics, + hcd->bPwrOn2PwrGood, + hcd->DeviceRemovable, + hcd->PortPwrCtrlMask + ); + + //INIT_LIST_HEAD(&hcd->hcd.dev_list); + + //hcd->hcd.self.release = &mxc_hcd_release; + + hcd->hcd.driver = &mxc_hc_driver; + hcd->hcd.irq = -1; + //hcd->hcd.state = USB_STATE_HALT; + + spin_lock_init(&hcd->lock); + + THROW_IF(usb_add_hcd(&hcd->hcd, 0, IRQF_SHARED), error);; + + hcd_instance->privdata = &hcd->hcd; + + THROW_IF(mxc_host_gpio(), error); + + return 0; + + CATCH(error) { + if (hcd) { + if (hcd) mxc_hcd_stop(&hcd->hcd); + usb_put_hcd(&hcd->hcd); + } + return -ENODEV; + } +} + +/*! mxc_hcd_driver - define a platform_driver structure for this driver + */ +static struct platform_driver mxc_hcd_driver = { + .driver = { + .name = "mxc-hcd", + }, + + .probe = mxc_hcd_probe, + .remove = mxc_hcd_remove, +}; + +static void mxc_hcd_release(struct usb_bus *bus) +{ + TRACE_MSG0(HCD, "--"); + //usb_hcd_release((struct usb_bus *) dev); +} + +/*! mxc_hcd_platform - define a platform_device structure for this driver + */ +static struct platform_device mxc_hcd_platform = { + .name = "mxc-hcd", + .id = 1, +}; + +/* ********************************************************************************************* */ +/* ********************************************************************************************* */ + +extern char *etd_urb_state_name[]; +/*! mxc_hcd_show - called to display hcd information + * @param s + * @param unused + */ +static int mxc_hcd_show(struct seq_file *s, void *unused) +{ + struct mxc_hcd *mxc_hcd = hcd_instance->privdata; // XXX this should come from dev_id + struct mxc_req *mxc_req; + struct mxc_req *mxc_req_save; + + int etdn; + unsigned long flags; + + + seq_printf(s, "MXC HCD\n\n"); + + for (etdn = 0; etdn < NUM_ETDS; etdn++) { + + local_irq_save(flags); + + if ((mxc_req = mxc_hcd->active[etdn])) { + seq_printf(s, "[%02x] Active urb: %08x epnum: %02x state: %s\n", etdn, + mxc_req->urb, + mxc_req->epnum, + etd_urb_state_name[mxc_req->etd_urb_state] + ); + } + + local_irq_restore(flags); + + } + seq_printf(s, "\n"); + + local_irq_save(flags); + etdn = 0; + list_for_each_entry(mxc_req, &mxc_hcd->inactive, queue) { + seq_printf(s, "[%02x] Inactive urb: %08x epnum: %02x state: %s\n", etdn++, + mxc_req->urb, + mxc_req->epnum, + etd_urb_state_name[mxc_req->etd_urb_state] + ); + } + local_irq_restore(flags); + + seq_printf(s, "\n"); + seq_printf(s, "Requests: Allocated: %d Active: %d Inactive: %d Total: %d\n", + mxc_hcd->allocated_count, mxc_hcd->active_count, etdn, mxc_hcd->request_count); + + seq_printf(s, "\n"); + return 0; +} + +/*! mxc_hcd_open - open a hcd file + * @param inode + * @param file + */ + +static int mxc_hcd_open(struct inode *inode, struct file *file) +{ + return single_open(file, mxc_hcd_show, PDE(inode)->data); +} +/*! struct file_operatons mxc_hcd_proc_ops */ +static struct file_operations mxc_hcd_proc_ops = { + .open = mxc_hcd_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static const char proc_filename[] = "mxchcd"; + + +/* ********************************************************************************************* */ +/* ********************************************************************************************* */ +/*! Module init/exit + * + * Register this a linux device driver and platform. This provides the necessary framework + * expected by the Linux 2.6 USB Core. + * + */ +/*! mxc_hcd_mod_init_l26 -called to register hcd driver and platform + * @param otg - otg instance + */ + +int mxc_hcd_mod_init_l26(struct otg_instance *otg) +{ + u32 hwmode; + int driver_registered = 0; + int platform_registered = 0; + struct proc_dir_entry *pde = NULL; + + printk(KERN_INFO"%s: MXC-HCD.C (dev)\n", __FUNCTION__); + + if (usb_disabled()) { + //printk(KERN_INFO"%s: YYYYY\n", __FUNCTION__); + return -ENOMEM; + } + + THROW_IF((driver_registered = platform_driver_register(&mxc_hcd_driver)), error); + + THROW_IF((platform_registered = platform_device_register(&mxc_hcd_platform)), error); + + THROW_UNLESS((pde = create_proc_entry(proc_filename, 0, NULL)), error); + pde->proc_fops = &mxc_hcd_proc_ops; + + CATCH(error) { + //printk(KERN_INFO"%s: ZZZZ\n", __FUNCTION__); + if (platform_registered) platform_device_unregister(&mxc_hcd_platform); + if (driver_registered) platform_driver_unregister(&mxc_hcd_driver); + if (pde) remove_proc_entry(proc_filename, NULL); + } + + return 0; +} + +#ifdef MODULE +/*! mxc_hcd_mod_exit_l26 - called to release hcd module + * @param otg - otg instance + */ +void mxc_hcd_mod_exit_l26(struct otg_instance *otg) +{ + remove_proc_entry(proc_filename, NULL); + + platform_driver_unregister(&mxc_hcd_driver); + + platform_device_unregister(&mxc_hcd_platform); + +} +#endif diff --git a/drivers/otg/hardware/mxc-lnx.h b/drivers/otg/hardware/mxc-lnx.h new file mode 100644 index 000000000000..f62a13b18e43 --- /dev/null +++ b/drivers/otg/hardware/mxc-lnx.h @@ -0,0 +1,546 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/hardware/mxc-lnx.h + * @(#) balden@belcarra.com/seth2.rillanon.org|otg/platform/mxc/mxc-lnx.h|20070726000034|21621 + * + * Copyright (c) 2004-2005 Belcarra + * Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + * + * By: + * Stuart Lynne <sl@belcarra.com>, + * Bruce Balden <balden@belcarra.com> + * + */ + +/*! + * @file otg/hardware/mxc-lnx.h + * @brief Hardware defines for Freescale USBOTG Hardware + * + * This supports the Freescale MXC implementation of the Trans Dimension + * USBOTG. + * + * This is used on: + * + * - i.MX21 + * - SCM-A11 + * - Argon+ + * - Zeus + * + * @ingroup FSOTG + * @ingroup LINUXAPI + */ + +#if defined(CONFIG_ARCH_MX2ADS) +#include <linux/pci.h> +#include <asm/arch/clk.h> +#include <asm/arch/gpt.h> +#undef GPT_BASE_ADDR +#include <asm/arch/mx2.h> +//#include <otghw/mx2ads.h> +//#include <otghw/mx2ads-hardware.h> +#endif /* defined(CONFIG_ARCH_MX2ADS) */ + +//#if defined(CONFIG_ARCH_SCMA11) || defined(CONFIG_ARCH_ARGONPLUS) || defined(CONFIG_ARCH_ARGONLV) || defined(CONFIG_ARCH_ZEUS) +#if defined(CONFIG_ARCH_MXC91231) || defined(CONFIG_ARCH_MXC91331) || defined(CONFIG_ARCH_MXC91321) || defined(CONFIG_ARCH_MXC91131) +#include <linux/pci.h> +//#include <otghw/scma11.h> +//#include <otghw/scma11-hardware.h> +#endif /* defined(CONFIG_ARCH_SCMA11) */ + + + +#if defined(CONFIG_ARCH_MX2ADS) + +/*! + * @name MX2ETDS + * R1 = i.MX21 Application Processor Reference Manual Rev 0.6 2003/11/01 TO 1.0 + * (aka "IMX21RM_TO1.pdf") + * R2 = "USBOTG_L3_Specification_v.C1.0.pdf" + * @{ + */ +#define NUM_ETDS 32 +#define DATA_BUFF_SIZE 64 +#define DATA_BUFFER_TOTAL 4096 +#define NUM_DATA_BUFFS (4096/DATA_BUFF_SIZE) +#define LITTLE_ENDIAN 1 + + +#define UDC_NAME "MX21" +#define UDC_MAX_ENDPOINTS 32 +//#define EP0_PACKETSIZE 8 +#define EP0_PACKETSIZE 64 + + +/* @} */ + +/*! + * @name MX2Interrupts + * @{ + */ +#define OTG_USBWKUP 53 +#define OTG_USBDMA 54 +#define OTG_USBHOST 55 +#define OTG_USBFUNC 56 +#define OTG_USBHNP 57 +#define OTG_USBCTRL 58 +/* @} */ + +/*! + * @name I2CTransceiver + * C.f. 23.14.10 I2C OTG Transceiver Controller Registers + * These are the I2C controller and access registers. + * + * N.B. I2C_ERROR is not documented. + * @{ + */ + +#define I2C_BUSY (1 << 7) +#define I2C_ERROR (1 << 2) +#define I2C_HWSMODE (1 << 1) +#define I2C_I2COE (1 << 0) + +#define I2C_SCLK_TO_SCL_DIVISION (OTG_I2C_BASE+0x1E) + +#define I2C_INTERRUPT_AND_CONTROL (OTG_I2C_BASE+0x1F) + +#define I2C_STATUS_MASK (0x7) + +#define I2C_NOACK_EN (1 << 6) +#define I2C_RWREADY_EN (1 << 5) +#define I2C_OTGXCVRINT_EN (1 << 4) + +#define I2C_NOACK (1 << 2) +#define I2C_RWREADY (1 << 1) +#define I2C_OTGXCVRINT (1 << 0) + +#define MX2_OTG_XCVR_DEVAD OTG_I2C_BASE+0x18 +#define MX2_SEQ_OP_REG OTG_I2C_BASE+0x19 +#define MX2_SEQ_RD_STARTAD OTG_I2C_BASE+0x1a +#define MX2_I2C_OP_CTRL_REG OTG_I2C_BASE+0x1b +#define MX2_SCLK_TO_SCL_HPER OTG_I2C_BASE+0x1e +#define MX2_I2C_INTERRUPT_AND_CTRL OTG_I2C_BASE+0x1f + +/* @} */ + +#endif /* defined(CONFIG_ARCH_MX2ADS) */ + + +//#if defined(CONFIG_ARCH_SCMA11) || defined(CONFIG_ARCH_ARGONPLUS) || defined(CONFIG_ARCH_ARGONLV) || defined(CONFIG_ARCH_ZEUS) +#if defined(CONFIG_ARCH_MXC91231) || defined(CONFIG_ARCH_MXC91331) || defined(CONFIG_ARCH_MXC91321) || defined(CONFIG_ARCH_MXC91131) + +/*! + * @name MX2ETDS + * R1 = i.MX21 Application Processor Reference Manual Rev 0.6 2003/11/01 TO 1.0 + * (aka "IMX21RM_TO1.pdf") + * R2 = "USBOTG_L3_Specification_v.C1.0.pdf" + * @{ + */ + +#define OTG_USBDMA 54 + +#define NUM_ETDS 16 +#define DATA_BUFF_SIZE 64 +#define DATA_BUFFER_TOTAL 4096 +#define NUM_DATA_BUFFS (4096/DATA_BUFF_SIZE) +#define LITTLE_ENDIAN 1 + + +#define UDC_NAME "MXC" +#define UDC_MAX_ENDPOINTS 16 +#define EP0_PACKETSIZE 64 + + +/* @} */ + + +#if defined (CONFIG_ARCH_MXC91331) || defined(CONFIG_ARCH_MXC91321) +#define OTG_BASE_ADDR 0x50020000 +#define INT_USB_WAKEUP 35 +#define INT_USB_SOF 36 +#define INT_PMU_EVTMON 37 +#define INT_USB_FUNC 38 +#define INT_USB_DMA 39 +#define INT_USB_CTRL 40 +#define _reg_GPT_GPTCR ((volatile __u32 *)(IO_ADDRESS(GPT_BASE_ADDR + 0x00))) +#define _reg_GPT_GPTPR ((volatile __u32 *)(IO_ADDRESS(GPT_BASE_ADDR + 0x04))) +#define _reg_GPT_GPTSR ((volatile __u32 *)(IO_ADDRESS(GPT_BASE_ADDR + 0x08))) +#define _reg_GPT_GPTIR ((volatile __u32 *)(IO_ADDRESS(GPT_BASE_ADDR + 0x0C))) +#define _reg_GPT_GPTOCR1 ((volatile __u32 *)(IO_ADDRESS(GPT_BASE_ADDR + 0x10))) +#define _reg_GPT_GPTOCR2 ((volatile __u32 *)(IO_ADDRESS(GPT_BASE_ADDR + 0x14))) +#define _reg_GPT_GPTOCR3 ((volatile __u32 *)(IO_ADDRESS(GPT_BASE_ADDR + 0x18))) +#define _reg_GPT_GPTICR1 ((volatile __u32 *)(IO_ADDRESS(GPT_BASE_ADDR + 0x1C))) +#define _reg_GPT_GPTICR2 ((volatile __u32 *)(IO_ADDRESS(GPT_BASE_ADDR + 0x20))) +#define _reg_GPT_GPTCNT ((volatile __u32 *)(IO_ADDRESS(GPT_BASE_ADDR + 0x24))) + +#elif defined (CONFIG_ARCH_MXC91231) +#define OTG_BASE_ADDR 0x50024000 + +#elif defined (CONFIG_ARCH_MXC91131) +#define OTG_BASE_ADDR 0x50024000 + +#define _reg_GPT_GPTCR ((volatile __u32 *)(IO_ADDRESS(GPT_BASE_ADDR + 0x00))) +#define _reg_GPT_GPTPR ((volatile __u32 *)(IO_ADDRESS(GPT_BASE_ADDR + 0x04))) +#define _reg_GPT_GPTSR ((volatile __u32 *)(IO_ADDRESS(GPT_BASE_ADDR + 0x08))) +#define _reg_GPT_GPTIR ((volatile __u32 *)(IO_ADDRESS(GPT_BASE_ADDR + 0x0C))) +#define _reg_GPT_GPTOCR1 ((volatile __u32 *)(IO_ADDRESS(GPT_BASE_ADDR + 0x10))) +#define _reg_GPT_GPTOCR2 ((volatile __u32 *)(IO_ADDRESS(GPT_BASE_ADDR + 0x14))) +#define _reg_GPT_GPTOCR3 ((volatile __u32 *)(IO_ADDRESS(GPT_BASE_ADDR + 0x18))) +#define _reg_GPT_GPTICR1 ((volatile __u32 *)(IO_ADDRESS(GPT_BASE_ADDR + 0x1C))) +#define _reg_GPT_GPTICR2 ((volatile __u32 *)(IO_ADDRESS(GPT_BASE_ADDR + 0x20))) +#define _reg_GPT_GPTCNT ((volatile __u32 *)(IO_ADDRESS(GPT_BASE_ADDR + 0x24))) + + +#endif /* defined (CONFIG_ARCH_XXXX) */ + +#define OTG_CORE_BASE (OTG_BASE_ADDR+0x000) // base location for core +#define OTG_FUNC_BASE (OTG_BASE_ADDR+0x040) // base location for function +#define OTG_HOST_BASE (OTG_BASE_ADDR+0x080) // base location for host +#define OTG_DMA_BASE (OTG_BASE_ADDR+0x800) // base location for dma +#define OTG_ETD_BASE (OTG_BASE_ADDR+0x200) // base location for etd memory +#define OTG_EP_BASE (OTG_BASE_ADDR+0x400) // base location for ep memory +#define OTG_SYS_BASE (OTG_BASE_ADDR+0x600) // base location for system + +//#if defined (CONFIG_ARCH_SCMA11) +#if defined (CONFIG_ARCH_MXC91231) +#define OTG_DATA_BASE (OTG_BASE_ADDR+0x4000) // base location for data memory +#else /* defined (CONFIG_ARCH_SCMA11) */ +#define OTG_DATA_BASE (OTG_BASE_ADDR+0x1000) // base location for data memory +#endif /* defined (CONFIG_ARCH_SCMA11) */ + +#define OTG_SYS_CTRL (OTG_SYS_BASE+0x000) // base location for system + +/* @} */ + +#endif /* defined(CONFIG_ARCH_SCMA11) */ + + +/*! + * @name C.f. 23.11.11 Host Registers + */ + /*! @{ */ + +/* ep descriptor access + */ +static __inline__ u32 etd_word(int n, int word) +{ + u32 offset = n * 16; + offset += word * 4; + return OTG_ETD_BASE + offset; +} + +/* ep descriptor access + */ +static __inline__ u32 ep_word(int n, int dir, int word) +{ + u32 offset = n * 2; + offset += dir ? 1 : 0; + offset *= 16; + offset += word * 4; + return OTG_EP_BASE + offset; +} + +/* endpoint data buffer access + * + * This is a simplistic allocator, will do until we want to support ISO or host mode. + * + * This works because we are assuming a maximum of 16 allocate endpoints, and no + * overlapped endpoints (both in and out are allocated). + */ + +#define VOLATILE +static VOLATILE __inline__ u16 data_x_buf(int n, int dir) +{ + return 0x40 * (n * 4 + 2 * (dir ? 1 : 0)); +} +static VOLATILE __inline__ u16 data_y_buf(int n, int dir) +{ + return 0x40 * (n * 4 + 2 * (dir ? 1 : 0) + 1); +} + +static VOLATILE __inline__ u8 * data_x_address(int n, int dir) +{ + return (VOLATILE u8 *) IO_ADDRESS(OTG_DATA_BASE + data_x_buf(n, dir)); +} +static VOLATILE __inline__ u8 * data_y_address(int n, int dir) +{ + return (VOLATILE u8 *) IO_ADDRESS(OTG_DATA_BASE + data_y_buf(n, dir)); +} +/*! @} */ + + +/* ********************************************************************************************** */ + +#if 0 +/*! + * rel_data_buff() - release data buff + */ +static __inline__ fs_data_buff *rel_data_buff(fs_hcpriv *fs_hcpriv, fs_data_buff *db) +{ + // Release db to the pool of available data_buffs. + unsigned long flags; + u16 ndx; + if (NULL != db) { + local_irq_save(flags); + ndx = db - (fs_data_buff *)OTG_DATA_BASE; + fs_hcpriv->buff_list[ndx] = fs_hcpriv->free_buffs; + fs_hcpriv->free_buffs = ndx; +#if 0 + db->next = fs_hcpriv->free_buffs; + fs_hcpriv->free_buffs = db; +#endif + local_irq_restore(flags); + } + return(NULL); +} + +/*! + * data_buff_boff() - get data buffer offset given address + */ +static __inline__ u16 data_buff_boff(fs_data_buff *db) +{ + return((u16)(((void *) db) - ((void *) OTG_DATA_BASE))); +} + +/*! + * data_buff_addr() - get data buffer address given offset + */ +static __inline__ fs_data_buff *data_buff_addr(u16 boff) +{ + // Return the address of the fs_data_buffer that is boff bytes from the start of data buffer memory. + return(boff + ((void *) OTG_DATA_BASE)); +} +/*! @} */ +#endif + +/* ********************************************************************************************** */ + +/*! + * @name FSUSBOTGIO + * @brief Freescale USBOTG I/O support. + */ + /*! @{ */ + +/*! + * fs_rb() - read a byte + * @param port + * @return byte read + */ +static u8 __inline__ fs_rb(u32 port) +{ + return *(volatile u8 *) (IO_ADDRESS(port)); +} + +/*! + * fs_rl() - read a long + * @param port + * @return word read + */ +static u32 __inline__ fs_rl(u32 port) +{ + return *(volatile u32 *) (IO_ADDRESS(port)); +} + +/*! + * fs_wb() - write a byte + * @param port + * @param val + */ +static void __inline__ fs_wb(u32 port, u8 val) +{ + *(volatile u8 *)(IO_ADDRESS(port)) = val; +} + +/*! + * fs_orb() - or a byte + * @param port + * @param val + */ +static void __inline__ fs_orb(u32 port, u8 val) +{ + u8 set = fs_rb(port) | val; + *(volatile u8 *)(IO_ADDRESS(port)) = set; +} + +/*! + * fs_andb() - and a byte + * @param port + * @param val + */ +static void __inline__ fs_andb(u32 port, u8 val) +{ + u8 set = fs_rb(port) & val; + *(volatile u8 *)(IO_ADDRESS(port)) = set; +} + +/*! + * fs_wl() - write a long + * @param port + * @param val + */ +static void __inline__ fs_wl(u32 port, u32 val) +{ + u32 set; + *(volatile u32 *)(IO_ADDRESS(port)) = val; + set = fs_rl(port); +} + +/*! + * fs_orl() - or a long + * @param port + * @param val + */ +static void __inline__ fs_orl(u32 port, u32 val) +{ + u32 set = fs_rl(port); + *(volatile u32 *)(IO_ADDRESS(port)) = (set | val); +} + +/*! + * fs_andl() - and a long + * @param port + * @param val + */ +static void __inline__ fs_andl(u32 port, u32 val) +{ + u32 set = fs_rl(port); + *(volatile u32 *)(IO_ADDRESS(port)) = (set & val); +} + +/*! + * fs_wl_set() - set a word and verify + * @param tag + * @param port + * @param val + */ +static void inline fs_wl_set(otg_tag_t tag, u32 port, u32 val) +{ + u32 set; + *(volatile u32 *)(IO_ADDRESS(port)) = val; +#if 0 + set = fs_rl(port); + if ((set & val) != val) { + TRACE_MSG1(tag, "SET FAILED: %08x", set); + } +#endif +} + +#if 1 +/*! + * fs_wl_clr() - clr a word and verify + * @param tag + * @param port + * @param clr + */ +static void inline fs_wl_clr(otg_tag_t tag, u32 port, u32 clr) +{ + u32 set; + *(volatile u32 *)(IO_ADDRESS(port)) = clr; + set = fs_rl(port); + if (set & clr) { + TRACE_MSG1(tag, "CLEAR FAILED 1: %08x", set); + *(volatile u32 *)(IO_ADDRESS(port)) = clr; + set = fs_rl(port); +#if 1 + if (set & clr) + TRACE_MSG1(tag, "CLEAR FAILED 2: %08x", set); +#endif + } +} +#endif + +/*! + * fs_memcpy32() - emulate memcpy using long copy + * @param dp destination pointer + * @param sp source pointer + * @param words number of 32bit words to copy + * + */ +static void inline fs_memcpy32(u32 *dp, u32 *sp, volatile int words) +{ + while (words--) *dp++ = *sp++; +} +/*! + * fs_memcpy() - emulate memcpy using byte copy + * @param dp destination pointer + * @param sp source pointer + * @param bytes number of 8bit bytes to copy + */ +static void inline fs_memcpy(u8 *dp, u8 *sp, volatile int bytes) +{ + while (bytes--) *dp++ = *sp++; +} +/*! + * fs_clear_words() - clear memory + * @param addr address to clear from + * @param words number of 32bit words to clear. + */ +static void inline fs_clear_words(volatile u32 *addr, int words) +{ + while (words--) *addr++ = 0; +} + + + +/*! @} */ + +//#if defined(CONFIG_ARCH_SCMA11) || defined(CONFIG_ARCH_ARGONPLUS) || defined(CONFIG_ARCH_ARGONLV) || defined(CONFIG_ARCH_ZEUS) +#if defined(CONFIG_ARCH_MXC91231) || defined(CONFIG_ARCH_MXC91331) || defined(CONFIG_ARCH_MXC91321) || defined(CONFIG_ARCH_MXC91131) + +#define _reg_CRM_AP_ASCSR ((volatile __u32 *)(IO_ADDRESS(CRM_AP_BASE_ADDR + 0x00))) +#define _reg_CRM_AP_ACDR ((volatile __u32 *)(IO_ADDRESS(CRM_AP_BASE_ADDR + 0x04))) +#define _reg_CRM_AP_ACDER1 ((volatile __u32 *)(IO_ADDRESS(CRM_AP_BASE_ADDR + 0x08))) +#define _reg_CRM_AP_ACDER2 ((volatile __u32 *)(IO_ADDRESS(CRM_AP_BASE_ADDR + 0x0c))) + +#define _reg_CRM_AP_ACGCR ((volatile __u32 *)(IO_ADDRESS(CRM_AP_BASE_ADDR + 0x10))) +#define _reg_CRM_AP_ACCGCR ((volatile __u32 *)(IO_ADDRESS(CRM_AP_BASE_ADDR + 0x14))) +#define _reg_CRM_AP_AMLPMRA ((volatile __u32 *)(IO_ADDRESS(CRM_AP_BASE_ADDR + 0x18))) +#define _reg_CRM_AP_AMLPMRB ((volatile __u32 *)(IO_ADDRESS(CRM_AP_BASE_ADDR + 0x1c))) + +#define _reg_CRM_AP_AMLPMRC ((volatile __u32 *)(IO_ADDRESS(CRM_AP_BASE_ADDR + 0x20))) +#define _reg_CRM_AP_AMLPRMD ((volatile __u32 *)(IO_ADDRESS(CRM_AP_BASE_ADDR + 0x24))) +#define _reg_CRM_AP_AMLPRME1 ((volatile __u32 *)(IO_ADDRESS(CRM_AP_BASE_ADDR + 0x28))) +#define _reg_CRM_AP_AMLPRME2 ((volatile __u32 *)(IO_ADDRESS(CRM_AP_BASE_ADDR + 0x2c))) + +#define _reg_CRM_AP_AMLPMRF ((volatile __u32 *)(IO_ADDRESS(CRM_AP_BASE_ADDR + 0x30))) +#define _reg_CRM_AP_AMLPMRG ((volatile __u32 *)(IO_ADDRESS(CRM_AP_BASE_ADDR + 0x34))) +#define _reg_CRM_AP_APGCR ((volatile __u32 *)(IO_ADDRESS(CRM_AP_BASE_ADDR + 0x38))) +#define _reg_CRM_AP_ACSR ((volatile __u32 *)(IO_ADDRESS(CRM_AP_BASE_ADDR + 0x3c))) + +#define _reg_CRM_AP_ADCR ((volatile __u32 *)(IO_ADDRESS(CRM_AP_BASE_ADDR + 0x40))) +#define _reg_CRM_AP_ACR ((volatile __u32 *)(IO_ADDRESS(CRM_AP_BASE_ADDR + 0x44))) +#define _reg_CRM_AP_AMCR ((volatile __u32 *)(IO_ADDRESS(CRM_AP_BASE_ADDR + 0x48))) +#define _reg_CRM_AP_APCR ((volatile __u32 *)(IO_ADDRESS(CRM_AP_BASE_ADDR + 0x4c))) + +#define _reg_CRM_AP_AMORA ((volatile __u32 *)(IO_ADDRESS(CRM_AP_BASE_ADDR + 0x50))) +#define _reg_CRM_AP_AMORB ((volatile __u32 *)(IO_ADDRESS(CRM_AP_BASE_ADDR + 0x54))) +#define _reg_CRM_AP_AGPR ((volatile __u32 *)(IO_ADDRESS(CRM_AP_BASE_ADDR + 0x58))) +#define _reg_CRM_AP_APRA ((volatile __u32 *)(IO_ADDRESS(CRM_AP_BASE_ADDR + 0x5c))) + +#define _reg_CRM_AP_APRB ((volatile __u32 *)(IO_ADDRESS(CRM_AP_BASE_ADDR + 0x60))) +#define _reg_CRM_AP_APOR ((volatile __u32 *)(IO_ADDRESS(CRM_AP_BASE_ADDR + 0x64))) + + +//#define _reg_CRM_AP_APOR ((volatile __u32 *)(IO_ADDRESS(CRM_AP_BASE_ADDR + 0x64))) + +#define BCTRL_BASE_ADDR 0xb4000000 +#define _reg_BCTRL_VERSION ((volatile __u16 *)(IO_ADDRESS(BCTRL_BASE_ADDR + 0x0))) +#define _reg_BCTRL_STATUS ((volatile __u16 *)(IO_ADDRESS(BCTRL_BASE_ADDR + 0x2))) +#define _reg_BCTRL_1 ((volatile __u16 *)(IO_ADDRESS(BCTRL_BASE_ADDR + 0xa))) +#define _reg_BCTRL_2 ((volatile __u16 *)(IO_ADDRESS(BCTRL_BASE_ADDR + 0xa))) + +#define BCTRL_2_USBSP (1 << 9) +#define BCTRL_2_USBSD (1 << 10) + + +#endif /* defined(CONFIG_MACH_SCMA11EVB) || defined(CONFIG_MACH_SCMA11BB) || defined(CONFIG_MACH_ARGONPLUSEVB) */ diff --git a/drivers/otg/hardware/mxc-ocd.c b/drivers/otg/hardware/mxc-ocd.c new file mode 100644 index 000000000000..16636c22eff2 --- /dev/null +++ b/drivers/otg/hardware/mxc-ocd.c @@ -0,0 +1,692 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/hardware/mxc-ocd.c -- USB Device Controller driver + * @(#) sl@belcarra.com/whiskey.enposte.net|otg/platform/mxc/mxc-ocd.c|20070612233038|32668 + * + * Copyright (c) 2004-2005 Belcarra + * Copyright (c) 2005-2007 Belcarra Technologies 2005 Corp + * + * By: + * Stuart Lynne <sl@lbelcarra.com>, + * Bruce Balden <balden@belcarra.com> + * + */ +/*! + * @file otg/hardware/mxc-ocd.c + * @brief Freescale USB OTG Controller Driver + * + * This is the OTG Controller Driver. It does generic configuration + * and handles the all USBOTG interrupts. + * + * There is no board or platform level code here. + * + * @ingroup FSOTG + * @ingroup OCD + * + */ + +#include <otg/pcd-include.h> +#include "mxc-lnx.h" +#include "mxc-hardware.h" + +int mxc_transceiver_mode; + +/* ********************************************************************************************* */ +#define TIMEOUT_VALUE 1000 +/*! + * mxc_func_clock_on() - enable function controller clock + */ +void mxc_func_clock_on(void) +{ + u32 timeout = TIMEOUT_VALUE; + fs_orl(OTG_CORE_CLK_CTRL, MODULE_FUNC_CLK); + while(!( fs_rl(OTG_CORE_CLK_CTRL) & MODULE_FUNC_CLK)) { timeout--; if (!timeout) break; } + + //TRACE_MSG1(OCD, "FUNC CLOCK ON: %08x", fs_rl(OTG_CORE_CLK_CTRL)); + + //fs_orl(OTG_CORE_CINT_STEN, MODULE_ASFCINT_EN |MODULE_FCINT_EN); +} + +/*! + * mxc_host_clock_on() - enable host controller clock + */ +void mxc_host_clock_on(void) +{ + u32 timeout = TIMEOUT_VALUE; + fs_orl(OTG_CORE_CLK_CTRL, MODULE_HOST_CLK); + while(!( fs_rl(OTG_CORE_CLK_CTRL) & MODULE_HOST_CLK)) { timeout--; if (!timeout) break; } + //TRACE_MSG1(OCD, "HOST CLOCK ON: %08x", fs_rl(OTG_CORE_CLK_CTRL)); +} + +/*! + * mxc_func_clock_off() - disable function controller clock + */ +void mxc_func_clock_off(void) +{ + u32 timeout = TIMEOUT_VALUE; + fs_andl(OTG_CORE_CLK_CTRL, 0x0); + while((fs_rl(OTG_CORE_CLK_CTRL) & (MODULE_FUNC_CLK | MODULE_MAIN_CLK))) { timeout--; if (!timeout) break; } + //TRACE_MSG1(OCD, "FUNC CLOCK OFF: %08x", fs_rl(OTG_CORE_CLK_CTRL)); +} + +/*! + * mxc_host_clock_off() - disable host controller clock + */ +void mxc_host_clock_off(void) +{ + u32 timeout = TIMEOUT_VALUE; + fs_andl(OTG_CORE_CLK_CTRL, 0x0); + while((fs_rl(OTG_CORE_CLK_CTRL) & (MODULE_HOST_CLK | MODULE_MAIN_CLK))) { timeout--; if (!timeout) break; } + //TRACE_MSG1(OCD, "HOST CLOCK OFF: %08x", fs_rl(OTG_CORE_CLK_CTRL)); +} + +/*! + * mxc_main_clock_on() - enable main clock + */ +void mxc_main_clock_on(void) +{ + u32 timeout = TIMEOUT_VALUE; + fs_orl(OTG_CORE_CLK_CTRL, MODULE_MAIN_CLK); + while(!( fs_rl(OTG_CORE_CLK_CTRL) & MODULE_MAIN_CLK)) { timeout--; if (!timeout) break; } + //TRACE_MSG1(OCD, "MAIN CLOCK ON: %08x", fs_rl(OTG_CORE_CLK_CTRL)); +} + +/*! + * mxc_main_clock_off() - disable main clock + */ +void mxc_main_clock_off(void) +{ + u32 timeout = TIMEOUT_VALUE; + //fs_wl_set(OCD, OTG_CORE_CLK_CTRL, 0); + fs_wl(OTG_CORE_CLK_CTRL, 0); + while((fs_rl(OTG_CORE_CLK_CTRL) & MODULE_MAIN_CLK)) { timeout--; if (!timeout) break; } + //TRACE_MSG1(OCD, "MAIN CLOCK OFF: %08x", fs_rl(OTG_CORE_CLK_CTRL)); +} + +/*! + * mxc_set_transceiver_mode(int) + * @param mode - mode to set + */ +void mxc_set_transceiver_mode(int mode) +{ + mxc_transceiver_mode = mode; + fs_andl(OTG_CORE_HWMODE, ~0xf0); + fs_orl(OTG_CORE_HWMODE, (mode << 6) | (mode << 4)); // set to software hnp + //TRACE_MSG2(OCD, "set hwmode: %08x want %08x", fs_rl(OTG_CORE_HWMODE), (mode << 6) | (mode << 4)); + //TRACE_MSG1(OCD, "hwmode: %08x", fs_rl(OTG_CORE_HWMODE)); +} + + + +/* ********************************************************************************************* */ + +/*! + * mxc_disable_interrupts() - disable interrupts + */ +void mxc_disable_interrupts (void) +{ + fs_wl(OTG_CORE_CINT_STEN, 0 ); + fs_wl(OTG_CORE_HINT_STEN, 0 ); + fs_wl(OTG_HOST_SINT_STEN, 0 ); + fs_wl(OTG_HOST_XYINT_STEN, 0 ); + fs_wl(OTG_HOST_ETD_EN, 0); + fs_wl(OTG_HOST_ETD_DONE, 0 ); + fs_wl(OTG_FUNC_SINT_STEN, 0 ); + fs_wl(OTG_FUNC_XYINT_STEN, 0 ); + fs_wl(OTG_FUNC_EP_EN, 0 ); + fs_wl(OTG_FUNC_EP_DEN, 0 ); +} + +/* ********************************************************************************************* */ + +/* ********************************************************************************************* */ +/*! + * mxc_init() - initial tcd setup + * Allocate interrupts and setup hardware. + * @param otg - otg instance + */ +void mxc_init (struct otg_instance *otg) +{ + int timeout; + unsigned long flags; + //u32 mode = XCVR_D_SE0; + u32 mode = XCVR_SE0_D_NEW; + + TRACE_MSG0(otg->ocd->TAG, "FS_INIT"); + local_irq_save (flags); + + fs_wl(OTG_SYS_CTRL, 0x0); + + /* 2. Ensure hardware is reset and cleared + */ + // XXX + //fs_clear_words((volatile u32 *)IO_ADDRESS(OTG_DMA_BASE), (32*16/4)); + fs_clear_words((volatile u32 *)IO_ADDRESS(OTG_DMA_BASE), (16*16/4)); + fs_clear_words((void *)IO_ADDRESS(OTG_FUNC_BASE), 0x200); + mxc_main_clock_off(); + + //fs_wl_set(OCD, OTG_CORE_RST_CTRL, MODULE_RSTI2C | 0x3f); + fs_wl(OTG_CORE_RST_CTRL, MODULE_RSTI2C | 0x3f); + while (fs_rl(OTG_CORE_RST_CTRL)); + + /* 3. OTG Hardware Mode and clocks + * set to diff, diff and Configure the OTG to behave as function + */ + TRACE_MSG0(otg->ocd->TAG, "3. OTG Software Mode and clock"); + + fs_orl(OTG_CORE_HWMODE, MODULE_CRECFG_SHNP); // set to software hnp + mxc_set_transceiver_mode(mxc_transceiver_mode); + TRACE_MSG2(otg->ocd->TAG, "FS_INIT: set hwmode: %08x want %08x", fs_rl(OTG_CORE_HWMODE), MODULE_CRECFG_SHNP); + + fs_andl(OTG_CORE_HNP_CSTAT, ~0x00000800); + fs_rl(OTG_CORE_HNP_CSTAT); + + //fs_wl_set(OCD, OTG_CORE_HNP_T3PCR, 0x00000000); + fs_wl(OTG_CORE_HNP_T3PCR, 0x00000000); + fs_rl(OTG_CORE_HNP_T3PCR); + + TRACE_MSG0(otg->ocd->TAG, "6. Enable "); + TRACE_MSG0(otg->ocd->TAG, "enable core interrupts"); + + fs_wl(OTG_CORE_CINT_STEN, 0); + + fs_wl(OTG_CORE_CINT_STEN, MODULE_ASHNPINT_EN | + MODULE_ASHCINT_EN | MODULE_HNPINT_EN | MODULE_FCINT | MODULE_HCINT); + + + TRACE_MSG0(otg->ocd->TAG, "enable host interrupts"); + fs_wl(OTG_CORE_HINT_STEN, HNP_I2COTGINT_EN | HNP_AWAITBTO_EN | + HNP_AIDLEBDTO_EN | HNP_SRPSUCFAIL_EN | HNP_SRPINT_EN | HNP_VBUSERROR_EN | + HNP_ABSEVAILD_EN | HNP_ABUSVALID_EN | HNP_MASSLVCHG_EN | HNP_IDCHANGE_EN); + + TRACE_MSG0(otg->ocd->TAG, "disable various host interrupts"); + fs_wl(OTG_HOST_XYINT_STEN, 0); + fs_wl(OTG_HOST_ETD_EN, 0); + fs_wl(OTG_HOST_ETD_DONE, 0); + fs_wl(OTG_HOST_SINT_STEN, 0); + + TRACE_MSG0(otg->ocd->TAG, "disable various function interrupts"); + fs_wl(OTG_FUNC_XYINT_STEN, 0); + fs_wl(OTG_FUNC_EP_EN, 0); + fs_wl(OTG_FUNC_EP_DEN, 0); + + fs_wl(OTG_DMA_DINT_STEN, 0x3); + //fs_wb(I2C_MASTER_INT_REG_ADD, 0xf0); + //fs_wb(I2C_MASTER_INT_REG_ADD, 0x00); + + // XXX note that newer designs than the mx21 will also need to check + // and/or set OTG_CORE_INTERRUPT_STEN + + + TRACE_MSG0(otg->ocd->TAG, "--"); + TRACE_MSG0(otg->ocd->TAG, "8. Ready "); + TRACE_MSG1(otg->ocd->TAG, "CINT_STEN: %08x", fs_rl(OTG_CORE_CINT_STEN)); + + local_irq_restore (flags); +} + +extern void mxc_stop_ep(int epn, int dir); + +/*! + * mxc_exit() - de-initialize + */ +void mxc_exit(struct otg_instance *otg) +{ + int i; + unsigned long flags; + TRACE_MSG0(otg->ocd->TAG, "FS_EXIT"); + + //mxc_disable_interrupts(); + //_reg_CRM_PCCR1 &= ~(1<<27); // disable GPT3 + + local_irq_save (flags); + for (i = 0; i < 16; i++) { + mxc_stop_ep(i, USB_DIR_IN); + mxc_stop_ep(i, USB_DIR_OUT); + } + fs_clear_words((volatile u32 *)IO_ADDRESS(OTG_EP_BASE), (32*16/4)); + //fs_clear((void *)IO_ADDRESS(OTG_EP_BASE), 0x200); + //fs_clear((volatile u32 *)IO_ADDRESS(OTG_DMA_BASE), 32*16); + //fs_clear((void *)IO_ADDRESS(OTG_FUNC_BASE), 0x200); + fs_wl( OTG_CORE_HWMODE, 0xa3); + mxc_main_clock_off(); + local_irq_restore (flags); +} + + +/*! + * mxc_ocd_init() - used to initialize/enable or disable the tcd driver + * @param otg + * @param flag + */ +void mxc_ocd_init(struct otg_instance *otg, u8 flag) +{ + TRACE_MSG0(otg->ocd->TAG, "--"); + switch (flag) { + case SET: + TRACE_MSG0(otg->ocd->TAG, "FS_otg->ocd->TAG_EN SET"); + mxc_init(otg); + break; + case RESET: + mxc_exit(otg); + TRACE_MSG0(otg->ocd->TAG, "FS_otg->ocd->TAG_EN RESET"); + break; + } + otg_event(otg, OCD_OK, otg->ocd->TAG, "MX21 OK"); +} + +/* ********************************************************************************************* */ + + +struct ocd_ops ocd_ops; + +extern irqreturn_t mxc_pcd_int_hndlr (void); +extern irqreturn_t mxc_hcd_hw_int_hndlr(int irq, void *dev_id, struct pt_regs *regs); + +extern void mxc_func_clock_on(void); +extern void mxc_func_clock_off(void); +extern void mxc_main_clock_on(void); +extern void mxc_main_clock_off(void); +extern void mxc_set_hw_mode(int); + +#if defined(CONFIG_OTG_USB_PERIPHERAL) || defined(CONFIG_OTG_BDEVICE_WITH_SRP) +irqreturn_t mxc_hcd_hw_int_hndlr(int irq, void *dev_id, struct pt_regs *regs) +{ + return IRQ_HANDLED; +} +#endif /* defined(CONFIG_OTG_USB_PERIPHERAL) || defined(CONFIG_OTG_BDEVICE_WITH_SRP) */ + + +/* ********************************************************************************************* */ +/*! + * ocd_hnp_int_hndlr() - HNP interrupt handler + * @param irq + * @param dev_id + * @param regs + */ +irqreturn_t ocd_hnp_int_hndlr (int irq, void *dev_id, struct pt_regs *regs) +{ + u32 hint_stat = fs_rl(OTG_CORE_HINT_STAT); + u32 hnp_cstat = fs_rl(OTG_CORE_HNP_CSTAT); + + /* get and clear interrupts + */ + //fs_wl_set(OCD, OTG_CORE_HINT_STAT, hint_stat); + fs_wl(OTG_CORE_HINT_STAT, hint_stat); + printk(KERN_INFO"%s: HINT_STAT: %08x HNP_CSTAT: %08x\n", __FUNCTION__, hint_stat, hnp_cstat); + + return IRQ_HANDLED; +} + +/*! + * pcd_bwkup_int_hndlr() - wakeup interrupt + * @param irq + * @param dev_id + * @param regs + */ +static irqreturn_t pcd_bwkup_int_hndlr (int irq, void *dev_id, struct pt_regs *regs) +{ + u32 sys_ctrl = fs_rl(OTG_SYS_CTRL); + //static int bwkup_count = 0; + + //TRACE_MSG2(otg->ocd->TAG, "SYS_CTRL: %08x CORE_CLK: %08x", sys_ctrl, fs_rl(OTG_CORE_CLK_CTRL)); + //printk(KERN_INFO"%s: %08x CORE_CLK: %08x\n", __FUNCTION__, sys_ctrl, fs_rl(OTG_CORE_CLK_CTRL)); + + //fs_wl(OTG_SYS_CTRL, sys_ctrl & ~0x0F000000); + mxc_main_clock_on(); // turn on Main Clock + mxc_func_clock_on(); // turn on Function Clock + + //if (bwkup_count++ > 10) + // fs_wl(OTG_SYS_CTRL, 0); + + return IRQ_HANDLED; +} + +/*! + * ocd_dma_int_hndlr() - DMA interrupt + * @param irq + * @param dev_id + * @param regs + */ +static irqreturn_t ocd_dma_int_hndlr (int irq, void *dev_id, struct pt_regs *regs) +{ + u32 dint_stat = fs_rl(OTG_DMA_DINT_STAT); + u32 etd_err = fs_rl(OTG_DMA_ETD_ERR); + u32 ep_err = fs_rl(OTG_DMA_EP_ERR); + + static u32 dma_interrupts = 0; + + dma_interrupts += 1; + + printk(KERN_INFO"%s: dint_stat: %08x etd_err: %08x ep_err: %08x\n", __FUNCTION__, dint_stat, etd_err, ep_err); + + if (dma_interrupts > 10) { + //TRACE_MSG1(otg->ocd->TAG, "DMA INT #%08x disabling DMA error interrupts",dint_stat); + fs_wl(OTG_DMA_DINT_STEN, 0x0); + fs_wl(OTG_DMA_EP_ERR, 0x0); + } + + //TRACE_MSG3(otg->ocd->TAG, "dint_stat: %08x etd_err: %08x ep_err: %08x", dint_stat, etd_err, ep_err); + + if (dint_stat) fs_wl(OTG_DMA_DINT_STAT, dint_stat); + if (etd_err) fs_wl(OTG_DMA_ETD_ERR, etd_err); + if (ep_err) fs_wl(OTG_DMA_EP_ERR, ep_err); + + return IRQ_HANDLED; +} + +/*! + * hcd_host_int_hndlr() - Host interrupt + * @param irq + * @param dev_id + * @param regs + */ +static irqreturn_t hcd_host_int_hndlr (int irq, void *dev_id, struct pt_regs *regs) +{ + u32 sint_stat = fs_rl(OTG_HOST_SINT_STAT); + static u32 hcd_interrupts = 0; + //printk(KERN_INFO"%s: %d\n", __FUNCTION__, hcd_interrupts++); + mxc_hcd_hw_int_hndlr(irq, NULL, regs); + return IRQ_HANDLED; +} + +/*! + * ocd_ctrl_int_hndlr - Control and I2C interrupt + * Process USBOTG related interrupts. + * @param irq + * @param dev_id + * @param regs + */ +static irqreturn_t ocd_ctrl_int_hndlr (int irq, void *dev_id, struct pt_regs *regs) +{ + u32 sys_ctrl = fs_rl(OTG_SYS_CTRL); // C.f. 23.8 USB Control Register + u32 cint_stat = fs_rl(OTG_CORE_CINT_STAT); // C.f. 23.9.2 USBOTG Module Interrupt Status Register + u32 hint_stat = fs_rl(OTG_CORE_HINT_STAT); // C.f. 23.9.12 HNP Interrupt Status Register + u32 hnp_cstat = fs_rl(OTG_CORE_HNP_CSTAT); + + static u32 ocd_interrupts = 0; + + /* N.B. OTG_CORE_CINT_STAT interrupts are self clearing when interrupt + * sources have been cleared. + */ + RETURN_IRQ_HANDLED_UNLESS(cint_stat || hint_stat); + + //if (sys_ctrl & (SYS_CTRL_HOST_WU_INT_STAT | SYS_CTRL_FNT_WU_INT_STAT)) + // TRACE_MSG1(OCD, "SYS_CTRL: HOST/FUNC %08x", sys_ctrl); + + //TRACE_MSG4(OCD, "CLK: %08x sys: %08x cint: %08x hint: %08x", fs_rl(OTG_CORE_CLK_CTRL), sys_ctrl, cint_stat, hint_stat); + + + if (hint_stat) { + //if (hint_stat & HNP_I2COTGINT) + // TRACE_MSG1(OCD, "OTG_CORE_HINT_STAT HNP_I2COTGINT %08x ignored (periodic?)", hint_stat); + + fs_wl(OTG_CORE_HINT_STAT, hint_stat); + printk(KERN_INFO"%s: HINT_STAT: %08x HNP_CSTAT: %08x\n", __FUNCTION__, hint_stat, hnp_cstat); + } + + if (cint_stat & MODULE_ASHNPINT) { // asynchronous HNP interrupt, enable Main clock + u32 hnp_cstat = fs_rl(OTG_CORE_HNP_CSTAT); + u32 hint_stat = fs_rl(OTG_CORE_HINT_STAT); + //TRACE_MSG1(OCD, "MODULE_ASHNPINT %08x", hint_stat); + + mxc_main_clock_on(); // turn on Main Clock + + //if (hnp_cstat & MODULE_ISBDEV) + // TRACE_MSG0(OCD, "ISBDEV"); + + //if (hnp_cstat & MODULE_ISADEV) + // TRACE_MSG0(OCD, "ISADEV"); + + //fs_wl_set(OCD, OTG_CORE_HINT_STAT, hint_stat); + fs_wl(OTG_CORE_HINT_STAT, hint_stat); + ocd_hnp_int_hndlr(irq, dev_id, regs); + } + if (cint_stat & MODULE_ASFCINT) { // Asynchronous Function interrupt, enable Func clock + //TRACE_MSG1(OCD, "MODULE_ASFCINT %08x", cint_stat); + // XXX otg_queue_event(ocd_instance->otg, B_SESS_VLD | A_SESS_VLD, PCD, "MX2ADS ASFCINT"); + mxc_func_clock_on(); // turn on Function Clock + } + + if (cint_stat & MODULE_ASHCINT) { // Asynchronous Host interrupt, enable Host clock + //TRACE_MSG1(OCD, "MODULE_ASHCINT %08x", cint_stat); + mxc_host_clock_on(); // turn on Host Clock + } + + if ((cint_stat & MODULE_HNPINT) || hint_stat) // HNP interrupt + ocd_hnp_int_hndlr(irq, dev_id, regs); + + if (cint_stat & MODULE_FCINT) + mxc_pcd_int_hndlr(); + + if (cint_stat & MODULE_HCINT) { + //TRACE_MSG1(OCD, "MODULE_HCINT %08x", cint_stat); + mxc_hcd_hw_int_hndlr(irq, NULL, regs); + } + + return IRQ_HANDLED; +} + +/* ********************************************************************************************* */ +/*! + * pcd_bwkup_int_hndlr_isr() - main bwkkup interrupt handler + * @param irq + * @param dev_id + * @param regs + */ +irqreturn_t pcd_bwkup_int_hndlr_isr (int irq, void *dev_id, struct pt_regs *regs) +{ + //pcd_instance->otg->interrupts++; + //TRACE_MSG0(OCD, "--"); + //printk(KERN_INFO"%s:\n", __FUNCTION__); + return pcd_bwkup_int_hndlr (irq, dev_id, regs); +} + +/*! + * ocd_hnp_int_hndlr_isr() - main ocd hnp interrupt handler + * @param irq + * @param dev_id + * @param regs + */ +irqreturn_t ocd_hnp_int_hndlr_isr (int irq, void *dev_id, struct pt_regs *regs) +{ + //pcd_instance->otg->interrupts++; + //TRACE_MSG0(OCD, "--"); + //printk(KERN_INFO"%s:\n", __FUNCTION__); + return ocd_hnp_int_hndlr (irq, dev_id, regs); +} + +/*! + * pcd_func_int_hndlr_isr() - main pcd function interrupt handler + * @param irq + * @param dev_id + * @param regs + */ +irqreturn_t pcd_func_int_hndlr_isr (int irq, void *dev_id, struct pt_regs *regs) +{ + //pcd_instance->otg->interrupts++; + //TRACE_MSG0(OCD, "--"); + //printk(KERN_INFO"%s:\n", __FUNCTION__); + //TRACE_MSG1(OCD, "CLK: %08x", fs_rl(OTG_CORE_CLK_CTRL) ); + + return mxc_pcd_int_hndlr(); +} + +/*! + * hcd_host_int_hndlr_isr() - main hcd host interrupt handler + * @param irq + * @param dev_id + * @param regs + */ +irqreturn_t hcd_host_int_hndlr_isr (int irq, void *dev_id, struct pt_regs *regs) +{ + //pcd_instance->otg->interrupts++; + //TRACE_MSG0(OCD, "--"); + //printk(KERN_INFO"%s:\n", __FUNCTION__); + return hcd_host_int_hndlr (irq, dev_id, regs); +} + +/*! + * ocd_ctrl_hndlr_isr() - main ocd controller interrupt handler + * @param irq + * @param dev_id + * @param regs + */ +irqreturn_t ocd_ctrl_int_hndlr_isr (int irq, void *dev_id, struct pt_regs *regs) +{ + //pcd_instance->otg->interrupts++; + //TRACE_MSG0(OCD, "--"); + //printk(KERN_INFO"%s:\n", __FUNCTION__); + return ocd_ctrl_int_hndlr (irq, dev_id, regs); +} + +/*! + * ocd_dma_int_hndlr_isr() - main dma interrupt handler + * @param irq + * @param dev_id + * @param regs + */ +irqreturn_t ocd_dma_int_hndlr_isr (int irq, void *dev_id, struct pt_regs *regs) +{ + //pcd_instance->otg->interrupts++; + //TRACE_MSG0(OCD, "--"); + return ocd_dma_int_hndlr (irq, dev_id, regs); +} + +/* ********************************************************************************************* */ +extern void mxc_ocd_init (struct otg_instance *otg, u8 flag); +extern int mxc_ocd_mod_init (struct otg_instance *otg); +extern void mxc_ocd_mod_exit (struct otg_instance *otg); + +#if defined(CONFIG_OTG_GPTR) +int mxc_gptcr_start_timer(struct otg_instance *otg, int usec); +#endif /* defined(CONFIG_OTG_GPTR) */ +#if defined(CONFIG_OTG_HRT) +int mxc_hrt_start_timer(struct otg_instance *otg, int usec); +//otg_tick_t mxc_hrt_trace_ticks (void); +//otg_tick_t mxc_hrt_trace_elapsed(otg_tick_t *t1, otg_tick_t *t2); +#endif /* defined(CONFIG_OTG_HRT) */ + +//otg_tick_t l26_ocd_ticks (void); +//otg_tick_t l26_ocd_elapsed(otg_tick_t *t1, otg_tick_t *t2); + +#if defined(CONFIG_OTG_GPTR) +otg_tick_t mxc_gptcr_trace_ticks (void); +otg_tick_t mxc_gptcr_trace_elapsed(otg_tick_t *t1, otg_tick_t *t2); +#endif /* defined(CONFIG_OTG_GPTR) */ +#if defined(CONFIG_OTG_HRT) +otg_tick_t mxc_hrt_trace_ticks (void); +otg_tick_t mxc_hrt_trace_elapsed(otg_tick_t *t1, otg_tick_t *t2); +#endif /* defined(CONFIG_OTG_HRT) */ + +#if !defined(OTG_C99) +struct ocd_ops ocd_ops; +void mxc_ocd_global_init(void) +{ + ZERO(ocd_ops); + #if defined(CONFIG_OTG_TR_AUTO) + ocd_ops.capabilities = OCD_CAPABILITIES_TR | OCD_CAPABILITIES_AUTO; + #else + ocd_ops.capabilities = OCD_CAPABILITIES_TR; + #endif + ocd_ops.ocd_init_func = mxc_ocd_init; + ocd_ops.mod_init = mxc_ocd_mod_init; + ocd_ops.mod_exit = mxc_ocd_mod_exit; +#if defined(CONFIG_OTG_GPTR) + ocd_ops.start_timer = mxc_gptcr_start_timer; + ocd_ops.ticks = mxc_gptcr_trace_ticks; + ocd_ops.elapsed = mxc_gptcr_trace_elapsed; +#endif /* defined(CONFIG_OTG_GPTR) */ +#if defined(CONFIG_OTG_HRT) + ocd_ops.start_timer = mxc_hrt_start_timer; + ocd_ops.ticks = mxc_hrt_trace_ticks; + ocd_ops.elapsed = mxc_hrt_trace_elapsed; +#endif /* defined(CONFIG_OTG_HRT) */ +} +#else /* !defined(OTG_C99) */ +//void mxc_ocd_global_init(void) { + +//} +struct ocd_ops ocd_ops = { + #if defined(CONFIG_OTG_TR_AUTO) + .capabilities = OCD_CAPABILITIES_TR | OCD_CAPABILITIES_AUTO, + #else + .capabilities = OCD_CAPABILITIES_TR, + #endif + .ocd_init_func = mxc_ocd_init, + .mod_init = mxc_ocd_mod_init, + .mod_exit = mxc_ocd_mod_exit, +#if defined(CONFIG_OTG_GPTR) + .start_timer = mxc_gptcr_start_timer, + .ticks = l26_ocd_trace_ticks, + .elapsed = l26_ocd_trace_elapsed, +#endif /* defined(CONFIG_OTG_GPTR) */ +#if defined(CONFIG_OTG_HRT) + .start_timer = mxc_hrt_start_timer, + .ticks = mxc_hrt_trace_ticks, + .elapsed = mxc_hrt_trace_elapsed, +#endif /* defined(CONFIG_OTG_HRT) */ +}; +#endif /* !defined(OTG_C99) */ + +/* ********************************************************************************************* */ + +/*! + * mxc_ocd_mod_init() - initial tcd setup + * Allocate interrupts and setup hardware. + */ +int mxc_ocd_mod_init (struct otg_instance *otg) +{ + int bwkup = request_irq (INT_USB_WAKEUP, pcd_bwkup_int_hndlr_isr, OTG_INTERRUPT, UDC_NAME " USBD BWKUP", NULL); + int func = request_irq (INT_USB_FUNC, pcd_func_int_hndlr_isr, OTG_INTERRUPT, UDC_NAME " USBD FUNC", NULL); + int ctrl = request_irq (INT_USB_CTRL, ocd_ctrl_int_hndlr_isr, OTG_INTERRUPT, UDC_NAME " USBD CTRL", NULL); + //int sof = request_irq (INT_USB_SOF, sof_int_hndlr_isr, OTG_INTERRUPT, UDC_NAME " USBD SOF", NULL); + //int host = request_irq (OTG_USBHOST, hcd_host_int_hndlr_isr, OTG_INTERRUPT, UDC_NAME " USBD HOST", NULL); + int host = 0; + int dma = request_irq (INT_USB_DMA, ocd_dma_int_hndlr_isr, OTG_INTERRUPT, UDC_NAME " USBD DMA", NULL); + + TRACE_MSG0(otg->ocd->TAG, "1. Interrupts requested"); + + RETURN_EINVAL_IF(bwkup || dma || func || host || ctrl); + + return 0; +} + +extern void mxc_stop_ep(int epn, int dir); + +/*! + * mxc_ocd_mod_exit() - de-initialize + */ +void mxc_ocd_mod_exit (struct otg_instance *otg) +{ + #if 1 + int i; + unsigned long flags; + +// fsptrcr_mod_exit(); +// _reg_CRM_PCCR1 &= ~(1<<27); // disable GPT3 + + TRACE_MSG0(otg->ocd->TAG, "free irqs"); +// for (i = INT_USB_WAKEUP; i <= INT_USB_DMA; i++) free_irq (i, NULL); // Free IRQ's + free_irq (INT_USB_WAKEUP, NULL); + free_irq (INT_USB_FUNC, NULL); + free_irq (INT_USB_CTRL, NULL); + free_irq (INT_USB_DMA, NULL); + //free_irq (INT_GPT2, NULL); + //free_irq (INT_GPIO, NULL); + #endif +} + + +/* ********************************************************************************************* */ diff --git a/drivers/otg/hardware/mxc-pcd.c b/drivers/otg/hardware/mxc-pcd.c new file mode 100644 index 000000000000..4c3878af98cf --- /dev/null +++ b/drivers/otg/hardware/mxc-pcd.c @@ -0,0 +1,1584 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/hardware/mxc-pcd.c -- Freescale USBOTG Peripheral Controller driver + * @(#) sl@belcarra.com/whiskey.enposte.net|otg/platform/mxc/mxc-pcd.c|20070822214154|21475 + * + * Copyright (c) 2004-2005 Belcarra Technologies Corp + * Copyright (c) 2005-2007 Belcarra Technologies 2005 Corp + * + * By: + * Stuart Lynne <sl@lbelcarra.com>, + * Bruce Balden <balden@belcarra.com> + * + */ +/*! + * @file otg/hardware/mxc-pcd.c + * @brief Freescale USB Peripheral Controller Driver + * This implements the Freescale USBOTG Peripheral Controller Driver. + * + * There is some processor specific code here to support the alternate processors + * that implement this type of USB support. + * + * There is no board or platform level code here. + * + * @ingroup FSOTG + * @ingroup PCD + * + * + * Notes + * + * 1. EP_RDY is a toggle register, not R/W. + * + * 2. There is an EP_RDY_CLR register (only for TO2?) + * + * 3. EP_TOGGLE is RW for TO1 and toggle for TO2. + * + * 4. DMA transfers always transfer buffersize amounts of data. + * + * 5. DMA IN transfers for very short (2 byte) transfers can take longer than the + * data being sent causing done status change to fail. + * + * 6. DMA IN transfers must be monitored and the DMA enable forcibly reset if it + * does not clear by itself. + * + */ + +#include <otg/pcd-include.h> +#if defined(CONFIG_OTG_LNX) +#include "mxc-lnx.h" +#endif /* defined(CONFIG_OTG_LNX) */ +#if defined(CONFIG_OTG_QNX) +#include "mx21.h" +#include "mxc-qnx.h" +#endif /* defined(CONFIG_OTG_QNX) */ +#include "mxc-hardware.h" + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,15) +#include <linux/platform_device.h> +#endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,15) */ + + +//#define SHORT_DMA_IN /* define this to force transfers to be maximum of 2*wMaxPacketSize */ +//#define NO_DMA_IN /* define this to use memcpy instead of DMA */ + +//#define SHORT_DMA_OUT /* define this to force transfers to be maximum of 2*wMaxPacketSize */ +//#define NO_DMA_OUT /* define this to use memcpy instead of DMA */ + +//#define MXC_ACTIVE /* active SOF checking */ +//#define MXC_TRACK_TIMER + +#define TRACE_VERBOSE 0 +#define TRACE_VERY_VERBOSE 0 + +struct pcd_instance *mxc_pcd_instance; + +void mxc_func_clock_on(void); /* defined in mx2-ocd.c */ +void mxc_func_clock_off(void); /* defined in mx2-ocd.c */ +void mxc_main_clock_on(void); +void mxc_main_clock_off(void); + + +u8 mxc_new_address, mxc_usb_address; /* used to save and set USB address */ +u16 mxc_funcrev; + +#ifdef MXC_TRACK_TIMER +void mxc_pcd_hrt_callback (unsigned long arg) +{ + struct usbd_endpoint_instance *endpoint=(struct usbd_endpoint_instance *) arg; + printk(KERN_INFO "\n"); + printk(KERN_INFO"%s: The endpoint_instance got time Out while waiting Done interrupt-----urbs in queue:%ld\n", __FUNCTION__,(endpoint->rdy).total); +} + +#endif + +/* ********************************************************************************************* */ + +/* The EP Toggle register has different ways of setting and clearing + * depending on hardware version. The older hardware is R/W. The newer + * hardware is write to toggle the current value. + */ + + +void mxc_halt_endpoint0_in(int pipe) +{ + + u32 ep0_current, ep0_new; + + ep0_current = fs_rl(ep_word(0, pipe, 0)); + ep0_new = (1 << 31) | (ep0_current); + + UNLESS (ep0_current == ep0_new) fs_wl(ep_word(0,pipe, 0), ep0_new); + +} + +/*! mxc_reset_toggles + */ +static void inline +mxc_reset_toggles(void) +{ + //u32 toggle = fs_rl(OTG_FUNC_EP_TOGGLE); + if (mxc_funcrev == 0x11) { + /* TO1 - RW register, simply or the bit and write + */ + fs_wl(OTG_FUNC_EP_TOGGLE, 0); + //if (TRACE_VERY_VERBOSE) + // TRACE_MSG2(mxc_pcd_instance->TAG, "EP_TOGGLES: %08x -> %08x (1.1)", toggle, fs_rl(OTG_FUNC_EP_TOGGLE)); + } + else { + /* TO2 - RC - check and then write the bit to toggle to the other value if required + */ + fs_wl(OTG_FUNC_EP_TOGGLE, fs_rl(OTG_FUNC_EP_TOGGLE)); + //if (TRACE_VERY_VERBOSE) + // TRACE_MSG2(mxc_pcd_instance->TAG, "EP_TOGGLES: %08x -> %08x (2.0)", toggle, fs_rl(OTG_FUNC_EP_TOGGLE)); + } +} + +/*! mxc_reset_toggle_ep + */ +static void inline +mxc_reset_toggle_ep(int ep_num) +{ + //u32 toggle = fs_rl(OTG_FUNC_EP_TOGGLE); + if (mxc_funcrev == 0x11) { + /* TO1 - RW register, simply or the bit and write + */ + fs_andl(OTG_FUNC_EP_TOGGLE, ~ep_num); + //if (TRACE_VERY_VERBOSE) + // TRACE_MSG3(mxc_pcd_instance->TAG, "EP_TOGGLE[%d]: %08x -> %08x (1.1)", + // toggle, ep_num, fs_rl(OTG_FUNC_EP_TOGGLE)); + } + else { + /* TO2 - RC - check and then write the bit to toggle to the other value if required + */ + if (fs_rl(OTG_FUNC_EP_TOGGLE) & ep_num) fs_wl(OTG_FUNC_EP_TOGGLE, ep_num); + //if (TRACE_VERY_VERBOSE) + // TRACE_MSG3(mxc_pcd_instance->TAG, "EP_TOGGLE[%d]: %08x -> %08x (2.0)", + // toggle, ep_num, fs_rl(OTG_FUNC_EP_TOGGLE)); + } +} + +/*! mxc_set_toggle_ep + */ +static void inline +mxc_set_toggle_ep(int ep_num) +{ + //u32 toggle = fs_rl(OTG_FUNC_EP_TOGGLE); + if (mxc_funcrev == 0x11) { + /* TO1 - RW register, simply or the bit and write + */ + fs_orl(OTG_FUNC_EP_TOGGLE, ep_num); + //if (TRACE_VERY_VERBOSE) + // TRACE_MSG2(mxc_pcd_instance->TAG, "EP OR: %08x -> %08x (1.1)", toggle, fs_rl(OTG_FUNC_EP_TOGGLE)); + } + else { + /* TO2 - RC - check and then write the bit to toggle to the other value if required + */ + UNLESS (fs_rl(OTG_FUNC_EP_TOGGLE) & ep_num) fs_wl(OTG_FUNC_EP_TOGGLE, ep_num); + //if (TRACE_VERY_VERBOSE) + // TRACE_MSG2(mxc_pcd_instance->TAG, "EP TOGGLE: %08x -> %08x (2.0)", toggle, fs_rl(OTG_FUNC_EP_TOGGLE)); + } +} +/* ********************************************************************************************* */ +/* + * N.B. EP_RDY is a toggle not RW register. + * + * XXX need to check what TO1 does. I think it is RW register. + */ + +/*! mxc_reset_ep_rdy + */ +static void /* inline */ +mxc_reset_ep_rdy(int ep_num) +{ + u32 ep_rdy; + RETURN_UNLESS ((ep_rdy = fs_rl(OTG_FUNC_EP_RDY)) & ep_num); + if (mxc_funcrev == 0x11) { + if (TRACE_VERBOSE) + TRACE_MSG1(mxc_pcd_instance->TAG, "Toggle: ep_rdy: %08x", fs_rl(OTG_FUNC_EP_RDY_CLR)); + fs_wl(OTG_FUNC_EP_RDY, ep_num); // toggle ready bit + if (TRACE_VERBOSE) + TRACE_MSG1(mxc_pcd_instance->TAG, "Toggle: ep_rdy: %08x", fs_rl(OTG_FUNC_EP_RDY_CLR)); + } + else { + if (TRACE_VERBOSE) + TRACE_MSG1(mxc_pcd_instance->TAG, "Clear: ep_rdy: %08x", fs_rl(OTG_FUNC_EP_RDY_CLR)); + fs_wl(OTG_FUNC_EP_RDY_CLR, ep_num); // clear ready bit + if (TRACE_VERBOSE) + TRACE_MSG1(mxc_pcd_instance->TAG, "Clear: ep_rdy: %08x", fs_rl(OTG_FUNC_EP_RDY_CLR)); + } +} + +/*! mxc_set_ep_rdy + * + * N.B. EP_RDY is a toggle not RW register. + */ +static void /* inline */ +mxc_set_ep_rdy(int ep_num) +{ + u32 ep_rdy;; + RETURN_IF ((ep_rdy = fs_rl(OTG_FUNC_EP_RDY)) & ep_num); + fs_wl_set(mxc_pcd_instance->TAG, OTG_FUNC_EP_RDY, ep_num); // set ready bit +} +/* ********************************************************************************************* */ +/* + * These track IN endpoint transfers and enable the SOF interrupt + * when (if) there is a transfer pending in any IN endpoint + */ +#ifdef MXC_ACTIVE +static u32 mxc_pcd_active; +#endif /* MXC_ACTIVE */ + +/*! mxc_sof_active + */ +void mxc_sof_active(u32 ep_num) +{ + #ifdef MXC_ACTIVE + /* enable SOF interrupt if nothing is currently active */ + UNLESS (mxc_pcd_active) + fs_orl(OTG_FUNC_SINT_STEN, SYSTEM_SOFDETINT_EN); // XXX ORL - ok + mxc_pcd_active |= ep_num; + + if (TRACE_VERY_VERBOSE) + TRACE_MSG3(mxc_pcd_instance->TAG, "ep_num: %08x active: %08x sint_sten: %08x", + ep_num, mxc_pcd_active, fs_rl(OTG_FUNC_SINT_STEN)); + #endif /* MXC_ACTIVE */ +} + +/*! mxc_sof_cancel + */ +void mxc_sof_cancel(u32 ep_num) +{ + #ifdef MXC_ACTIVE + /* disable SOF interrupt if nothing is now active */ + mxc_pcd_active &= ~ep_num; + UNLESS (mxc_pcd_active) + fs_wl(OTG_FUNC_SINT_STEN_CLR, SYSTEM_SOFDETINT_EN); + + if (TRACE_VERY_VERBOSE) + TRACE_MSG3(mxc_pcd_instance->TAG, "ep_num: %08x active: %08x sint_sten: %08x", + ep_num, mxc_pcd_active, fs_rl(OTG_FUNC_SINT_STEN)); + #endif /* MXC_ACTIVE */ +} + + +/* ********************************************************************************************* */ + +/*! + * mxc_ep_config() - configure and enable an endpoint to send or receive data + * First setup the endpoint descriptor words, and then enable the endpoint and done interrupt + * @param epn - endpoint number + * @param dir - direction IN or OUT + * @param ep_num - + * @param wMaxPacketSize + * @param buffersize + * @param format + * @param ttlbtecnt + * @param stall + * + * Adjust buffersize to match transfersize for IN transfers less than 2 * + * packet size. Note that this requires that the x/y buffers are + * sequentially allocated. Effectively this makes the DMA only transfer the + * exact amount of the transfer. + */ +static void inline +mxc_ep_config(int epn, int dir, u32 ep_num, int wMaxPacketSize, int buffersize, int format, int ttlbtecnt, int stall) +{ + u32 ep_en = fs_rl(OTG_FUNC_EP_EN); + u32 ep0_current = fs_rl(ep_word(epn, dir, 0)); + u32 ep1_current = fs_rl(ep_word(epn, dir, 1)); + u32 ep3_current = fs_rl(ep_word(epn, dir, 3)); + + u32 ep0_new = ((stall ? 1 : 0) << 31) | (wMaxPacketSize << 16) | ((format & 3) << 14); + u32 ep1_new = ttlbtecnt ? (data_y_buf(epn, dir) << 16) | data_x_buf(epn, dir): 0; + u32 ep3_new = ((buffersize - 1) << 21) | (ttlbtecnt & 0x1fffff); + + + if (TRACE_VERBOSE) { + TRACE_MSG7(mxc_pcd_instance->TAG, "epn[%02x:%02x] ep_num: %08x sz: %d fmt: %x cnt: %d stall: %d", + epn, dir, ep_num, wMaxPacketSize, format, ttlbtecnt, stall); + TRACE_MSG7(mxc_pcd_instance->TAG, "ep_en: %08x ep0[%08x:%08x] ep1[%08x:%08x] ep3[%08x:%08x]", ep_en, + ep0_current, ep0_new, ep1_current, ep1_new, ep3_current, ep3_new); + TRACE_MSG2(mxc_pcd_instance->TAG, "xfill: %08x yfill: %08x", + fs_rl(OTG_FUNC_XFILL_STAT), fs_rl(OTG_FUNC_YFILL_STAT)); + } + + /* disable interrupts to ensure that all changes to endpoint + * configuration are completed with minimal latency + */ + otg_disable_interrupts(); + + UNLESS (ep0_current == ep0_new) fs_wl(ep_word(epn, dir, 0), ep0_new); + UNLESS (ep1_current == ep1_new) fs_wl(ep_word(epn, dir, 1), ep1_new); + UNLESS (ep3_current == ep3_new) fs_wl(ep_word(epn, dir, 3), ep3_new); + UNLESS(ep_en & ep_num) fs_orl(OTG_FUNC_EP_EN, ep_num); // enable the endpoint for use // XXX or required mx21 + + fs_orl(OTG_FUNC_IINT, ep_num); // issue done status interrupts immediately + fs_orl(OTG_FUNC_EP_DEN, ep_num); // enable endpoint done interrupt + + otg_enable_interrupts(); + + /* interrupts enabled again */ + + if (TRACE_VERBOSE) { + TRACE_MSG7(mxc_pcd_instance->TAG, "epn[%02x:%02x] %08x %08x %08x TOG: %08x EP_EN: %08x", epn, dir, + fs_rl(ep_word(epn, dir, 0)), fs_rl(ep_word(epn, dir, 1)), + fs_rl(ep_word(epn, dir, 3)), fs_rl(OTG_FUNC_EP_TOGGLE), fs_rl(OTG_FUNC_EP_EN)); + + TRACE_MSG7(mxc_pcd_instance->TAG, "epn[%02x:%02x] ep_num: %08x sz: %d fmt: %x cnt: %d stall: %d", + epn, dir, ep_num, wMaxPacketSize, format, ttlbtecnt, stall); + } +#if 0 + printk (KERN_INFO"%s: epn[%02x:%02x] %08x %08x %08x TOG: %08x EP_EN: %08x\n", __FUNCTION__, + epn, dir, fs_rl(ep_word(epn, dir, 0)), fs_rl(ep_word(epn, dir, 1)), + fs_rl(ep_word(epn, dir, 3)), fs_rl(OTG_FUNC_EP_TOGGLE), fs_rl(OTG_FUNC_EP_EN)); + printk (KERN_INFO"%s: epn[%02x:%02x] ep_num: %08x sz: %d fmt: %x cnt: %d stall: %d\n", __FUNCTION__, + epn, dir, ep_num, wMaxPacketSize, format, ttlbtecnt, stall); +#endif +} + +/*! + * mxc_ep_config_nodma() - configure and enable an endpoint to send or receive data + * Non-DMA setup, optional stall, ready etc. + * @param epn - endpoint number + * @param dir + * @param stall - stall endpoint + * @param wMaxPacketSize + * @param format + * @param ttlbtecnt + * @param rdy1 + * @param rdy2 + */ +static void +mxc_ep_config_nodma(int epn, int dir, int stall, int wMaxPacketSize, int format, int ttlbtecnt, int rdy1, int rdy2) +{ + u32 ep_num = ep_num_dir(epn, dir); + + if (TRACE_VERBOSE) + TRACE_MSG5(mxc_pcd_instance->TAG, "EP CONFIG NODMA: epn[%02x] num: %02x stall: %d r1: %d r2: %d", + epn, ep_num, stall, rdy1, rdy2); + if (rdy1) { + if (TRACE_VERBOSE) + TRACE_MSG3(mxc_pcd_instance->TAG, "EP CONFIG NODMA: epn: %02x num: %02x checking RDY: %02x", + epn, ep_num, rdy1 && fs_rl(OTG_FUNC_EP_RDY) & ep_num); + RETURN_IF (rdy1 && (fs_rl(OTG_FUNC_EP_RDY) & ep_num)); + } + + //UNLESS(epn) + // fs_orl(OTG_FUNC_XYINT_STEN, ep_num); // enable x or y buffer interrupt + + mxc_ep_config(epn, dir, ep_num, wMaxPacketSize, wMaxPacketSize, format, ttlbtecnt, stall); + RETURN_UNLESS(rdy2); + + mxc_set_ep_rdy(ep_num); + + if (TRACE_VERY_VERBOSE) + TRACE_MSG4(mxc_pcd_instance->TAG, "IINT: %08x DEN: %08x EN: %08x RDY: %08x", fs_rl(OTG_FUNC_IINT), + fs_rl(OTG_FUNC_EP_DEN), fs_rl(OTG_FUNC_EP_EN), fs_rl(OTG_FUNC_EP_RDY)); +} + +/*! + * mxc_pcd_start_ep0_setup() - enable ep0 for receiving SETUP request + * Unless already ready, configure ep0 to receive. + * @param endpoint - endpoint instance + */ +static void inline +mxc_pcd_start_ep0_setup (struct usbd_endpoint_instance *endpoint) +{ + if (TRACE_VERY_VERBOSE) + TRACE_MSG0(mxc_pcd_instance->TAG, "START EP0: config"); + + // XXX rdy1 must be FALSE for OUT and TRUE for IN + mxc_ep_config_nodma(0, USB_DIR_OUT, 0, endpoint->wMaxPacketSize[0], EP_FORMAT_CONTROL, + endpoint->wMaxPacketSize[0], 0, 1); + mxc_ep_config_nodma(0, USB_DIR_IN, 0, endpoint->wMaxPacketSize[0], EP_FORMAT_CONTROL, 0, 1, 0); +} + +/*! + * mxc_sendzlp() - start sending a buffer on a specified endpoint + * Toggle XFILL_STAT or YFILL_STAT bit, then call mxc_ep_config_nodma(). + * @param epn - endpoint number + * @param endpoint - endpoint instance + * @param wMaxPacketSize + */ +static void inline +mxc_sendzlp (int epn, struct usbd_endpoint_instance *endpoint, int wMaxPacketSize) +{ + u32 ep_num_in = ep_num_in(epn); + + if (TRACE_VERBOSE) + TRACE_MSG0(mxc_pcd_instance->TAG, "ZLP"); + if (!(fs_rl(OTG_FUNC_XFILL_STAT) & ep_num_in)) + fs_wl_set(mxc_pcd_instance->TAG, OTG_FUNC_XFILL_STAT, ep_num_in); + else if (!(fs_rl(OTG_FUNC_YFILL_STAT) & ep_num_in)) + fs_wl_set(mxc_pcd_instance->TAG, OTG_FUNC_YFILL_STAT, ep_num_in); + + mxc_ep_config_nodma(epn, USB_DIR_IN, 0, endpoint->wMaxPacketSize[0], endpoint->bmAttributes[0] & 0x3, 0, 1, 1); +} + +/*! + * mxc_stop_ep() - stop endpoint + * @param epn - endpoint number + * @param dir - direction IN or OUT + */ +void mxc_stop_ep(int epn, int dir) +{ + int ep_num = ep_num_dir(epn, dir); + + if (TRACE_VERY_VERBOSE) + TRACE_MSG7(mxc_pcd_instance->TAG, "--> ep_rdy: %08x dma_ep: %08x iint: %08x int: %08x %08x fill: %08x %08x", + fs_rl(OTG_FUNC_EP_RDY), fs_rl(OTG_DMA_EP_CH_CLR), fs_rl(OTG_FUNC_IINT), + fs_rl(OTG_FUNC_XINT_STAT), fs_rl(OTG_FUNC_YINT_STAT), + fs_rl(OTG_FUNC_XFILL_STAT), fs_rl(OTG_FUNC_YFILL_STAT)); + + mxc_reset_ep_rdy(ep_num); + + fs_wl(OTG_DMA_EP_CH_CLR, ep_num); + + fs_wl_set(mxc_pcd_instance->TAG,OTG_FUNC_IINT_CLR, ep_num); + + if (fs_rl(OTG_FUNC_XINT_STAT) & ep_num) { + fs_wl_clr(mxc_pcd_instance->TAG, OTG_FUNC_XINT_STAT, ep_num); + fs_wl_clr(mxc_pcd_instance->TAG, OTG_FUNC_XFILL_STAT, ep_num); + } + if (fs_rl(OTG_FUNC_YINT_STAT) & ep_num) { + fs_wl_clr(mxc_pcd_instance->TAG, OTG_FUNC_YINT_STAT, ep_num); + fs_wl_clr(mxc_pcd_instance->TAG, OTG_FUNC_YFILL_STAT, ep_num); + } +} + +/* ********************************************************************************************* */ +/*! + * mxc_pcd_setup_ep() - setup endpoint + * @param pcd - pcd instance + * @param epn - endpoint number + * @param endpoint - endpoint instance + */ +static void inline +mxc_pcd_setup_ep (struct pcd_instance *pcd, unsigned int epn, struct usbd_endpoint_instance *endpoint) +{ + u32 ep_num; + int dirr,epnn; + + if (TRACE_VERY_VERBOSE) + TRACE_MSG2(mxc_pcd_instance->TAG, "epn[%d] START EPN: config %02x", epn, endpoint->bEndpointAddress[0]); + + /* check if not endpoint zero and we have an assigned address */ + RETURN_UNLESS(epn && endpoint->bEndpointAddress[0]); + + dirr=endpoint->bEndpointAddress[0] & USB_DIR_IN; + epnn=endpoint->bEndpointAddress[0] & 0x7f; + ep_num= ep_num_dir(epnn, dirr); + + /* Start from DATA0 */ + mxc_reset_toggle_ep(ep_num); + + mxc_ep_config_nodma(endpoint->bEndpointAddress[0] & 0x7f, endpoint->bEndpointAddress[0] & USB_DIR_IN, 0, + endpoint->wMaxPacketSize[0], endpoint->bmAttributes[0] & 0x3, 0, 0, 0); + + +} + +/* ********************************************************************************************* */ +/*! + * mxc_pcd_start_endpoint_out() - start receive + * @param pcd - pcd instance + * @param endpoint - endpoint number + */ +static void +mxc_pcd_start_endpoint_out(struct pcd_instance *pcd, struct usbd_endpoint_instance *endpoint) +{ + struct usbd_urb *rcv_urb = pcd_rcv_next_irq(endpoint); + int epn = endpoint->bEndpointAddress[0] & 0x7f; + u32 ep_num = ep_num_dir(epn, USB_DIR_OUT); + u32 dma_num = dma_num_dir(epn, USB_DIR_OUT); + UNLESS (rcv_urb) { + UNLESS (epn) mxc_pcd_start_ep0_setup(endpoint); // if EP0 then restart for receive setup + return; + } + + + #if defined(SHORT_DMA_OUT) || defined(NO_DMA_OUT) + endpoint->last = MIN(rcv_urb->buffer_length, 2 * endpoint->wMaxPacketSize[0]); + #else /* defined(SHORT_DMA_OUT) || defined(NO_DMA_OUT) */ + endpoint->last = rcv_urb->buffer_length; + #endif /* defined(SHORT_DMA_OUT) || defined(NO_DMA_OUT) */ + + if (TRACE_VERBOSE) + TRACE_MSG7(mxc_pcd_instance->TAG, + "epn[%02x] EP CONFIG DMA: num: %02x buffer: %p length: %d dir: %d rcv_urb: %x last: %d", + epn, ep_num, rcv_urb->buffer, rcv_urb->buffer_length, + USB_DIR_OUT, rcv_urb, endpoint->last); + + mxc_ep_config(epn, USB_DIR_OUT, ep_num, endpoint->wMaxPacketSize[0], + endpoint->wMaxPacketSize[0], endpoint->bmAttributes[0] & 0x3, endpoint->last, 0); + + #if !defined(NO_DMA_OUT) + CACHE_SYNC_RCV (rcv_urb->buffer + rcv_urb->actual_length, endpoint->last); + fs_wl(OTG_DMA_EPN_MSA(dma_num), virt_to_phys(rcv_urb->buffer + rcv_urb->actual_length)); + fs_wl_set(mxc_pcd_instance->TAG, OTG_DMA_EP_EN, ep_num); + #endif /* NO_DMA_OUT */ + mxc_set_ep_rdy(ep_num); + if (TRACE_VERY_VERBOSE) + TRACE_MSG2(mxc_pcd_instance->TAG, "epn[%02x] EP_RDY: %08x", epn, fs_rl(OTG_FUNC_EP_RDY)); +} + +/*! + * mxc_pcd_stop_out() - process interrupt for received buffer + * @param pcd - pcd instance + * @param epn - endpoint number + * @param endpoint - endpoint instance + */ +static void +mxc_pcd_stop_out (struct pcd_instance *pcd, int epn, struct usbd_endpoint_instance *endpoint) +{ + struct usbd_urb *rcv_urb = pcd_rcv_next_irq(endpoint); + u32 ep3 = fs_rl(ep_word(epn, USB_DIR_OUT, 3)) & 0x1fffff; // XXX fix mask + //u32 ep_num = ep_num_out(epn); + #if defined(NO_DMA_OUT) + int wMaxPacketSize = endpoint->wMaxPacketSize[0]; + #endif /* defined(NO_DMA_OUT) */ + int last; + int xfer; + + mxc_stop_ep(epn, USB_DIR_OUT); + + RETURN_UNLESS (rcv_urb); // XXX should reset fill bits... + last = endpoint->last; + + xfer = last - ep3; + + if (TRACE_VERBOSE) + TRACE_MSG6(mxc_pcd_instance->TAG, "epn{%02x] actual: %d rcv_urb: %x last: %d ep3: %04x xfer: %d", + epn, rcv_urb->actual_length, rcv_urb, last, ep3, xfer); + + #if defined(NO_DMA_OUT) + if (TRACE_VERBOSE) + TRACE_MSG7(mxc_pcd_instance->TAG, "--> ep_rdy: %08x dma_ep: %08x iint: %08x int: %08x %08x fill: %08x %08x", + fs_rl(OTG_FUNC_EP_RDY), fs_rl(OTG_DMA_EP_CH_CLR), fs_rl(OTG_FUNC_IINT), + fs_rl(OTG_FUNC_XINT_STAT), fs_rl(OTG_FUNC_YINT_STAT), + fs_rl(OTG_FUNC_XFILL_STAT), fs_rl(OTG_FUNC_YFILL_STAT)); + + if (xfer /*&& (fs_rl(OTG_FUNC_XFILL_STAT) & ep_num)*/) { + + if (TRACE_VERBOSE) + TRACE_MSG3(mxc_pcd_instance->TAG, "DATA_X: dst: %x src: %x len: %d", + rcv_urb->buffer + rcv_urb->actual_length, + (void *)data_x_address(epn, USB_DIR_OUT), wMaxPacketSize); + + memcpy(rcv_urb->buffer + rcv_urb->actual_length, (void *)data_x_address(epn, USB_DIR_OUT), wMaxPacketSize); + + //pcd_rcv_complete_irq (endpoint, MIN(xfer, wMaxPacketSize), 0); + //fs_wl(OTG_FUNC_XFILL_STAT, ep_num); // XXX this should be necessary + } + if ((xfer > wMaxPacketSize) /*&& (fs_rl(OTG_FUNC_YFILL_STAT) & ep_num)*/) { + + if (TRACE_VERBOSE) + TRACE_MSG3(mxc_pcd_instance->TAG, "DATA_Y: dst: %x src: %x len: %d", + rcv_urb->buffer + rcv_urb->actual_length + wMaxPacketSize, + (void *)data_y_address(epn, USB_DIR_OUT), wMaxPacketSize); + + memcpy(rcv_urb->buffer + rcv_urb->actual_length + wMaxPacketSize, + (void *)data_y_address(epn, USB_DIR_OUT), wMaxPacketSize); + + //pcd_rcv_complete_irq (endpoint, MIN(xfer - wMaxPacketSize, wMaxPacketSize), 0); + //fs_wl(OTG_FUNC_YFILL_STAT, ep_num); // XXX this should be necessary + } + if (TRACE_VERBOSE) + TRACE_MSG7(mxc_pcd_instance->TAG, "<-- ep_rdy: %08x dma_ep: %08x iint: %08x int: %08x %08x fill: %08x %08x", + fs_rl(OTG_FUNC_EP_RDY), fs_rl(OTG_DMA_EP_CH_CLR), fs_rl(OTG_FUNC_IINT), + fs_rl(OTG_FUNC_XINT_STAT), fs_rl(OTG_FUNC_YINT_STAT), + fs_rl(OTG_FUNC_XFILL_STAT), fs_rl(OTG_FUNC_YFILL_STAT)); + #endif /* NO_DMA_OUT */ + + //pcd_rcv_finished_irq (endpoint, xfer, 0); + pcd_rcv_complete_irq (endpoint, xfer, 0); + + if (!epn) mxc_sendzlp(epn, endpoint, endpoint->wMaxPacketSize[0]); // ZLP + else mxc_pcd_start_endpoint_out(pcd, endpoint); // restart +} +/*! + * mxc_pcd_cancel_out_irq() - cancel OUT urb + * @param pcd + * @param urb + */ +static void +mxc_pcd_cancel_out_irq (struct pcd_instance *pcd,struct usbd_urb *urb) +{ + struct usbd_endpoint_instance *endpoint = urb->endpoint; + int epn = endpoint->bEndpointAddress[0] & 0x7f; + if (TRACE_VERBOSE) + TRACE_MSG3(mxc_pcd_instance->TAG, "urb: %x endpoint: %x epn: %d", urb, endpoint, epn); + mxc_stop_ep(epn, USB_DIR_OUT); +} +/* ********************************************************************************************* */ +/*! + * mxc_pcd_start_endpoint_in() - start transmit + * Get next tx_urb (completing old one if there is one) and start sending it. + * @param pcd - pcd instance + * @param endpoint - endpoint instance + */ +static void +mxc_pcd_start_endpoint_in(struct pcd_instance *pcd, struct usbd_endpoint_instance *endpoint) +{ + struct usbd_urb *tx_urb; + int epn = endpoint->bEndpointAddress[0] & 0x7f; + u32 ep_num = ep_num_dir(epn, USB_DIR_IN); + u32 dma_num = dma_num_dir(epn, USB_DIR_IN); + int wMaxPacketSize = endpoint->wMaxPacketSize[0]; + //int i; + int remaining; +#ifdef MXC_TRACK_TIMER + if (timer_pending(&(endpoint->pcd_hr_timer))){ + TRACE_MSG0(mxc_pcd_instance->TAG, "Trying to reconfiure EP before previous finished"); + printk(KERN_INFO"%s:Trying to reconfiure EP before previous finished\n", __FUNCTION__); + } +#endif + + //printk(KERN_INFO"%s:\n", __FUNCTION__); + + if (TRACE_VERBOSE) + TRACE_MSG3(mxc_pcd_instance->TAG, "urb: %x sent: %d last: %d", endpoint->tx_urb, endpoint->sent, endpoint->last); + tx_urb = pcd_tx_complete_irq(endpoint, 0); + + UNLESS (tx_urb) { + if (TRACE_VERBOSE) + TRACE_MSG1(mxc_pcd_instance->TAG, "START IN: %02x finished", endpoint->bEndpointAddress[0]); + UNLESS (epn) { // if not EP0 we are done + if (mxc_new_address != mxc_usb_address) { + mxc_usb_address = mxc_new_address; + if (TRACE_VERY_VERBOSE) + TRACE_MSG1(mxc_pcd_instance->TAG, "START IN: SETTING ADDRESS %x", mxc_usb_address); + fs_wl(OTG_FUNC_DEV_ADDR, mxc_usb_address); + } + mxc_pcd_start_ep0_setup(endpoint); // if EP0 then restart for receive setup + } + return; + } + + if (pcd_tx_sendzlp(endpoint)) { // check if we need to send ZLP + if (TRACE_VERBOSE) + TRACE_MSG1(mxc_pcd_instance->TAG, "START IN: ZLP tx_urb: %x", tx_urb); + mxc_sendzlp(epn, endpoint, wMaxPacketSize); // ZLP + return; + } + + if (TRACE_VERY_VERBOSE) + TRACE_MSG6(mxc_pcd_instance->TAG, "START IN: %02x tx_urb: %x actual: %d sent: %d last: %d %s", endpoint->bEndpointAddress[0], tx_urb, + tx_urb->actual_length, endpoint->sent, + endpoint->last, (tx_urb->flags & USBD_URB_SENDZLP) ? "ZLP": ""); + + + /* check if xfil/yfil bits are set, clear if necessary + */ + if (epn && fs_rl(OTG_FUNC_XFILL_STAT) & ep_num) { + fs_wl(OTG_FUNC_XFILL_STAT, ep_num); + if (TRACE_VERY_VERBOSE) + TRACE_MSG1(mxc_pcd_instance->TAG, "CLEAR XFILL: %08x", fs_rl(OTG_FUNC_XFILL_STAT)); + } + if (epn && fs_rl(OTG_FUNC_YFILL_STAT) & ep_num) { + fs_wl(OTG_FUNC_YFILL_STAT, ep_num); + if (TRACE_VERY_VERBOSE) + TRACE_MSG1(mxc_pcd_instance->TAG, "CLEAR YFILL: %08x", fs_rl(OTG_FUNC_YFILL_STAT)); + } + + /* reset ready status + */ + // XXX mxc_reset_ep_rdy(ep_num); + + /* Three cases + * 1. Complete transfer less than 2 buffers - non DMA + * 2. Multiples of 2 buffers - DMA + * 3. Remainder after sending all full sized buffers, same as 1, non DMA + */ + remaining = tx_urb->actual_length - endpoint->sent; + + if (remaining < (2*wMaxPacketSize)) { + endpoint->last = remaining; + if (TRACE_VERY_VERBOSE) + TRACE_MSG1(mxc_pcd_instance->TAG, "LAST: %d SHORT", endpoint->last); + } + else { + #ifdef NO_DMA_IN + endpoint->last = 2*wMaxPacketSize; + #else /* NO_DMA_IN */ + endpoint->last = (remaining / (2*wMaxPacketSize)) * (2*wMaxPacketSize); + #endif /* NO_DMA_IN */ + if (TRACE_VERBOSE) + TRACE_MSG1(mxc_pcd_instance->TAG, "LAST: %d EVEN", endpoint->last); + } + + #ifdef NO_DMA_IN + if (endpoint->last <= (2*wMaxPacketSize)) { + + if (TRACE_VERBOSE) { + TRACE_MSG2(mxc_pcd_instance->TAG, "XCNT: %d memcpy: %d", endpoint->last, + MAX(32, (endpoint->last + 3) & ~3)); + TRACE_NSEND(mxc_pcd_instance->TAG, endpoint->last, tx_urb->buffer + endpoint->sent); + } + + /* N.b. - copies must be 32bit */ + memcpy((void *)data_x_address(epn, USB_DIR_IN), + tx_urb->buffer + endpoint->sent, + MAX(32, (endpoint->last + 3) & ~3)); + fs_wl(OTG_FUNC_XFILL_STAT, ep_num); + fs_wl(OTG_FUNC_YFILL_STAT, ep_num); + mxc_ep_config(epn, USB_DIR_IN, ep_num, wMaxPacketSize, wMaxPacketSize, USB_DIR_IN, endpoint->last, 0); + + if (TRACE_VERBOSE) + TRACE_MSG6(mxc_pcd_instance->TAG, + "EP CONFIG NODMA: epn: %02x num: %02x ttlbtecnt: %d dir: %d XFILL: %08x YFILL: %08x", + epn, ep_num, endpoint->last, USB_DIR_IN, + fs_rl(OTG_FUNC_XFILL_STAT), fs_rl(OTG_FUNC_YFILL_STAT)); + } + else { + #endif + /* configure endpoint first to setup buffersize correctly, we set it to + * packetsize or the actual transfer size IFF less than twice packetsize. + * Note that this requires sequential x/y buffer allocation. + */ + CACHE_SYNC_TX (tx_urb->buffer + endpoint->sent, endpoint->last); + + mxc_ep_config(epn, USB_DIR_IN, ep_num, wMaxPacketSize, + (endpoint->last > wMaxPacketSize) ? /* XXX 2*wMaxPacketSize ??? */ + wMaxPacketSize : endpoint->last, + endpoint->bmAttributes[0] & 0x3, + endpoint->last, 0); + + fs_wl(OTG_DMA_EPN_MSA(dma_num), virt_to_phys(tx_urb->buffer + endpoint->sent)); + fs_wl_set(mxc_pcd_instance->TAG, OTG_DMA_EP_EN, ep_num); + + if (TRACE_VERBOSE) + TRACE_MSG5(mxc_pcd_instance->TAG, "EP CONFIG DMA: buf: %08x epn: %02x num: %02x ttlbtecnt: %d dir: %d", + (void *)virt_to_phys(tx_urb->buffer), epn, ep_num, endpoint->last, USB_DIR_IN); + #ifdef NO_DMA_IN + } + #endif +#ifdef MXC_TRACK_TIMER + // setup a tracking timer + init_timer (&(endpoint->pcd_hr_timer)); + endpoint->pcd_hr_timer.expires = jiffies + 1000; + endpoint->pcd_hr_timer.function = mxc_pcd_hrt_callback; + endpoint->pcd_hr_timer.data = (unsigned long) endpoint; + endpoint->pcd_hr_timer.arch_cycle_expires = 0; + add_timer(&endpoint->pcd_hr_timer); +#endif + mxc_sof_active(ep_num); + mxc_set_ep_rdy(ep_num); +} + +/*! + * mxc_pcd_stop_in() - process interrupt for transmitted buffer + * @param pcd - pcd instance + * @param epn -endpoint number + * @param endpoint - endpoint instance + */ +static void inline +mxc_pcd_stop_in (struct pcd_instance *pcd, int epn, struct usbd_endpoint_instance *endpoint) +{ + u32 ep_num = ep_num_dir(epn, USB_DIR_IN); + //TRACE_MSG1(mxc_pcd_instance->TAG, "%02x", epn); + mxc_stop_ep(epn, USB_DIR_IN); + mxc_sof_cancel(ep_num); +#ifdef MXC_TRACK_TIMER + del_timer(&endpoint->pcd_hr_timer); +#endif + + mxc_pcd_start_endpoint_in(pcd, endpoint); +} +/*! + * mxc_pcd_cancel_in_irq() - cancel IN urb + */ +static void inline +mxc_pcd_cancel_in_irq (struct pcd_instance *pcd,struct usbd_urb *urb) +{ + struct usbd_endpoint_instance *endpoint = urb->endpoint; + int epn = endpoint->bEndpointAddress[0] & 0x7f; + if (TRACE_VERBOSE) + TRACE_MSG3(mxc_pcd_instance->TAG, "urb: %x endpoint: %x epn: %d", urb, endpoint, epn); + mxc_stop_ep(epn, USB_DIR_IN); +} + +/* ********************************************************************************************* */ +/*! + * mxc_pcd_stop_ep0_setup() - setup endpoint zero + * @param pcd - pcd instance + * @param endpoint - endpoint instance + */ +static void +mxc_pcd_stop_ep0_setup (struct pcd_instance *pcd, struct usbd_endpoint_instance *endpoint) +{ + static struct usbd_device_request request; + //u8 *cp = (u8 *) &request; + u32 ep0 = fs_rl(ep_word(0, USB_DIR_OUT, 0)); + u32 ep3 = fs_rl(ep_word(0, USB_DIR_OUT, 3)); + + //fs_wl_set(mxc_pcd_instance->TAG,OTG_FUNC_XYINT_STEN_CLR, ep_num_both(0)); + fs_wl_set(mxc_pcd_instance->TAG,OTG_FUNC_IINT_CLR, ep_num_both(0)); + + UNLESS (ep0 & EP0_SETUP) { // check if SETUP flag set + mxc_pcd_stop_out(pcd, 0, endpoint); + return; + } + if (TRACE_VERBOSE) + TRACE_MSG1(mxc_pcd_instance->TAG, "EP0 - SETUP: size: %x", ep3 & 0xffff); + pcd_tx_cancelled_irq(endpoint); + pcd_rcv_cancelled_irq(endpoint); + + /* + * Ensure that toggle is set so that any data returned starts as DATA1 + * XXX It might be more appropriate to do this in start_endpoint_in(). + */ + mxc_set_toggle_ep(0x2); + fs_memcpy((u8 *)&request, (u8 *)data_x_address(0, USB_DIR_OUT), 8); + fs_wl_set(mxc_pcd_instance->TAG, OTG_FUNC_XINT_STAT, ep_num_out(0)); + fs_wl_set(mxc_pcd_instance->TAG, OTG_FUNC_XFILL_STAT, ep_num_out(0)); + + if (pcd_recv_setup_irq(pcd, &request)) { // process setup packet + if (TRACE_VERBOSE) + TRACE_MSG0(mxc_pcd_instance->TAG, "pcd_ep0: STALLING"); + //fs_rl(OTG_FUNC_XINT_STAT); + //mxc_ep_config_nodma(0, USB_DIR_IN, 1, endpoint->wMaxPacketSize[0], EP_FORMAT_CONTROL, 0, 1, 1); + mxc_halt_endpoint0_in(1); + mxc_halt_endpoint0_in(0); + return; + } + if (request.wLength) { + // mxc_pcd_start_endpoint_in() will start xfer + //mxc_pcd_start_ep0_setup(endpoint); // restart endpoint + return; + } + if ((request.bmRequestType & USB_REQ_DIRECTION_MASK) == USB_REQ_HOST2DEVICE) { + //RETURN_IF (request.wLength); // mxc_pcd_start_endpoint_in() will start xfer + mxc_sendzlp(0, endpoint, endpoint->wMaxPacketSize[0]); // wLength zero, so send ZLP + return; + } + mxc_pcd_start_ep0_setup(endpoint); // restart endpoint +} +/* ********************************************************************************************* */ +#ifdef MXC_ACTIVE +static u32 mxc_pcd_missed; +#endif /* MXC_ACTIVE */ + +static u32 mxc_pcd_missing; +static u32 mxc_pcd_fixed; + +/*! + * mxc_pcd_int_hndlr() - interrupt handle + * @return interrupt handled status + */ +irqreturn_t mxc_pcd_int_hndlr (void) +{ + struct pcd_instance *pcd = mxc_pcd_instance; + otg_tick_t ticks = (pcd->otg && pcd->otg->ocd_ops && pcd->otg->ocd_ops->ticks) ? pcd->otg->ocd_ops->ticks () : 0; + //u32 cmd_stat, sint_stat, ep_dstat, ep_stats, mask; + u32 cmd_stat, sint_stat, ep_dstat, ep_stats; + //u32 sint_sten = fs_rl(OTG_FUNC_SINT_STEN); + + int i; + BOOL ep0_int = FALSE; + BOOL epn_int = FALSE; + BOOL other_int = FALSE; + + if (!pcd) + return IRQ_HANDLED; + + + /* Reset, clear and process any System interrupts + */ + if ((sint_stat = fs_rl(OTG_FUNC_SINT_STAT))) + fs_wl_set(mxc_pcd_instance->TAG, OTG_FUNC_SINT_STAT, sint_stat); + + cmd_stat = fs_rl(OTG_FUNC_CMD_STAT); + + //TRACE_MSG0(mxc_pcd_instance->TAG, "--"); + + //if (TRACE_VERY_VERBOSE) + TRACE_MSG6(mxc_pcd_instance->TAG, "sint: %08x cmd: %08x EP_EN: %08x EP_RDY: %08x EP_DSTAT: %08x FIXED: %d", + sint_stat, cmd_stat, fs_rl(OTG_FUNC_EP_EN), fs_rl(OTG_FUNC_EP_RDY), + fs_rl(OTG_FUNC_EP_DSTAT), mxc_pcd_fixed); + + + if ((cmd_stat & COMMAND_RESETDET) || (sint_stat & SYSTEM_RESETINT)) { + if (TRACE_VERBOSE) + TRACE_MSG1(mxc_pcd_instance->TAG, "RESETDET: %08x", sint_stat); + //printk(KERN_INFO"%s: RESETDET\n", __FUNCTION__); + pcd_bus_event_handler_irq (pcd->bus, DEVICE_RESET, 0); + //mxc_func_clock_on(); + fs_wl(OTG_FUNC_DEV_ADDR, 0); + mxc_new_address = mxc_usb_address = 0; + fs_wl(OTG_DMA_EP_CH_CLR, 0xffffffff); + + //fs_wl(OTG_FUNC_XYINT_STEN_CLR, 0xffffffff); + fs_wl(OTG_FUNC_IINT_CLR, 0xffffffff); + fs_wl(OTG_FUNC_EP_DEN_CLR, 0xffffffff); // XXX this fails, + + mxc_reset_toggles(); + + //fs_wl_clr(mxc_pcd_instance->TAG, OTG_FUNC_EP_RDY, fs_rl(OTG_FUNC_EP_RDY)); + fs_wl_set(mxc_pcd_instance->TAG, OTG_FUNC_EP_RDY_CLR, fs_rl(OTG_FUNC_EP_RDY)); + + fs_wl_set(mxc_pcd_instance->TAG, OTG_FUNC_EP_DSTAT, fs_rl(OTG_FUNC_EP_DSTAT)); + + fs_wl_set(mxc_pcd_instance->TAG, OTG_FUNC_XINT_STAT, fs_rl(OTG_FUNC_XINT_STAT)); + fs_wl_set(mxc_pcd_instance->TAG, OTG_FUNC_YINT_STAT, fs_rl(OTG_FUNC_YINT_STAT)); + if (pcd->bus) + mxc_pcd_start_ep0_setup(pcd->bus->endpoint_array); + other_int = TRUE; + } + if ((cmd_stat & COMMAND_RSMINPROG) || (sint_stat & SYSTEM_RSMFININT)) { + if (TRACE_VERBOSE) + TRACE_MSG1(mxc_pcd_instance->TAG, "RSMINPRO: %08x", sint_stat); + fs_wl_set(mxc_pcd_instance->TAG, OTG_FUNC_SINT_STAT,SYSTEM_RSMFININT); + mxc_func_clock_on(); + pcd_bus_event_handler_irq (pcd->bus, DEVICE_BUS_ACTIVITY, 0); + other_int = TRUE; + } + if ((cmd_stat & COMMAND_SUPDET) || (sint_stat & SYSTEM_SUSPDETINT)) { + //int timeout = 0; + if (TRACE_VERBOSE) + TRACE_MSG1(mxc_pcd_instance->TAG, "SUSPENDDET: %08x", sint_stat); + fs_wl(OTG_FUNC_CMD_STAT, COMMAND_SUPDET); /* tell hardware to suspend */ + fs_wl(OTG_FUNC_SINT_STAT,SYSTEM_SUSPDETINT); + mxc_func_clock_off(); + pcd_bus_event_handler_irq (pcd->bus, DEVICE_BUS_INACTIVE, 0); + other_int = TRUE; + } + + while ((ep_dstat = fs_rl(OTG_FUNC_EP_DSTAT))) { + + ep_stats = (ep_dstat | fs_rl(OTG_FUNC_XINT_STAT) | fs_rl(OTG_FUNC_YINT_STAT)); + + /* clear the done status flags */ + if (TRACE_VERBOSE) + TRACE_MSG1(mxc_pcd_instance->TAG, "EP STATS: %08x", ep_stats); + //printk (KERN_INFO"%s: EP STATS: %08x\n", __FUNCTION__, ep_stats); + fs_wl_set(mxc_pcd_instance->TAG, OTG_FUNC_EP_DSTAT, ep_dstat); + + /* check ep0 first - special handling for control endpoint, need to check dstat, y/x stats */ + if (ep_stats & EP_OUT) { + ep0_int = TRUE; + mxc_pcd_stop_ep0_setup(pcd, pcd->bus->endpoint_array); + } + if (ep_stats & EP_IN) { + ep0_int = TRUE; + mxc_pcd_stop_in(pcd, 0, pcd->bus->endpoint_array); + } + + /* now check all of the data endpoints, only if in dstat */ + for (i = 1, ep_dstat >>= 2; ep_dstat && (i < 16); i++, ep_dstat >>= 2) { + if (ep_dstat & EP_OUT) { + epn_int = TRUE; + mxc_pcd_stop_out(pcd, i, pcd->bus->endpoint_array + (2 * i)); + } + if (ep_dstat & EP_IN) { + //epn_int = TRUE; + mxc_pcd_stop_in(pcd, i, pcd->bus->endpoint_array + (2 * i) + 1); + } + } + } + + if (epn_int) + TRACE_ELAPSED(mxc_pcd_instance->TAG, "EPN 0. INT", ticks, 0); + + return IRQ_HANDLED; +} + +/* ********************************************************************************************* */ +/*! + * mxc_pcd_set_address() - set the USB address for this device + * @param pcd + * @param address + */ +static void inline +mxc_pcd_set_address (struct pcd_instance *pcd, u8 address) +{ + mxc_usb_address = 0; + mxc_new_address = address; /* this will be used in the interrupt handler */ +} + +/* ********************************************************************************************* */ +/*! + * mxc_pcd_en_func() - enable + * This is called to enable / disable the PCD and USBD stack. + * @param otg - otg instance + * @param flag - SET or RESET + */ +static void +mxc_pcd_en_func (struct otg_instance *otg, u8 flag) +{ + struct pcd_instance *pcd = otg->pcd; + struct usbd_bus_instance *bus = pcd->bus; + u32 hwmode = fs_rl(OTG_CORE_HWMODE); + int epn; + + //printk(KERN_INFO"%s:\n", __FUNCTION__); + switch (flag) { + case SET: + TRACE_MSG0(otg->pcd->TAG, "PCD_EN: SET" ); + otg_pthread_mutex_lock(&pcd->mutex); + + fs_andl(OTG_CORE_HWMODE, ~MODULE_CRECFG_HOST); // set to FUNCTION hnp + + TRACE_MSG1(otg->pcd->TAG, "PCD_EN: clr hwmode: %08x", fs_rl(OTG_CORE_HWMODE)); + + fs_orl(OTG_CORE_HWMODE, MODULE_CRECFG_FUNC); // set to software hnp + + TRACE_MSG2(otg->pcd->TAG, "PCD_EN: set hwmode: %08x want %08x", + fs_rl(OTG_CORE_HWMODE), + MODULE_CRECFG_FUNC); // set to software hnp + + mxc_main_clock_on(); + mxc_func_clock_on(); + + if (pcd->bus){ + mxc_pcd_start_ep0_setup(pcd->bus->endpoint_array); + for (epn = 1; epn < bus->endpoints; epn++) + mxc_pcd_setup_ep (pcd, epn, bus->endpoint_array + epn); + } + /* C.f. L3 12.1 - enable Slave, Band Gap enable, and Comparator enable. + * C.f. SCM-11 version of the USBOTG specification, ABBusReq enables the charge pump + */ + fs_andl(OTG_CORE_HNP_CSTAT, ~(MODULE_MASTER | MODULE_SLAVE | + MODULE_CMPEN | MODULE_BGEN | MODULE_SWAUTORST | MODULE_ABBUSREQ)); + + fs_orl(OTG_CORE_HNP_CSTAT, MODULE_SLAVE | MODULE_CMPEN | MODULE_BGEN | MODULE_SWAUTORST | MODULE_ABBUSREQ); + //fs_rl(OTG_CORE_HNP_CSTAT); + + fs_orl(OTG_FUNC_SINT_STEN, ( SYSTEM_DONEREGINT_EN | + SYSTEM_SUSPDETINT_EN | SYSTEM_RSMFININT_EN | SYSTEM_RESETINT_EN)); + + fs_orl(OTG_CORE_CINT_STEN, MODULE_FCINT_EN | MODULE_ASFCINT_EN); + otg->pcd->active = TRUE; + otg_pthread_mutex_lock(&pcd->mutex); + + break; + + case RESET: + TRACE_MSG1(otg->pcd->TAG, "PCD_EN: RESET hwmode: %08x", hwmode); + + otg_pthread_mutex_lock(&pcd->mutex); + otg->pcd->active = FALSE; + mxc_func_clock_on(); + fs_andl(OTG_FUNC_SINT_STEN, 0); + fs_andl(OTG_CORE_CINT_STEN, ~(MODULE_FCINT_EN | MODULE_ASFCINT_EN)); + + mxc_func_clock_off(); + + /* reset the function core + */ + fs_wl_set(otg->pcd->TAG, OTG_CORE_RST_CTRL, MODULE_RSTFSIE | MODULE_RSTFC); + while (fs_rl(OTG_CORE_RST_CTRL)); + fs_orl(OTG_CORE_HWMODE,MODULE_CRECFG_SHNP); // set to software hnp + otg_pthread_mutex_lock(&pcd->mutex); + + pcd_bus_event_handler_irq (bus, DEVICE_RESET, 0); + pcd_bus_event_handler_irq (bus, DEVICE_DESTROY, 0); + + break; + } + TRACE_MSG4(otg->pcd->TAG, "HWMODE: %08x CINT_STEN: %08x SINT_STEN: %08x HNP_CSTAT: %08x", + fs_rl(OTG_CORE_HWMODE), fs_rl(OTG_CORE_CINT_STEN), + fs_rl(OTG_FUNC_SINT_STEN), fs_rl(OTG_CORE_HNP_CSTAT) ); +} + +#if CONFIG_OTG_REMOTE_WAKEUP +/*! + * mxc_remote_wakeup() - perform remote wakeup. + * Initiate a remote wakeup to the host. + * @param otg - otg instance + * @param flag - SET or RESET + */ +static void +mxc_remote_wakeup(struct otg_instance *otg, u8 flag) +{ + switch(flag) { + case PULSE: + TRACE_MSG1(otg->pcd->TAG, "MX2_REMOTE_WAKEUP: OTG_FUNC_CMD_STAT: %08x ", fs_rl(OTG_FUNC_CMD_STAT) ); + otg_pthread_mutex_lock(&otg->pcd->mutex); + mxc_func_clock_on(); + fs_wl(OTG_FUNC_CMD_STAT, COMMAND_RSMINPROG); + otg_pthread_mutex_unlock(&otg->pcd->mutex); + TRACE_MSG1(otg->pcd->TAG, "MX2_REMOTE_WAKEUP: OTG_FUNC_CMD_STAT: %08x RESUMED", fs_rl(OTG_FUNC_CMD_STAT) ); + default: + break; + } +} +#endif /* CONFIG_OTG_REMOTE_WAKEUP */ + +/* ********************************************************************************************* */ +/*! + * mxc_pcd_start() - start the UDC + * @param pcd + */ +static void +mxc_pcd_start (struct pcd_instance *pcd) +{ + //fs_rl(OTG_FUNC_SINT_STEN); + fs_wl(OTG_DMA_MISC_CTRL, OTG_DMA_MISC_ARBMODE); /* round-robin mode */ +} + +/*! + * mxc_pcd_stop() - stop the UDC + * @param pcd + */ +static void +mxc_pcd_stop (struct pcd_instance *pcd) +{ + TRACE_MSG0(pcd->TAG, "UDC STOP"); +} + +/*! + * mxc_pcd_set_endpoints() - setup the physical endpoints for the endpoint map + * @param pcd + * @param endpointsRequested + * @param endpoint_map_array + */ +static int +mxc_pcd_set_endpoints (struct pcd_instance *pcd, int endpointsRequested, struct usbd_endpoint_map *endpoint_map_array) +{ + TRACE_MSG0(pcd->TAG, "Enable system control interrupts"); + #ifdef MX2_OTG_XCVR_DEVAD + fs_wl(OTG_SYS_CTRL, SYS_CTRL_I2C_WU_INT_EN | SYS_CTRL_OTG_WU_INT_EN | SYS_CTRL_HOST_WU_INT_EN | + SYS_CTRL_FNT_WU_INT_EN | 0x1); + #else /* MX2_OTG_XCVR_DEVAD */ + fs_wl(OTG_SYS_CTRL, SYS_CTRL_OTG_WU_INT_EN | SYS_CTRL_HOST_WU_INT_EN | SYS_CTRL_FNT_WU_INT_EN | 0x1); + #endif /* MX2_OTG_XCVR_DEVAD */ + return 0; +} + +/*! + * mxc_pcd_framenum() - get current framenum + */ +static u16 +mxc_pcd_framenum (struct otg_instance *otg) +{ + return fs_rl(OTG_FUNC_FRM_NUM); +} + +/* + * mxc_endpoint_halted() - is endpoint halted + */ +static int +mxc_endpoint_halted (struct pcd_instance *pcd, struct usbd_endpoint_instance *endpoint) +{ + int epn = endpoint->bEndpointAddress[0] & 0x7f; + int dir = (endpoint->bEndpointAddress[0] & 0x80) ? 1 : 0; + + TRACE_MSG2(pcd->TAG, "epn: %02x dir: %x", epn, dir); + return fs_rl(ep_word(epn, dir, 0)) & (1 << 31) ? TRUE : FALSE; +} + + +/* mxc_halt_endpoint - halt endpoint + */ +int mxc_halt_endpoint(struct pcd_instance *pcd, struct usbd_endpoint_instance *endpoint, int flag) +{ + int epn = endpoint->bEndpointAddress[0] & 0x7f; + int dir = (endpoint->bEndpointAddress[0] & 0x80) ? 1 : 0; + u32 ep_num = ep_num_dir(epn, dir); + u32 temp=~(1<<31); + //u32 toggle = fs_rl(OTG_FUNC_EP_TOGGLE); + u32 ep0_current, ep0_new; + + /* + * Ensure that toggle is reset so that any data returned starts as DATA0 + * XXX It might be more appropriate to do this in start_endpoint_in(). + */ + mxc_reset_toggle_ep(ep_num); + // XXX fs_wl(OTG_DMA_EP_CH_CLR, ep_num); // XXX causes problems + + ep0_current = fs_rl(ep_word(epn, dir, 0)); + ep0_new = ((flag ? 1 : 0) << 31) | (ep0_current & temp); + + UNLESS (ep0_current == ep0_new) fs_wl(ep_word(epn, dir, 0), ep0_new); + return 0; +} + +/* mxc_pcd_disable - + */ +void mxc_pcd_disable(struct pcd_instance *pcd) +{ + TRACE_MSG0(pcd->TAG, "--"); +} + +/* mxc_pcd_disable_ep - + */ +void mxc_pcd_disable_ep(struct pcd_instance *pcd, unsigned int ep, struct usbd_endpoint_instance * ep_instance) +{ + TRACE_MSG0(pcd->TAG, "--"); +} + +/* ********************************************************************************************* */ + +#if defined(CONFIG_OTG_NOC99) +struct usbd_pcd_ops usbd_pcd_ops; + +extern pcd_ops pcd_ops; + +#if 0 +#if defined(CONFIG_OTG_LNX) && defined(LINUX26) +int mxc_pcd_mod_init_l26(void); +void mxc_pcd_mod_exit_l26(void); +struct pcd_ops pcd_ops = { mxc_pcd_mod_init_l26, mxc_pcd_mod_exit_l26, }; +#else /* defined(CONFIG_OTG_LNX) && defined(LINUX26) */ + +int mxc_pcd_mod_init(struct otg_instance *otg); +void mxc_pcd_mod_exit(struct otg_instance *otg); +struct pcd_ops pcd_ops = { mxc_pcd_mod_init, mxc_pcd_mod_exit, }; +#endif /* defined(CONFIG_OTG_LNX) && defined(LINUX26) */ +#endif + +/*! + * mxc_pcd_global_init() - initialize globals + */ +void +mxc_pcd_global_init(void) +{ + ZERO(usbd_pcd_ops); + usbd_pcd_ops.bmAttributes = + USB_ABMTTRIBUTE_RESERVED | + #ifdef CONFIG_OTG_SELF_POWERED + USB_BMATTRIBUTE_SELF_POWERED | + #endif /* CONFIG_OTG_SELF_POWERED */ + #ifdef CONFIG_OTG_REMOTE_WAKEUP + USB_BMATTRIBUTE_REMOTE_WAKEUP | + #endif /* CONFIG_OTG_REMOTE_WAKEUP */ + USB_OTG_HNP_SUPPORTED | USB_OTG_SRP_SUPPORTED; + #ifndef CONFIG_OTG_SELF_POWERED + usbd_pcd_ops.bMaxPower = CONFIG_OTG_BMAXPOWER; + #endif /* CONFIG_OTG_SELF_POWERED */ + usbd_pcd_ops.max_endpoints = UDC_MAX_ENDPOINTS; + usbd_pcd_ops.ep0_packetsize = EP0_PACKETSIZE; + //usbd_pcd_ops.capabilities = REMOTE_WAKEUP_SUPPORTED; + usbd_pcd_ops.name = UDC_NAME; + usbd_pcd_ops.start = mxc_pcd_start; + usbd_pcd_ops.stop = mxc_pcd_stop; + usbd_pcd_ops.disable = mxc_pcd_disable; + usbd_pcd_ops.disable_ep = mxc_pcd_disable_ep; + usbd_pcd_ops.start_endpoint_in = mxc_pcd_start_endpoint_in; + usbd_pcd_ops.start_endpoint_out = mxc_pcd_start_endpoint_out; + usbd_pcd_ops.request_endpoints = pcd_request_endpoints; + usbd_pcd_ops.set_endpoints = mxc_pcd_set_endpoints; + usbd_pcd_ops.set_address = mxc_pcd_set_address; + usbd_pcd_ops.setup_ep = mxc_pcd_setup_ep; + usbd_pcd_ops.halt_endpoint = mxc_halt_endpoint; + usbd_pcd_ops.endpoint_halted = mxc_endpoint_halted; + + usbd_pcd_ops.cancel_in_irq = mxc_pcd_cancel_in_irq; + usbd_pcd_ops.cancel_out_irq = mxc_pcd_cancel_out_irq; + usbd_pcd_ops.max_endpoints = 8; + +} +/* ********************************************************************************************* */ +#else /* defined(CONFIG_OTG_NOC99) */ +struct usbd_pcd_ops usbd_pcd_ops; +struct +usbd_pcd_ops usbd_pcd_ops = { + .bmAttributes = + USB_BMATTRIBUTE_RESERVED + #ifdef CONFIG_OTG_SELF_POWERED + | USB_BMATTRIBUTE_SELF_POWERED + #endif /* CONFIG_OTG_SELF_POWERED */ + #ifdef CONFIG_OTG_REMOTE_WAKEUP + | USB_BMATTRIBUTE_REMOTE_WAKEUP + #endif /* CONFIG_OTG_REMOTE_WAKEUP */ + | USB_OTG_HNP_SUPPORTED | USB_OTG_SRP_SUPPORTED, + #ifndef CONFIG_OTG_SELF_POWERED + .bMaxPower = CONFIG_OTG_BMAXPOWER; + #endif /* CONFIG_OTG_SELF_POWERED */ + .max_endpoints = UDC_MAX_ENDPOINTS, + .high_speed_capable = FALSE, + .ep0_packetsize = EP0_PACKETSIZE, + //.capabilities = REMOTE_WAKEUP_SUPPORTED, + .name = UDC_NAME, + .start = mxc_pcd_start, + .stop = mxc_pcd_stop, + .disable = mxc_pcd_disable, + .disable_ep = mxc_pcd_disable_ep, + .start_endpoint_in = mxc_pcd_start_endpoint_in, + .start_endpoint_out = mxc_pcd_start_endpoint_out, + .request_endpoints = pcd_request_endpoints, + .set_endpoints = mxc_pcd_set_endpoints, + .set_address = mxc_pcd_set_address, + .setup_ep = mxc_pcd_setup_ep, + .halt_endpoint = mxc_halt_endpoint, + .endpoint_halted = mxc_endpoint_halted, + + .cancel_in_irq = mxc_pcd_cancel_in_irq, + .cancel_out_irq = mxc_pcd_cancel_out_irq, + + .max_endpoints = 8, +}; + +#if 0 +#if defined(CONFIG_OTG_LNX) && defined(LINUX26) +int mxc_pcd_mod_init_l26(struct otg_instance *otg); +void mxc_pcd_mod_exit_l26(struct otg_instance *otg); +struct pcd_ops pcd_ops = { + .mod_init = mxc_pcd_mod_init_l26, // called for module init +#ifdef MODULE + .mod_exit = mxc_pcd_mod_exit_l26, // called for module exit +#endif + .pcd_en_func = mxc_pcd_en_func, + .pcd_init_func = pcd_init_func, + #ifdef CONFIG_OTG_REMOTE_WAKEUP + .remote_wakeup_func = mxc_remote_wakeup, + #endif /* CONFIG_OTG_REMOTE_WAKEUP */ + .framenum = mxc_pcd_framenum, +}; + +#else /* defined(CONFIG_OTG_LNX) && defined(LINUX26) */ +int mxc_pcd_mod_init(struct otg_instance *otg); +void mxc_pcd_mod_exit(struct otg_instance *otg); +struct pcd_ops pcd_ops = { + .mod_init = mxc_pcd_mod_init, // called for module init + .mod_exit = mxc_pcd_mod_exit, // called for module exit + .pcd_en_func = mxc_pcd_en_func, + .pcd_init_func = pcd_init_func, + #ifdef CONFIG_OTG_REMOTE_WAKEUP + .remote_wakeup_func = mxc_remote_wakeup, + #endif /* CONFIG_OTG_REMOTE_WAKEUP */ + .framenum = mxc_pcd_framenum, +}; +#endif /* defined(CONFIG_OTG_LNX) && defined(LINUX26) */ +#endif + + +#endif /* defined(CONFIG_OTG_NOC99) */ + +int mxc_pcd_mod_init_l26(struct otg_instance *otg); +void mxc_pcd_mod_exit_l26(struct otg_instance *otg); + +/*! + * mxc_pcd_ops_init() - initialize globals + */ +void +mxc_pcd_ops_init(void) +{ + ZERO(pcd_ops); + pcd_ops.pcd_en_func = mxc_pcd_en_func; + pcd_ops.pcd_init_func = pcd_init_func; + #ifdef CONFIG_OTG_REMOTE_WAKEUP + pcd_ops.remote_wakeup_func = mxc_remote_wakeup; + #endif /* CONFIG_OTG_REMOTE_WAKEUP */ + pcd_ops.framenum = mxc_pcd_framenum; + #if defined(CONFIG_OTG_LNX) && defined(LINUX26) + pcd_ops.mod_init = mxc_pcd_mod_init_l26; // called for module init + #ifdef MODULE + pcd_ops.mod_exit = mxc_pcd_mod_exit_l26; // called for module exit + #endif + + #else /* defined(CONFIG_OTG_LNX) && defined(LINUX26) */ + pcd_ops.mod_init = mxc_pcd_mod_init; // called for module init + #ifdef MODULE + pcd_ops.mod_exit = mxc_pcd_mod_exit; // called for module exit + #endif + #endif /* defined(CONFIG_OTG_LNX) && defined(LINUX26) */ +} + +/* ********************************************************************************************* */ + +/*! default initialization + */ + +int pcd_mod_init (struct otg_instance *otg); +void pcd_mod_exit (struct otg_instance *otg); + +int mxc_pcd_mod_init(struct otg_instance *otg) +{ + u32 hwmode; + + mxc_pcd_instance = otg->pcd; + TRACE_MSG0(otg->pcd->TAG, "--"); + pcd_mod_init(otg); + + mxc_main_clock_on(); + #if 0 + fs_clear_words((volatile u32 *)IO_ADDRESS(OTG_EP_BASE), (NUM_ETDS*16/4)); + fs_clear_words((volatile u32 *)IO_ADDRESS(OTG_EP_BASE), 0x200/4); + fs_clear_words((volatile u32 *)IO_ADDRESS(OTG_DATA_BASE), 0x400/4); + #endif + + + hwmode = fs_rl(OTG_CORE_HWMODE); + mxc_funcrev = hwmode >> 24; + + TRACE_MSG1(otg->pcd->TAG, "funcrev: %04x", mxc_funcrev); + + mxc_main_clock_off(); + + return 0; +} + +void mxc_pcd_mod_exit(struct otg_instance *otg) +{ + int i; + + + TRACE_MSG0(otg->pcd->TAG, "--"); + otg_pthread_mutex_lock(&otg->pcd->mutex); + for (i = 0; i < 16; i++) { + mxc_stop_ep(i, USB_DIR_IN); + mxc_stop_ep(i, USB_DIR_OUT); + } + otg_pthread_mutex_unlock(&otg->pcd->mutex); + pcd_mod_exit(otg); + mxc_pcd_instance = NULL; +} + +/* ********************************************************************************************* */ + + + +/*! New style linux 2.6 initialization. + */ + +#if defined(LINUX26) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,15) +static void mxc_pcd_release(struct platform_device *dev) +#else +static void mxc_pcd_release(struct device *dev) +#endif +{ + //TRACE_MSG0(mxc_pcd_instance->TAG, "--"); +} + +static int __init_or_module +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,15) +mxc_pcd_remove(struct platform_device *dev) +#else +mxc_pcd_remove(struct device *dev) +#endif +{ + //TRACE_MSG0(mxc_pcd_instance->TAG, "--"); + //mxc_pcd_mod_exit_l24(otg); + //TRACE_MSG0(mxc_pcd_instance->TAG, "ok"); + return 0; +} + + +static int __init +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,15) +mxc_pcd_probe(struct platform_device *dev) +#else +mxc_pcd_probe(struct device *dev) +#endif +{ + //TRACE_MSG0(mxc_pcd_instance->TAG, "--"); + #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,15) + #else + dev->release = mxc_pcd_release; + #endif + //mxc_pcd_mod_init_l24(otg); + return 0; +} +static int +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,15) +mxc_pcd_suspend(struct platform_device *dev, pm_message_t state) +#else +mxc_pcd_suspend(struct device *dev, u32 state, u32 phase) +#endif +{ + //TRACE_MSG0(mxc_pcd_instance->TAG, "--"); + return 0; +} + +static int +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,18) +mxc_pcd_resume(struct platform_device *dev) +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,15) +mxc_pcd_resume(struct platform_device *dev, pm_message_t state) +#else +mxc_pcd_resume(struct device *dev, u32 phase) +#endif +{ + //TRACE_MSG0(mxc_pcd_instance->TAG, "--"); + return 0; +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,15) +static struct platform_driver mxc_pcd_driver = { + .driver = { + .name = "mxc-pcd", + }, +#else +static struct device_driver mxc_pcd_driver = { + + .name = "mxc-pcd", + .bus = &platform_bus_type, +#endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,15) */ + + .probe = mxc_pcd_probe, + .remove = mxc_pcd_remove, + + .suspend = mxc_pcd_suspend, + .resume = mxc_pcd_resume, + +}; +/* } FORMATTING */ + +static struct platform_device mxc_pcd_platform = { + .name = "mxc-pcd", + .id = 1, +}; + + +int mxc_pcd_mod_init_l26(struct otg_instance *otg) +{ + u32 hwmode; + int driver_registered = 0; + int platform_registered = 0; + + //mxc_pcd_instance = otg->pcd; + TRACE_MSG0(otg->pcd->TAG, "--"); + + #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,15) + THROW_IF((driver_registered = platform_driver_register(&mxc_pcd_driver)), error); + #else + THROW_IF((driver_registered = driver_register(&mxc_pcd_driver)), error); + #endif + THROW_IF((platform_registered = platform_registered = platform_device_register(&mxc_pcd_platform)), error); + + mxc_pcd_mod_init(otg); + + CATCH(error) { + if (platform_registered) platform_device_unregister(&mxc_pcd_platform); + #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,15) + if (driver_registered) platform_driver_unregister(&mxc_pcd_driver); + #else + if (driver_registered) driver_unregister(&mxc_pcd_driver); + #endif + } + + return 0; +} + +void mxc_pcd_mod_exit_l26(struct otg_instance *otg) +{ + TRACE_MSG0(otg->pcd->TAG, "--"); + mxc_pcd_mod_exit(otg); + #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,15) + platform_driver_unregister(&mxc_pcd_driver); + #else + driver_unregister(&mxc_pcd_driver); + #endif + platform_device_unregister(&mxc_pcd_platform); + //otg->pcd->TAG = 0; + //mxc_pcd_instance = NULL; +} + + +#endif /* defined(LINUX26) */ + +/* ********************************************************************************************* */ diff --git a/drivers/otg/hardware/mxc-pmic.c b/drivers/otg/hardware/mxc-pmic.c new file mode 100644 index 000000000000..1b214d7969ea --- /dev/null +++ b/drivers/otg/hardware/mxc-pmic.c @@ -0,0 +1,1111 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/hardware/mc13783.c -- Freescale mc13783 Connectiviey Transceiver Controller driver + * @(#) balden@belcarra.com/seth2.rillanon.org|otg/platform/mxc/mxc-pmic.c|20070710021518|30741 + * + * Copyright (c) 2005 Belcarra Technologies Corp + * + * By: + * Stuart Lynne <sl@lbelcarra.com>, + * Bruce Balden <balden@belcarra.com>, + * Shahrad Payandeh <sp@belcarra.com> + * + */ +/*! + * @file otg/hardware/mxc-pmic.c + * @brief mc13783 Transciever Controller Driver + * + * This is a simple transceiver driver for the mc13783 transceiver + * using the Freescale mc13783 connectivity driver. + * + * @ingroup FSOTG + * + * + */ + +#include <asm/delay.h> + +#include <otg/pcd-include.h> +#include <asm/arch/gpio.h> + + +#include <asm/arch/pmic_external.h> +#include <asm/arch/pmic_convity.h> + + + +//WORK_ITEM pmic_work_bh; +//WORK_ITEM pmic_otg_wq; + +struct otg_task *pmic_work_task; +struct otg_task *pmic_otg_task; + +int global_flag = 0; +int global_flag_array[20], start_flag, end_flag; +int det_dm_hi, det_dp_hi; + +struct otg_task *pmic_task; + +#define PUDP_FLAG_SET 1 +#define PUDP_FLAG_RESET 2 +#define UPD_FLAG_SET 4 +#define UPD_FLAG_RESET 8 +#define UDM_FLAG_SET 16 +#define UDM_FLAG_RESET 32 +#define DRV_VBUS_SET 64 +#define DRV_VBUS_RESET 128 +#define PUDM_FLAG_SET 256 +#define PUDM_FLAG_RESET 512 +#define DISCHRG_VBUS_SET 1024 +#define DISCHRG_VBUS_RESET 2048 +#define CHRG_VBUS_SET 4096 +#define CHRG_VBUS_RESET 8192 + +//#define VBUS_TIMER + +PMIC_CONVITY_HANDLE pmic_handle = (PMIC_CONVITY_HANDLE) NULL; + +/*! pmic_bh- work task bottom handler + * @param data - otg_instance type pointer + * + */ +void pmic_bh(void *data) +{ + struct otg_instance *otg = (struct otg_instance *) data; + + do { + +// printk(KERN_INFO"%s: AAAA\n", __FUNCTION__); + TRACE_MSG0(REMOVE_TCD, "--"); + global_flag = global_flag_array[start_flag]; + if (global_flag & PUDP_FLAG_SET) { //set DP pullup + pmic_convity_usb_set_speed(pmic_handle, USB_FULL_SPEED); + pmic_convity_usb_otg_set_config(pmic_handle, USB_PU); + } + if (global_flag & PUDP_FLAG_RESET) { //reset DP pullup + pmic_convity_usb_set_speed(pmic_handle, USB_FULL_SPEED); + pmic_convity_usb_otg_clear_config(pmic_handle, USB_PU); + } + if (global_flag & PUDM_FLAG_SET) { //set DM pullup + pmic_convity_usb_set_speed(pmic_handle, USB_LOW_SPEED); + pmic_convity_usb_otg_set_config(pmic_handle, USB_PU); + } + if (global_flag & PUDM_FLAG_RESET) { //reset DM pullup + pmic_convity_usb_set_speed(pmic_handle, USB_LOW_SPEED); + pmic_convity_usb_otg_clear_config(pmic_handle, USB_PU); + } + if (global_flag & UPD_FLAG_SET) { //set DP pulldown + pmic_convity_usb_otg_set_config(pmic_handle, USB_UDP_PD); //DP pull down switch is on + } + if (global_flag & UPD_FLAG_RESET) { //reset DP pulldown + pmic_convity_usb_otg_clear_config(pmic_handle, USB_UDP_PD); //DP pull down switch is off + } + if (global_flag & UDM_FLAG_SET) { //set DM pulldown + pmic_convity_usb_otg_set_config(pmic_handle, USB_UDM_PD); //DP pull down switch is on + } + if (global_flag & UDM_FLAG_RESET) { //reset DM pulldown + pmic_convity_usb_otg_clear_config(pmic_handle, USB_UDM_PD); //DP pull down switch is off + } + if (global_flag & DRV_VBUS_SET) { //enable vbus voltage + pmic_convity_set_output(pmic_handle, TRUE, TRUE); //enable VBUS + } + if (global_flag & DRV_VBUS_RESET) { //disable vbus voltage + pmic_convity_set_output(pmic_handle, TRUE, FALSE); //disable VBUS + } + if (global_flag & CHRG_VBUS_SET) { //enable vbus + pmic_convity_usb_otg_set_config(pmic_handle, + USB_VBUS_CURRENT_LIMIT_LOW_30MS); + } + if (global_flag & CHRG_VBUS_RESET) { //disable vbus + pmic_convity_set_output(pmic_handle, TRUE, FALSE); //disable VBUS + } + if (global_flag & DISCHRG_VBUS_SET) { //discharge vbus + pmic_convity_set_output(pmic_handle, TRUE, FALSE); //disable VBUS + pmic_convity_usb_otg_clear_config(pmic_handle, + USB_VBUS_PULLDOWN); + pmic_convity_usb_set_power_source(pmic_handle, + USB_POWER_INTERNAL, + USB_POWER_3V3); + pmic_convity_set_output(pmic_handle, FALSE, TRUE); //enable VUSB + } + if (global_flag & DISCHRG_VBUS_RESET) { //discharge vbus disable + pmic_convity_set_output(pmic_handle, TRUE, FALSE); //disable VBUS + pmic_convity_usb_otg_set_config(pmic_handle, USB_VBUS_PULLDOWN); + pmic_convity_set_output(pmic_handle, FALSE, TRUE); //enable VUSB + } +#if 1 + TRACE_MSG3(REMOVE_TCD, "gloabl flag %d start_flag %d end_flag %d", global_flag, + start_flag, end_flag); +#endif +#if 0 + printk(KERN_INFO "%d %d %d %d %d %d %d %d\n", + (global_flag & PU_FLAG_SET), (global_flag & PU_FLAG_RESET), + (global_flag & UPD_FLAG_SET), (global_flag & UPD_FLAG_RESET), + (global_flag & UDM_FLAG_SET), (global_flag & UDM_FLAG_RESET), + (global_flag & VBUSPDENB_RESET), + (global_flag & VBUSREGEN_RESET)); +#endif + global_flag = 0; + global_flag_array[start_flag] = 0; + if (start_flag++ > 15) + start_flag = 0; + //if (start_flag != end_flag) + // SCHEDULE_WORK(pmic_work_bh); + +// printk(KERN_INFO"%s: BBBB start: %d end: %d\n", __FUNCTION__, start_flag, end_flag); + } while (start_flag != end_flag); + +// printk(KERN_INFO"%s: CCCC\n", __FUNCTION__); +} +/*! pmic_work_proc - pmic tcd task roution + * @param data + */ +void *pmic_work_proc(otg_task_arg_t data) +{ + pmic_bh(data); + return NULL; +} + +/*! pmic_bh_wakeup - wakeup the pmic bottom half + * */ +void pmic_bh_wakeup(void) +{ + TRACE_MSG0(REMOVE_TCD, "--"); + //SCHEDULE_WORK(pmic_work_bh); + otg_up_work(pmic_work_task); +} + +/* ********************************************************************************************** */ +/*! mxc_pmic_vbus - Do we have Vbus (cable attached?) + * Return non-zero if Vbus is detected. + * @param otg - otg instance pointer + */ +int mxc_pmic_vbus(struct otg_instance *otg) +{ +#if 1 + t_sensor_bits sense_bits; + if (pmic_get_sensors(&sense_bits)) { + printk(KERN_INFO "%s: pmic_get_sensors() failed\n", + __FUNCTION__); + return 0; + } + return sense_bits.sense_usb2v0s; +#else + return pmic_check_sense(sense_usb2v0s); +#endif +} +/*! pmic_otg_event_bh_old - + * @param arg + */ +void pmic_otg_event_bh_old(void *arg) +{ + otg_event_set_irq(REMOVE_tcd_instance->otg, 1, + mxc_pmic_vbus(REMOVE_tcd_instance->otg), B_SESS_VLD, REMOVE_TCD, + "B_SESS_VLD"); +} + +/*! pmic_otg_event_bh - pmic otg event handler + * @param data - otg instance + */ +void pmic_otg_event_bh(void *data) +{ + struct otg_instance *otg = (struct otg_instance *) data; + otg_current_t inputs; + t_sensor_bits sense_bits; + static BOOL force = TRUE; + static otg_current_t inputs_saved = 0; + + if (pmic_get_sensors(&sense_bits)) { + printk(KERN_INFO "%s: pmic_get_sensors() failed\n", + __FUNCTION__); + return; + } + TRACE_MSG6(REMOVE_TCD, + "usb4v4s%c usb2v0s%c usb0v8s:%c id_gnds%c id_floats%c id_se1s%c", + sense_bits.sense_usb4v4s ? ' ' : '/', + sense_bits.sense_usb2v0s ? ' ' : '/', + sense_bits.sense_usb0v8s ? ' ' : '/', + sense_bits.sense_id_gnds ? ' ' : '/', + sense_bits.sense_id_floats ? ' ' : '/', + sense_bits.sense_se1s ? ' ' : '/'); + + inputs = (sense_bits.sense_usb4v4s ? VBUS_VLD : VBUS_VLD_) | + (sense_bits. + sense_usb2v0s ? (B_SESS_VLD | A_SESS_VLD) : (B_SESS_VLD_ | + A_SESS_VLD_)) | + (sense_bits.sense_usb0v8s ? B_SESS_END_ : B_SESS_END) | (sense_bits. + sense_id_gnds + ? ID_GND : + ID_GND_) | + (sense_bits.sense_id_floats ? ID_FLOAT : ID_FLOAT_) | (sense_bits. + sense_se1s ? + SE1_DET : + SE1_DET_) | + (det_dp_hi ? DP_HIGH : DP_HIGH_) | (det_dm_hi ? DM_HIGH : DM_HIGH_); + + // printk(KERN_INFO" inputs: %8X\n", inputs); + TRACE_MSG4(REMOVE_TCD, + "MC13783 EVENT: sense_bits: %8x otg inputs: %8x saved: %x diff: %x", + sense_bits.sense_se1s, inputs, inputs_saved, + inputs ^ inputs_saved); + + RETURN_UNLESS(force || (inputs ^ inputs_saved)); + + inputs_saved = inputs; + otg_event(REMOVE_tcd_instance->otg, inputs, REMOVE_TCD, "PMIC OTG EVENT"); + + // gpio_config_int_en(2, 17, TRUE); + // gpio_config_int_en(2, 16, TRUE); + + // gpio_clear_int (2, 17); + // gpio_clear_int (2, 16); + +} +/*! pmic_otg_proc - called to porcess pmic otg event + * @param data - otg instance type pointer + */ +void *pmic_otg_proc(otg_task_arg_t data) +{ + pmic_otg_event_bh(data); + return NULL; +} + +/*! pmic_otg_wakeup - called to wake up pmic_otg_task + */ +void pmic_otg_wakeup(void) +{ + TRACE_MSG0(REMOVE_TCD, "start"); + //SCHEDULE_WORK(pmic_otg_wq); + otg_up_work(pmic_otg_task); + TRACE_MSG0(REMOVE_TCD, "finsih"); +} + +/*! gpio_c17_int_hndlr - interrupt handler + * @param irq - interrupt number + * @param dev_id - interrupt device ip + * @param regs - cpu registers snapshot + * @return interrput process result + */ +static irqreturn_t gpio_c17_int_hndlr(int irq, void *dev_id, + struct pt_regs *regs) +{ + udelay(100); + if (gpio_get_data(2, 17) == 1) { + det_dm_hi = 1; + // mc13783_otg_wakeup (); + // gpio_config_int_en(2, 16, FALSE); + } else { + det_dm_hi = 0; + // gpio_config_int_en(2, 17, TRUE); + } + TRACE_MSG1(REMOVE_TCD, "Changing the state of DM to %d", det_dm_hi); + pmic_otg_wakeup(); + return IRQ_HANDLED; +} +/*! gpio_c16_int_hndlr - interrupt handler + * @param irq - interrupt number + * @param dev_id - interrupt device ip + * @param regs - cpu registers snapshot + * @return interrput process result + */ + +static irqreturn_t gpio_c16_int_hndlr(int irq, void *dev_id, + struct pt_regs *regs) +{ + udelay(100); + if (gpio_get_data(2, 16) == 1) { + det_dp_hi = 1; + // mc13783_otg_wakeup (); + // gpio_config_int_en(2, 17, FALSE); + } else { + det_dp_hi = 0; + // gpio_config_int_en(2, 16, TRUE); + } + TRACE_MSG1(REMOVE_TCD, "Changing the state of DP to %d", det_dp_hi); + pmic_otg_wakeup(); + return IRQ_HANDLED; +} + +/*! mxc_pmic_id - Do we have Vbus (cable attached?) + * Return non-zero if Vbus is detected. + * + * @param otg - otg instance + */ +int mxc_pmic_id(struct otg_instance *otg) +{ + struct tcd_instance *tcd = otg->tcd; +#if 1 + t_sensor_bits sense_bits; + if (pmic_get_sensors(&sense_bits)) { + printk(KERN_INFO "%s: pmic_get_sensors() failed\n", + __FUNCTION__); + return 0; + } + return sense_bits.sense_id_gnds; +#else + return pmic_check_sense(sense_id_gnds); +#endif +} + +/* ********************************************************************************************* */ +/*! mxc_pmic_tcd_en() - used to enable/ disable mc13783 + * + * @param otg - otg instance + * @param flag - enable/ disable flag + */ +void mxc_pmic_tcd_en(struct otg_instance *otg, u8 flag) +{ + struct tcd_instance *tcd = otg->tcd; + switch (flag) { + case SET: + case PULSE: + TRACE_MSG0(tcd->TAG, "SET/PULSE"); + pmic_otg_wakeup(); + // otg_event_set_irq(tcd_instance->otg, 1, mxc_mc13783_vbus(tcd_instance->otg), B_SESS_VLD, TCD, "B_SESS_VLD"); + break; + case RESET: + TRACE_MSG0(tcd->TAG, "RESET"); + break; + } +} + +/* ********************************************************************************************* */ +/*! mxc_pmic_tcd_init() - used to enable mc13783 + * @param otg - otg instance pointer + * @param flag - + * + */ +void mxc_pmic_tcd_init(struct otg_instance *otg, u8 flag) +{ + struct tcd_instance *tcd = otg->tcd; + switch (flag) { + case SET: + case PULSE: + TRACE_MSG0(tcd->TAG, "SET/PULSE"); + break; + case RESET: + TRACE_MSG0(tcd->TAG, "RESET"); + break; + } + pmic_otg_wakeup(); + otg_event (otg, OCD_OK, otg->tcd->TAG, "MC13783 OK"); +} + +/*! mxc_pmic_chrg_vbus - used to enable or disable B-device Vbus charging + * @param otg - otg instance + * @param flag - enable/disable flag + */ +void mxc_pmic_chrg_vbus(struct otg_instance *otg, u8 flag) +{ + struct tcd_instance *tcd = otg->tcd; + //TRACE_MSG0(tcd->TAG, "--"); + switch (flag) { + case SET: + TRACE_MSG0(tcd->TAG, "CHRG_VBUS_SET"); + global_flag_array[end_flag] = CHRG_VBUS_SET; + pmic_bh_wakeup(); + break; + case RESET: + TRACE_MSG0(tcd->TAG, "CHRG_VBUS_RESET"); + global_flag_array[end_flag] = CHRG_VBUS_RESET; + pmic_bh_wakeup(); + break; + case PULSE: + break; + } + if (end_flag++ > 15) + end_flag = 0; + +} + +/*! mxc_pmic_drv_vbus - used to enable or disable A-device driving Vbus + * @param otg - otg instance + * @param flag - enable/disable flag + */ +void mxc_pmic_drv_vbus(struct otg_instance *otg, u8 flag) +{ + struct tcd_instance *tcd = otg->tcd; + //TRACE_MSG0(tcd->TAG, "--"); + switch (flag) { + case SET: + TRACE_MSG0(tcd->TAG, "DRV_VBUS_SET"); + global_flag_array[end_flag] = DRV_VBUS_SET; + pmic_bh_wakeup(); + break; + case RESET: + TRACE_MSG0(tcd->TAG, "DRV_VBUS_RESET"); + global_flag_array[end_flag] = DRV_VBUS_RESET; + pmic_bh_wakeup(); + break; + } + if (end_flag++ > 15) + end_flag = 0; + +} + +/*! mxc_pmic_dischrg_vbus - used to enable Vbus discharge + * @param otg - otg instance + * @param flag - enable/disable flag + */ +void mxc_pmic_dischrg_vbus(struct otg_instance *otg, u8 flag) +{ + struct tcd_instance *tcd = otg->tcd; + //TRACE_MSG0(tcd->TAG, "--"); + switch (flag) { + case SET: + TRACE_MSG0(tcd->TAG, "DISCHRG VBUS SET"); + global_flag_array[end_flag] = DISCHRG_VBUS_SET; + pmic_bh_wakeup(); + break; + case RESET: + TRACE_MSG0(tcd->TAG, "DISCHRG VBUS RESET"); + global_flag_array[end_flag] = DISCHRG_VBUS_RESET; + pmic_bh_wakeup(); + break; + } +} + +/*! mxc_pmic_mx21_vbus_drain - used to enable Vbus discharge + * @param otg - otg instance + * @param flag - enable/disable flag + */ +void mxc_pmic_mx21_vbus_drain_func(struct otg_instance *otg, u8 flag) +{ + struct tcd_instance *tcd = otg->tcd; + //TRACE_MSG0(tcd->TAG, "--"); + switch (flag) { + case SET: + TRACE_MSG0(tcd->TAG, "OUTPUT: TCD_DISCHRG_VBUS_SET"); + break; + case RESET: + TRACE_MSG0(tcd->TAG, "OUTPUT: TCD_DISCHRG_VBUS_RESET"); + break; + } +} + +/*! mxc_pmic_dp_pullup_func - used to enable or disable peripheral connecting to bus + * + * C.f. 5.1.6, 5.1.7, 5.2.4 and 5.2.5 + * + * host peripheral + * d+ pull-up clr set + * d+ pull-down set clr + * + * d- pull-up clr clr + * d- pull-down set set + * + * @param otg - otg instance + * @param flag - enable/disable flag + + */ +void mxc_pmic_dp_pullup_func(struct otg_instance *otg, u8 flag) +{ + struct tcd_instance *tcd = (struct tcd_instance *)otg->tcd; + //TRACE_MSG0(tcd->TAG, "--"); + switch (flag) { + case SET: + TRACE_MSG0(tcd->TAG, "MC13783_LOC_CONN SET - Set DP PULLUP"); + // global_flag |= PU_FLAG_SET; + global_flag_array[end_flag] = PUDP_FLAG_SET; + pmic_bh_wakeup(); + // mc13783_convity_set_pull_down_switch(PD_PU, TRUE); + break; + + case RESET: + TRACE_MSG0(tcd->TAG, "MC13783_LOC_CONN RESET - Clr DP PULLUP"); + // global_flag |= PU_FLAG_RESET; + global_flag_array[end_flag] = PUDP_FLAG_RESET; + pmic_bh_wakeup(); + // mc13783_convity_set_pull_down_switch(PD_PU, FALSE); + break; + } + if (end_flag++ > 15) + end_flag = 0; +} + +/*! mxc_pmic_dm_pullup_func - used to enable or disable peripheral connecting to bus + * @param otg - otg instance + * @param flag - enable/disable flag + + */ +void mxc_pmic_dm_pullup_func(struct otg_instance *otg, u8 flag) +{ + struct tcd_instance *tcd = (struct tcd_instance *)otg->tcd; + //TRACE_MSG0(tcd->TAG, "--"); + switch (flag) { + case SET: + TRACE_MSG0(tcd->TAG, "MC13783_LOC_CONN SET - Set DM PULLUP"); + // global_flag |= PU_FLAG_SET; + global_flag_array[end_flag] = PUDM_FLAG_SET; + pmic_bh_wakeup(); + //mc13783_convity_set_pull_down_switch(PD_UDB_15, TRUE); + break; + + case RESET: + TRACE_MSG0(tcd->TAG, "MC13783_LOC_CONN RESET - Clr DM PULLUP"); + // global_flag |= PU_FLAG_RESET; + global_flag_array[end_flag] = PUDM_FLAG_RESET; + pmic_bh_wakeup(); + //mc13783_convity_set_pull_down_switch(PD_UDB_15, FALSE); + break; + } + if (end_flag++ > 15) + end_flag = 0; + +} + +/*! mxc_pmic_dp_pulldown_func - used to enable or disable peripheral connecting to bus + * + * C.f. 5.1.6, 5.1.7, 5.2.4 and 5.2.5 + * + * host peripheral + * d+ pull-up clr set + * d+ pull-down set clr + * + * d- pull-up clr clr + * d- pull-down set set + * + * + * @param otg - otg instance + * @param flag - enable/disable flag + */ +void mxc_pmic_dp_pulldown_func(struct otg_instance *otg, u8 flag) +{ + struct tcd_instance *tcd = (struct tcd_instance *)otg->tcd; + //TRACE_MSG0(tcd->TAG, "--"); + switch (flag) { + case SET: + TRACE_MSG0(tcd->TAG, "MC13783_LOC_CONN SET - Set DP PULLDOWN"); + // global_flag |= UPD_FLAG_SET; + global_flag_array[end_flag] = UPD_FLAG_SET; + pmic_bh_wakeup(); + // mc13783_convity_set_pull_down_switch(PD_UPD_15, TRUE); + break; + + case RESET: + TRACE_MSG0(tcd->TAG, "MC13783_LOC_CONN RESET - Clr DP PULLDOWN"); + // global_flag |= UPD_FLAG_RESET; + global_flag_array[end_flag] = UPD_FLAG_RESET; + pmic_bh_wakeup(); + // mc13783_convity_set_pull_down_switch(PD_UPD_15, TRUE); + break; + } + if (end_flag++ > 15) + end_flag = 0; + +} + +/*! mxc_pmic_dm_pulldown_func - used to enable or disable peripheral connecting to bus + * + * @param otg - otg instance + * @param flag - enable/disable flag + */ +void mxc_pmic_dm_pulldown_func(struct otg_instance *otg, u8 flag) +{ + struct tcd_instance *tcd = (struct tcd_instance *)otg->tcd; + //TRACE_MSG0(tcd->TAG, "--"); + switch (flag) { + case SET: + TRACE_MSG0(tcd->TAG, "MC13783_LOC_CONN SET - Set DM PULLDOWN"); + // global_flag |= UDM_FLAG_SET; + global_flag_array[end_flag] = UDM_FLAG_SET; + pmic_bh_wakeup(); + // mc13783_convity_set_pull_down_switch(PD_UDM_15, TRUE); + break; + + case RESET: + TRACE_MSG0(tcd->TAG, "MC13783_LOC_CONN RESET - Clr DM PULLDOWN"); + // global_flag |= UDM_FLAG_RESET; + global_flag_array[end_flag] = UDM_FLAG_RESET; + pmic_bh_wakeup(); + // mc13783_convity_set_pull_down_switch(PD_UDM_15, TRUE); + break; + } + if (end_flag++ > 15) + end_flag = 0; + +} + +/*! mxc_pmic_peripheral_host_func - used to enable or disable peripheral connecting to bus + * + * A-Device D+ pulldown D- pulldown + * idle set set + * host set set + * peripheral reset set + * + * B-Device + * idle set set + * host set set + * peripheral reset set + * + * @param otg - otg instance + * @param flag - enable/disable flag + */ +void mxc_pmic_peripheral_host_func(struct otg_instance *otg, u8 flag) +{ + struct tcd_instance *tcd = (struct tcd_instance *)otg->tcd; + //TRACE_MSG0(tcd->TAG, "--"); + switch (flag) { + case SET: // peripheral + TRACE_MSG0(tcd->TAG, "SET - CLR DP PULLDOWN"); + break; + + case RESET: // host + TRACE_MSG0(tcd->TAG, "RESET - SET DM PULLDOWN"); + break; + } +} + +/*! mxc_pmic_dm_det_func - used to enable or disable D- detect + * + * @param otg - otg instance + * @param flag - enable/disable flag + + */ +void mxc_pmic_dm_det_func(struct otg_instance *otg, u8 flag) +{ + struct tcd_instance *tcd = (struct tcd_instance *)otg->tcd; + //TRACE_MSG0(tcd->TAG, "--"); + switch (flag) { + case SET: + TRACE_MSG0(tcd->TAG, "setting DM_HI detect"); + break; + + case RESET: + TRACE_MSG0(tcd->TAG, "reseting DM_HI detect"); + break; + } +} + +/*! mxc_pmic_dp_det_func - used to enable or disable D+ detect + * + * @param otg - otg instance + * @param flag - enable/disable flag + */ +void mxc_pmic_dp_det_func(struct otg_instance *otg, u8 flag) +{ + struct tcd_instance *tcd = (struct tcd_instance *)otg->tcd; + //TRACE_MSG0(tcd->TAG, "--"); + switch (flag) { + case SET: + TRACE_MSG0(tcd->TAG, "setting DP_HI detect"); + break; + + case RESET: + TRACE_MSG0(tcd->TAG, "reseting DP_HI detect"); + break; + } +} + +/*! mxc_pmic_cr_det_func - used to enable or disable D+ detect + * @param otg - otg instance + * @param flag - enable/disable flag + * + */ +void mxc_pmic_cr_det_func(struct otg_instance *otg, u8 flag) +{ + struct tcd_instance *tcd = (struct tcd_instance *)otg->tcd; + //TRACE_MSG0(tcd->TAG, "--"); + switch (flag) { + case SET: + TRACE_MSG0(tcd->TAG, "setting CR_INT detect"); + break; + + case RESET: + TRACE_MSG0(tcd->TAG, "reseting CR_INT detect"); + break; + } +} + +/*! mxc_pmic_bdis_acon_func - used to enable or disable auto a-connect + * @param otg - otg instance + * @param flag - enable/disable flag + * + */ +void mxc_pmic_bdis_acon_func(struct otg_instance *otg, u8 flag) +{ + struct tcd_instance *tcd = (struct tcd_instance *)otg->tcd; + //TRACE_MSG0(tcd->TAG, "--"); + switch (flag) { + case SET: + TRACE_MSG0(tcd->TAG, "setting BDIS ACON"); + break; + + case RESET: + TRACE_MSG0(tcd->TAG, "reseting BDIS ACON"); + break; + } +} + +/*! mxc_pmic_id_pulldown_func - used to enable or disable ID pulldown + * @param otg - otg instance + * @param flag - enable/disable flag + * + */ +void mxc_pmic_id_pulldown_func(struct otg_instance *otg, u8 flag) +{ + struct tcd_instance *tcd = (struct tcd_instance *)otg->tcd; + //TRACE_MSG0(tcd->TAG, "--"); + switch (flag) { + case SET: + TRACE_MSG0(tcd->TAG, "setting ID PULLDOWN"); + break; + + case RESET: + TRACE_MSG0(tcd->TAG, "reseting ID PULLDOWN"); + break; + } +} + +/*! mxc_pmic_audio_func - used to enable or disable Carkit Interrupt + * @param otg - otg instance + * @param flag - enable/disable flag + * + */ +void mxc_pmic_audio_func(struct otg_instance *otg, u8 flag) +{ + struct tcd_instance *tcd = (struct tcd_instance *)otg->tcd; + //TRACE_MSG0(tcd->TAG, "--"); + switch (flag) { + case SET: + TRACE_MSG0(tcd->TAG, "SET AUDIO_EN"); + break; + + case RESET: + TRACE_MSG0(tcd->TAG, "RESET AUDIO_EN"); + break; + } +} + +/*! mxc_pmic_uart_func - used to enable or disable transparent uart mode + * @param otg - otg instance + * @param flag - enable/disable flag + * + */ +void mxc_pmic_uart_func(struct otg_instance *otg, u8 flag) +{ + struct tcd_instance *tcd = (struct tcd_instance *)otg->tcd; + //TRACE_MSG0(tcd->TAG, "--"); + switch (flag) { + case SET: + TRACE_MSG0(tcd->TAG, "setting UART_EN"); + /* XXX enable uart */ + break; + + case RESET: + TRACE_MSG0(tcd->TAG, "reseting UART_EN"); + /* XXX disable uart */ + break; + } +} + +/*! mxc_pmic_mono_func - used to enable or disable mono audio connection + * @param otg - otg instance + * @param flag - enable/disable flag + * + */ +void mxc_pmic_mono_func(struct otg_instance *otg, u8 flag) +{ + struct tcd_instance *tcd = (struct tcd_instance *)otg->tcd; + //TRACE_MSG0(tcd->TAG, "--"); + switch (flag) { + case SET: + TRACE_MSG0(tcd->TAG, "setting MONO"); + /* XXX enable mono output */ + break; + + case RESET: + TRACE_MSG0(tcd->TAG, "reseting MONO"); + /* XXX disable mono output */ + break; + } +} + +/* ********************************************************************************************* */ +/*! + * mxc_pmic_usbi_handler() - event handler + * + * + * + */ +void pmic_usbi_handler(void) +{ + + TRACE_MSG0(REMOVE_TCD, "--"); + pmic_otg_wakeup(); + +#if 0 +#if 1 + t_sensor_bits sense_bits; + if (pmic_get_sensors(&sense_bits)) { + printk(KERN_INFO "%s: pmic_get_sensors() failed\n", + __FUNCTION__); + return; + } + + TRACE_MSG3(REMOVE_TCD, "MC13783 EVENT: 4V4S: %d 2V0S: %d 0V8S: %d", + sense_bits.sense_usb4v4s, + sense_bits.sense_usb2v0s, sense_bits.sense_usb0v8s); + + otg_event(tcd_instance->otg, + (sense_bits.sense_usb4v4s ? VBUS_VLD : VBUS_VLD_) | + (sense_bits. + sense_usb2v0s ? (B_SESS_VLD | A_SESS_VLD) : (B_SESS_VLD_ | + A_SESS_VLD_)) | + (sense_bits.sense_usb0v8s ? B_SESS_END : B_SESS_END_), REMOVE_TCD, + "MC13783 USBI"); +#else + otg_event(tcd_instance->otg, + (pmic_check_sense(sense_usb4v4s) ? VBUS_VLD : VBUS_VLD_) | + (pmic_check_sense(sense_usb4v4s) + ? (B_SESS_VLD | A_SESS_VLD) : (B_SESS_VLD_ | A_SESS_VLD_)) | + (pmic_check_sense(sense_usb0v8s) ? B_SESS_END : + B_SESS_END_), REMOVE_TCD, "MC13783 USBI"); +#endif +#endif +} + +void pmic_idi_handler(void) +{ + + TRACE_MSG0(REMOVE_TCD, "--"); + pmic_otg_wakeup(); + +#if 0 +#if 1 + t_sensor_bits sense_bits; + if (pmic_get_sensors(&sense_bits)) { + printk(KERN_INFO "%s: pmic_get_sensors() failed\n", + __FUNCTION__); + return; + } + + TRACE_MSG2(REMOVE_TCD, "MC13783 EVENT: IDGNDS: %d IDFLOATS: %d", + sense_bits.sense_id_gnds, sense_bits.sense_id_floats); + + otg_event(tcd_instance->otg, + (sense_bits.sense_id_gnds ? ID_GND : ID_GND_) | + (sense_bits.sense_id_floats ? ID_FLOAT : ID_FLOAT_), + REMOVE_TCD, "MC13783 IDI"); +#else + otg_event(tcd_instance->otg, + (pmic_check_sense(sense_id_gnds) ? ID_GND : ID_GND_) | + (pmic_check_sense(sense_id_floats) ? ID_FLOAT : ID_FLOAT_), + REMOVE_TCD, "MC13783 IDI"); +#endif +#endif +} + +void pmic_se1i_handler(void) +{ + + TRACE_MSG0(REMOVE_TCD, "--"); + pmic_otg_wakeup(); + +#if 0 +#if 1 + t_sensor_bits sense_bits; + if (pmic_get_sensors(&sense_bits)) { + printk(KERN_INFO "%s: pmic_get_sensors() failed\n", + __FUNCTION__); + return; + } + TRACE_MSG1(REMOVE_TCD, "MC13783 EVENT: se1: %d", sense_bits.sense_se1s); + otg_event(tcd_instance->otg, + (sense_bits.sense_se1s ? SE1_DET : SE1_DET_), + REMOVE_TCD, "MC13783 SE1"); +#else + otg_event(tcd_instance->otg, + (pmic_check_sense(sense_se1s) ? SE1_DET : SE1_DET_), + REMOVE_TCD, "MC13783 SE1"); +#endif +#endif +} + + + + +void pmic_detect_event(const PMIC_CONVITY_EVENTS event) +{ + unsigned int flags = 0; + + switch (event) { + case USB_DETECT_4V4_RISE: + flags &= ~(VBUS_VLD_); + flags |= VBUS_VLD; + break; + case USB_DETECT_4V4_FALL: + flags &= ~(VBUS_VLD); + flags |= VBUS_VLD_; + break; + case USB_DETECT_2V0_RISE: + flags &= ~(B_SESS_VLD_ | A_SESS_VLD_); + flags |= (B_SESS_VLD | A_SESS_VLD); + break; + case USB_DETECT_2V0_FALL: + flags &= ~(B_SESS_VLD | A_SESS_VLD); + flags |= (B_SESS_VLD_ | A_SESS_VLD_); + break; + case USB_DETECT_0V8_RISE: + flags &= ~(B_SESS_END_); + flags |= B_SESS_END; + break; + case USB_DM_HI: + flags &= ~(DM_HIGH); + flags |= DM_HIGH; + break; + case USB_DP_HI: + flags &= ~(DP_HIGH); + flags |= DP_HIGH; + break; + case USB_DETECT_0V8_FALL: + flags &= ~(B_SESS_END); + flags |= B_SESS_END_; + break; + case USB_DETECT_MINI_A: + flags &= ~(ID_GND); + flags |= ID_GND; + break; + case USB_DETECT_MINI_B: + flags &= ~(ID_FLOAT); + flags |= ID_FLOAT; + break; + case USB_DETECT_NON_USB_ACCESSORY: + flags &= (ID_FLOAT | ID_GND); + flags |= (ID_FLOAT_ | ID_GND_); + break; + case USB_DETECT_FACTORY_MODE: + flags &= (ID_FLOAT | ID_GND); + flags |= (ID_FLOAT | ID_GND); + break; + } +#if 0 + flags = (USB_DETECT_4V4_RISE ? VBUS_VLD : VBUS_VLD_) | + (USB_DETECT_2V0_RISE ? (B_SESS_VLD | A_SESS_VLD) + : (B_SESS_VLD_ | A_SESS_VLD_)) | (USB_DETECT_0V8_RISE ? B_SESS_END + : B_SESS_END_); +#endif + pmic_otg_wakeup(); +} + + + +void mxc_pmic_mod_exit(struct otg_instance *otg) +{ + + PMIC_STATUS rc; + if (pmic_work_task) { + otg_task_exit(pmic_work_task); + pmic_work_task = NULL; + } + if (pmic_otg_task) { + otg_task_exit(pmic_otg_task); + pmic_otg_task = NULL; + } + //while (PENDING_WORK_ITEM(pmic_work_bh)) { + // printk(KERN_ERR "%s: waiting for mc13783_work_bh\n", + // __FUNCTION__); + // schedule_timeout(10 * HZ); + //} + //while (PENDING_WORK_ITEM(pmic_otg_wq)) { + // printk(KERN_ERR "%s: waiting for mc13783_otg_wq\n", + // __FUNCTION__); + // schedule_timeout(10 * HZ); + //} + + + pmic_convity_set_mode(pmic_handle, RS232_1); + pmic_convity_usb_otg_set_config(pmic_handle, USB_PULL_OVERRIDE); + pmic_convity_usb_otg_set_config(pmic_handle, USB_OTG_SE0CONN); + pmic_convity_usb_otg_clear_config(pmic_handle, USB_USBCNTRL); + + pmic_convity_usb_otg_clear_config(pmic_handle, USB_PU); + pmic_convity_usb_otg_clear_config(pmic_handle, USB_UDP_PD); + pmic_convity_usb_otg_clear_config(pmic_handle, USB_UDM_PD); + pmic_convity_clear_callback(pmic_handle); + + rc = pmic_convity_close(pmic_handle); + if (rc != PMIC_SUCCESS) { + pr_debug("pmic_convity_close() returned error %d", rc); + } + + +} + +/* ********************************************************************************************* */ + +int mxc_pmic_mod_init(struct otg_instance *otg) +{ + + + PMIC_CONVITY_USB_TRANSCEIVER_MODE transceiver; + PMIC_STATUS rc = PMIC_ERROR; + + rc = pmic_convity_open(&pmic_handle, USB); + if (rc != PMIC_SUCCESS) { + printk(KERN_INFO "Failed to connect to the transceiver\n"); + } + + + TRACE_MSG0(REMOVE_TCD, "Setup the work item"); + //PREPARE_WORK_ITEM(pmic_work_bh, &pmic_bh, NULL); + //PREPARE_WORK_ITEM(pmic_otg_wq, &pmic_otg_event_bh, NULL); + + THROW_UNLESS((pmic_work_task = otg_task_init2("tcdwork", pmic_work_proc, otg, otg->tcd->TAG)), error); + THROW_UNLESS((pmic_otg_task = otg_task_init2("tcdotg", pmic_otg_proc, otg, otg->tcd->TAG)), error); + + otg_task_start(pmic_work_task); + otg_task_start(pmic_otg_task); + + + + if (pmic_convity_usb_get_xcvr(pmic_handle, &transceiver)) + printk(KERN_INFO + "%s: pmic_convity_get_usb_transciver failed\n", + __FUNCTION__); + + printk(KERN_INFO "%s: tw: %02x\n", __FUNCTION__, transceiver); + + + pmic_convity_set_callback(pmic_handle, pmic_detect_event, + USB_DETECT_4V4_RISE | USB_DETECT_4V4_FALL | + USB_DETECT_2V0_RISE | USB_DETECT_2V0_FALL | + USB_DETECT_0V8_RISE | USB_DETECT_0V8_FALL | + USB_DETECT_MINI_A | USB_DETECT_MINI_B); + if (rc != PMIC_SUCCESS) { + } + + // XXX it may be more appropriate to do this in the enable function + // // and reset to something when disaabled + pmic_convity_usb_otg_set_config(pmic_handle, USB_PULL_OVERRIDE); + pmic_convity_usb_otg_set_config(pmic_handle, USB_USBCNTRL); + + start_flag = end_flag = 0; + + CATCH(error) { + + if (pmic_work_task) { + otg_task_exit(pmic_work_task); + pmic_work_task = NULL; + } + if (pmic_otg_task) { + otg_task_exit(pmic_otg_task); + pmic_otg_task = NULL; + } + return -EINVAL; + } + return 0; + + +} diff --git a/drivers/otg/hardware/mxc-procfs.c b/drivers/otg/hardware/mxc-procfs.c new file mode 100644 index 000000000000..b5a3b3b22c5a --- /dev/null +++ b/drivers/otg/hardware/mxc-procfs.c @@ -0,0 +1,772 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/hardware/mxc-procfs.c - USB Device Core Layer + * @(#) balden@belcarra.com/seth2.rillanon.org|otg/platform/mxc/mxc-procfs.c|20070614183950|47700 + * + * Copyright (c) 2004-2005 Belcarra Technologies Corp + * Copyright (c) 2005-2007 Belcarra Technologies 2005 Corp + * + * By: + * Stuart Lynne <sl@belcarra.com>, + * Bruce Balden <balden@belcarra.com> + */ +/*! + * @file otg/hardware/mxc-procfs.c + * @brief FREESCAELE Procfs register dump + * + * + * @ingroup FSOTG + * + */ + +#include <otg/otg-compat.h> +#if defined(CONFIG_OTG_LNX) || defined(_OTG_DOXYGEN) + +#include <otg/pcd-include.h> +#include <linux/module.h> +//#include <asm/arch/mx2.h> + +//#include "mx2ads.h" +//#include <otghw/mx2ads-hardware.h> +#include "mxc-lnx.h" +#include "mxc-hardware.h" + + +/* ********************************************************************************************* */ +/*! char *fs_hnpstate - hnp state array */ + +char * fs_hnpstate[32] = { + "b_idle", // 0 + "b_master", // 1 + "b_slave", // 2 + "b_srp_init_v", // 3 + "b_srb_init_d", // 4 + "b_short_db", // 5 + "b_wait_conn_a", // 6 + "b_wait_conn_b", // 7 + "b_wait_bus_lo", // 8 + "", // 9 + "b_wait_rst", // a + "", // b + "", // c + "", // d + "", // e + "", // f + "a_idle", // 0 + "a_master", // 1 + "a_slave", // 2 + "a_wait_vpluse", // 3 + "a_wait_dpulse", // 4 + "a_wait_conn_a", // 5 + "", // 6 + "a_wait_conn_b", // 7 + "a_wait_vrise", // 8 + "a_suspend", // 9 + "a_wait_vfall", // a + "a_vbus_err", // b + "conn_debounce", // c + "a_wait_abreq", // d + "a_wait_rst", // e + "", // f +}; + +/*! char *fs_core_regs - USBOTG Module Registers array */ +char * fs_core_regs[] = { + // USBOTG Module Registers + "OTG_CORE_HWMODE (0x000)", + "OTG_CORE_CINT_STAT (0x004)" , + "OTG_CORE_CINT_STEN (0x008)" , + "OTG_CORE_CLK_CTRL (0x00c)", + "OTG_CORE_RST_CTRL (0x010)", + "OTG_CORE_FRM_INTVL (0x014)", + "OTG_CORE_FRM_REMAIN (0x018)", + "OTG_CORE_HNP_CSTAT (0x01c)", + "OTG_CORE_HNP_TIMER1 (0x020)", + "OTG_CORE_HNP_TIMER2 (0x024)", + "OTG_CORE_HNP_T3PCR (0x028)", + "OTG_CORE_HINT_STAT (0x02c)", + "OTG_CORE_HINT_STEN (0x030)", + "OTG_CORE_CPUEPSEL_STAT (0x034)", + "UNUSED (0x038)", + "OTG_CORE_INTERRUPT_STEN (0x03c)", + + // USB Function + "OTG_FUNC_CMD_STAT (0x040)", "OTG_FUNC_DEV_ADDR (0x044)", "OTG_FUNC_SINT_STAT (0x048)", "OTG_FUNC_SINT_STEN (0x04c)", + "OTG_FUNC_XINT_STAT (0x050)", "OTG_FUNC_YINT_STAT (0x054)", "OTG_FUNC_XYINT_STEN (0x058)", "OTG_FUNC_XFILL_STAT (0x05c)", + "OTG_FUNC_YFILL_STAT (0x060)", "OTG_FUNC_EP_EN (0x064)", "OTG_FUNC_EP_RDY (0x068)", "OTG_FUNC_IINT (0x06c)", + "OTG_FUNC_EP_DSTAT (0x070)", "OTG_FUNC_EP_DEN (0x074)", "OTG_FUNC_EP_TOGGLE (0x078)", "OTG_FUNC_FRM_NUM (0x07c)", + + // USB Host + "OTG_HOST_CONTROL (0x080)", "OTG_HOST_SINT_STAT (0x084)", "OTG_HOST_SINT_STEN (0x088)", "HOST_UNUSED (0x08c)", + "HOST_UNUSED (0x090)", "OTG_HOST_XINT_STAT (0x094)", "OTG_HOST_YINT_STAT (0x098)", "HOST_UNUSED (0x09c)", + "OTG_HOST_XYINT_STEN (0x0a0)", "HOST_UNUSED (0x0a4)", "OTG_HOST_XFILL_STAT (0x0a8)", "OTG_HOST_YFILL_STAT (0x0ac)", + "HOST_UNUSED (0x0b0)", "HOST_UNUSED (0x0b4)", "HOST_UNUSED (0x0b8)", "HOST_UNUSED (0x0bc)", + + // USB Host + "OTG_HOST_ETD_EN (0x0c0)", "HOST_UNUSED (0x0c4)", "OTG_HOST_DIR_ROUTE (0x0c8)", "OTG_HOST_IINT (0x0cc)", + "OTG_HOST_EP_DSTAT (0x0d0)", "OTG_HOST_ETD_DONE (0x0d4)", "HOST_UNUSED (0x0d8)", "HOST_UNUSED (0x0dc)", + "OTG_HOST_FRM_NUM (0x0e0)", "OTG_HOST_LSP_THRESH (0x0e4)", "OTG_HOST_HUB_DESCA (0x0e8)", "OTG_HOST_HUB_DESCB (0x0ec)", + "OTG_HOST_HUB_STAT (0x0f0)", "OTG_HOST_PORT_STATUS_1 (0x0f4)", "OTG_HOST_PORT_STATUS_2 (0x0f8)", "OTG_HOST_PORT_STATUS_3 (0x0fc)", + +}; + + +#if 0 +char *fs_i2c_regs1[] = { + "VENDOR_ID_LOW (0x100)", "VENDOR_ID_HIGH (0x101)", + "PRODUCT_ID_LOW (0x102)", "PRODUCT_ID_HIGH (0x103)", + + "MODE_CONTROL_1_SET (0x104)", "MODE_CONTROL_1_CLR (0x105)", + "OTG_CONTROL_SET (0x106)", "OTG_CONTROL_CLR (0x107)", + "INTERRUPT_SOURCE (0x108)", "RESERVED (0x109)", + "INT_LAT_REG_SET (0x10a)", "INT_LAT_REG_CLR (0x10b)", + "INT_FALSE_REG_SET (0x10c)", "INT_FALSE_REG_CLR (0x10d)", + "INT_TRUE_REG_SET (0x10e)", "INT_TRUE_REG_CLR (0x10f)", + "OTG_STATUS (0x110)", "UNUSED (0x111)", + "MODE_CONTROL_2_SET (0x112)", "MODE_CONTROL_2_CLR (0x113)", + + "BCD_DEV_LOW (0x114)", "BCD_DEV_HIGH (0x115)", + + "RESERVED (0x116)", "RESERVED (0x117)", + "OTG_XCVR_DEVAD (0x118)", "SEQ_OP_REG (0x119)", + "SEQ_RD_STARTAD (0x11a)", "I2C_OP_CTRL_REG (0x11b)", + "RESERVED (0x11c)", "RESERVED (0x11d)", + "SCLK_TO_SCL_HPER (0x11e)", "I2C_INTERRUPT_AND_CTRL (0x11f)", +}; + +char *fs_i2c_regs2[] = { + "VENDOR_ID", "PRODUCT_ID", + + "MODE_CONTROL_1_SETCLR", "OTG_CONTROL_SETCLR", + "INTERRUPT_SOURCE", "INT_LAT_REG_SETCLR", + "INT_FALSE_REG_SETCLR", "INT_TRUE_REG_SETCLR", + "OTG_STATUS", "MODE_CONTROL_2_SETCLR", + + "BCD_DEV", +}; + +char *fs_i2c_regs3[] = { + "OTG_XCVR_DEVAD", + "SEQ_OP_REG", + "SEQ_RD_STARTAD", + "I2C_OP_CTRL_REG", + "RESERVED", + "RESERVED", + "SCLK_TO_SCL_HPER", + "I2C_INTERRUPT_AND_CTRL", +}; +#endif + +/*! char* fs_etd_momory - etd memory array */ +char * fs_etd_memory[] = { + // 0x200 ETD Memory Access + "ETD 0 OUT WORD 0", "ETD 0 OUT WORD 1", "ETD 0 OUT WORD 2", "ETD 0 OUT WORD 3", + "ETD 0 IN WORD 0", "ETD 0 IN WORD 1", "ETD 0 IN WORD 2", "ETD 0 IN WORD 3", + "ETD 1 OUT WORD 0", "ETD 1 OUT WORD 1", "ETD 1 OUT WORD 2", "ETD 1 OUT WORD 3", + "ETD 1 IN WORD 0", "ETD 1 IN WORD 1", "ETD 1 IN WORD 2", "ETD 1 IN WORD 3", + "ETD 2 OUT WORD 0", "ETD 2 OUT WORD 1", "ETD 2 OUT WORD 2", "ETD 2 OUT WORD 3", + "ETD 2 IN WORD 0", "ETD 2 IN WORD 1", "ETD 2 IN WORD 2", "ETD 2 IN WORD 3", + "ETD 3 OUT WORD 0", "ETD 3 OUT WORD 1", "ETD 3 OUT WORD 2", "ETD 3 OUT WORD 3", + "ETD 3 IN WORD 0", "ETD 3 IN WORD 1", "ETD 3 IN WORD 2", "ETD 3 IN WORD 3", + "ETD 4 OUT WORD 0", "ETD 4 OUT WORD 1", "ETD 4 OUT WORD 2", "ETD 4 OUT WORD 3", + "ETD 4 IN WORD 0", "ETD 4 IN WORD 1", "ETD 4 IN WORD 2", "ETD 4 IN WORD 3", + "ETD 5 OUT WORD 0", "ETD 5 OUT WORD 1", "ETD 5 OUT WORD 2", "ETD 5 OUT WORD 3", + "ETD 5 IN WORD 0", "ETD 5 IN WORD 1", "ETD 5 IN WORD 2", "ETD 5 IN WORD 3", + "ETD 6 OUT WORD 0", "ETD 6 OUT WORD 1", "ETD 6 OUT WORD 2", "ETD 6 OUT WORD 3", + "ETD 6 IN WORD 0", "ETD 6 IN WORD 1", "ETD 6 IN WORD 2", "ETD 6 IN WORD 3", + "ETD 7 OUT WORD 0", "ETD 7 OUT WORD 1", "ETD 7 OUT WORD 2", "ETD 7 OUT WORD 3", + "ETD 7 IN WORD 0", "ETD 7 IN WORD 1", "ETD 7 IN WORD 2", "ETD 7 IN WORD 3", + "ETD 8 OUT WORD 0", "ETD 8 OUT WORD 1", "ETD 8 OUT WORD 2", "ETD 8 OUT WORD 3", + "ETD 8 IN WORD 0", "ETD 8 IN WORD 1", "ETD 8 IN WORD 2", "ETD 8 IN WORD 3", + "ETD 9 OUT WORD 0", "ETD 9 OUT WORD 1", "ETD 9 OUT WORD 2", "ETD 9 OUT WORD 3", + "ETD 9 IN WORD 0", "ETD 9 IN WORD 1", "ETD 9 IN WORD 2", "ETD 9 IN WORD 3", + "ETD A OUT WORD 0", "ETD A OUT WORD 1", "ETD A OUT WORD 2", "ETD A OUT WORD 3", + "ETD A IN WORD 0", "ETD A IN WORD 1", "ETD A IN WORD 2", "ETD A IN WORD 3", + "ETD B OUT WORD 0", "ETD B OUT WORD 1", "ETD B OUT WORD 2", "ETD B OUT WORD 3", + "ETD B IN WORD 0", "ETD B IN WORD 1", "ETD B IN WORD 2", "ETD B IN WORD 3", + "ETD C OUT WORD 0", "ETD C OUT WORD 1", "ETD C OUT WORD 2", "ETD C OUT WORD 3", + "ETD C IN WORD 0", "ETD C IN WORD 1", "ETD C IN WORD 2", "ETD C IN WORD 3", + "ETD D OUT WORD 0", "ETD D OUT WORD 1", "ETD D OUT WORD 2", "ETD D OUT WORD 3", + "ETD D IN WORD 0", "ETD D IN WORD 1", "ETD D IN WORD 2", "ETD D IN WORD 3", + "ETD E OUT WORD 0", "ETD E OUT WORD 1", "ETD E OUT WORD 2", "ETD E OUT WORD 3", + "ETD E IN WORD 0", "ETD E IN WORD 1", "ETD E IN WORD 2", "ETD E IN WORD 3", + "ETD F OUT WORD 0", "ETD F OUT WORD 1", "ETD F OUT WORD 2", "ETD F OUT WORD 3", + "ETD F IN WORD 0", "ETD F IN WORD 1", "ETD F IN WORD 2", "ETD F IN WORD 3", +}; + +/*! char * fs_ep_memory - endpoint memory array */ +char * fs_ep_memory[] = { + // 0x400 EP Memory Access + + "EP 0 OUT WORD 0", "EP 0 OUT WORD 1", "EP 0 OUT WORD 2", "EP 0 OUT WORD 3", + "EP 0 IN WORD 0", "EP 0 IN WORD 1", "EP 0 IN WORD 2", "EP 0 IN WORD 3", + "EP 1 OUT WORD 0", "EP 1 OUT WORD 1", "EP 1 OUT WORD 2", "EP 1 OUT WORD 3", + "EP 1 IN WORD 0", "EP 1 IN WORD 1", "EP 1 IN WORD 2", "EP 1 IN WORD 3", + "EP 2 OUT WORD 0", "EP 2 OUT WORD 1", "EP 2 OUT WORD 2", "EP 2 OUT WORD 3", + "EP 2 IN WORD 0", "EP 2 IN WORD 1", "EP 2 IN WORD 2", "EP 2 IN WORD 3", + "EP 3 OUT WORD 0", "EP 3 OUT WORD 1", "EP 3 OUT WORD 2", "EP 3 OUT WORD 3", + "EP 3 IN WORD 0", "EP 3 IN WORD 1", "EP 3 IN WORD 2", "EP 3 IN WORD 3", + "EP 4 OUT WORD 0", "EP 4 OUT WORD 1", "EP 4 OUT WORD 2", "EP 4 OUT WORD 3", + "EP 4 IN WORD 0", "EP 4 IN WORD 1", "EP 4 IN WORD 2", "EP 4 IN WORD 3", + "EP 5 OUT WORD 0", "EP 5 OUT WORD 1", "EP 5 OUT WORD 2", "EP 5 OUT WORD 3", + "EP 5 IN WORD 0", "EP 5 IN WORD 1", "EP 5 IN WORD 2", "EP 5 IN WORD 3", + "EP 6 OUT WORD 0", "EP 6 OUT WORD 1", "EP 6 OUT WORD 2", "EP 6 OUT WORD 3", + "EP 6 IN WORD 0", "EP 6 IN WORD 1", "EP 6 IN WORD 2", "EP 6 IN WORD 3", + "EP 7 OUT WORD 0", "EP 7 OUT WORD 1", "EP 7 OUT WORD 2", "EP 7 OUT WORD 3", + "EP 7 IN WORD 0", "EP 7 IN WORD 1", "EP 7 IN WORD 2", "EP 7 IN WORD 3", + "EP 8 OUT WORD 0", "EP 8 OUT WORD 1", "EP 8 OUT WORD 2", "EP 8 OUT WORD 3", + "EP 8 IN WORD 0", "EP 8 IN WORD 1", "EP 8 IN WORD 2", "EP 8 IN WORD 3", + "EP 9 OUT WORD 0", "EP 9 OUT WORD 1", "EP 9 OUT WORD 2", "EP 9 OUT WORD 3", + "EP 9 IN WORD 0", "EP 9 IN WORD 1", "EP 9 IN WORD 2", "EP 9 IN WORD 3", + "EP A OUT WORD 0", "EP A OUT WORD 1", "EP A OUT WORD 2", "EP A OUT WORD 3", + "EP A IN WORD 0", "EP A IN WORD 1", "EP A IN WORD 2", "EP A IN WORD 3", + "EP B OUT WORD 0", "EP B OUT WORD 1", "EP B OUT WORD 2", "EP B OUT WORD 3", + "EP B IN WORD 0", "EP B IN WORD 1", "EP B IN WORD 2", "EP B IN WORD 3", + "EP C OUT WORD 0", "EP C OUT WORD 1", "EP C OUT WORD 2", "EP C OUT WORD 3", + "EP C IN WORD 0", "EP C IN WORD 1", "EP C IN WORD 2", "EP C IN WORD 3", + "EP D OUT WORD 0", "EP D OUT WORD 1", "EP D OUT WORD 2", "EP D OUT WORD 3", + "EP D IN WORD 0", "EP D IN WORD 1", "EP D IN WORD 2", "EP D IN WORD 3", + "EP E OUT WORD 0", "EP E OUT WORD 1", "EP E OUT WORD 2", "EP E OUT WORD 3", + "EP E IN WORD 0", "EP E IN WORD 1", "EP E IN WORD 2", "EP E IN WORD 3", + "EP F OUT WORD 0", "EP F OUT WORD 1", "EP F OUT WORD 2", "EP F OUT WORD 3", + "EP F IN WORD 0", "EP F IN WORD 1", "EP F IN WORD 2", "EP F IN WORD 3", +}; + +/*! char * fs_control_regs - control registers array */ +char * fs_control_regs[] = { + // 0x600 USB Control Registers + "OTG_SYS_CTRL", +}; + +/*! char * fs_dma_regs - dma registers array */ +char * fs_dma_regs[] = { + // 0x800 DMA Registers + "OTG_DMA_REV_NUM (0x800)", "OTG_DMA_DINT_STAT (0x804)", "OTG_DMA_DINT_STEN (0x808)", "OTG_DMA_ETD_ERR (0x80c)", + "OTG_DMA_EP_ERR (0x810)", "DMA UNUSED (0x814)", "DMA UNUSED (0x818)", "DMA UNUSED (0x81c)", + "OTG_DMA_ETD_EN (0x820)", "OTG_DMA_EP_EN (0x824)", "OTG_DMA_ETD_ENXREQ (0x828)", "OTG_DMA_EP_ENXREQ (0x82c)", + "OTG_DMA_ETD_ENXYREQ (0x830)", "OTG_DMA_EP_ENXYREQ (0x834)", "OTG_DMA_ETD_BURST4 (0x838)", "OTG_DMA_EP_BURST4 (0x83c)", + "OTG_DMA_MISC_CTRL (0x840)", "OTG_DMA_ETD_CH_CLR (0x844)", "OTG_DMA_EP_CH_CLR (0x848)", "DMA UNUSED (0x84c)", +}; + +char * fs_ep_msa[] = { + "EP 0 OUT MSA", "EP 0 IN MSA", + "EP 1 OUT MSA", "EP 1 IN MSA", + "EP 2 OUT MSA", "EP 2 IN MSA", + "EP 3 OUT MSA", "EP 3 IN MSA", + "EP 4 OUT MSA", "EP 4 IN MSA", + "EP 5 OUT MSA", "EP 5 IN MSA", + "EP 6 OUT MSA", "EP 6 IN MSA", + "EP 7 OUT MSA", "EP 7 IN MSA", + "EP 8 OUT MSA", "EP 8 IN MSA", + "EP 9 OUT MSA", "EP 9 IN MSA", + "EP A OUT MSA", "EP A IN MSA", + "EP B OUT MSA", "EP B IN MSA", + "EP C OUT MSA", "EP C IN MSA", + "EP D OUT MSA", "EP D IN MSA", + "EP E OUT MSA", "EP E IN MSA", + "EP F OUT MSA", "EP F IN MSA", +}; + +char *fs_ureg_name(u32 port) +{ + u32 low = port & 0xfff; + + //TRACE_MSG32("PORT: %x", (int)port); + + port = (port >> 2) & 0xfff; + + //TRACE_MSG32("PORT: %x", (int)port); + + if ((int)port < (0x100 >> 2)) + return (port < (sizeof(fs_core_regs)/4)) ? fs_core_regs[port] : "unknown"; + + port -= 0x100 >> 2; + + if ((int)port < (0x100 >> 2)) { + low &= 0x1f; + #if 0 + if (low < 0x18) { + low >>= 2; + return (low < (sizeof(fs_i2c_regs1)/4)) ? fs_i2c_regs1[low] : "unknown"; + } + low -= 0x18; + return (low < sizeof(fs_i2c_regs3)/4) ? fs_i2c_regs3[low] : "unknown"; + return (low < sizeof(fs_i2c_regs1)/4) ? fs_i2c_regs1[low] : "unknown"; + #endif + return "unknown"; + } + + port -= 0x100 >> 2; + + if ((int)port < (0x200 >> 2)) + return (port < (sizeof(fs_etd_memory)/4)) ? fs_etd_memory[port] : "unknown"; + + port -= 0x200 >> 2; + + if ((int)port < (0x200 >> 2)) + return (port < (sizeof(fs_ep_memory)/4)) ? fs_ep_memory[port] : "unknown"; + + port -= 0x200 >> 2; + + if ((int)port < (0x200 >> 2)) + return (port < (sizeof(fs_control_regs)/4)) ? fs_control_regs[port] : "unknown"; + + port -= 0x200 >> 2; + + if ((int)port < (0x180>>2)) + return (port < (sizeof(fs_dma_regs)/4)) ? fs_dma_regs[port] : "unknown"; + + port -= 0x180 >> 2; + + if ((int)port < (0x100>>2)) + return (port < (sizeof(fs_ep_msa)/4)) ? fs_ep_msa[port] : "unknown"; + + return "unknown"; + +} + + +/* Proc Filesystem *************************************************************************** */ + + + +u8 udc_regs1[0x1000]; +u8 udc_regs2[0x1000]; + +u32 zeros[100]; + + + +void fs_copy(u8 *dp) +{ + fs_memcpy32((u32 *) (dp + 0x000), (u32 *)IO_ADDRESS(OTG_CORE_BASE), 0x100/4); + //fs_memcpy ((u8 *) (dp + 0x100), (u8 *)IO_ADDRESS(OTG_I2C_BASE), 0x100); + fs_memcpy32((u32 *) (dp + 0x200), (u32 *)IO_ADDRESS(OTG_ETD_BASE), 0xe00/4); +} +void fs_clear(volatile u32 *addr, int words) +{ + while (words--) *addr++ = 0; +} + + +/* * + * dohex + * + */ +static void dohexdigit (char *cp, unsigned char val) +{ + if (val < 0xa) { + *cp = val + '0'; + } else if ((val >= 0x0a) && (val <= 0x0f)) { + *cp = val - 0x0a + 'a'; + } +} + +/* * + * dohex + * + */ +static void dohexval (char *cp, unsigned char val) +{ + dohexdigit (cp++, val >> 4); + dohexdigit (cp++, val & 0xf); +} + +int fs_regl(char *page, u32 reg) +{ + u32 offset = reg & 0xfff; + u32 *p1 = (u32 *) (udc_regs1 + offset); + u32 *p2 = (u32 *) (udc_regs2 + offset); + + if (*p1 == *p2) + return sprintf (page, " %-34s [%03x]: %08x\n", fs_ureg_name(reg), reg & 0xfff, *p2); + else + return sprintf (page, " %-34s [%03x]: %08x (%08x)\n", fs_ureg_name(reg), reg & 0xfff, *p2, *p1); +} + +int fs_regb(char *page, u32 reg) +{ + u32 offset = reg & 0xfff; + u8 *p1 = (u8 *) (udc_regs1 + offset); + u8 *p2 = (u8 *) (udc_regs2 + offset); + + if (*p1 == *p2) + return sprintf (page, " %-34s [%03x]: %02x\n", fs_ureg_name(reg), reg & 0xfff, *p2); + else + return sprintf (page, " %-34s [%03x]: %02x (%02x)\n", fs_ureg_name(reg), reg & 0xfff, *p2, *p1); +} + +int fs_regb2(char *page, u32 reg) +{ + u32 offset = reg & 0xfff; + u8 *p1 = (u8 *) (udc_regs1 + offset); + u8 *p2 = (u8 *) (udc_regs2 + offset); + + if (!memcmp(p1, p2, 2)) + return sprintf (page, " %-34s [%03x]: %02x %02x\n", + fs_ureg_name(reg), reg & 0xfff, p2[1], p2[0]); + else + return sprintf (page, " %-34s [%03x]: %02x %02x (%02x %02x)\n", + fs_ureg_name(reg+1), reg & 0xfff, + p2[1], p2[0], p1[1], p1[0]); +} + +int fs_regb4(char *page, u32 reg) +{ + u32 offset = reg & 0xfff; + u8 *p1 = (u8 *) (udc_regs1 + offset); + u8 *p2 = (u8 *) (udc_regs2 + offset); + + if (!memcmp(p1, p2, 4)) + return sprintf (page, " %-34s [%03x]: %02x %02x %02x %02x\n", + fs_ureg_name(reg), reg & 0xfff, p2[3], p2[2], p2[1], p2[0]); + else + return sprintf (page, " %-34s [%03x]: %02x %02x %02x %02x (%02x %02x %02x %02x)\n", + fs_ureg_name(reg), reg & 0xfff, + p2[3], p2[2], p2[1], p2[0], p1[3], p1[2], p1[1], p1[0]); +} + +int fs_etd(char *page, u32 reg) +{ + u32 offset = reg & 0xfff; + u32 *p1 = (u32 *) (udc_regs1 + offset); + u32 *p2 = (u32 *) (udc_regs2 + offset); + + if (!memcmp(p1, p2, 16)) { + if ((reg == OTG_ETD_BASE) || memcmp(zeros, p2, 16)) + return sprintf (page, " %-34s [%03x]: %08x %08x %08x %08x\n", + fs_ureg_name(reg), reg & 0xfff, p2[3], p2[2], p2[1], p2[0]); + return 0; + } + else + return sprintf (page, " %-34s [%03x]: %08x %08x %08x %08x (%08x %08x %08x %08x)\n", + fs_ureg_name(reg), reg & 0xfff, + p2[3], p2[2], p2[1], p2[0], p1[3], p1[2], p1[1], p1[0]); +} + +int fs_ep(char *page, u32 reg) +{ + u32 offset = reg & 0x7ff; + u32 *p1 = (u32 *) (udc_regs1 + offset); + u32 *p2 = (u32 *) (udc_regs2 + offset); + + if (!memcmp(p1, p2, 16)) { + if (memcmp(zeros, p2, 16)) + return sprintf (page, " %-34s [%03x]: %08x %08x %08x %08x\n", + fs_ureg_name(reg), reg & 0xfff, p2[0], p2[1], p2[2], p2[3]); + return 0; + } + else + return sprintf (page, " %-34s [%03x]: %08x %08x %08x %08x (%08x %08x %08x %08x)\n", + fs_ureg_name(reg), reg & 0xfff, + p2[0], p2[1], p2[2], p2[3], p1[0], p1[1], p1[2], p1[3]); +} +/*! dump_mem - dump mem information + * @param page - buffer to save information + * @param dp - memory point to dump + * @param count - message length to dump + */ +int dump_mem(char *page, u8 * dp, int count) +{ + return sprintf (page, + "[%08x] %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x \n", + dp, + dp[0], dp[1], dp[2], dp[3], dp[4], dp[5], dp[6], dp[7], + dp[8], dp[9], dp[10], dp[11], dp[12], dp[13], dp[14], dp[15] + ); +} + +/*! fs_cstat - + * @param page + */ + +static int fs_cstat(char *page) +{ + u32 hnp_cstat = fs_rl(OTG_CORE_HNP_CSTAT); + u32 cint_stat = fs_rl(OTG_CORE_CINT_STAT); + u32 hint_stat = fs_rl(OTG_CORE_HINT_STAT); + int len = 0; + + //TRACE_MSG32("cint_stat: %08x", cint_stat); + //TRACE_MSG32("hnp_cstat: %08x", hnp_cstat); + //TRACE_MSG32("hint_stat: %08x", hint_stat); + // + len += sprintf(page + len, "\nHNP Status: "); + + if (hnp_cstat & MODULE_HNPDAT) len += sprintf(page + len, "HNPDAT "); + if (hnp_cstat & MODULE_VBUSBSE) len += sprintf(page + len, "VBUSBSE "); + if (hnp_cstat & MODULE_VBUSABSV) len += sprintf(page + len, "VBUSABSV "); + if (hnp_cstat & MODULE_VBUSGTAVV) len += sprintf(page + len, "VBUSGTAVV "); + + if (hnp_cstat & MODULE_ARMTHNPE) len += sprintf(page + len, "ARMTHNPE "); + if (hnp_cstat & MODULE_BHNPEN) len += sprintf(page + len, "BHNPEN "); + + if (hnp_cstat & MODULE_SLAVE) len += sprintf(page + len, "SLAVE "); + if (hnp_cstat & MODULE_MASTER) len += sprintf(page + len, "MASTER "); + if (hnp_cstat & MODULE_BGEN) len += sprintf(page + len, "BGEN "); + if (hnp_cstat & MODULE_CMPEN) len += sprintf(page + len, "CMPEN "); + if (hnp_cstat & MODULE_ISBDEV) len += sprintf(page + len, "ISBDEV "); + if (hnp_cstat & MODULE_ISADEV) len += sprintf(page + len, "ISADEV"); + + if (hnp_cstat & MODULE_SWVBUSPUL) len += sprintf(page + len, "SWVBUSPUL "); + if (hnp_cstat & MODULE_SWAUTORST) len += sprintf(page + len, "SWAUTORS T"); + if (hnp_cstat & MODULE_SWPUDP) len += sprintf(page + len, "SWPUDP "); + + len += sprintf(page + len, "HNPSTAT: %02x ", (hnp_cstat >> 4) & 0x1f); + + if (hnp_cstat & MODULE_CLRERROR) len += sprintf(page + len, "CLRERROR "); + if (hnp_cstat & MODULE_ADROPBUS) len += sprintf(page + len, "ADROPBUS "); + if (hnp_cstat & MODULE_ABBUSREQ) len += sprintf(page + len, "ABBUSREQ "); + + len += sprintf(page + len, "\n"); + return len; +} + +/* * + * fs_device_proc_read - implement proc file system read. + * @file + * @buf + * @count + * @pos + * + * Standard proc file system read function. + * + * We let upper layers iterate for us, *pos will indicate which device to return + * statistics for. + */ +static ssize_t fs_device_proc_read_functions (struct file *file, char *buf, size_t count, loff_t * pos) +{ + unsigned long page; + int len = 0; + int index; + int i; + u32 r; + + u8 config_descriptor[512]; + int config_size; + + //struct list_head *lhd; + + // get a page, max 4095 bytes of data... + if (!(page = GET_KERNEL_PAGE())) { + return -ENOMEM; + } + + len = 0; + index = (*pos)++; + + switch(index) { + + case 0: + len += sprintf ((char *) page + len, "MX21 OTG Dump\n"); + fs_copy(udc_regs2); + + r = udc_regs2[0]; + switch (r & MODULE_CRECFG) { + case 0: len += sprintf ((char *) page + len, "Hardware HNP\n"); break; + case 1: len += sprintf ((char *) page + len, "Host Only\n"); break; + case 2: len += sprintf ((char *) page + len, "Function Only\n"); break; + case 3: len += sprintf ((char *) page + len, "Software HNP\n"); break; + } + len += sprintf ((char *) page + len, "OTG Transceiver: "); + switch (((r & MODULE_OTGXCVR) >> 6) & MODULE_CRECFG) { + case 0: len += sprintf ((char *) page + len, "Differential / Differential\n"); break; + case 2: len += sprintf ((char *) page + len, "Single-Ended / Differential\n"); break; + case 1: len += sprintf ((char *) page + len, "Differential / Single-Ended\n"); break; + case 3: len += sprintf ((char *) page + len, "Single-Ended / Single-Ended\n"); break; + } + break; + + case 1: + len += sprintf ((char *) page + len, "\nUSB Control\n"); + len += fs_regl((char *) page + len, OTG_SYS_CTRL); + break; + + case 2: + len += sprintf ((char *) page + len, "\nUSB Core\n"); + for (i = OTG_CORE_HWMODE; i <= OTG_CORE_INTERRUPT_STEN; i += 4) + len += fs_regl((char *) page + len, i); + len += fs_cstat((char *)page + len); + break; + + case 3: + len += sprintf ((char *) page + len, "\nUSB Host\n"); + for (i = OTG_HOST_CONTROL; i <= OTG_HOST_PORT_STATUS_3; i += 4) + len += fs_regl((char *) page + len, i); + break; + + case 4: + len += sprintf ((char *) page + len, "\nUSB ETD\n"); + for (i = OTG_ETD_BASE; i < OTG_EP_BASE; i += 16) + len += fs_etd((char *) page + len, i); + break; + + case 5: + len += sprintf ((char *) page + len, "\nUSB Func\n"); + for (i = OTG_FUNC_CMD_STAT; i <= OTG_FUNC_FRM_NUM; i += 4) + len += fs_regl((char *) page + len, i); + break; + + case 6: + len += sprintf ((char *) page + len, "\nUSB EP\n"); + //for (i = OTG_EP_BASE; i < OTG_SYS_BASE; i += 16) + // len += fs_ep((char *) page + len, i); + for (i = OTG_EP_BASE; i < 8; i++) + len += fs_ep((char *) page + len, i*16); + break; + + case 7: + len += sprintf ((char *) page + len, "\nUSB DMA\n"); + for (i = OTG_DMA_REV_NUM; i <= OTG_DMA_EP_CH_CLR; i += 4) + len += fs_regl((char *) page + len, i); + break; + case 8: + len += sprintf ((char *) page + len, "\nUSB DMA MSA\n"); + for (i = OTG_DMA_EPN_MSA(0); i <= OTG_DMA_EPN_MSA(32); i += 4) + len += fs_regl((char *) page + len, i); + break; + case 9: + len += sprintf ((char *) page + len, "\nUSB Bufs\n"); + break; + + case 10: + len += dump_mem((char *) page + len, (u8 *)data_x_address(0, USB_DIR_OUT), 16); + len += dump_mem((char *) page + len, (u8 *)data_x_address(0, USB_DIR_OUT)+16, 16); + len += dump_mem((char *) page + len, (u8 *)data_x_address(0, USB_DIR_OUT)+32, 16); + len += dump_mem((char *) page + len, (u8 *)data_x_address(0, USB_DIR_OUT)+48, 16); + len += sprintf ((char *) page + len, "\n"); + len += dump_mem((char *) page + len, (u8 *)data_y_address(0, USB_DIR_OUT), 16); + len += dump_mem((char *) page + len, (u8 *)data_y_address(0, USB_DIR_OUT)+16, 16); + len += dump_mem((char *) page + len, (u8 *)data_y_address(0, USB_DIR_OUT)+32, 16); + len += dump_mem((char *) page + len, (u8 *)data_y_address(0, USB_DIR_OUT)+48, 16); + len += sprintf ((char *) page + len, "\n"); + break; + + case 11: + len += dump_mem((char *) page + len, (u8 *)data_x_address(0, USB_DIR_IN), 16); + len += dump_mem((char *) page + len, (u8 *)data_x_address(0, USB_DIR_IN)+16, 16); + len += dump_mem((char *) page + len, (u8 *)data_x_address(0, USB_DIR_IN)+32, 16); + len += dump_mem((char *) page + len, (u8 *)data_x_address(0, USB_DIR_IN)+48, 16); + len += sprintf ((char *) page + len, "\n"); + len += dump_mem((char *) page + len, (u8 *)data_y_address(0, USB_DIR_IN), 16); + len += dump_mem((char *) page + len, (u8 *)data_y_address(0, USB_DIR_IN)+16, 16); + len += dump_mem((char *) page + len, (u8 *)data_y_address(0, USB_DIR_IN)+32, 16); + len += dump_mem((char *) page + len, (u8 *)data_y_address(0, USB_DIR_IN)+48, 16); + len += sprintf ((char *) page + len, "\n"); + break; + + case 12: + len += dump_mem((char *) page + len, (u8 *)data_x_address(1, USB_DIR_OUT), 16); + len += dump_mem((char *) page + len, (u8 *)data_x_address(1, USB_DIR_OUT)+16, 16); + len += dump_mem((char *) page + len, (u8 *)data_x_address(1, USB_DIR_OUT)+32, 16); + len += dump_mem((char *) page + len, (u8 *)data_x_address(1, USB_DIR_OUT)+48, 16); + len += sprintf ((char *) page + len, "\n"); + len += dump_mem((char *) page + len, (u8 *)data_y_address(1, USB_DIR_OUT), 16); + len += dump_mem((char *) page + len, (u8 *)data_y_address(1, USB_DIR_OUT)+16, 16); + len += dump_mem((char *) page + len, (u8 *)data_y_address(1, USB_DIR_OUT)+32, 16); + len += dump_mem((char *) page + len, (u8 *)data_y_address(1, USB_DIR_OUT)+48, 16); + len += sprintf ((char *) page + len, "\n"); + break; + + case 13: + len += dump_mem((char *) page + len, (u8 *)data_x_address(1, USB_DIR_IN), 16); + len += dump_mem((char *) page + len, (u8 *)data_x_address(1, USB_DIR_IN)+16, 16); + len += dump_mem((char *) page + len, (u8 *)data_x_address(1, USB_DIR_IN)+32, 16); + len += dump_mem((char *) page + len, (u8 *)data_x_address(1, USB_DIR_IN)+48, 16); + len += sprintf ((char *) page + len, "\n"); + len += dump_mem((char *) page + len, (u8 *)data_y_address(1, USB_DIR_IN), 16); + len += dump_mem((char *) page + len, (u8 *)data_y_address(1, USB_DIR_IN)+16, 16); + len += dump_mem((char *) page + len, (u8 *)data_y_address(1, USB_DIR_IN)+32, 16); + len += dump_mem((char *) page + len, (u8 *)data_y_address(1, USB_DIR_IN)+48, 16); + len += sprintf ((char *) page + len, "\n"); + break; + #if 0 + case 9: + len += sprintf ((char *) page + len, "\nUSB OTG\n"); + for (i = OTG_I2C_BASE; i < MX2_OTG_XCVR_DEVAD; i += 1) + len += fs_regb((char *) page + len, i); + break; + case 10: + len += sprintf ((char *) page + len, "\nUSB I2C Control\n"); + for (i = MX2_OTG_XCVR_DEVAD; i <= MX2_I2C_INTERRUPT_AND_CTRL; i += 1) + len += fs_regb((char *) page + len, i); + break; + #endif + + + + + + default: + memcpy(udc_regs1, udc_regs2, sizeof(udc_regs1)); + break; + } + + //printk(KERN_INFO"%s: len: %d count: %d\n", __FUNCTION__, len, count); + + if (len > count) { + //printk(KERN_INFO"%s: len > count\n", __FUNCTION__); + //printk(KERN_INFO"%s", page); + len = -EINVAL; + } + else if ((len > 0) && copy_to_user (buf, (char *) page, len)) { + //printk(KERN_INFO"%s: EFAULT\n", __FUNCTION__); + len = -EFAULT; + } + else { + //printk(KERN_INFO"%s: OK\n", __FUNCTION__); + } + free_page (page); + return len; +} + +/*! struct file_operations fs_device_proc_operations_functions */ +static struct file_operations fs_device_proc_operations_functions = { + read:fs_device_proc_read_functions, +}; + + + + +/* Module init ******************************************************************************* */ + + +int mxc_procfs_init (void) +{ + { + struct proc_dir_entry *p; + + // create proc filesystem entries + if ((p = create_proc_entry ("mxclib", 0, 0)) == NULL) + return -ENOMEM; + p->proc_fops = &fs_device_proc_operations_functions; + } + + //printk(KERN_INFO"%s: %08x", __FUNCTION__, IO_ADDRESS(OTG_ETD_BASE)); + //fs_clear((volatile u32 *)IO_ADDRESS(OTG_ETD_BASE), 0x200); + //fs_clear((volatile u32 *)IO_ADDRESS(OTG_EP_BASE), 0x200); + fs_copy(udc_regs1); + memset(udc_regs1 + 0x18, 0, 4); + + return 0; +} + +void mxc_procfs_exit (void) +{ + // remove proc filesystem entry + remove_proc_entry ("mxclib", NULL); +} + + +#endif /* defined(CONFIG_OTG_LNX) */ diff --git a/drivers/otg/hardware/mxc91321-gpio.c b/drivers/otg/hardware/mxc91321-gpio.c new file mode 100644 index 000000000000..df925771a05c --- /dev/null +++ b/drivers/otg/hardware/mxc91321-gpio.c @@ -0,0 +1,217 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/hardware/mxc91321-gpio.c - Freescale USBOTG ArgonLV gpio and iomux setting + * @(#) sp/root@belcarra.com/debian-black.(none)|otg/platform/mxc/mxc91321-gpio.c|20070822230929|24222 + * + * Copyright (c) 2004-2005 Belcarra Technologies Corp + * Copyright (c) 2005-2007 Belcarra Technologies 2005 Corp + * + * By: + * Stuart Lynne <sl@belcarra.com>, + * Bruce Balden <balden@belcarra.com>, + * Shahrad Payandeh <sp@belcarra.com> + */ +/*! + * @file otg/hardware/mxc91321-gpio.c + * @brief Freescale USB Host Controller Driver + * + * @ingroup FSOTG + */ + +#include <linux/irq.h> +#include <otg/pcd-include.h> +#include <../arch/arm/mach-mxc91321/iomux.h> + +#if defined(CONFIG_OTG_ZASEVB_MC13783_CONNECTIVITY) || defined(CONFIG_OTG_ZASEVB_MC13783_PMIC) +#include <asm/arch/gpio.h> +#include "mxc-lnx.h" +#include "mxc-hardware.h" +#include "isp1301.h" +#include "isp1301-hardware.h" +#endif + +#if defined(CONFIG_OTG_ZASEVB_ISP1301) +#include <asm/arch/gpio.h> +#include "mxc-lnx.h" +#include "mxc-hardware.h" +#include "isp1301.h" +#include "isp1301-hardware.h" +#endif + +#define ZGPIO_PORT 0 +#define ZGPIO_PIN 2 + + +#if defined(CONFIG_OTG_ZASEVB_ISP1301) || defined(_OTG_DOXYGEN) + +BOOL zasevb_int_disabled = FALSE; +/* ********************************************************************************************* */ +/*! + * zasevb_gpio_int_hndlr() - gpio interrupt handler + * @param irq + * @param dev_id + * @param regs + * @return interrupt handler status + * This disables the gpio interrup and schedules the isp1301 bottom half handler. + * + */ +static irqreturn_t zasevb_gpio_int_hndlr (int irq, void *dev_id, struct pt_regs *regs) +{ + disable_irq(IOMUX_TO_IRQ(PIN_GPIO2)); + zasevb_int_disabled = TRUE; + + TRACE_MSG0(REMOVE_TCD, "ZASEVB GPIO INTERRUPT: SCHEDULE WORK"); + // printk(KERN_INFO"%s:\n", __FUNCTION__); + isp1301_bh_wakeup(REMOVE_tcd_instance->otg, FALSE); + + return IRQ_HANDLED; +} + +/*! + * zasevb_isp1301_bh()- call isp1301 bottom half handler + * @param arg + * This is a wrapper to the isp1301 bottom half handler, it + * re-enables the gpio interrupt after processing complete. + */ +void *zasevb_isp1301_bh(void *arg) +{ + TRACE_MSG0(REMOVE_TCD, "ZASEVB GPIO INTERRUPT: ISP1301_BH"); + isp1301_bh(arg); + TRACE_MSG0(REMOVE_TCD, "ZASEVB GPIO INTERRUPT: REENABLE"); + + if (zasevb_int_disabled) { + zasevb_int_disabled = FALSE; + enable_irq(IOMUX_TO_IRQ(PIN_GPIO2)); + } + return NULL; +} + + + +/*! mxc_iomux_gpio_isp1301_set + * This function set iomux settings depends on platform and usb mode. + * + * @param otg - otg instance + * @param usb_mode setting usb mode. + * + */ + +int mxc_iomux_gpio_isp1301_set (struct otg_instance *otg, int usb_mode) +{ + + int gpio = 1; + + printk (KERN_INFO"MXC gpio setting for isp1301\n"); + + isp1301_mod_init(otg, &zasevb_isp1301_bh); + + TRACE_MSG0(REMOVE_TCD, "5. IOMUX and GPIO Interrupt Configuration"); + iomux_config_mux(PIN_GPIO2, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + + //Setting interrupt for ISP1301 + #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,15) + set_irq_type(IOMUX_TO_IRQ(PIN_GPIO2), IRQF_TRIGGER_FALLING); + #else + set_irq_type(IOMUX_TO_IRQ(PIN_GPIO2), IRQT_FALLING); + #endif + gpio = request_irq(IOMUX_TO_IRQ(PIN_GPIO2), zasevb_gpio_int_hndlr, + 0, "ISP1301", (void *)&ocd_ops); + THROW_IF(gpio, error); + + + iomux_config_mux(PIN_USB_XRXD, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + iomux_config_mux(PIN_USB_VMOUT, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + iomux_config_mux(PIN_USB_VPOUT, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + iomux_config_mux(PIN_USB_VPIN, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + iomux_config_mux(PIN_USB_TXENB, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + iomux_config_mux(PIN_USB_VMIN, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + + + + CATCH(error) { + printk(KERN_INFO"%s: failed\n", __FUNCTION__); + UNLESS (gpio) gpio_free_irq (ZGPIO_PORT, ZGPIO_PIN, GPIO_HIGH_PRIO); + return -EINVAL; + } + + return 0; +} +/*! mxc_iomux_gpio_isp1301_reset - clear isp1301 iomux settings + */ +int mxc_iomux_gpio_isp1301_reset (void) +{ + free_irq(IOMUX_TO_IRQ(PIN_GPIO2), &ocd_ops); + return 0; +} + +#endif //CONFIG_OTG_ZASEVB_ISP1301 + + + +#if defined(CONFIG_OTG_ZASEVB_MC13783_CONNECTIVITY) || defined(CONFIG_OTG_ZASEVB_MC13783_PMIC) +/*! mxc_iomux_gpio_mc13783_set - set mc13783 iomux settings + * @param usb_mode + * @return 0 + */ +int mxc_iomux_gpio_mc13783_set (int usb_mode) +{ + + printk (KERN_INFO"MXC gpio setting for Atlas\n"); + + printk(KERN_INFO"IOMUX setting for Argon+\n"); + iomux_config_mux(PIN_USB_XRXD, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + iomux_config_mux(PIN_USB_VMOUT, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + iomux_config_mux(PIN_USB_VPOUT, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + iomux_config_mux(PIN_USB_VPIN, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + iomux_config_mux(PIN_USB_TXENB, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + iomux_config_mux(PIN_USB_VMIN, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + + + return 0; +} + +/*! mxc_iomux_gpio_mc13783_reset - clear mc13783 iomux settings + */ +int mxc_iomux_gpio_mc13783_reset (void) +{ + + iomux_config_mux(PIN_USB_XRXD, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + iomux_config_mux(PIN_USB_VMOUT, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + iomux_config_mux(PIN_USB_VPOUT, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + iomux_config_mux(PIN_USB_VPIN, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + iomux_config_mux(PIN_USB_TXENB, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + iomux_config_mux(PIN_USB_VMIN, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + + return 0; +} +#endif //CONFIG_OTG_ZASEVB_MC13783_CONNECTIVITY + + +int mxc_host_gpio (void) +{ + iomux_config_mux(PIN_GPIO5, OUTPUTCONFIG_ALT1, INPUTCONFIG_ALT1); // USB1_OC + printk(KERN_INFO"%s: NONE/GPIO\n", __FUNCTION__); + return 0; +} + +#if defined(CONFIG_OTG_ZASEVB_ISP1301) +OTG_EXPORT_SYMBOL(mxc_iomux_gpio_isp1301_set); +OTG_EXPORT_SYMBOL(mxc_iomux_gpio_isp1301_reset); +#endif +#if defined(CONFIG_OTG_ZASEVB_MC13783_CONNECTIVITY) || defined(CONFIG_OTG_ZASEVB_MC13783_PMIC) +OTG_EXPORT_SYMBOL(mxc_iomux_gpio_mc13783_set); +OTG_EXPORT_SYMBOL(mxc_iomux_gpio_mc13783_reset); +#endif + +OTG_EXPORT_SYMBOL(mxc_host_gpio); diff --git a/drivers/otg/hardware/mxc91331-gpio.c b/drivers/otg/hardware/mxc91331-gpio.c new file mode 100644 index 000000000000..3fa472fc059d --- /dev/null +++ b/drivers/otg/hardware/mxc91331-gpio.c @@ -0,0 +1,215 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/hardware/mxc91331-gpio.c - Freescale USBOTG ArgonPlus gpio and iomux setting + * @(#) sp/root@belcarra.com/debian-black.(none)|otg/platform/mxc/mxc91331-gpio.c|20070822230929|09899 + * + * Copyright (c) 2004-2005 Belcarra Technologies Corp + * Copyright (c) 2005-2007 Belcarra Technologies 2005 Corp + * + * By: + * Stuart Lynne <sl@belcarra.com>, + * Bruce Balden <balden@belcarra.com>, + * Shahrad Payandeh <sp@belcarra.com> + */ +/*! + * @file otg/hardware/mxc91331-gpio.c + * @brief Freescale USB Host Controller Driver + * + * @ingroup FSOTG + */ + +#include <linux/irq.h> +#include <otg/pcd-include.h> +#include <../arch/arm/mach-mxc91321/iomux.h> + +#if defined(CONFIG_OTG_ZASEVB_MC13783_CONNECTIVITY) || defined(CONFIG_OTG_ZASEVB_MC13783_PMIC) +#include <asm/arch/gpio.h> +#include "mxc-lnx.h" +#include "mxc-hardware.h" +#include "isp1301.h" +#include "isp1301-hardware.h" +#endif + +#if defined(CONFIG_OTG_ZASEVB_ISP1301) +#include <asm/arch/gpio.h> +#include "mxc-lnx.h" +#include "mxc-hardware.h" +#include "isp1301.h" +#include "isp1301-hardware.h" +#endif + +#define ZGPIO_PORT 0 +#define ZGPIO_PIN 2 + + +#if defined(CONFIG_OTG_ZASEVB_ISP1301) || defined(_OTG_DOXYGEN) + +BOOL zasevb_int_disabled = FALSE; + +/* ********************************************************************************************* */ +/*! + * zasevb_gpio_int_hndlr() - gpio interrupt handler + * @param irq + * @param dev_id + * @param regs + * @return interrupt handler status + * This disables the gpio interrup and schedules the isp1301 bottom half handler. + * + */ +static irqreturn_t zasevb_gpio_int_hndlr (int irq, void *dev_id, struct pt_regs *regs) +{ + disable_irq(irq); + zasevb_int_disabled = TRUE; + TRACE_MSG0(REMOVE_TCD, "ZASEVB GPIO INTERRUPT: SCHEDULE WORK"); + isp1301_bh_wakeup(REMOVE_tcd_instance->otg, FALSE); + + return IRQ_HANDLED; +} + +/*! + * zasevb_isp1301_bh()- call isp1301 bottom half handler + * @param arg + * This is a wrapper to the isp1301 bottom half handler, it + * re-enables the gpio interrupt after processing complete. + */ +void *zasevb_isp1301_bh(void *arg) +{ + TRACE_MSG0(REMOVE_TCD, "ZASEVB GPIO INTERRUPT: ISP1301_BH"); + isp1301_bh(arg); + TRACE_MSG0(REMOVE_TCD, "ZASEVB GPIO INTERRUPT: REENABLE"); + + if (zasevb_int_disabled) { + zasevb_int_disabled = FALSE; + enable_irq(IOMUX_TO_IRQ(PIN_GPIO2)); + } + return 0; +} + + + + +/*! + * This function set iomux settings depends on platform and usb mode. + * @param otg otg instance + * @param usb_mode setting usb mode. + * + */ + +int mxc_iomux_gpio_isp1301_set (struct otg_instance *otg, int usb_mode) +{ + + int gpio = 1; + + printk (KERN_INFO"MXC gpio setting for isp1301\n"); + + isp1301_mod_init(otg, &zasevb_isp1301_bh); + + TRACE_MSG0(otg->tcd->TAG, "5. IOMUX and GPIO Interrupt Configuration"); + iomux_config_mux(PIN_GPIO2, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + + //Setting interrupt for ISP1301 + #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,15) + set_irq_type(IOMUX_TO_IRQ(PIN_GPIO2), IRQF_TRIGGER_FALLING); + #else + set_irq_type(IOMUX_TO_IRQ(PIN_GPIO2), IRQT_FALLING); + #endif + gpio = request_irq(IOMUX_TO_IRQ(PIN_GPIO2), zasevb_gpio_int_hndlr, + 0, "ISP1301", (void *)&ocd_ops); + THROW_IF(gpio, error); + + + iomux_config_mux(PIN_USB_XRXD, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + iomux_config_mux(PIN_USB_VMOUT, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + iomux_config_mux(PIN_USB_VPOUT, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + iomux_config_mux(PIN_USB_VPIN, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + iomux_config_mux(PIN_USB_TXENB, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + iomux_config_mux(PIN_USB_VMIN, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + + + + CATCH(error) { + printk(KERN_INFO"%s: failed\n", __FUNCTION__); + UNLESS (gpio) gpio_free_irq (ZGPIO_PORT, ZGPIO_PIN, GPIO_HIGH_PRIO); + return -EINVAL; + } + + return 0; +} + +int mxc_iomux_gpio_isp1301_reset (void) +{ + free_irq(IOMUX_TO_IRQ(PIN_GPIO2), &ocd_ops); + return 0; +} + +#endif //CONFIG_OTG_ZASEVB_ISP1301 + + + +#if defined(CONFIG_OTG_ZASEVB_MC13783_CONNECTIVITY) || defined(CONFIG_OTG_ZASEVB_MC13783_PMIC) +/*! mxc_iomux_gpio_mc1383_set - set mc13783 iomux setting + * @param usb_mode + */ +int mxc_iomux_gpio_mc13783_set (int usb_mode) +{ + + printk (KERN_INFO"MXC gpio setting for Atlas\n"); + + printk(KERN_INFO"IOMUX setting for Argon+\n"); + iomux_config_mux(PIN_USB_XRXD, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + iomux_config_mux(PIN_USB_VMOUT, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + iomux_config_mux(PIN_USB_VPOUT, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + iomux_config_mux(PIN_USB_VPIN, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + iomux_config_mux(PIN_USB_TXENB, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + iomux_config_mux(PIN_USB_VMIN, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + + + return 0; +} + +/*! mxc_iomux_gpio_mc13783_reset -clear mc13783 iomux settings + */ + +int mxc_iomux_gpio_mc13783_reset (void) +{ + + iomux_config_mux(PIN_USB_XRXD, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + iomux_config_mux(PIN_USB_VMOUT, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + iomux_config_mux(PIN_USB_VPOUT, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + iomux_config_mux(PIN_USB_VPIN, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + iomux_config_mux(PIN_USB_TXENB, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + iomux_config_mux(PIN_USB_VMIN, OUTPUTCONFIG_FUNC, INPUTCONFIG_FUNC); + + return 0; +} +#endif //CONFIG_OTG_ZASEVB_MC13783_CONNECTIVITY + + +int mxc_host_gpio (void) +{ + iomux_config_mux(PIN_GPIO5, OUTPUTCONFIG_ALT1, INPUTCONFIG_ALT1); // USB1_OC + printk(KERN_INFO"%s: NONE/GPIO\n", __FUNCTION__); + return 0; +} + +#if defined(CONFIG_OTG_ZASEVB_ISP1301) +OTG_EXPORT_SYMBOL(mxc_iomux_gpio_isp1301_set); +OTG_EXPORT_SYMBOL(mxc_iomux_gpio_isp1301_reset); +#endif +#if defined(CONFIG_OTG_ZASEVB_MC13783_CONNECTIVITY) || defined(CONFIG_OTG_ZASEVB_MC13783_PMIC) +OTG_EXPORT_SYMBOL(mxc_iomux_gpio_mc13783_set); +OTG_EXPORT_SYMBOL(mxc_iomux_gpio_mc13783_reset); +#endif + +OTG_EXPORT_SYMBOL(mxc_host_gpio); diff --git a/drivers/otg/hardware/otg-dev.c b/drivers/otg/hardware/otg-dev.c new file mode 100644 index 000000000000..bb42b8062e5b --- /dev/null +++ b/drivers/otg/hardware/otg-dev.c @@ -0,0 +1,484 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/hardware/otg-dev.c -- Generic DEV driver + * @(#) sl@belcarra.com/whiskey.enposte.net|otg/platform/otglib/otg-dev.c|20070918212346|28546 + * + * Copyright (c) 2005 Belcarra Technologies Corp + * Copyright (c) 2005-2007 Belcarra Technologies 2005 Corp + * + * By: + * Stuart Lynne <sl@belcarra.com> + * + */ +/*! + * @file otg/hardware/otg-dev.c + * @brief Generic DEV Driver. + * + * This supports using a single DEV device to implement various USB OTG + * subsidiary drivers. + * + * The hardware driver is split into drivers for the following. + * + * Required: + * + * 1. dev + * + * One or more of the following: + * + * 2. ocd + * 3. pcd + * 4. hcd + * 5. tcd + * + * The dev driver implements a standard dev driver structure which will + * be used to register with the linux dev support. During the probe function + * the otg_dev_probe() function should be called to create an otg_dev + * structure for the device. This is used to keep track of all of the additional + * drivers. + * + * Each of the optional drivers will use otg_dev_register_driver() to let the + * dev layer know what optional drivers are available. + * + * @ingroup OTGDEV + * @ingroup LINUXOS + */ + + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> + +//#include <linux/usb.h> +#include <linux/delay.h> + +#include <otg/otg-compat.h> + +#include <linux/ioport.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/smp_lock.h> +#include <linux/init.h> +#include <linux/timer.h> +#include <linux/list.h> +#include <linux/interrupt.h> + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,15) +#include <linux/platform_device.h> +#endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,15) */ + +#include <otg/otg-trace.h> +#include <otg/usbp-chap9.h> +#include <otg/otg-api.h> +#include <otg/otg-dev.h> +#include <otg/otg-utils.h> +#include <otg/otg-hcd.h> +#include <otg/otg-ocd.h> +#include <otg/otg-pcd.h> +#include <otg/otg-tcd.h> + + +#define TRACE_VERBOSE 0 + +/* ********************************************************************************************* */ +static struct otg_dev *otg_devs; + +/* ********************************************************************************************* */ +/*! + * otg_dev_isr() - interrupt service handler + */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,19) +irqreturn_t otg_dev_isr(int irq, void *data, struct pt_regs *r) +#else /* LINUX_VERSION_CODE < KERNEL_VERSION(2,6,19) */ +irqreturn_t otg_dev_isr(int irq, void *data) +#endif /* LINUX_VERSION_CODE < KERNEL_VERSION(2,6,19) */ +{ + struct otg_interrupt *otg_interrupt; // = data; + struct device *device; // = otg_interrupt->device; + struct otg_dev *otg_dev; // = dev_get_drvdata(device); + struct otg_instance *otg; // = otg_dev->otg_instance; + int irqmask; // = 1 << otg_interrupt->irq; + int i; + + otg_interrupt = data; + device = otg_interrupt->device; + otg_dev = dev_get_drvdata(device); + irqmask = 1 << otg_interrupt->irq; + + // XXX need to determine what pt_regs *r can tell us, it may + // allow sub-driver isr functions to tell if they should do something + // so we may want to send that down + + /* XXX spinlock */ + + RETURN_IRQ_HANDLED_UNLESS(otg_dev); + otg = otg_dev->otg_instance; + otg->interrupts++; + + if (TRACE_VERBOSE) + TRACE_MSG0(otg_dev->DEV, "---------------------------------------- Start"); + + for (i = OTG_DRIVER_TCD; i < OTG_DRIVER_TYPES; i++) { + struct otg_dev_driver *otg_dev_driver = otg_dev->otg_device_driver->drivers[i]; + + CONTINUE_UNLESS(otg_dev_driver); + CONTINUE_UNLESS(otg_dev_driver->isr); + CONTINUE_UNLESS(otg_dev_driver->irqs & irqmask); + RETURN_IRQ_HANDLED_IF_IRQ_HANDLED (otg_dev_driver->isr(otg_dev, data, irqmask)); + //TRACE_MSG2(otg_dev->DEV, "not handled by %s %d", otg_dev_driver->name, i); + } + + /* XXX spinlock */ + TRACE_MSG0(otg_dev->DEV, "---------------------------------------- IRQ_NONE ----"); + return IRQ_NONE; +} + +/* ********************************************************************************************* */ + +/*! + * otg_dev_free_dev + */ +void otg_dev_free_dev(struct device *device, struct otg_dev *otg_dev) +{ + #if 0 + int region; + unsigned long resource_start; + unsigned long resource_len; + + RETURN_UNLESS(otg_dev); + + for (region = 0; region < DEVICE_COUNT_RESOURCE; region++) { + + CONTINUE_UNLESS(otg_dev->pci_regions & (1 << region)); + + if (otg_dev->regs[region]) iounmap(otg_dev->regs[region]); + + resource_start = pci_resource_start(pci_dev, region); + resource_len = pci_resource_len(pci_dev, region); + release_mem_region(resource_start, resource_len); + } + #endif +} + +/*! otg_free_irqs + */ +void otg_free_irqs(struct otg_dev *otg_dev, struct device *device, int num_resources) +{ + struct platform_device *platform_device = to_platform_device(device); + struct otg_interrupt *otg_interrupts = otg_dev->otg_interrupts; + int resource; + + for (resource = 0; resource < MIN(platform_device->num_resources, num_resources); resource++) { + + struct resource *resources = platform_device->resource + resource; + struct otg_interrupt *otg_interrupt = otg_interrupts + resource; + + + CONTINUE_UNLESS(resources->flags == IORESOURCE_IRQ); + + free_irq(resources->start, otg_interrupt); + } +} + + +/*! + * otg_dev_probe() - otg dev probe function + * + */ +int otg_dev_probe (struct device *device, struct otg_device_driver *otg_device_driver, void *privdata) +{ + struct platform_device *platform_device = to_platform_device(device); + + struct otg_dev_driver *otg_dev_driver; + struct otg_dev *otg_dev = NULL; + struct otg_interrupt *otg_interrupts = NULL; + int resource = 0; + + u8 latency, limit; + int i; + + struct otg_instance *otg = NULL; + + + /* allocate otg structure */ + THROW_UNLESS((otg = otg_create()), error); + + /* allocate otg_dev structure and fill in standard fields */ + THROW_UNLESS((otg_dev = kmalloc(sizeof(struct otg_dev), GFP_KERNEL)), error); + THROW_UNLESS((otg_interrupts = + kmalloc(sizeof(struct otg_interrupt) * platform_device->num_resources, GFP_KERNEL)), error); + + memset(otg_dev, 0, sizeof(struct otg_dev)); + otg_dev->num_resources = platform_device->num_resources; + otg_dev->otg_interrupts = otg_interrupts; + otg_dev->device = device; + otg_dev->otg_instance = otg; + otg_dev->DEV = otg_trace_obtain_tag(otg, "otg-dev"); + otg_dev->privdata = privdata; + + otg_dev->otg_device_driver = otg_device_driver; + dev_set_drvdata(device, otg_dev); + otg_dev_set_drvdata (otg_dev, device); + + for (resource = 0; resource < platform_device->num_resources; resource++) { + + struct resource *resources = platform_device->resource + resource; + struct otg_interrupt *otg_interrupt = otg_interrupts + resource; + + CONTINUE_UNLESS(resources->flags == IORESOURCE_IRQ); + + TRACE_MSG2(otg_dev->DEV, "irq: %d start: %d", resource, resources->start); + + /* request irq - use hardware wrapper isr if available */ + + otg_interrupt->device = device; + otg_interrupt->irq = resource; + + printk(KERN_INFO"%s: REQUEST_IRQ resource: %d start: %d otg_interrupt: %x\n", + __FUNCTION__, resource, resources->start, otg_interrupt); + THROW_IF(request_irq(resources->start, + + //(otg_dev->otg_device_driver->isr) ? + //(otg_dev->otg_device_driver->isr) : + otg_dev_isr, + + #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,19) + SA_SHIRQ, + #else /* LINUX_VERSION_CODE < KERNEL_VERSION(2,6,19) */ + 0, + #endif /* LINUX_VERSION_CODE < KERNEL_VERSION(2,6,19) */ + otg_dev->otg_device_driver->name, + otg_interrupt + ), error); + + } + + if (otg_devs) { + TRACE_MSG2(otg_dev->DEV, "otg_devs: %x new: %x", otg_devs, otg_dev); + otg_dev->next = otg_devs; + } + + otg_devs = otg_dev; + + for (i = 0; i < OTG_DRIVER_TYPES; i++) { + struct otg_dev_driver *driver = otg_device_driver->drivers[i]; + + /* check if we have a driver */ + CONTINUE_UNLESS(driver); + + /* register sub-driver with otg */ + switch (driver->id) { + case OTG_DRIVER_TCD: + otg_dev->tcd_instance = otg_set_tcd_ops(otg_dev->otg_instance, driver->ops); + otg_dev->tcd_instance->privdata = otg_dev; + break; + case OTG_DRIVER_PCD: + otg_dev->pcd_instance = otg_set_pcd_ops(otg_dev->otg_instance, driver->ops); + otg_dev->pcd_instance->privdata = otg_dev; + break; + case OTG_DRIVER_HCD: + otg_dev->hcd_instance = otg_set_hcd_ops(otg_dev->otg_instance, driver->ops); + otg_dev->hcd_instance->privdata = otg_dev; + break; + case OTG_DRIVER_OCD: + otg_dev->ocd_instance = otg_set_ocd_ops(otg_dev->otg_instance, driver->ops); + otg_dev->ocd_instance->privdata = otg_dev; + break; + } + + /* call sub-driver probe() function */ + CONTINUE_UNLESS(driver->probe); + CONTINUE_UNLESS(driver->probe(otg_dev)); + + } + + /* start otg state machine */ + if (otg_device_driver->serial_number && strlen(otg_device_driver->serial_number)) { + //TRACE_MSG1(ZAS, "serial_number: %s", otg_device_driver->serial_number); + otg_serial_number (otg, otg_device_driver->serial_number); + } + otg->privdata = otg_dev; + otg_init(otg); + + return 0; + + CATCH(error) { + + printk(KERN_INFO"%s: FAILED resource: %d\n", __FUNCTION__, resource); + + if (resource) + otg_free_irqs(otg_dev, device, resource); + + if (otg_interrupts) + kfree(otg_interrupts); + + dev_set_drvdata(device, NULL); + + otg_dev_free_dev(device, otg_dev); + + if (otg_dev) kfree(otg_dev); + + if (otg) + otg_destroy(otg); + + return -EINVAL; + } +} + +/*! + * otg_dev_remove() - pci remove function + */ +void otg_dev_remove (struct device *device , struct otg_device_driver *otg_device_driver) +{ + struct platform_device *platform_device = to_platform_device(device); + + struct otg_dev *otg_dev = dev_get_drvdata(device); + struct otg_instance *otg = otg_dev->otg_instance; + int i; + + /* stop otg state machine */ + otg_exit(otg); + + /* release interrupts */ + otg_free_irqs(otg_dev, device, platform_device->num_resources); + + /* de-register sub-drivers from otg */ + for (i = 0; i < OTG_DRIVER_TYPES; i++) { + struct otg_dev_driver *driver = otg_device_driver->drivers[i]; + CONTINUE_UNLESS(driver); + CONTINUE_UNLESS(driver->remove); + driver->remove(otg_dev); + switch (driver->id) { + case OTG_DRIVER_TCD: + otg_dev->tcd_instance = otg_set_tcd_ops(otg_dev->otg_instance, NULL); + break; + case OTG_DRIVER_PCD: + otg_dev->pcd_instance = otg_set_pcd_ops(otg_dev->otg_instance, NULL); + break; + case OTG_DRIVER_HCD: + otg_dev->hcd_instance = otg_set_hcd_ops(otg_dev->otg_instance, NULL); + break; + case OTG_DRIVER_OCD: + otg_dev->ocd_instance = otg_set_ocd_ops(otg_dev->otg_instance, NULL); + break; + } + + } + + /* finally destroy otg instance */ + otg_destroy(otg); + + /* cleanupj */ + dev_set_drvdata(device, NULL); + + otg_dev->otg_instance = NULL; + kfree(otg_dev->otg_interrupts); + + + #if 0 + if (otg_devs == otg_dev) { + otg_devs = otg_dev->next; + } + else { + struct otg_dev *link; + for (link = otg_devs; link; link = otg_dev->next) { + if (link->next == otg_dev) { + link->next = otg_dev->next; + break; + } + } + } + #endif + + otg_dev_free_dev(device, otg_dev); + otg_trace_invalidate_tag(otg_dev->DEV); + kfree(otg_dev); +} + + +/* ********************************************************************************************* */ + +/*! + * otg_dev_register_driver() - sub-driver registration function + */ +int otg_dev_register_driver(struct otg_device_driver *otg_device_driver, struct otg_dev_driver *otg_dev_driver) +{ + struct otg_dev *otg_dev; + + otg_device_driver->drivers[otg_dev_driver->id] = otg_dev_driver; + + #if 0 + if (otg_dev_driver->probe) { + for (otg_dev = otg_devs; otg_dev; otg_dev = otg_dev->next) { + + if (otg_dev_driver->probe(otg_dev, otg_dev_driver->id)) { + otg_dev->otg_device_driver->drivers[otg_dev_driver->id] = NULL; + return -EINVAL; + } + return 0; + } + } + #endif + return 0; +} + +/*! + * otg_dev_unregister_driver() - sub-driver unregistration function + */ +void otg_dev_unregister_driver(struct otg_device_driver *otg_device_driver, struct otg_dev_driver *otg_dev_driver) +{ + struct otg_dev *otg_dev; + + #if 0 + if (otg_dev_driver->probe) { + for (otg_dev = otg_devs; otg_dev; otg_dev = otg_dev->next) { + otg_dev_driver->remove(otg_dev, otg_dev_driver->id); + return; + } + } + #endif + otg_device_driver->drivers[otg_dev_driver->id] = NULL; +} + +/*! + * otg_dev_set_drvdata - set otg_dev data + */ + +void otg_dev_set_drvdata(struct otg_dev *otg_dev, void *data) +{ + otg_dev->drvdata = data; +} +/*! + * otg_dev_get_drvdata - get otg_dev data + */ +void * otg_dev_get_drvdata(struct otg_dev *otg_dev) +{ + return otg_dev->drvdata; +} + +/* ********************************************************************************************* */ + +/* ********************************************************************************************* */ + + + + +OTG_EXPORT_SYMBOL(otg_dev_probe); +OTG_EXPORT_SYMBOL(otg_dev_remove); + + +OTG_EXPORT_SYMBOL(otg_dev_register_driver); +OTG_EXPORT_SYMBOL(otg_dev_unregister_driver); + +OTG_EXPORT_SYMBOL(otg_dev_set_drvdata); +OTG_EXPORT_SYMBOL(otg_dev_get_drvdata); diff --git a/drivers/otg/hardware/pcd.c b/drivers/otg/hardware/pcd.c new file mode 100644 index 000000000000..56a9dbf6510c --- /dev/null +++ b/drivers/otg/hardware/pcd.c @@ -0,0 +1,1642 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/hardware/pcd.c - OTG Peripheral Controller Driver + * @(#) sl@belcarra.com/whiskey.enposte.net|otg/platform/otglib/pcd.c|20070820053759|59156 + * + * Copyright (c) 2004-2005 Belcarra Technologies Corp + * Copyright (c) 2005-2007 Belcarra Technologies 2005 Corp + * + * By: + * Stuart Lynne <sl@belcarra.com>, + * Bruce Balden <balden@belcarra.com> + * + */ +/*! + * @file otg/hardware/pcd.c + * @brief PCD only driver init. + * Notes + * + * 1. The pcd file abstracts much of the UDC complexity as possible and + * provides a common implementation that is shared to various extents by the + * various pcd drivers. + * + * @ingroup PCD + */ + +#include <otg/pcd-include.h> +#include <otg/usbp-func.h> + +#define TRACE_VERBOSE 1 +#define TRACE_VERY_VERBOSE 0 + +/* ******************************************************************************************* */ +/* ******************************************************************************************* */ + +/*! + * @brief pcd_urb_finished_irq() - tell function that an urb has been finished. + * + * Used by a USB Bus driver to pass a sent urb back to the function + * driver via the endpoints finished queue. + * @param urb urb to process + * @param rc result code + * + */ +void pcd_urb_finished_irq(struct usbd_urb *urb, int rc) +{ + struct usbd_bus_instance *bus = (struct usbd_bus_instance *)urb->bus; + struct pcd_instance *pcd = (struct pcd_instance *)(bus ? bus->privdata : NULL); + struct otg_instance *otg = bus ? bus->otg : NULL; + //struct ocd_instance *ocd = otg ? otg->ocd : NULL; + //struct ocd_ops *ocd_ops = otg ? otg->ocd_ops : NULL; + + UNLESS(urb && urb->bus) { + //TRACE_MSG2(urb->bus->otg->pcd->TAG, "urb: %x bus: %x", urb, urb ? urb->bus : NULL); + return; + } + urb->irq_flags = USBD_URB_FINISHED; + urb->status = (usbd_urb_status_t)rc; + + /* fasttrack bus->ep0_urb */ + if (urb == bus->ep0_urb) { + (urb = usbd_first_finished_urb_detached (urb->endpoint, &urb->endpoint->rdy)); + return; + } + + + TRACE_ELAPSED(pcd->TAG, "PCD 0. DONE", urb->ticks, (u32)urb); + otg_get_ocd_info(pcd->otg, &urb->ticks, &urb->framenum); + + if (rc==USBD_URB_CANCELLED){ + usbd_unlink_urb(urb); + usbd_do_urb_callback (urb, urb->status); + return; + } + + if (urb->flags & USBD_URB_FAST_RETURN) { + #ifdef CONFIG_OTG_LATENCY_CHECK + otg_tick_t ticks; + u16 framenum; + otg_tick_t urb_ticks = urb->ticks; + otg_get_ocd_info(pcd->otg, &ticks, &framenum); + #endif + usbd_unlink_urb(urb); + usbd_do_urb_callback (urb, urb->status); + #ifdef CONFIG_OTG_LATENCY_CHECK + /* elapsed time for the callback */ + TRACE_ELAPSED(pcd->TAG, "PCD 1. CB", ticks, (u32)urb); + + /* elapsed time for all processing since the urb was finished */ + TRACE_ELAPSED(pcd->TAG, "PCD 2. TOTAL", urb_ticks, (u32)urb); + #endif + return; + } + + //TRACE_MSG1(pcd->TAG, "FINISH %p", urb); + TRACE_MSG0(pcd->TAG, "otg_up_work()"); + otg_up_work(pcd->task); +} + +/* ******************************************************************************************* */ +/*! pcd_rcv_cancelled_irq - cancel current receive urb + * Called by UDC driver to cancel the current receive urb. + * @param endpoint - endpoint instance pointer + */ +void pcd_rcv_cancelled_irq (struct usbd_endpoint_instance *endpoint) +{ + struct usbd_urb *rcv_urb; + struct usbd_bus_instance *bus = endpoint->bus; + struct pcd_instance *pcd = (struct pcd_instance *)bus->privdata; + + TRACE_MSG1(pcd->TAG, "BUS_RCV CANCELLED: %p", (int) endpoint->rcv_urb); + RETURN_IF (! (rcv_urb = endpoint->rcv_urb)); + TRACE_MSG1(pcd->TAG, "rcv_urb: %p", (int)endpoint->rcv_urb); + pcd_urb_finished_irq (rcv_urb, USBD_URB_CANCELLED); + endpoint->sent = endpoint->last = 0; + endpoint->rcv_urb = NULL; +} + + +/*! pcd_tx_next_irq - get the current or next urb to transmit + * Called by UDC driver to get current transmit urb or next available transmit urb. + * @param endpoint - endpoint instance pointer + * @return urb to transmit + */ +struct usbd_urb * pcd_tx_next_irq (struct usbd_endpoint_instance *endpoint) +{ + struct usbd_bus_instance *bus = endpoint->bus; + struct pcd_instance *pcd = (struct pcd_instance *)bus->privdata; + struct otg_instance *otg = pcd->otg; + if (!endpoint->tx_urb) { + + if ( (endpoint->tx_urb = usbd_first_ready_urb(endpoint, &endpoint->rdy))) { + int i; + u8 *cp = endpoint->tx_urb->buffer; + if (otg) + otg_get_ocd_info(otg, &endpoint->tx_urb->ticks, &endpoint->tx_urb->framenum); + + if (TRACE_VERBOSE) + TRACE_MSG3(pcd->TAG, "[%2x] NEXT TX: length: %d flags: %x", + endpoint->bEndpointAddress, + endpoint->tx_urb->actual_length, endpoint->tx_urb->flags); + + if (TRACE_VERY_VERBOSE) + for (i = 0; i < endpoint->tx_urb->actual_length; i+= 8) + TRACE_MSG8(pcd->TAG, "SENT %02x %02x %02x %02x %02x %02x %02x %02x", + cp[i + 0], cp[i + 1], cp[i + 2], cp[i + 3], + cp[i + 4], cp[i + 5], cp[i + 6], cp[i + 7] + ); + + endpoint->tx_urb->status = USBD_URB_ACTIVE; + } + } + //TRACE_MSG1(endpoint->bus->otg->pcd->TAG, "BUS_TX NEXT TX_URB: %p", (int)endpoint->tx_urb); + return endpoint->tx_urb; +} + +/*! pcd_rcv_next_irq - get the current or next urb to receive into + * Called by UDC driver to get current receive urb or next available receive urb. + * @param endpoint - endpoint instance pointer + * @return urb to receive + */ +struct usbd_urb * pcd_rcv_next_irq (struct usbd_endpoint_instance *endpoint) +{ + struct usbd_bus_instance *bus = endpoint->bus; + struct pcd_instance *pcd = (struct pcd_instance *)bus->privdata; + struct otg_instance *otg = pcd->otg; + if (!endpoint->rcv_urb) { + + if ( (endpoint->rcv_urb = usbd_first_ready_urb(endpoint, &endpoint->rdy))) { + if (otg) + otg_get_ocd_info(otg, &endpoint->tx_urb->ticks, &endpoint->tx_urb->framenum); + endpoint->rcv_urb->status = USBD_URB_ACTIVE; + } + + } + //TRACE_MSG1(endpoint->bus->otg->pcd->TAG, "BUS_RCV_URB: %p", endpoint->rcv_urb); + return endpoint->rcv_urb; +} + +/* ******************************************************************************************* */ +/*! + * pcd_assign_endpoint() + */ +static int +pcd_assign_endpoint(struct pcd_instance *pcd, u8 index, u8 physicalEndpoint, u32 *used, + struct usbd_endpoint_map *endpoint_map, u8 bmAttributes, + u16 wMaxPacketSize_fs, + u16 wMaxPacketSize_hs, + u16 fs_transferSize, + u16 hs_transferSize, + u8 bInterval) +{ + RETURN_EINVAL_IF( *used & (1 << physicalEndpoint) ); + + endpoint_map->index = index; + endpoint_map->bEndpointAddress[0] = endpoint_map->bEndpointAddress[1] = + physicalEndpoint | (USB_DIR_IN & bmAttributes); + endpoint_map->physicalEndpoint[0] = endpoint_map->physicalEndpoint[1] = + (physicalEndpoint * 2) + ((USB_DIR_IN & bmAttributes) ? 1 : 0); + endpoint_map->wMaxPacketSize[0] = wMaxPacketSize_fs; + endpoint_map->wMaxPacketSize[1] = wMaxPacketSize_hs; + endpoint_map->transferSize[0] = fs_transferSize; + endpoint_map->transferSize[1] = hs_transferSize; + endpoint_map->bmAttributes[0] = endpoint_map->bmAttributes[1] = bmAttributes; + endpoint_map->bInterval[0] = endpoint_map->bInterval[1] = bInterval; + *used |= (1 << physicalEndpoint); + + TRACE_MSG6(pcd->TAG, "ASSIGN: index: %02x phys: %02x addr: %02x wMaxPacketSize: %02x:%02x", + physicalEndpoint, + endpoint_map->physicalEndpoint[0], + endpoint_map->bEndpointAddress[0], + endpoint_map->bEndpointAddress[1], + endpoint_map->wMaxPacketSize[0], + endpoint_map->wMaxPacketSize[1] + ); + //printk(KERN_INFO"%s: DDDD\n", __FUNCTION__); + return 0; +} + +/*! + * pcd_request_endpoints() + * @param pcd + * @param endpoint_map_array + * @param endpointsRequested + * @param requestedEndpoints + * + */ +int +pcd_request_endpoints(struct pcd_instance *pcd, struct usbd_endpoint_map *endpoint_map_array, int endpointsRequested, + struct usbd_endpoint_request *requestedEndpoints) +{ + //struct usbd_device_description *device_description; + int i, in, out; + u32 in_used = 0; + u32 out_used = 0; + //printk(KERN_INFO"%s:\n", __FUNCTION__); + TRACE_MSG1(pcd->TAG, "REQUEST ENDPOINTS: %d", endpointsRequested); + for (in = out = 1, i = 0; i < endpointsRequested; i++) { + struct usbd_endpoint_map *endpoint_map = endpoint_map_array + i; + u8 index = requestedEndpoints[i].index; + u8 bInterval = requestedEndpoints[i].bInterval; + u8 bmAttributes = requestedEndpoints[i].bmAttributes; + u16 fs_transferSize = requestedEndpoints[i].fs_requestedTransferSize; + u16 hs_transferSize = requestedEndpoints[i].hs_requestedTransferSize; + TRACE_MSG5(pcd->TAG, "REQUEST ENDPOINTS[%2x] index: %02x bm: %02x trans: %04x:%04x", + i, index, bmAttributes, fs_transferSize, hs_transferSize); + + + switch(bmAttributes) { + + case USB_DIR_IN | USB_ENDPOINT_BULK: + RETURN_EINVAL_UNLESS(in < usbd_pcd_ops.max_endpoints); + CONTINUE_IF(!pcd_assign_endpoint(pcd, index, in++, &in_used, endpoint_map, bmAttributes, + 0x40, 0x200, fs_transferSize, hs_transferSize, bInterval)); + THROW(error); + + case USB_DIR_IN | USB_ENDPOINT_INTERRUPT: + case USB_DIR_IN | USB_ENDPOINT_INTERRUPT | USB_ENDPOINT_OPT: + RETURN_EINVAL_UNLESS(in < usbd_pcd_ops.max_endpoints); + CONTINUE_IF(!pcd_assign_endpoint(pcd, index, in++, &in_used, endpoint_map, bmAttributes, + 0x40, 0x40, fs_transferSize, hs_transferSize, bInterval)); + THROW(error); + + case USB_DIR_OUT | USB_ENDPOINT_BULK: + RETURN_EINVAL_UNLESS(out < usbd_pcd_ops.max_endpoints); + CONTINUE_IF(!pcd_assign_endpoint(pcd, index, out++, &out_used, endpoint_map, bmAttributes, + 0x40, 0x200, fs_transferSize, hs_transferSize, bInterval)); + THROW(error); + case USB_DIR_OUT | USB_ENDPOINT_INTERRUPT: + case USB_DIR_OUT | USB_ENDPOINT_INTERRUPT | USB_ENDPOINT_OPT: + RETURN_EINVAL_UNLESS(out < usbd_pcd_ops.max_endpoints); + CONTINUE_IF(!pcd_assign_endpoint(pcd, index, out++, &out_used, endpoint_map, bmAttributes, + 0x40, 0x40, fs_transferSize, hs_transferSize, bInterval)); + //CONTINUE_IF(!pcd_assign_endpoint(pcd, index, in++, &in_used, endpoint_map, bmAttributes, + // 0x40, fs_transferSize, hs_transferSize, bInterval)); + THROW(error); + + case USB_ENDPOINT_CONTROL: + case USB_DIR_IN | USB_ENDPOINT_ISOCHRONOUS: + case USB_DIR_OUT | USB_ENDPOINT_ISOCHRONOUS: + THROW(error); + } + THROW(error); + CATCH(error) { + TRACE_MSG4(pcd->TAG, "FAILED num: %d bmAttributes: %02x transferSize: %04x:%04x", + i, bmAttributes, fs_transferSize, hs_transferSize); + + //printk(KERN_ERR"%s: FAILED num: %d bmAttributes: %02x transferSize: %02x\n", + // __FUNCTION__, i, bmAttributes, transferSize); + return -EINVAL; + } + } + return 0; +} + +/* ******************************************************************************************* */ +/*! bus_request_endpoints - + * @param bus + * @param endpoint_map_array + * @param endpointsRequested + * @param requestedEndpoints + * @return 0 on finish, or -EINVAL + */ +int bus_request_endpoints(struct usbd_bus_instance *bus, struct usbd_endpoint_map *endpoint_map_array, int endpointsRequested, + struct usbd_endpoint_request *requestedEndpoints) +{ + struct pcd_instance *pcd = (struct pcd_instance *)bus->privdata; + int i; + TRACE_MSG0(pcd->TAG, "BUS_REQUEST ENDPOINTS:"); + if (usbd_pcd_ops.request_endpoints(pcd, endpoint_map_array, endpointsRequested, requestedEndpoints)) { + printk(KERN_INFO"%s: failed\n", __FUNCTION__); + return -EINVAL; + } + for (i = 0; i < endpointsRequested; i++) { + struct usbd_endpoint_map *endpoint_map = endpoint_map_array + i; + //TRACE_MSG8(pcd->TAG, "address: %02x:%02x physical: %02x:%02x request: %02x:%02x size: %04x:%04x", + // endpoint_map->bEndpointAddress[0], endpoint_map->bEndpointAddress[1], + // endpoint_map->physicalEndpoint[0], endpoint_map->physicalEndpoint[1], + // endpoint_map->bmAttributes[0], endpoint_map->bmAttributes[1], + // endpoint_map->wMaxPacketSize[0], endpoint_map->wMaxPacketSize[1] + // ); + TRACE_MSG8(pcd->TAG, "REQUEST[%02d:%02d] endpoint_map: %x address: %04x physical: %04x request: %04x size: %04x:%04x", + i, endpointsRequested, + endpoint_map, + endpoint_map->bEndpointAddress[0] << 8 | endpoint_map->bEndpointAddress[1], + endpoint_map->physicalEndpoint[0] << 8 | endpoint_map->physicalEndpoint[1], + endpoint_map->bmAttributes[0] << 8 | endpoint_map->bmAttributes[1], + endpoint_map->wMaxPacketSize[0], + endpoint_map->wMaxPacketSize[1] + ); + } + TRACE_MSG0(pcd->TAG, "BUS_REQUEST ENDPOINTS: finished"); + return 0; +} + +/*! bus_set_endpoints - initialize pcd instance endpoints array + * @param bus - usbd_bus_instance pointer + * @param endpointsRequested - number of requested endpoints + * @param endpoint_map_array - usbd_endpoint_map pointer + */ +int bus_set_endpoints(struct usbd_bus_instance *bus, int endpointsRequested, struct usbd_endpoint_map *endpoint_map_array) +{ + struct pcd_instance *pcd = (struct pcd_instance *)bus->privdata; + TRACE_MSG5(pcd->TAG, "SET ENDPOINTS bus: %p endpointsRequested: %d endpoint_map_array: %p pcd: %x pcd->otg: %x", + bus, endpointsRequested, endpoint_map_array, pcd, pcd ? pcd->otg : NULL); + // XXX do we need to set bEndpointAddress + return usbd_pcd_ops.set_endpoints ? usbd_pcd_ops.set_endpoints(pcd, endpointsRequested, endpoint_map_array) : 0; +} + +/*! bus_set_configuration - + * @param bus - bus instance pointer + * @param config + */ +int bus_set_configuration(struct usbd_bus_instance *bus, int config) +{ + pcd_bus_event_handler_irq (bus, config? DEVICE_CONFIGURED : DEVICE_DE_CONFIGURED, 0); + return 0; +} + +/*! bus_set_address - called to set device address + * @param bus - bus instance pointer + * @param address - address to set + */ +int bus_set_address(struct usbd_bus_instance *bus, int address) +{ + struct pcd_instance *pcd = (struct pcd_instance *)bus->privdata; + pcd->new_address = address; + pcd_bus_event_handler_irq (bus, DEVICE_ADDRESS_ASSIGNED, address); + if (usbd_pcd_ops.set_address) usbd_pcd_ops.set_address (pcd, address); + return 0; +} + +/*! bus_set_speed - called to set bus high speed capability + * @param bus - bus instance pointer + * @param hs - high speed capability + */ +void bus_set_speed(struct usbd_bus_instance *bus, int hs) +{ + bus->high_speed = hs; +} + +/* ******************************************************************************************* */ + +/*! pcd_search_endpoint_index - find endpoint map index given endpoint address + * @param bus - usbd_bus_instance pointer + * @param bEndpointAddress - endbpoint address + * @return found endpoint index + */ +int pcd_search_endpoint_index(struct usbd_bus_instance *bus, int bEndpointAddress) +{ + int i; + for (i = 0; i < bus->endpoints; i++) + BREAK_IF (bus->endpoint_array[i].bEndpointAddress[bus->high_speed] == bEndpointAddress); + return i; +} + +/*! pcd_search_endpoint - find endpoint given endpoint address + * @param bus - bus instance pointer + * @param bEndpointAddress - endpoint address + * @return found endpoint instance pointer + */ +struct usbd_endpoint_instance * pcd_search_endpoint(struct usbd_bus_instance *bus, int bEndpointAddress) +{ + int i = pcd_search_endpoint_index(bus, bEndpointAddress); + //TRACE_MSG2(pcd->TAG, "BUS_SEARCH ENDPOINT: addr: %02x index: %d", bEndpointAddress, i); + RETURN_NULL_UNLESS(i < bus->endpoints); + return &(bus->endpoint_array[i]); +} + +/*! bus_endpoint_halted - check if endpoint halted + * Used by the USB Device Core to check endpoint halt status. + * + * Return actual halted status if available or'd with endpoint->feature_setting set value. + * Assume not halted if udc driver does not provide an endpoint halted function. + * @param bus - bus instance pointer + * @param bEndpointAddress - endpoint to check halt status + * @return check result + */ +int bus_endpoint_halted (struct usbd_bus_instance *bus, int bEndpointAddress) +{ + struct pcd_instance *pcd = (struct pcd_instance *)bus->privdata; + int endpoint_index = pcd_search_endpoint_index(bus, bEndpointAddress); + struct usbd_endpoint_instance *endpoint = pcd_search_endpoint(bus, bEndpointAddress); + int halted; + + TRACE_MSG1(pcd->TAG, "BUS_ENDPOINT HALTED: endpoint: %p", (int) endpoint); + RETURN_EINVAL_UNLESS(endpoint); + + /* return true IFF endpoint was previously halted by host OR if hardware + * has been stalled. + */ + + halted = usbd_pcd_ops.endpoint_halted ? usbd_pcd_ops.endpoint_halted(pcd, endpoint) : 0; + TRACE_MSG2(pcd->TAG, "BUS_ENDPOINT STALLED %d FEATURE: %08x", + halted, bus->endpoint_array[endpoint_index].feature_setting); + + return halted || (endpoint->feature_setting & FEATURE(USB_ENDPOINT_HALT)? 1 : 0); +} + +/*! bus_halt_endpoint - handle set/clear feature requests + * Used by the USB Device Core to set/clear endpoint halt status. + * + * We assume that if the udc driver does not implement anything then + * we should just return zero for ok. + * + * XXX should do endpoint instance as arg + * @param bus - bus instance pointer + * @param bEndpointAddress - endpoint address + * @param flag - set/clear flag + * + */ +int bus_halt_endpoint (struct usbd_bus_instance *bus, int bEndpointAddress, int flag) +{ + struct pcd_instance *pcd = (struct pcd_instance *)bus->privdata; + //int endpoint_index = pcd_search_endpoint_index(bus, bEndpointAddress); + struct usbd_endpoint_instance *endpoint = pcd_search_endpoint(bus, bEndpointAddress); + + TRACE_MSG3(pcd->TAG, "BUS_ENDPOINT HALT: endpoint: %p bEndpointAddress: %02x flag: %d", + endpoint, bEndpointAddress, flag); + + RETURN_EINVAL_UNLESS(endpoint); + + endpoint->feature_setting = flag ? + (endpoint->feature_setting | FEATURE(USB_ENDPOINT_HALT)) : + (endpoint->feature_setting & ~FEATURE(USB_ENDPOINT_HALT)); + + if (flag) + usbd_flush_endpoint(endpoint); + + return usbd_pcd_ops.halt_endpoint ? usbd_pcd_ops.halt_endpoint(pcd, endpoint, flag) : 0; +} + +/*! bus_device_feature - handle set/clear feature requests + * Used by the USB Device Core to set/clear device feature requests + * + * We assume that if the udc driver does not implement anything then + * we should just return -EINVAL. + * + * @param bus - bus instance pointer + * @param selector - selector + * @param flag - set/clear flag + * + */ +int bus_device_feature (struct usbd_bus_instance *bus, int selector, int flag) +{ + struct pcd_instance *pcd = (struct pcd_instance *)bus->privdata; + + TRACE_MSG2(pcd->TAG, "BUS_DEVICE_FEATURE selector: %d flag: %d", selector, flag); + + return usbd_pcd_ops.device_feature ? usbd_pcd_ops.device_feature(pcd, selector, flag) : -EINVAL; +} + +/*! pcd_disable_endpoints - disable udc and all endpoints + * @param bus - bus instance pointer + */ +static void pcd_disable_endpoints (struct usbd_bus_instance *bus) +{ + struct pcd_instance *pcd = (struct pcd_instance *)bus->privdata; + int epn; + TRACE_MSG0(pcd->TAG, "--"); + RETURN_IF (!bus || !bus->endpoint_array); + for (epn = 0; epn < usbd_pcd_ops.max_endpoints; epn++) { + struct usbd_endpoint_instance *endpoint = (bus->endpoint_array + epn); + TRACE_MSG2(pcd->TAG, "epn: %d endpoint: %p", epn, endpoint); + CONTINUE_IF (!endpoint); + usbd_flush_endpoint (endpoint); + } +} + + +/*! bus_event_handler - handle generic bus event + * Called by usb core layer to inform bus of an event. + * @param bus - bus instance pointer + * @param event - usbd_device_event + * @param data - + */ +int bus_event_handler (struct usbd_bus_instance *bus, usbd_device_event_t event, int data) +{ + struct pcd_instance *pcd = (struct pcd_instance *)bus->privdata; + int epn; + //struct usbd_endpoint_map *endpoint_map_array = bus->function_instance->endpoint_map_array; + int hs = bus->high_speed; + + TRACE_MSG1(pcd->TAG, "BUS_EVENT %d", event); + switch (event) { + case DEVICE_UNKNOWN: + break; + + case DEVICE_INIT: + TRACE_MSG0(pcd->TAG, "EVENT INIT"); + break; + + case DEVICE_CREATE: + TRACE_MSG0(pcd->TAG, "EVENT CREATE"); + pcd_disable_endpoints (bus); + if (usbd_pcd_ops.start) usbd_pcd_ops.start (pcd); + break; + + case DEVICE_HUB_CONFIGURED: + TRACE_MSG0(pcd->TAG, "EVENT HUB_CONFIGURED"); + //bi_connect (bus, NULL); + break; + + case DEVICE_RESET: + TRACE_MSG0(pcd->TAG, "EVENT RESET"); + + if (usbd_pcd_ops.set_address) usbd_pcd_ops.set_address (pcd, 0); + if (usbd_pcd_ops.reset_ep) usbd_pcd_ops.reset_ep (pcd, 0); + + pcd_disable_endpoints (bus); + otg_queue_event(pcd->otg, (otg_current_t)(BUS_RESET | BUS_SUSPENDED_), pcd->TAG, "DEVICE_RESET"); + + break; + + case DEVICE_ADDRESS_ASSIGNED: + TRACE_MSG1(pcd->TAG, "EVENT ADDRESSED: %d", data); + otg_queue_event(pcd->otg, ADDRESSED, pcd->TAG, "ADDRESSED"); + + break; + + case DEVICE_CONFIGURED: + TRACE_MSG0(pcd->TAG, "EVENT CONFIGURED"); + otg_queue_event(pcd->otg, CONFIGURED, pcd->TAG, "CONFIGURED"); + // iterate across the physical endpoint instance array to enable the endpoints + if (usbd_pcd_ops.setup_ep) + for (epn = 1; epn < bus->endpoints; epn++) + if (bus->endpoint_array[epn].physicalEndpoint[hs]) + usbd_pcd_ops.setup_ep (pcd, epn, bus->endpoint_array + epn); + break; + + case DEVICE_DE_CONFIGURED: + TRACE_MSG0(pcd->TAG, "EVENT DE-CONFIGURED"); + otg_queue_event(pcd->otg, CONFIGURED_, pcd->TAG, "DE-CONFIGURED"); + break; + + case DEVICE_SET_INTERFACE: + TRACE_MSG0(pcd->TAG, "EVENT SET INTERFACE"); + break; + + case DEVICE_SET_FEATURE: + TRACE_MSG0(pcd->TAG, "EVENT SET FEATURE"); + break; + + case DEVICE_CLEAR_FEATURE: + TRACE_MSG0(pcd->TAG, "EVENT CLEAR FEATURE"); + break; + + case DEVICE_BUS_INACTIVE: + TRACE_MSG0(pcd->TAG, "EVENT INACTIVE"); + TRACE_MSG1(pcd->TAG, "EVENT INACTIVE: state: %x", bus->device_state); + otg_queue_event(pcd->otg, BUS_SUSPENDED, pcd->TAG, "DEVICE_BUS_INACTIVE"); + break; + + case DEVICE_BUS_ACTIVITY: + TRACE_MSG0(pcd->TAG, "EVENT ACTIVITY"); + otg_queue_event(pcd->otg, BUS_SUSPENDED_, pcd->TAG, "DEVICE_BUS_ACTIVITY"); + break; + + case DEVICE_POWER_INTERRUPTION: + TRACE_MSG0(pcd->TAG, "POWER INTERRUPTION"); + break; + + case DEVICE_HUB_RESET: + TRACE_MSG0(pcd->TAG, "HUB RESET"); + break; + + case DEVICE_DESTROY: + //printk(KERN_INFO"%s: DESTROY\n", __FUNCTION__); + TRACE_MSG0(pcd->TAG, "DEVICE DESTROY"); + //bi_disconnect (bus, NULL); + pcd_disable_endpoints (bus); + if (usbd_pcd_ops.stop) usbd_pcd_ops.stop (pcd); + break; + + case DEVICE_CLOSE: + TRACE_MSG0(pcd->TAG, "DEVICE CLOSE"); + break; + default: + TRACE_MSG1(pcd->TAG, "DEVICE UNKNOWN: %d", event); + break; + } + TRACE_MSG1(pcd->TAG, "BUS_EVENT %d finished", event); + //TRACE_MSG2(pcd->TAG, "EVENT bNumInterfaces: %x alternates: %x", bus->bNumInterfaces, bus->alternates); + return 0; +} + + +/*! bus_start_endpoint_in - called to send urb + * @param bus - bus instance pointer + * @param endpoint - endpoint pointer + * @return 0 on finish + */ +int bus_start_endpoint_in (struct usbd_bus_instance *bus, struct usbd_endpoint_instance *endpoint) +{ + struct pcd_instance *pcd = (struct pcd_instance *)bus->privdata; + + RETURN_ZERO_IF (!endpoint); + pcd->otg->interrupts++; + + /* call pcd_start_endpoint_in IFF we didn't previously have a tx urb */ + otg_pthread_mutex_lock(&pcd->mutex); + if (!endpoint->tx_urb && pcd_tx_next_irq (endpoint)) + usbd_pcd_ops.start_endpoint_in (pcd, endpoint); + otg_pthread_mutex_unlock(&pcd->mutex); + return 0; +} + +/*! bus_start_endpoint_out -called to receive urb + * @param bus - bus instance pointer + * @param endpoint - endpoint instance pointer + * @return 0 on finish + */ +int bus_start_endpoint_out (struct usbd_bus_instance *bus, struct usbd_endpoint_instance *endpoint) +{ + struct pcd_instance *pcd = (struct pcd_instance *)bus->privdata; + RETURN_ZERO_IF (!endpoint); + pcd->otg->interrupts++; + + /* call pcd_start_endpoint_OUT IFF we didn't previously have a rcv urb */ + otg_pthread_mutex_lock(&pcd->mutex); + if (!endpoint->rcv_urb && pcd_rcv_next_irq (endpoint)) + usbd_pcd_ops.start_endpoint_out (pcd, endpoint); + otg_pthread_mutex_unlock(&pcd->mutex); + return 0; +} + +/*! bus_cancel_urb_irq - cancel sending an urb + * Used by the USB Device Core to cancel an urb. + * @param urb - urb to cancel sending operation + */ +int bus_cancel_urb_irq (struct usbd_urb *urb) +{ + struct usbd_bus_instance *bus = urb->bus; + struct pcd_instance *pcd = (struct pcd_instance *)bus->privdata; + struct usbd_endpoint_instance *endpoint; + RETURN_EINVAL_IF (!urb); + TRACE_MSG1(pcd->TAG, "BUS_CANCEL URB: %x", (int)urb); + endpoint = urb->endpoint; + switch (urb->endpoint->bEndpointAddress[urb->bus->high_speed] & USB_ENDPOINT_DIR_MASK) { + case USB_DIR_IN: + // is this the active urb? + if (urb->endpoint->tx_urb == urb) { + urb->endpoint->tx_urb = NULL; + //TRACE_MSG1(pcd->TAG, "CANCEL IN URB: %02x", urb->endpoint->bEndpointAddress[urb->bus->high_speed]); + if (usbd_pcd_ops.cancel_in_irq) usbd_pcd_ops.cancel_in_irq (pcd, urb); + } + pcd_urb_finished_irq (urb, USBD_URB_CANCELLED); + break; + + case USB_DIR_OUT: + // is this the active urb? + if (urb->endpoint->rcv_urb == urb) { + urb->endpoint->rcv_urb = NULL; + //TRACE_MSG1(pcd->TAG, "CANCEL OUT URB: %02x", urb->endpoint->bEndpointAddress[urb->bus->high_speed]); + if (usbd_pcd_ops.cancel_out_irq) usbd_pcd_ops.cancel_out_irq (pcd, urb); + } + //TRACE_MSG0(pcd->TAG, "CANCEL RECV URB"); + pcd_urb_finished_irq (urb, USBD_URB_CANCELLED); + break; + } + endpoint->last = endpoint->sent = 0; + return 0; +} + +/*! pcd_rcv_finished_irq-- + * @param endpoint - endpoint instance pointer + * @param len - + * @param urb_bad + */ +struct usbd_urb * pcd_rcv_finished_irq (struct usbd_endpoint_instance *endpoint, int len, int urb_bad) +{ + struct usbd_urb *rcv_urb; + + // if we had an urb then update actual_length, dispatch if neccessary + if (otg_likely ( (int) (rcv_urb = endpoint->rcv_urb))) { + struct usbd_bus_instance *bus = rcv_urb->bus; + struct pcd_instance *pcd; + pcd = (struct pcd_instance *)bus->privdata; + + //TRACE_MSG5(pcd->TAG, "BUS_RCV COMPLETE: finished buffer: %x actual: %d len: %d bad: %d: status: %d", + // rcv_urb->buffer, rcv_urb->actual_length, len, urb_bad, rcv_urb->status); + + if (!urb_bad && !endpoint->rcv_error && (rcv_urb->bus->status == USBD_OK)) { + +#if 0 + int i; + u8 *cp = rcv_urb->buffer; + for (i = 0; i < rcv_urb->actual_length + len; i+= 8) { + + TRACE_MSG8(pcd->TAG, "RECV %02x %02x %02x %02x %02x %02x %02x %02x", + cp[i + 0], cp[i + 1], cp[i + 2], cp[i + 3], + cp[i + 4], cp[i + 5], cp[i + 6], cp[i + 7] + ); + } +#endif + // increment the received data size + rcv_urb->actual_length += len; + + if (rcv_urb->actual_length && !len) + rcv_urb->flags |= USBD_URB_SENDZLP; + + endpoint->rcv_urb = NULL; + rcv_urb->framenum = pcd_ops.framenum ? pcd_ops.framenum (bus->otg) : 0; + TRACE_MSG2(pcd->TAG, "BUS_RCV COMPLETE: urb: %p framenum: %x", rcv_urb, (int) rcv_urb->framenum); + pcd_urb_finished_irq (rcv_urb, USBD_URB_OK); + rcv_urb = NULL; + } + else { + rcv_urb->actual_length = 0; + //endpoint->rcv_error = 1; + } + } + + // if we don't have an urb see if we can get one + return pcd_rcv_next_irq (endpoint); +} + +/*! pcd_rcv_complete_irq + */ +struct usbd_urb * pcd_rcv_complete_irq (struct usbd_endpoint_instance *endpoint, int len, int urb_bad) +{ + struct usbd_urb *rcv_urb; + + /* if we had an urb then update actual_length, dispatch if neccessary */ + if (otg_likely ( (int) (rcv_urb = endpoint->rcv_urb))) { + struct usbd_bus_instance *bus = rcv_urb->bus; + struct pcd_instance *pcd = (struct pcd_instance *)bus->privdata; + + #if 0 + TRACE_MSG5(pcd->TAG, "BUS_RCV COMPLETE: buffer: %d actual: %d len: %d bad: %d: status: %d", + rcv_urb->buffer_length, rcv_urb->actual_length, len, urb_bad, rcv_urb->status); + #endif + + if (TRACE_VERBOSE) + TRACE_MSG7(pcd->TAG, "[%2x] alloc: %d buffer: %d actual: %d packet: %d transfer: %d len: %d", + endpoint->bEndpointAddress, + rcv_urb->alloc_length, rcv_urb->buffer_length, rcv_urb->actual_length, + endpoint->wMaxPacketSize[bus->high_speed], endpoint->rcv_transferSize, len); + + + // check the urb is ok, are we adding data less than the packetsize + if (!urb_bad && !endpoint->rcv_error && (rcv_urb->bus->status == USBD_OK) + /*&& (len <= endpoint->wMaxPacketSize)*/) + { + + /* increment the received data size */ + rcv_urb->actual_length += len; + + /* if the current received data is short (less than full packetsize) which + * indicates the end of the bulk transfer, we have received the maximum + * transfersize, or if we do not have enough room to receive another packet + * then pass this data up to the function driver + */ + + if ( + ( + ((rcv_urb->actual_length % endpoint->wMaxPacketSize[bus->high_speed])) || + (rcv_urb->actual_length >= rcv_urb->buffer_length) || + (rcv_urb->actual_length + endpoint->wMaxPacketSize[bus->high_speed] > + rcv_urb->alloc_length))) + { + //TRACE_MSG2(pcd->TAG, "BUS_RCV COMPLETE: finished length: %d framenum: %x", + // rcv_urb->actual_length, (int) rcv_urb->framenum); + + /* N.B. we don't reset endpoint->rcv_urb until finish urb is run + */ + rcv_urb->framenum = pcd_ops.framenum ? pcd_ops.framenum (bus->otg) : 0; + pcd_urb_finished_irq (rcv_urb, USBD_URB_OK); + endpoint->rcv_urb = rcv_urb = NULL; + endpoint->last = 0; + } + } + else { + rcv_urb->actual_length = 0; + } + } + + /* if we don't have an urb see if we can get one */ + return pcd_rcv_next_irq (endpoint); +} + +/*! pcd_tx_complete_irq + */ +struct usbd_urb * pcd_tx_complete_irq (struct usbd_endpoint_instance *endpoint, int restart) +{ + struct usbd_urb *tx_urb; + + //TRACE_MSG2(pcd->TAG, "tx_urb: %x restart: %d", endpoint->tx_urb, restart); + + // if we have a tx_urb advance or reset, finish if complete + + + if ( (tx_urb = endpoint->tx_urb)) { + + struct usbd_bus_instance *bus = tx_urb->bus; + struct pcd_instance *pcd = (struct pcd_instance *)bus->privdata; + + //printk(KERN_INFO"%s: tx_urb: %x endpoint->tx_urb: %x\n", __FUNCTION__, tx_urb, endpoint->tx_urb); + + //printk(KERN_INFO"%s: finishing tx_urb\n", __FUNCTION__); + + if (TRACE_VERBOSE) + TRACE_MSG6(pcd->TAG, "[%2x] BUS_TX CURRENT TX_URB: %p actual: %d sent: %d last: %d: flags: %x", + endpoint->bEndpointAddress, + (int)endpoint->tx_urb, tx_urb->actual_length, endpoint->sent, + endpoint->last, tx_urb->flags); + + if (otg_likely (!restart)) { + endpoint->sent += endpoint->last; + } + else { + TRACE_MSG1(pcd->TAG, "[%2x] RESTARTING", endpoint->bEndpointAddress); + } + endpoint->last = 0; + + if ( (endpoint->sent >= tx_urb->actual_length) && ! (tx_urb->flags & USBD_URB_SENDZLP) ) { + endpoint->tx_urb = NULL; + endpoint->last = endpoint->sent = 0; + tx_urb->framenum = pcd_ops.framenum ? pcd_ops.framenum (bus->otg) : 0; + if (TRACE_VERBOSE) + TRACE_MSG3(pcd->TAG, "[%2x] BUS_TX COMPLETE: finished tx_urb: %p framenum: %x", + endpoint->bEndpointAddress, + (int)tx_urb, (int)tx_urb->framenum); + pcd_urb_finished_irq (tx_urb, USBD_URB_OK); + } + } + return pcd_tx_next_irq (endpoint); +} + +/* ******************************************************************************************* */ + +/*! pcd_disable - Stop using the USB Device Controller + * Stop using the USB Device Controller. + * Shutdown and free dma channels, de-register the interrupt handler. + */ +void pcd_disable (struct usbd_bus_instance *bus) +{ + struct pcd_instance *pcd = (struct pcd_instance *)bus->privdata; + int hs = bus->high_speed; + int epn; + + TRACE_MSG0(pcd->TAG, "BUS_UDC DISABLE"); + if (usbd_pcd_ops.disable_ep) usbd_pcd_ops.disable_ep (pcd, 0, bus->endpoint_array); + + pcd_disable_endpoints (bus); + + if (usbd_pcd_ops.disable_ep) + for (epn = 1; epn < bus->endpoints; epn++) + if (bus->endpoint_array[epn].physicalEndpoint[hs]) + usbd_pcd_ops.disable_ep (pcd, epn, bus->endpoint_array + epn); + + if (usbd_pcd_ops.disable) usbd_pcd_ops.disable (pcd); +} + +/* ************************************************************************************* */ +/*! bus_framenum + */ +int bus_framenum (struct usbd_bus_instance *bus) +{ + //struct pcd_instance *pcd = (struct pcd_instance *)bus->privdata; + return (pcd_ops.framenum) ? pcd_ops.framenum (bus->otg) : 0; +} + +#if 0 +/*! bus_interrupts + */ +u64 bus_interrupts (void) +{ + return pcd_instance->otg->interrupts; +} +#endif + +/*! pcd_ticks + */ +otg_tick_t pcd_ticks (void) +{ + return usbd_pcd_ops.ticks ? usbd_pcd_ops.ticks (NULL) : 0; +} + +/*! pcd_elapsed + */ +otg_tick_t pcd_elapsed (otg_tick_t *t1, otg_tick_t *t2) +{ + return usbd_pcd_ops.elapsed ? (usbd_pcd_ops.elapsed (t1, t2)) : 0; +} + +/*! pcd_recv_setup_emulate_irq - emulate a device request + */ +int pcd_recv_setup_emulate_irq(struct usbd_bus_instance *bus, u8 bmRequestType, u8 bRequest, u16 wValue, u16 wIndex, u16 wLength) +{ + struct pcd_instance *pcd = (struct pcd_instance *)bus->privdata; + struct usbd_device_request request; + + //printk(KERN_INFO"bmRequestType: %02x bRequest: %02x wValue: %04x wIndex: %04x\n", + // bmRequestType, bRequest, wValue, wIndex); + TRACE_MSG4(pcd->TAG, "bmRequestType: %02x bRequest: %02x wValue: %04x wIndex: %04x", + bmRequestType, bRequest, wValue, wIndex); + request.bmRequestType = bmRequestType; + request.bRequest = bRequest; + request.wValue = cpu_to_le16(wValue); + request.wIndex = cpu_to_le16(wIndex); + request.wLength = cpu_to_le16(wLength); + return pcd_recv_setup_irq(pcd, &request); +} + +/*! pcd_check_device_feature - verify that feature is set or clear + * Check current feature setting and emulate SETUP Request to set or clear + * if required. + * @param bus - + * @param feature - + * @param flag + */ +void pcd_check_device_feature(struct usbd_bus_instance *bus, int feature, int flag) +{ + int status = bus->device_feature_settings & (1 << feature); + //TRACE_MSG2(pcd->TAG, "BUS_CHECK FEATURE: feature: %x flag: %x", feature, flag); + if (!status && flag) + pcd_recv_setup_emulate_irq(bus, USB_REQ_HOST2DEVICE, USB_REQ_SET_FEATURE, feature, 0, 0); + else if (status && !flag) + pcd_recv_setup_emulate_irq(bus, USB_REQ_HOST2DEVICE, USB_REQ_CLEAR_FEATURE, feature, 0, 0); + //TRACE_MSG1(pcd->TAG, "BUS_CHECK FEATURE: features; %08x", bus->device_feature_settings); +} + +/* ******************************************************************************************* */ +#if defined(CONFIG_OTG_NOC99) +struct usbd_bus_operations bus_ops; +/*! + * pcd_global_init - initialize pcd related bus operations + */ +void pcd_global_init(void) +{ + ZERO(bus_ops); + bus_ops.start_endpoint_in = bus_start_endpoint_in; + bus_ops.start_endpoint_out = bus_start_endpoint_out; + bus_ops.cancel_urb_irq = bus_cancel_urb_irq; + bus_ops.endpoint_halted = bus_endpoint_halted; + bus_ops.halt_endpoint = bus_halt_endpoint; + bus_ops.device_feature = bus_device_feature; + bus_ops.set_configuration = bus_set_configuration; + bus_ops.set_address = bus_set_address; + bus_ops.event_handler = bus_event_handler; + bus_ops.request_endpoints = bus_request_endpoints; + bus_ops.set_endpoints = bus_set_endpoints; + bus_ops.framenum = bus_framenum; + //bus_ops.ticks = bus_ticks; + //bus_ops.elapsed = bus_elapsed; + //bus_ops.interrupts = bus_interrupts; + //bus_ops.device_feature = bus_device_feature; +} + +#else /* defined(CONFIG_OTG_NOC99) */ +struct usbd_bus_operations bus_ops = { + .start_endpoint_in = bus_start_endpoint_in, + .start_endpoint_out = bus_start_endpoint_out, + .cancel_urb_irq = bus_cancel_urb_irq, + .endpoint_halted = bus_endpoint_halted, + .halt_endpoint = bus_halt_endpoint, + .device_feature = bus_device_feature, + .set_configuration = bus_set_configuration, + .set_address = bus_set_address, + .event_handler = bus_event_handler, + .request_endpoints = bus_request_endpoints, + .set_endpoints = bus_set_endpoints, + .framenum = bus_framenum, + //.ticks = bus_ticks, + //.elapsed = bus_elapsed, + //.interrupts = bus_interrupts, + //.device_feature = bus_device_feature, +}; +#endif /* defined(CONFIG_OTG_NOC99) */ + + +struct usbd_bus_driver bus_driver = { +bops: &bus_ops, +}; + + +/* ******************************************************************************************* */ + +/*! + * Bus Event Handling: + * pcd_bus_event_handler() + * + */ +static usbd_device_state_t event_states[DEVICE_CLOSE] = { + STATE_UNKNOWN, STATE_INIT, STATE_ATTACHED, STATE_POWERED, + STATE_DEFAULT, STATE_ADDRESSED, STATE_CONFIGURED, STATE_UNKNOWN, + STATE_UNKNOWN, STATE_UNKNOWN, STATE_ADDRESSED, STATE_SUSPENDED, + STATE_UNKNOWN, STATE_POWERED, STATE_ATTACHED, +}; + +static usbd_device_status_t event_status[DEVICE_CLOSE+1] = { + USBD_UNKNOWN, // DEVICE_UNKNOWN + USBD_OPENING, // DEVICE_INIT + USBD_OPENING, // DEVICE_CREATE + USBD_OPENING, // DEVICE_HUB_CONFIGURED + USBD_RESETING, // DEVICE_RESET + USBD_OK, // DEVICE_ADDRESS_ASSIGNED + USBD_OK, // DEVICE_CONFIGURED + USBD_OK, // DEVICE_SET_INTERFACE + USBD_OK, // DEVICE_SET_FEATURE + USBD_OK, // DEVICE_CLEAR_FEATURE + USBD_OK, // DEVICE_DE_CONFIGURED + USBD_SUSPENDED, // DEVICE_BUS_INACTIVE + USBD_OK, // DEVICE_BUS_ACTIVITY + USBD_RESETING, // DEVICE_POWER_INTERRUPTION + USBD_RESETING, // DEVICE_HUB_RESET + USBD_CLOSING, // DEVICE_DESTROY + USBD_CLOSED, // DEVICE_CLOSE +}; + +int usbd_device_reset(struct usbd_bus_instance *bus); +int usbd_device_suspended(struct usbd_bus_instance *bus); +int usbd_device_resumed(struct usbd_bus_instance *bus); + + +/*! + * @brief pcd_bus_event_handler_irq() - called to respond to various usb events + * + * This is called from an interrupt context. + * + * Used by a Bus driver to indicate an event. + * @param bus + * @param event + * @param data + */ +void pcd_bus_event_handler_irq (struct usbd_bus_instance *bus, usbd_device_event_t event, int data) +{ + usbd_device_state_t last_state; + struct pcd_instance *pcd = (struct pcd_instance *)(bus ? bus->privdata : NULL); + usbd_device_event_t real_event = event; + + RETURN_UNLESS(bus); + + event = (event == DEVICE_ADDRESS_ASSIGNED) && !data ? DEVICE_RESET : event; + + last_state = bus->device_state; + + if (TRACE_VERBOSE) + switch (event) { + case DEVICE_BUS_INACTIVE: + TRACE_MSG0(pcd->TAG, "BUS INACTIVE"); + break; + default: + TRACE_MSG0(pcd->TAG, "default"); + break; + case DEVICE_UNKNOWN: + TRACE_MSG0(pcd->TAG, "UNKNOWN"); + break; + case DEVICE_BUS_ACTIVITY: + TRACE_MSG0(pcd->TAG, "BUS ACTIVITY"); + break; + case DEVICE_CONFIGURED: + TRACE_MSG0(pcd->TAG, "CONFIGURED"); + break; + case DEVICE_SET_INTERFACE: + TRACE_MSG0(pcd->TAG, "SET INTEERFACE"); + break; + case DEVICE_SET_FEATURE: + case DEVICE_CLEAR_FEATURE: + TRACE_MSG0(pcd->TAG, "SET/CLEAR FEATURE"); + break; + } + /* get the next bus device state by table lookup of event, we need to save the + * current state IFF we are suspending so that it can be restored on resume. + */ + switch (event) { + case DEVICE_BUS_INACTIVE: + bus->suspended_state = bus->device_state; + /* FALL THROUGH */ + default: + bus->device_state = event_states[event]; + break; + case DEVICE_UNKNOWN: + break; + case DEVICE_BUS_ACTIVITY: + bus->device_state = bus->suspended_state; + break; + + case DEVICE_SET_INTERFACE: + case DEVICE_SET_FEATURE: + case DEVICE_CLEAR_FEATURE: + break; + } + + /* set the bus status by table lookup of event. + */ + switch (event) { + case DEVICE_BUS_ACTIVITY: + case DEVICE_BUS_INACTIVE: + BREAK_IF(USBD_CLOSING != bus->status); + /* FALL THROUGH */ + default: + bus->status = event_status[event]; + //TRACE_MSG1(pcd->TAG, "bus->status: %x", bus->status); + //TRACE_MSG0(PCD, "DEFAULT"); + break; + } + TRACE_MSG5(pcd->TAG, "DEVICE_STATE event: %d data: %d state: %d -> %d status: %d", + event, data, last_state, bus->device_state, bus->status); + + /* we need to reset things on some transitions + */ + switch (bus->device_state) { + default: + // if we lost configuration then get rid of alternate settings + #if 0 + if (bus->alternates) { + LKFREE(bus->alternates); + bus->alternates = NULL; + } + bus->bNumInterfaces = 0; + bus->device_feature_settings = 0; + bus->ConfigurationValue = 0; + #endif + case STATE_CONFIGURED: + break; + case STATE_SUSPENDED: + // XXX Checkme - is suspend equivalent to end of session? + // C.f. OTG 6.5.1-6.5.3 imply that these must be reset at the end of a session or reset + //bus->device_feature_settings &= + // ~(FEATURE(USB_OTG_A_HNP_ENABLE) | FEATURE(USB_OTG_B_HNP_ENABLE) | FEATURE(USB_OTG_A_ALT_HNP_ENABLE)); + break; + } + /* Pass the event to the bus interface driver and then the function driver and + * any interface function drivers. + */ + bus->driver->bops->event_handler (bus, real_event, data); + + RETURN_UNLESS (bus->function_instance); + + // XXX if (bus->function_instance->function_driver->fops->event_handler) + // XXX bus->function_instance->function_driver->fops->event_handler(bus->function_instance, event, data); + + switch (event) { + case DEVICE_BUS_ACTIVITY: + TRACE_MSG0(pcd->TAG, "schedule RESUME_BH"); + otg_workitem_start(bus->resume_bh); + break; + case DEVICE_BUS_INACTIVE: + TRACE_MSG0(pcd->TAG, "schedule SUSPEND_BH"); + otg_workitem_start(bus->suspend_bh); + break; + case DEVICE_RESET: + TRACE_MSG0(pcd->TAG, "schedule RESET_BH"); + otg_workitem_start(bus->reset_bh); + break; + default: + break; + } +} +/* ******************************************************************************************* */ +/* Prevent overlapp of bi administrative functions mainly: + * pcd_bus_register + * pcd_bus_deregister + */ +//DECLARE_MUTEX (usbd_bi_sem); +struct usbd_bus_instance *usbd_bus; + + +/*! + * @brief pcd_device_bh() - Bottom half handler to process sent or received urbs. + * @param data - pcd_instance pointer + */ +void *pcd_device_bh (otg_task_arg_t data) +{ + struct pcd_instance *pcd = (struct pcd_instance *) data; + struct usbd_bus_instance *bus = pcd->bus; + struct usbd_endpoint_instance *endpoint; + struct usbd_urb *urb; + int i; + RETURN_NULL_IF (!bus || !(endpoint = bus->endpoint_array)); + //otg_led(LED2, TRUE); + for (i = 0; i < bus->endpoints; i++) { + struct usbd_endpoint_instance *endpoint = &bus->endpoint_array[i]; + CONTINUE_UNLESS(endpoint); + for ( ; (urb = usbd_first_finished_urb_detached (endpoint, &endpoint->rdy)); ) { + #ifdef CONFIG_OTG_LATENCY_CHECK + otg_tick_t ticks; + u16 framenum; + otg_tick_t urb_ticks = urb->ticks; + + /* elapsed time to get BH started and find this urb */ + TRACE_ELAPSED(pcd->TAG, "PCD 1. BH", urb->ticks, (u32)urb); + otg_get_ocd_info(pcd->otg, &ticks, &framenum); + #endif + usbd_do_urb_callback (urb, urb->status); + + #ifdef CONFIG_OTG_LATENCY_CHECK + /* elapsed time for the callback */ + TRACE_ELAPSED(pcd->TAG, "PCD 2. CB", ticks, (u32)urb); + + /* elapsed time for all processing since the urb was finished */ + TRACE_ELAPSED(pcd->TAG, "PCD 3. TOTAL", urb_ticks, (u32)urb); + #endif + } + } + //otg_led(LED2, FALSE); + return NULL; +} + +/*! pcd_startup_events - called for start event processing + * @param bus - bus instance pointer + */ +void pcd_startup_events(struct usbd_bus_instance *bus) +{ + struct pcd_instance *pcd = (struct pcd_instance *)bus->privdata; + TRACE_MSG0(pcd->TAG, "BI_STARTUP"); + if (usbd_pcd_ops.startup_events) { + TRACE_MSG0(pcd->TAG, "BI_STARTUP_EVENTS - udc"); + usbd_pcd_ops.startup_events(pcd); + } + else { + TRACE_MSG0(pcd->TAG, "BI_STARTUP_EVENTS - default"); + pcd_bus_event_handler_irq (bus, DEVICE_INIT, 0); + pcd_bus_event_handler_irq (bus, DEVICE_CREATE, 0); + pcd_bus_event_handler_irq (bus, DEVICE_HUB_CONFIGURED, 0); + pcd_bus_event_handler_irq (bus, DEVICE_RESET, 0); + } +} + +/*! pcd_register_bh - + * @param data - pcd_instance pointer + */ +void *pcd_register_bh(void *data) +{ + struct pcd_instance *pcd = (struct pcd_instance *) data; + struct otg_instance *otg = pcd->otg; + struct usbd_bus_instance *bus = NULL; + struct usbd_endpoint_instance *endpoint; + BOOL usbd_enabled = FALSE; + + + TRACE_MSG1(pcd->TAG, "BUS_REGISTER_BH: pcd: %p", (int)data); + RETURN_NULL_UNLESS(pcd); + + TRACE_MSG0(pcd->TAG, "BUS_REGISTER_BH"); + + bus_driver.name = usbd_pcd_ops.name; + bus_driver.max_endpoints = usbd_pcd_ops.max_endpoints; + bus_driver.maxpacketsize = usbd_pcd_ops.ep0_packetsize; + bus_driver.high_speed_capable = usbd_pcd_ops.high_speed_capable; + //bus_driver.capabilities = usbd_pcd_ops.capabilities; + bus_driver.bMaxPower = usbd_pcd_ops.bMaxPower; + bus_driver.ports = usbd_pcd_ops.ports; + // XXX bus_driver.otg_bmAttributes = tcd_ops.bmAttributes; + bus_driver.bmAttributes = usbd_pcd_ops.bmAttributes; + + + TRACE_MSG1(pcd->TAG, "usbd_pcd_ops.bmAttributes: %x", usbd_pcd_ops.bmAttributes); + + /* register this bus interface driver and create the device driver instance */ + THROW_UNLESS ( (bus = usbd_register_bus (&bus_driver, usbd_pcd_ops.ep0_packetsize)), error); + bus->otg = otg; + + bus->privdata = (void *) pcd; + bus->high_speed = USB_SPEED_FULL; + pcd->bus = bus; + + #if 0 + if (serial_number_str && strlen(serial_number_str)) { + char *sp, *dp; + int i; + TRACE_MSG1(pcd->TAG, "prm serial_number_str: %s", serial_number_str); + for (sp = serial_number_str, dp = otg->serial_number, i = 0; + *sp && (i < (sizeof(otg->serial_number) - 1)); i++, sp++) + if (isxdigit(*sp)) *dp++ = toupper(*sp); + } + #endif + + TRACE_STRING(pcd->TAG, "otg serial_number_str: %s", pcd->otg->serial_number); + + if (pcd->otg && pcd->otg->function_name) + TRACE_STRING(pcd->TAG, "function_name: %s", pcd->otg->function_name); + + + THROW_IF ((usbd_enabled = usbd_enable_function (bus, pcd->otg->function_name, pcd->otg->serial_number)), error); + + TRACE_MSG0(pcd->TAG, "BUS_REGISTER_BH: task start"); + THROW_UNLESS((pcd->task = otg_task_init2("usbd", pcd_device_bh, pcd, pcd->TAG)), error); + //pcd->task->debug = TRUE; + otg_task_start(pcd->task); + + /* setup endpoint zero */ + endpoint = bus->endpoint_array + 0; + endpoint->bEndpointAddress[0] = endpoint->bEndpointAddress[1] = 0; + endpoint->wMaxPacketSize[0] = endpoint->wMaxPacketSize[1] = usbd_pcd_ops.ep0_packetsize; + endpoint->rcv_transferSize = 4096; // XXX should this be higher + + if (usbd_pcd_ops.setup_ep) usbd_pcd_ops.setup_ep (pcd, 0, endpoint); + + TRACE_MSG0(pcd->TAG, "BUS_REGISTER_BH: startup"); + pcd_startup_events (bus); + + TRACE_MSG0(pcd->TAG, "BUS_REGISTER_BH: PCD_OK"); + otg_event(pcd->otg, PCD_OK, pcd->TAG, "PCD_REGISTER_BH PCD_OK"); + + TRACE_MSG0(pcd->TAG, "BUS_REGISTER_BH: FINISHED"); + + CATCH(error) { + + TRACE_MSG0(pcd->TAG, "PCD_REGISTER_BH: register failed"); + otg_event(pcd->otg, enable_otg_ | PCD_OK, pcd->TAG, "BUS_REGISTER_BH"); + + if (pcd->task) { + otg_task_exit(pcd->task); + pcd->task = NULL; + } + + if (usbd_enabled) { + usbd_disable_function (bus); + } + + if (bus) { + usbd_deregister_bus (bus); + pcd->bus = NULL; + } + } + return NULL; +} + +/*! pcd_deregister_bh - + * @param data - pcd_instance pointer + */ +void *pcd_deregister_bh(void *data) +{ + struct pcd_instance *pcd = (struct pcd_instance *) data; + struct usbd_bus_instance *bus; + //struct bus_data *data; + + TRACE_MSG2(pcd->TAG, "PCD_DEREGISTER_BH: pcd: %x bus: %x", pcd, pcd ? pcd->bus : NULL); + + //Disable OTG state + otg_event(pcd->otg, enable_otg_, pcd->TAG, "BUS_DEREGISTER_BH"); + + if (pcd && (bus = pcd->bus) /*&& (usbd_bus_state_enabled == bus->bus_state)*/) { + + //otg_up_admin(pcd->task); + //otg_task_exit(bus->task); + //bus->task = NULL; + + //TRACE_MSG0(pcd->TAG, "BUS_DEREGISTER_BH: task stop"); + //otg_task_exit(pcd->task); + //pcd->task = NULL; + + if (usbd_pcd_ops.disable) usbd_pcd_ops.disable (pcd); + + if (bus->device_state == STATE_ATTACHED) { + pcd_bus_event_handler_irq (bus, DEVICE_RESET, 0); + pcd_bus_event_handler_irq (bus, DEVICE_POWER_INTERRUPTION, 0); + pcd_bus_event_handler_irq (bus, DEVICE_HUB_RESET, 0); + } + + pcd_bus_event_handler_irq (bus, DEVICE_DESTROY, 0); + pcd_disable_endpoints (bus); + pcd_disable (bus); + + TRACE_MSG0(pcd->TAG, "BUS_DEREGISTER_BH: task stop"); + if (pcd->task) otg_task_exit(pcd->task); + pcd->task = NULL; + + usbd_disable_function (bus); + + //if (bus->serial_number_str) + // lkfree (pcd->bus->serial_number_str); + + usbd_deregister_bus (bus); + pcd->bus = NULL; + + } + TRACE_MSG0(pcd->TAG, "sending PCD_OK"); + otg_event(pcd->otg, PCD_OK, pcd->TAG, "PCD_DEREGISTER_BH PCD_OK"); + return NULL; +} + +/* ************************************************************************************* */ + + +/*! pcd_framenum() - get current framenum + * @param otg + */ +u16 +pcd_framenum (struct otg_instance *otg) +{ + struct pcd_instance *pcd = otg ? otg->pcd : NULL; + RETURN_ZERO_UNLESS(otg && pcd && usbd_pcd_ops.framenum); + return usbd_pcd_ops.framenum(pcd); +} + + +/*! + * pcd_remote_wakeup() - perform remote wakeup. + * Initiate a remote wakeup to the host. + * @param otg - otg instance + * @param flag - SET or RESET + */ +void pcd_remote_wakeup(struct otg_instance *otg, u8 flag) +{ + struct pcd_instance *pcd = otg->pcd; + struct usbd_bus_instance *bus = pcd->bus; + + struct otg_dev *otg_dev = otg->privdata; + + TRACE_MSG1(pcd->TAG, "device_feature_settings: %04x", bus->device_feature_settings); + RETURN_UNLESS(bus->device_feature_settings & FEATURE(USB_DEVICE_REMOTE_WAKEUP)); + + TRACE_MSG1(pcd->TAG, "remote wakeup enabled flag: %d", flag); + + RETURN_UNLESS (usbd_pcd_ops.remote_wakeup); + + usbd_pcd_ops.remote_wakeup(pcd); +} + +/*! + * pcd_tcd_en_func() - enable + * This is called to enable / disable the PCD and USBD stack. + * @param otg - otg instance + * @param flag - SET or RESET + * + * Start or stop the UDC. + * + * SET Called after Vbus detected and before the pullup is enabled. + * + * RESET after pullup disabled to turn off. + * + */ +void +pcd_tcd_en_func (struct otg_instance *otg, u8 flag) +{ + struct pcd_instance *pcd = otg->pcd; + + int vbus = FALSE; + + RETURN_UNLESS (usbd_pcd_ops.vbus_status); + + vbus = usbd_pcd_ops.vbus_status(pcd); + + TRACE_MSG1(pcd->TAG, "vbus: %d", vbus); + + switch (flag) { + case PULSE: + case SET: + switch (vbus) { + case 1: + case 2: + otg_event(otg, VBUS_VLD | B_SESS_VLD, otg->pcd->TAG, "PCD TCD EN"); + break; + default: + break; + } + break; + } +} + +/*! pcd_dp_pullup_func + * @param otg - otg_instance pointer + * @param flag - + * + * Enable or disable pullup. + */ +void pcd_dp_pullup_func(struct otg_instance *otg, u8 flag) +{ + struct pcd_instance *pcd = otg->pcd; + + RETURN_UNLESS(usbd_pcd_ops.softcon); + usbd_pcd_ops.softcon(pcd, flag); +} + + +/*! pcd_en_func - enable + * + * This is called to enable / disable the PCD and USBD stack. + * @param otg - otg instance pointer + * @param flag - enable/disable flag + */ +void pcd_en_func (struct otg_instance *otg, u8 flag) +{ + struct pcd_instance *pcd = otg->pcd; + struct usbd_bus_instance *bus = pcd->bus; + + //TRACE_MSG0(pcd->TAG, "--"); + switch (flag) { + case SET: + TRACE_MSG0(pcd->TAG, "PCD_EN: SET"); + // check if we can see the UDC and register, then enable the function + if (usbd_pcd_ops.enable) usbd_pcd_ops.enable (pcd); + break; + + case RESET: + TRACE_MSG0(pcd->TAG, "PCD_EN: RESET"); + pcd_bus_event_handler_irq (bus, DEVICE_RESET, 0); + //TRACE_MSG0(pcd->TAG, "PCD_EN: DESTROY"); + //pcd_bus_event_handler_irq (bus, DEVICE_DESTROY, 0); + TRACE_MSG0(pcd->TAG, "PCD_EN: FINISHED"); + // XXX need to set a flag here + break; + } +} + +/*! pcd_init_func - per peripheral controller common initialization + * + * This is called to initialize / de-initialize the PCD and USBD stack. + * + * We start work items to do this. + * @param otg - otg instance pointer + * @param flag - operation flag + * + */ +void pcd_init_func (struct otg_instance *otg, u8 flag) +{ + struct pcd_instance *pcd = otg->pcd; + //struct usbd_bus_instance *bus = pcd->bus; + //struct bus_data *data = NULL; + + //TRACE_MSG0(pcd->TAG, "--"); + switch (flag) { + case SET: + TRACE_MSG0(pcd->TAG, "PCD_INIT: SET - start REGISTER BH"); + //printk(KERN_INFO"%s: SET pcd->register_bh: %p\n", __FUNCTION__, pcd->register_bh); + otg_workitem_start(pcd->register_bh); + //TRACE_MSG0(pcd->TAG, "BUS_REGISTER_SCHEDULE: finished"); + break; + + case RESET: + TRACE_MSG0(pcd->TAG, "PCD_INIT: RESET - start DEREGISTER BH"); + otg_workitem_start(pcd->deregister_bh); + //TRACE_MSG0(pcd->TAG, "BUS_DEREGISTER_SCHEDULE: finished"); + } +} + +/*! pcd_mod_init - per peripheral controller common initialization + * + * This is called to initialize / de-initialize the PCD and USBD stack. + * + * We start work items to do this. + * @param otg - otg instance pointer + * + */ +int pcd_mod_init (struct otg_instance *otg) +{ + struct pcd_instance *pcd = otg->pcd; + //struct usbd_bus_instance *bus = pcd->bus; + + + THROW_UNLESS((pcd->register_bh = otg_workitem_init("regbh", pcd_register_bh, pcd, pcd->TAG)), error); + THROW_UNLESS((pcd->deregister_bh = otg_workitem_init("deregbh", pcd_deregister_bh, pcd, pcd->TAG)), error); + //pcd->register_bh->debug = TRUE; + //pcd->deregister_bh->debug = TRUE; + return 0; + + CATCH(error) { + if (pcd->register_bh) otg_workitem_exit(pcd->register_bh); + if (pcd->deregister_bh) otg_workitem_exit(pcd->deregister_bh); + pcd->register_bh = pcd->deregister_bh = NULL; + return -EINVAL; + } +} + +/*! pcd_mod_exit - per peripheral controller common initialization + * + * This is called to initialize / de-initialize the PCD and USBD stack. + * + * We start work items to do this. + * @param otg - otg_instance pointer + * + */ +void pcd_mod_exit (struct otg_instance *otg) +{ + struct pcd_instance *pcd = otg->pcd; + //struct usbd_bus_instance *bus = pcd->bus; + // + + if (pcd->task) otg_task_exit(pcd->task); + if (pcd->register_bh) otg_workitem_exit(pcd->register_bh); + if (pcd->deregister_bh) otg_workitem_exit(pcd->deregister_bh); + + pcd->task = NULL; + pcd->register_bh = pcd->deregister_bh = NULL; +} + +struct pcd_ops pcd_ops = { + .pcd_en_func = pcd_en_func, + .pcd_init_func = pcd_init_func, + #ifdef CONFIG_OTG_REMOTE_WAKEUP + .remote_wakeup_func = pcd_remote_wakeup, + #endif /* CONFIG_OTG_REMOTE_WAKEUP */ + .framenum = pcd_framenum, + .tcd_en_func = pcd_tcd_en_func, + .dp_pullup_func = pcd_dp_pullup_func, + .ticks = pcd_ticks, + .elapsed = pcd_elapsed, +}; diff --git a/drivers/otg/hardware/zasevb-arc-l26.c b/drivers/otg/hardware/zasevb-arc-l26.c new file mode 100644 index 000000000000..ff48df99d319 --- /dev/null +++ b/drivers/otg/hardware/zasevb-arc-l26.c @@ -0,0 +1,192 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/hardware/zasevb-arc-l26.c - iMX31ADS EVB OTG Peripheral and OTG Controller Drivers Module Initialization + * @(#) balden@belcarra.com/seth2.rillanon.org|otg/platform/zasevb/zasevb-arc-l26.c|20070909224442|30419 + * + * Copyright (c) 2004-2005 Belcarra Technologies Corp + * Copyright (c) 2005-2007 Belcarra Technologies 2005 Corp + * + * By: + * Stuart Lynne <sl@belcarra.com>, + * Bruce Balden <balden@belcarra.com> + */ +/*! + * @addtogroup MX31 High Speed + * @ingroup Hardware + */ +/*! + * @file otg/hardware/zasevb-arc-l26.c + * @brief MX31 USB Host Controller Driver + * + * Linux MX31 OTG PCD/OCD/TCD Driver Initialization + * + * This file selects and initializes all of the low level hardware drivers + * for the MX31 High Speed. + * + * Notes. + * + * + * @ingroup MX31 + * @ingroup MXC + * @ingroup LINUXOS + */ +/* + * XXX Need to change mx31_ function names to arc_ + */ + +#include <otg/pcd-include.h> +#include <otg/otg-dev.h> +#include <linux/module.h> + +#include <linux/pci.h> +#include <asm/arch/gpio.h> +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,22) +#include <asm/arch/board.h> +#endif + +//#include "mxc-lnx.h" +//#include "mxc-hardware.h" + +MOD_AUTHOR ("sl@belcarra.com"); +MOD_DESCRIPTION ("Belcarra MX31"); +EMBED_LICENSE(); + + +MOD_PARM_STR(serial_number_str, "Serial Number String", NULL); + + +/* ************************************************************************************* */ + +extern int arc_dev_module_init(struct otg_device_driver *otg_device_driver, + int (*device_probe)(struct device *), + int (*device_remove)(struct device *) + ); + +extern void arc_dev_module_exit(struct otg_device_driver *); + +//extern int arc_ocd_module_init (struct otg_device_driver *); +//extern void arc_ocd_module_exit (struct otg_device_driver *); + + +extern int arc_pcd_module_init (struct otg_device_driver *); +extern void arc_pcd_module_exit (struct otg_device_driver *); + +//extern int arc_tcd_module_init (struct otg_device_driver *); +//extern void arc_tcd_module_exit (struct otg_device_driver *); + + +static struct otg_device_driver arc_otg_device_driver = { + .name = "arc_udc", +}; + + + +/*! zasevb_arc_device_probe - called to initialize platform + * @param device - device + * + * This is used to call the dev level probe functions with + * the additional otg_device_driver structure. + */ +static int +zasevb_arc_device_probe(struct device *device) +{ + printk(KERN_INFO"%s: l26\n", __FUNCTION__); + RETURN_ZERO_UNLESS (arc_otg_device_driver.probe); + return arc_otg_device_driver.probe(device, &arc_otg_device_driver); +} + +/*! zasevb_arc_device_remove- called to remote device + * @param device - device + * + * This is used to call the dev level probe functions with + * the additional otg_device_driver structure. + */ +static int +zasevb_arc_device_remove(struct device *device) +{ + int rc; + printk(KERN_INFO"%s: l26\n", __FUNCTION__); + RETURN_ZERO_UNLESS (arc_otg_device_driver.remove); + rc = arc_otg_device_driver.remove(device, &arc_otg_device_driver); + return rc; +} + + +/*! + * zasevb_arc_modexit() - This is used as module exit, and as cleanup if modinit fails. + */ +static void zasevb_arc_modexit (void) +{ + /* unload the dev driver, this will stop otg and destroy + * the otg_dev and otg instances etc. + */ + printk(KERN_INFO"%s:\n", __FUNCTION__); + arc_dev_module_exit(&arc_otg_device_driver); + + /* cleanup the rest of the sub-drivers + */ + arc_pcd_module_exit(&arc_otg_device_driver); + //arc_tcd_module_exit(&arc_otg_device_driver); + //arc_ocd_module_exit(&arc_otg_device_driver); +} + +/*! + * zasevb_arc_modinit() - linux module initialization + * + * This needs to initialize the hcd, pcd and tcd drivers. This includes tcd and possibly hcd + * for some architectures. + * + */ +static int zasevb_arc_modinit (void) +{ + int ocd = -1, tcd = -1, pcd = -1, dev = -1; + + /* initialize all of the sub-drivers except for dev + */ + //ocd = arc_ocd_module_init(&arc_otg_device_driver); + //tcd = arc_tcd_module_init(&arc_otg_device_driver); + pcd = arc_pcd_module_init(&arc_otg_device_driver); + + /* ensure everything is ok until now */ + //THROW_IF(ocd || tcd || pcd, error); + THROW_IF(pcd, error); + + /* serial number needs to be set prior to initializing the dev + * driver + */ + arc_otg_device_driver.serial_number = MODPARM(serial_number_str); + + /* initialize the dev driver, this will get all sub-drivers + * started via their probe functions, create otg and otg_dev + * instances and finally start otg state machine. + */ + THROW_IF((dev = arc_dev_module_init(&arc_otg_device_driver, zasevb_arc_device_probe, zasevb_arc_device_remove)), error); + + return 0; + + CATCH(error) { + + printk(KERN_INFO"%s: FAILED\n", __FUNCTION__); + + UNLESS (dev) arc_dev_module_exit(&arc_otg_device_driver); + //UNLESS (tcd) arc_tcd_module_exit(&arc_otg_device_driver); + UNLESS (pcd) arc_pcd_module_exit(&arc_otg_device_driver); + //UNLESS (ocd) arc_ocd_module_exit(&arc_otg_device_driver); + + return -EINVAL; + } +} + +module_init (zasevb_arc_modinit); +module_exit (zasevb_arc_modexit); diff --git a/drivers/otg/hardware/zasevb-isp1301.c b/drivers/otg/hardware/zasevb-isp1301.c new file mode 100644 index 000000000000..5584dc88411b --- /dev/null +++ b/drivers/otg/hardware/zasevb-isp1301.c @@ -0,0 +1,351 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/hardware/zasevb-isp1301.c -- ZASEVB ISP1301 Transceiver Controller driver + * @(#) sp/root@belcarra.com/debian-black.(none)|otg/platform/zasevb/zasevb-isp1301.c|20070822230929|14023 + * + * Copyright (c) 2004-2005 Belcarra Technologies Corp + * Copyright (c) 2005-2007 Belcarra Technologies 2005 Corp + * + * By: + * Stuart Lynne <sl@lbelcarra.com>, + * Bruce Balden <balden@belcarra.com> + * + */ +/*! + * @file otg/hardware/zasevb-isp1301.c + * @brief ZAS EVB USB ISP1301 Transceiver Driver + * + * This is a wrapper for the ISP1301 driver to support it on the ZASEVB. + * + * Notes + * + * 1. Ensure that S2.1 and S2.2 are both in the ON position. + * + * 2. The ISP1301 uses the system I2C bus, this is done throught the i2c-l26.c library. + * This library must be opened with the correct adapter name. + * + * 3. The ISP1301 uses a GPIO for interrupts. The GPIO must be properly setup using + * iomux(). + * + * 4. The ISP1301 and USBOTG HWMODE must both be setup to match. Currently it + * appears that only the SEO-SEO / DAT-Bidirectional combination works correctly. + * + * @ingroup ZASEVB + * @ingroup TCD + * + */ + + +#include <otg/pcd-include.h> + +#if defined(CONFIG_OTG_ZASEVB_ISP1301) || defined(_OTG_DOXYGEN) + +#include <asm/arch/gpio.h> + +#include "mxc-lnx.h" +#include "mxc-hardware.h" + +#include "isp1301.h" +#include "isp1301-hardware.h" + +#include <linux/delay.h> + +#ifndef PLL2_BASE_ADDR +#define PLL2_BASE_ADDR 0x5004C000 +#endif +#define PLL2_DP_CTL IO_ADDRESS(PLL2_BASE_ADDR + 0x00) +#define PLL2_DP_CONFIG IO_ADDRESS(PLL2_BASE_ADDR + 0x04) +#define PLL2_DP_OP IO_ADDRESS(PLL2_BASE_ADDR + 0x08) +#define PLL2_DP_MFD IO_ADDRESS(PLL2_BASE_ADDR + 0x0c) +#define PLL2_DP_MFN IO_ADDRESS(PLL2_BASE_ADDR + 0x10) +#define PLL2_DP_HFSOP IO_ADDRESS(PLL2_BASE_ADDR + 0x1c) +#define PLL2_DP_HFSMFD IO_ADDRESS(PLL2_BASE_ADDR + 0x20) +#define PLL2_DP_HFSMFN IO_ADDRESS(PLL2_BASE_ADDR + 0x24) + + + +#if defined(CONFIG_MACH_ARGONPLUSEVB) || defined(CONFIG_ARCH_ARGONPLUSEVB) || defined(CONFIG_ARCH_ARGONLV) +#define ZGPIO_PORT 0 +#define ZGPIO_PIN 2 +#endif/* defined(CONFIG_MACH_ARGONPLUSEVB)*/ + +#if defined(CONFIG_MACH_SCMA11EVB) || defined(CONFIG_ARCH_SCMA11EVB) +#define ZGPIO_PORT 2 +#define ZGPIO_PIN 12 +#endif /* CONFIG_MACH_SCMA11EVB */ + +#ifdef CONFIG_ARCH_ZEUS +#define ZGPIO_PORT 1 +#define ZGPIO_PIN 30 +#endif /* CONFIG_ARCH_ZEUS */ + + +void mxc_set_transceiver_mode(int mode); +void mxc_main_clock_on(void); +void mxc_disable_interrupts (void); +void isp1301_exit(void); +int mxc_iomux_gpio_isp1301_set (struct otg_instance *otg, int); +int mxc_iomux_gpio_isp1301_reset (struct otg_instance *otg); + + + +#if 0 //removing gpios + +/* ********************************************************************************************* */ +/*! + * zasevb_gpio_int_hndlr() - gpio interrupt handler + * @param irq + * @param dev_id + * @param regs + * @return interrupt handler status + * This disables the gpio interrup and schedules the isp1301 bottom half handler. + * + */ +static irqreturn_t zasevb_gpio_int_hndlr (int irq, void *dev_id, struct pt_regs *regs) +{ + gpio_config_int_en(ZGPIO_PORT, ZGPIO_PIN, FALSE); + + TRACE_MSG0(otg->tcd->TAG, "ZASEVB GPIO INTERRUPT: SCHEDULE WORK"); + isp1301_bh_wakeup(REMOVE_tcd_instance->otg, FALSE); + + return IRQ_HANDLED; +} + +/*! + * zasevb_isp1301_bh()- call isp1301 bottom half handler + * @param arg + * This is a wrapper to the isp1301 bottom half handler, it + * re-enables the gpio interrupt after processing complete. + */ +void zasevb_isp1301_bh(void *arg) +{ + TRACE_MSG0(otg->tcd->TAG, "ZASEVB GPIO INTERRUPT: ISP1301_BH"); + isp1301_bh(arg); + TRACE_MSG0(otg->tcd->TAG, "ZASEVB GPIO INTERRUPT: REENABLE"); + gpio_config_int_en(ZGPIO_PORT, ZGPIO_PIN, TRUE); +} + +#endif + +/* ********************************************************************************************* */ +extern int zasevb_tcd_mod_init (struct otg_instance *otg); +extern void zasevb_tcd_mod_exit (struct otg_instance *otg); +//struct tcd_instance *zasevb_tcd_instance; +#if !defined(OTG_C99) +struct tcd_ops tcd_ops; +/*! + * zasevb_tcd_global_init() - non c99 global initializer + */ +void zasevb_tcd_global_init(void) +{ + ZERO(tcd_ops); + + tcd_ops.vbus = isp1301_vbus; + tcd_ops.id = isp1301_id; + + tcd_ops.tcd_init_func = isp1301_tcd_init; + tcd_ops.tcd_en_func = isp1301_tcd_en; + tcd_ops.chrg_vbus_func = isp1301_chrg_vbus; + tcd_ops.drv_vbus_func = isp1301_drv_vbus; + tcd_ops.dischrg_vbus_func = isp1301_dischrg_vbus; + tcd_ops.dp_pullup_func = isp1301_dp_pullup_func; + tcd_ops.dm_pullup_func = isp1301_dm_pullup_func; + tcd_ops.dp_pulldown_func = isp1301_dp_pulldown_func; + tcd_ops.dm_pulldown_func = isp1301_dm_pulldown_func; + + tcd_ops.overcurrent_func = NULL; + tcd_ops.dm_det_func = isp1301_dm_det_func; + tcd_ops.dp_det_func = isp1301_dp_det_func; + tcd_ops.cr_det_func = isp1301_cr_det_func; + tcd_ops.peripheral_host_func = isp1301_peripheral_host_func; + //tcd_ops.mx21_vbus_drain_func = isp1301_mx21_vbus_drain_func; + tcd_ops.id_pulldown_func = isp1301_id_pulldown_func; + tcd_ops.audio_func = isp1301_audio_func; + tcd_ops.uart_func = isp1301_uart_func; + tcd_ops.mono_func = isp1301_mono_func; + + tcd_ops.mod_init = zasevb_tcd_mod_init; + tcd_ops.mod_exit = zasevb_tcd_mod_exit; +} +#else /* !defined(OTG_C99) */ +struct tcd_ops tcd_ops = { + + //.vbus = isp1301_vbus, + //.id = isp1301_id, + + .tcd_init_func = isp1301_tcd_init, + .tcd_en_func = isp1301_tcd_en, + .chrg_vbus_func = isp1301_chrg_vbus, + .drv_vbus_func = isp1301_drv_vbus, + .dischrg_vbus_func = isp1301_dischrg_vbus, + .dp_pullup_func = isp1301_dp_pullup_func, + .dm_pullup_func = isp1301_dm_pullup_func, + .dp_pulldown_func = isp1301_dp_pulldown_func, + .dm_pulldown_func = isp1301_dm_pulldown_func, + + .overcurrent_func = NULL, + .dm_det_func = isp1301_dm_det_func, + .dp_det_func = isp1301_dp_det_func, + .cr_det_func = isp1301_cr_det_func, + .peripheral_host_func = isp1301_peripheral_host_func, + //.mx21_vbus_drain_func = isp1301_mx21_vbus_drain_func, + .id_pulldown_func = isp1301_id_pulldown_func, + .audio_func = isp1301_audio_func, + .uart_func = isp1301_uart_func, + .mono_func = isp1301_mono_func, + + .mod_init = zasevb_tcd_mod_init, + .mod_exit = zasevb_tcd_mod_exit, +}; +#endif /* !defined(OTG_C99) */ + +/* ********************************************************************************************* */ +void zasevb_tcd_mod_exit (struct otg_instance *otg); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,15) +#define ADAPTER_NAME "mxc_i2c" +#else +#define ADAPTER_NAME "MXC I2C Adapter" +#endif + +/*! + * zasevb_tcd_mod_init() - initial tcd setup + * This performs the platform specific hardware setup for the MX2ADS. + * @param otg - otg_instance pointer + */ +int zasevb_tcd_mod_init (struct otg_instance *otg) +{ + int i2c = 1; + int gpio = 1; + + /* ------------------------------------------------------------------------ */ + #if defined(CONFIG_OTG_ZASEVB_DIFFERENTIAL_UNIDIRECTIONAL) + int hwmode = XCVR_D_SE0_NEW; + int newmode = XCVR_D_D; + isp1301_tx_mode_t tx_mode = vp_vm_unidirectional; // SCMA11 ok + printk (KERN_INFO"Current setting is DIFFERENTIAL UNIDIRECTIONAL\n"); + + /* ------------------------------------------------------------------------ */ + #elif defined(CONFIG_OTG_ZASEVB_SINGLE_ENDED_UNIDIRECTIONAL) + int hwmode = XCVR_SE0_D_NEW; + int newmode = XCVR_SE0_D_NEW; + isp1301_tx_mode_t tx_mode = dat_se0_unidirectional; // ArgonEVB ok + printk (KERN_INFO"Current setting is SINGLE ENDED UNIDIRECTIONAL\n"); + + /* ------------------------------------------------------------------------ */ + #elif defined(CONFIG_OTG_ZASEVB_DIFFERENTIAL_BIDIRECTIONAL) + int hwmode = XCVR_D_D; + int newmode = XCVR_D_D; + isp1301_tx_mode_t tx_mode = vp_vm_bidirectional; // ArgonEVB ok + printk (KERN_INFO"Current setting is DIFFERENTIAL BIDIRECTIONAL\n"); + + /* ------------------------------------------------------------------------ */ + #elif defined(CONFIG_OTG_ZASEVB_SINGLE_ENDED_BIDIRECTIONAL) + int hwmode = XCVR_SE0_SE0; + int newmode = XCVR_SE0_SE0; + isp1301_tx_mode_t tx_mode = dat_se0_bidirectional; //SCMA11 ok + printk (KERN_INFO"Current setting is SINGLE ENDED BIDIRECTIONAL\n"); + + /* ------------------------------------------------------------------------ */ + #else + #error Please Configure Transceiver Mode + #endif /* CONFIG_OTG_ZASEVB_.... */ + /* ------------------------------------------------------------------------ */ + + TRACE_MSG0(otg->tcd->TAG, "1. I2C setup"); + + THROW_IF ((i2c = i2c_configure(ADAPTER_NAME, ISP1301_I2C_ADDR_HIGH)), error); + + TRACE_MSG0(otg->tcd->TAG, "2. ISP1301 module setup"); + // isp1301_mod_init(&zasevb_isp1301_bh); + + //TRACE_MSG0(otg->tcd->TAG, "3. SET TCD OPS"); + //printk(KERN_INFO"%s: set_tcd_ops\n", __FUNCTION__); + //THROW_UNLESS(zasevb_tcd_instance = otg_set_tcd_ops(otg, &tcd_ops), error); + + TRACE_MSG0(otg->tcd->TAG, "4. ISP1301 device setup"); + + mxc_iomux_gpio_isp1301_set (otg, hwmode); + + #ifdef CONFIG_ARCH_MXC91131 + writel (0x00000051, PLL2_DP_HFSOP); + writel (0x00000051, PLL2_DP_OP); + #endif /* CONFIG_ARCH_ZEUS */ + + + /* ------------------------------------------------------------------------ */ + TRACE_MSG0(otg->tcd->TAG, "7. SET HWMODE"); + isp1301_configure(otg, tx_mode, spd_susp_reg); + mxc_main_clock_on(); + //mxc_host_clock_on(); + //mxc_func_clock_on(); + mxc_set_transceiver_mode(newmode); + + + +#if 0 //Test reading registers + + int int_src1, int_lat; + for (;;){ + int_src1 = i2c_readb(ISP1301_INTERRUPT_SOURCE); + int_lat = i2c_readb(ISP1301_INTERRUPT_LATCH_CLR); + printk(KERN_INFO"%s: interrupt source: %x latch: %x\n", __FUNCTION__, int_src1, int_lat); + mdelay (1000); + i2c_writeb(ISP1301_INTERRUPT_LATCH_CLR, int_lat); + mdelay (1000); + } + +#endif + +#if 0 //pulse on DP + for (;;){ + mdelay (100); + i2c_writeb(ISP1301_OTG_CONTROL_SET, ISP1301_DP_PULLUP); + mdelay (100); + i2c_writeb(ISP1301_OTG_CONTROL_CLR, ISP1301_DP_PULLUP); + } +#endif + + /* Success! + */ + TRACE_MSG0(otg->tcd->TAG, "8. Success!"); + + CATCH(error) { + printk(KERN_INFO"%s: failed\n", __FUNCTION__); + UNLESS (i2c) i2c_close(); + // UNLESS (gpio) gpio_free_irq (ZGPIO_PORT, ZGPIO_PIN, GPIO_HIGH_PRIO); + return -EINVAL; + } + TRACE_MSG0(otg->tcd->TAG, "MX2_MOD_TCD_INIT FINISHED"); + return 0; +} + +/*! + * zasevb_tcd_mod_exit() - de-initialize + * This is called from mx2-ocd.c + * @param otg - otg_instance pointer + */ +void zasevb_tcd_mod_exit (struct otg_instance *otg) +{ + //struct otg_instance *otg = tcd_instance->otg; + TRACE_MSG0(otg->tcd->TAG, "ZASEVB_TCD_MOD_EXIT"); + isp1301_mod_exit(otg); + mxc_disable_interrupts(); + // gpio_free_irq (ZGPIO_PORT, ZGPIO_PIN, GPIO_HIGH_PRIO); + mxc_iomux_gpio_isp1301_reset(otg); + //zasevb_tcd_instance = otg_set_tcd_ops(otg, NULL); + isp1301_exit(); + printk(KERN_INFO"%s: call i2c_close()\n", __FUNCTION__); + i2c_close(); +} +#endif /* CONFIG_ZASEVB_ISP1301 */ diff --git a/drivers/otg/hardware/zasevb-l26.c b/drivers/otg/hardware/zasevb-l26.c new file mode 100644 index 000000000000..70c5920c737a --- /dev/null +++ b/drivers/otg/hardware/zasevb-l26.c @@ -0,0 +1,394 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/hardware/zasevb-l26.c - ZAS EVB OTG Peripheral and OTG Controller Drivers Module Initialization + * @(#) sl@belcarra.com/whiskey.enposte.net|otg/platform/zasevb/zasevb-l26.c|20070827195306|47636 + * + * Copyright (c) 2004-2005 Belcarra Technologies Corp + * Copyright (c) 2005-2007 Belcarra Technologies 2005 Corp + * + * By: + * Stuart Lynne <sl@belcarra.com>, + * Bruce Balden <balden@belcarra.com> + */ +/*! + * @defgroup ZASEVB Freescale ZAS EVB + * @ingroup Hardware + */ +/*! + * @file otg/hardware/zasevb-l26.c + * @brief ZAS EVB USB Host Controller Driver + * + * + * Linux ZASEVB OTG PCD/HCD/OCD/TCD Driver Initialization + * + * This file initializes all of the low level hardware drivers for the ZAS EVB. + * + * Notes. + * + * + * @ingroup ZASEVB + * @ingroup LINUXOS + * + */ + +#include <otg/pcd-include.h> +#include <linux/module.h> + +#include <linux/pci.h> +#include <asm/arch/gpio.h> +#include <linux/clk.h> + +#include "mxc-lnx.h" +#include "mxc-hardware.h" + +MOD_AUTHOR ("sl@belcarra.com"); +MOD_DESCRIPTION ("Belcarra MXC"); +EMBED_LICENSE(); + + +MOD_PARM_STR(serial_number_str, "Serial Number String", NULL); + +/* OTG Driver + */ +otg_tag_t REMOVE_OCD; +struct ocd_instance *ocd_instance; +extern struct ocd_ops ocd_ops; + +/* Transceiver Driver + */ +otg_tag_t REMOVE_TCD; +struct tcd_instance *REMOVE_tcd_instance; +extern struct tcd_ops tcd_ops; + + +/* Peripheral Driver + */ +otg_tag_t REMOVE_PCD; +struct pcd_instance *REMOVE_pcd_instance; +#if !defined(CONFIG_USB_HOST) +extern struct pcd_ops pcd_ops; +#else /* !defined(CONFIG_USB_HOST) */ +irqreturn_t mxc_pcd_int_hndlr (int irq, void *dev_id, struct pt_regs *regs) +{ + return IRQ_HANDLED; +} +#endif /* !defined(CONFIG_USB_HOST) */ + +/* Host Driver + */ +otg_tag_t HCD; +struct hcd_instance *hcd_instance; +#if defined(CONFIG_OTG_USB_HOST) || defined(CONFIG_OTG_USB_PERIPHERAL_OR_HOST)|| defined(CONFIG_OTG_DEVICE) +extern struct hcd_ops hcd_ops; + +#else /* defined(CONFIG_OTG_USB_HOST) || defined(CONFIG_OTG_USB_PERIPHERAL_OR_HOST)|| defined(CONFIG_OTG_DEVICE) */ +irqreturn_t hcd_hw_int_hndlr(int irq, void *dev_id, struct pt_regs *regs) +{ + return IRQ_HANDLED; +} +#endif /* defined(CONFIG_OTG_USB_HOST) || defined(CONFIG_OTG_USB_PERIPHERAL_OR_HOST)|| defined(CONFIG_OTG_DEVICE) */ + +/* ************************************************************************************* */ + +#if defined(CONFIG_OTG_ZASEVB_ISP1301) || defined(CONFIG_OTG_ZASEVB_ISP1301_MODULE) +#define OTG_USE_I2C +#else +#define OTG_DISABLE_I2C +#endif +#ifdef OTG_USE_I2C +extern int i2c_mod_init(struct otg_instance *otg); +extern void i2c_mod_exit(struct otg_instance *otg); +#endif +extern int mxc_procfs_init(void); +extern void mxc_procfs_exit(void); + +//extern void fs_mod_exit(void); + +#if !defined(OTG_C99) +extern void pcd_global_init(void); +extern void fs_ocd_global_init(void); +extern void zasevb_tcd_global_init(void); +extern void fs_pcd_global_init(void); +#endif /* !defined(OTG_C99) */ +void mxc_pcd_ops_init(void); + +#if defined(CONFIG_OTG_GPTR) +extern int mxc_gptcr_mod_init (int divisor, int multiplier); +void mxc_gptcr_mod_exit (void); +#endif /* defined(CONFIG_OTG_GPTR) */ + +#if defined(CONFIG_OTG_HRT) +extern int mxc_hrt_mod_init (struct otg_instance *otg, int divisor, int multiplier); +void mxc_hrt_mod_exit (void); +#endif /* defined(CONFIG_OTG_GPTR) */ + + +otg_tag_t ZAS; + + +/*! + * zasevb_modexit() - This is used as module exit, and as cleanup if modinit fails. + */ +static void zasevb_modexit (void) +{ + struct otg_instance *otg = ocd_instance->otg; + //struct pcd_instance *pcd = (struct pcd_instance *)otg->pcd; + //struct usbd_bus_instance *bus= pcd->bus; + + TRACE_MSG0(ZAS, "Modules exit!"); + + if (otg) otg_exit(otg); + + mxc_procfs_exit(); + + /* Disable GPT + */ + #if defined(CONFIG_OTG_GPTR) + mxc_gptcr_mod_exit(); + #endif /* defined(CONFIG_OTG_GPTR) */ + + #if defined(CONFIG_OTG_HRT) + mxc_hrt_mod_exit(); + #endif /* defined(CONFIG_OTG_GPTR) */ + + #ifdef OTG_USE_I2C + TRACE_MSG0(ZAS, "0. I2C"); + i2c_mod_exit(otg); + #endif + + #if !defined(CONFIG_USB_HOST) + if (pcd_ops.mod_exit) pcd_ops.mod_exit(otg); + REMOVE_pcd_instance = otg_set_pcd_ops(otg, NULL); + #else /* !defined(CONFIG_USB_HOST) */ + printk(KERN_INFO"%s: PCD DRIVER N/A\n", __FUNCTION__); + #endif /* !defined(CONFIG_USB_HOST) */ + + + #if defined(CONFIG_OTG_USB_HOST) || defined(CONFIG_OTG_USB_PERIPHERAL_OR_HOST)|| defined(CONFIG_OTG_DEVICE) + if (hcd_ops.mod_exit) hcd_ops.mod_exit(otg); + hcd_instance = otg_set_hcd_ops(otg, NULL); + //HCD = otg_trace_invalidate_tag(HCD); + #else /* defined(CONFIG_OTG_USB_HOST) || defined(CONFIG_OTG_USB_PERIPHERAL_OR_HOST)|| defined(CONFIG_OTG_DEVICE) */ + printk(KERN_INFO"%s: HCD DRIVER N/A\n", __FUNCTION__); + #endif /* defined(CONFIG_OTG_USB_HOST) || defined(CONFIG_OTG_USB_PERIPHERAL_OR_HOST)|| defined(CONFIG_OTG_DEVICE) */ + + + if (tcd_ops.mod_exit) tcd_ops.mod_exit(otg); + printk(KERN_INFO"%s: set_tcd_ops\n", __FUNCTION__); + REMOVE_tcd_instance = otg_set_tcd_ops(otg, NULL); + //REMOVE_TCD = otg_trace_invalidate_tag(REMOVE_TCD); + + if (ocd_ops.mod_exit) ocd_ops.mod_exit(otg); + ocd_instance = otg_set_ocd_ops(otg, NULL); + + + ZAS = otg_trace_invalidate_tag(ZAS); + + + otg_destroy(otg); +} + +extern void mxc_pcd_ops_init(void); + +/*! + * zasevb_modinit() - linux module initialization + * + * This needs to initialize the hcd, pcd and tcd drivers. This includes tcd and possibly hcd + * for some architectures. + * + */ +static int zasevb_modinit (void) +{ + struct otg_instance *otg = NULL; + + #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,15) + struct clk *clk = clk_get(NULL, "usb_clk"); + clk_enable(clk); + clk_put(clk); + #endif + + THROW_UNLESS((otg = otg_create()), error); + + mxc_pcd_ops_init(); + #if !defined(OTG_C99) + pcd_global_init(); + fs_ocd_global_init(); + zasevb_tcd_global_init(); + fs_pcd_global_init(); + #endif /* !defined(OTG_C99) */ + + ZAS = otg_trace_obtain_tag(otg, "zas"); + + mxc_procfs_init(); + + TRACE_MSG0(ZAS, "1. ZAS"); + + #if 0 + /* ZAS EVB Platform setup + */ + TRACE_MSG4(ZAS, "BCTRL Version: %04x Status: %04x 1: %04x 2: %04x", + readw(PBC_BASE_ADDRESS ), + readw(PBC_BASE_ADDRESS + PBC_BSTAT), + readw(PBC_BASE_ADDRESS + PBC_BCTRL1_SET), + readw(PBC_BASE_ADDRESS + PBC_BCTRL2_SET)); + + #endif + + /* ZAS EVB Clock setup + */ + +#if defined(CONFIG_ARCH_ARGONPLUS) || defined(CONFIG_ARCH_ARGONLV) +#define ZASEVB_MULTIPLIER 12 +#define ZASEVB_DIVISOR 775 // ~10. +#else +#define ZASEVB_MULTIPLIER 12 +#define ZASEVB_DIVISOR 155 +#endif + + TRACE_MSG0(ZAS, "2. Setup GPT"); + + THROW_UNLESS(ocd_instance = otg_set_ocd_ops(otg, &ocd_ops), error); + REMOVE_OCD = ocd_instance->TAG; + // XXX THROW_IF((ocd_ops.mod_init ? ocd_ops.mod_init() : 0), error); + + #if defined(CONFIG_OTG_GPTR) + mxc_gptcr_mod_init(ZASEVB_DIVISOR, ZASEVB_MULTIPLIER); + #endif /* defined(CONFIG_OTG_GPTR) */ + + #if defined(CONFIG_OTG_HRT) + mxc_hrt_mod_init(otg, ZASEVB_DIVISOR, ZASEVB_MULTIPLIER); + #endif /* defined(CONFIG_OTG_GPTR) */ + + + #if !defined(CONFIG_USB_HOST) + TRACE_MSG0(ZAS, "3. PCD"); + THROW_UNLESS(REMOVE_pcd_instance = otg_set_pcd_ops(otg, &pcd_ops), error); + REMOVE_PCD = REMOVE_pcd_instance->TAG; + // XXX THROW_IF((pcd_ops.mod_init ? pcd_ops.mod_init() : 0), error); + #else /* !defined(CONFIG_USB_HOST) */ + printk(KERN_INFO"%s: PCD DRIVER N/A\n", __FUNCTION__); + #endif /* !defined(CONFIG_USB_HOST) */ + + + TRACE_MSG0(ZAS, "4. TCD"); + THROW_UNLESS(REMOVE_tcd_instance = otg_set_tcd_ops(otg, &tcd_ops), error); + REMOVE_TCD = REMOVE_tcd_instance->TAG; + // XXX THROW_IF((tcd_ops.mod_init ? tcd_ops.mod_init() : 0), error); +#ifdef OTG_USE_I2C + TRACE_MSG0(ZAS, "0. I2C"); + i2c_mod_init(otg); +#endif + + + #if defined(CONFIG_OTG_USB_HOST) || defined(CONFIG_OTG_USB_PERIPHERAL_OR_HOST)|| defined(CONFIG_OTG_DEVICE) + TRACE_MSG0(ZAS, "5. Host"); + THROW_UNLESS(hcd_instance = otg_set_hcd_ops(otg, &hcd_ops), error); + HCD = hcd_instance->TAG; + // XXX THROW_IF((hcd_ops.mod_init) ? hcd_ops.mod_init() : 0, error); + #else /* defined(CONFIG_OTG_USB_HOST) || defined(CONFIG_OTG_USB_PERIPHERAL_OR_HOST)|| defined(CONFIG_OTG_DEVICE) */ + printk(KERN_INFO"%s: HCD DRIVER N/A\n", __FUNCTION__); + #endif /* defined(CONFIG_OTG_USB_HOST) || defined(CONFIG_OTG_USB_PERIPHERAL_OR_HOST)|| defined(CONFIG_OTG_DEVICE) */ + + + + TRACE_MSG0(ZAS, "6. Init & check"); + THROW_IF((ocd_ops.mod_init ? ocd_ops.mod_init(otg) : 0), error); + + #if !defined(CONFIG_USB_HOST) + THROW_IF((pcd_ops.mod_init ? pcd_ops.mod_init(otg) : 0), error); + #endif /* !defined(CONFIG_USB_HOST) */ + + THROW_IF((tcd_ops.mod_init ? tcd_ops.mod_init(otg) : 0), error); + + #if defined(CONFIG_OTG_USB_HOST) || defined(CONFIG_OTG_USB_PERIPHERAL_OR_HOST)|| defined(CONFIG_OTG_DEVICE) + THROW_IF((hcd_ops.mod_init) ? hcd_ops.mod_init(otg) : 0, error); + #endif /* defined(CONFIG_OTG_USB_HOST) || defined(CONFIG_OTG_USB_PERIPHERAL_OR_HOST)|| defined(CONFIG_OTG_DEVICE) */ + + THROW_UNLESS(ocd_instance && (otg = ocd_instance->otg), error); + + + TRACE_MSG0(ZAS, "7. otg_init"); + if (MODPARM(serial_number_str) && strlen(MODPARM(serial_number_str))) { + + TRACE_MSG1(ZAS, "serial_number_str: %s", MODPARM(serial_number_str)); + otg_serial_number (otg, MODPARM(serial_number_str)); + } + otg_init(otg); + + return 0; + + CATCH(error) { + //zasevb_modexit(); + return -EINVAL; + } + + return 0; +} + +#if 0 +/* ************************************************************************************* */ +static int __init_or_module +zasevb_remove(struct device *dev) +{ + return 0; +} + + +static int __init +zasevb_probe(struct device *dev) +{ +} +static int +zasevb_suspend(struct device *dev, u32 state, u32 phase) +{ + return 0; +} + +static int +zasevb_resume(struct device *dev, u32 phase) +{ + return 0; +} + +static struct device_driver zasevb_driver = { + + .name = (char *) "ZASEVB-MXC-USBOTG, + .bus = &otg_bus_type, + + .probe = zasevb_probe, + .remove = zasevb_remove, + + .suspend = zasevb_suspend, + .resume = zasevb_resume, + +}; + + +static int __init zasevb_init(void) +{ + if (usb_disabled()) + return -ENODEV; + + INFO("driver %s, %s\n", hcd_name, DRIVER_VERSION); + return driver_register(&zasevb_driver); +} + +static void __exit zasevb_exit(void) +{ + driver_unregister(&zasevb_driver); +} +#endif + +module_init (zasevb_modinit); +module_exit (zasevb_modexit); diff --git a/drivers/otg/hardware/zasevb-mc13783.c b/drivers/otg/hardware/zasevb-mc13783.c new file mode 100644 index 000000000000..68d81df715c3 --- /dev/null +++ b/drivers/otg/hardware/zasevb-mc13783.c @@ -0,0 +1,317 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/hardware/zasevb-mc13783.c -- Freescale mc13783 Connectivity driver + * @(#) sl@belcarra.com/whiskey.enposte.net|otg/platform/zasevb/zasevb-mc13783.c|20070612232808|30186 + * + * Copyright (c) 2004-2005 Belcarra Technologies Corp + * + * By: + * Stuart Lynne <sl@lbelcarra.com>, + * Bruce Balden <balden@belcarra.com> + * + */ + +/*! + * @file otg/hardware/zasevb-mc13783.c + * @brief mc13783 USB Transceiver Driver + * + * This is a wrapper for the Freescale MC13783 driver to support it on the ZASEVB. + * + * Notes + * + * 1. Ensure that S2.1 is ON and S2.2 is OFF. + * + * 2. Note that this driver has not been tested on the ZAS EVB. + * + * 3. The MC13783 Transceiver and USBOTG HWMODE must both be setup to match. Currently it + * appears that only the SEO-SEO / DAT-Bidirectional combination works correctly. + * + * @ingroup ZASEVB + * @ingroup TCD + * + */ + +#include <asm/delay.h> + +#include <otg/pcd-include.h> +#if defined(CONFIG_OTG_ZASEVB_MC13783_CONNECTIVITY) || defined(_OTG_DOXYGEN) + +#include <asm/arch/gpio.h> + +#include "mxc-lnx.h" +#include "mxc-hardware.h" + +#include "isp1301.h" +#include "isp1301-hardware.h" + +/* + * These files are currently located in + * drivers/mxc/mc13783_legacy + */ +#include <core/mc13783_external.h> +#include <core/mc13783_event.h> +#include <module/mc13783_connectivity.h> + +/* ********************************************************************************************* */ +extern int mc13783_tcd_mod_init(struct otg_instance *otg); +extern void mc13783_tcd_mod_exit(struct otg_instance *otg); +struct tcd_instance *mc13783_tcd_instance; +/* ********************************************************************************************* */ +int mxc_mc13783_vbus(struct otg_instance *otg); +int mxc_mc13783_id(struct otg_instance *otg); +void mxc_mc13783_tcd_en(struct otg_instance *otg, u8 flag); +void mxc_mc13783_tcd_init(struct otg_instance *otg, u8 flag); +void mxc_mc13783_chrg_vbus(struct otg_instance *otg, u8 flag); +void mxc_mc13783_drv_vbus(struct otg_instance *otg, u8 flag); +void mxc_mc13783_dischrg_vbus(struct otg_instance *otg, u8 flag); +void mxc_mc13783_mx21_vbus_drain_func(struct otg_instance *otg, u8 flag); +void mxc_mc13783_dp_pullup_func(struct otg_instance *otg, u8 flag); +void mxc_mc13783_dm_pullup_func(struct otg_instance *otg, u8 flag); +void mxc_mc13783_dp_pulldown_func(struct otg_instance *otg, u8 flag); +void mxc_mc13783_dm_pulldown_func(struct otg_instance *otg, u8 flag); +void mxc_mc13783_peripheral_host_func(struct otg_instance *otg, u8 flag); +void mxc_mc13783_dm_det_func(struct otg_instance *otg, u8 flag); +void mxc_mc13783_dp_det_func(struct otg_instance *otg, u8 flag); +void mxc_mc13783_cr_det_func(struct otg_instance *otg, u8 flag); +void mxc_mc13783_bdis_acon_func(struct otg_instance *otg, u8 flag); +void mxc_mc13783_id_pulldown_func(struct otg_instance *otg, u8 flag); +void mxc_mc13783_audio_func(struct otg_instance *otg, u8 flag); +void mxc_mc13783_uart_func(struct otg_instance *otg, u8 flag); +void mxc_mc13783_mono_func(struct otg_instance *otg, u8 flag); +int mxc_mc13783_mod_init(struct otg_instance *); +void mxc_mc13783_mod_exit(struct otg_instance *); +void mxc_set_transceiver_mode(int); + +int mxc_iomux_gpio_mc13783_set(int); +int mxc_iomux_gpio_mc13783_reset(void); + +/* ********************************************************************************************* */ +#if !defined(OTG_C99) +struct tcd_ops tcd_ops; +/*! + * mc13783_tcd_global_init() - non c99 global initializer + */ +void mc13783_tcd_global_init(void) +{ + ZERO(tcd_ops); + + tcd_ops.id = mxc_mc13783_id; + tcd_ops.vbus = mxc_mc13783_vbus; + //tcd_ops.tcd_init_func = mx2_tcd_init; + + tcd_ops.tcd_init_func = mxc_mc13783_tcd_init; + tcd_ops.tcd_en_func = mxc_mc13783_tcd_en; + tcd_ops.chrg_vbus_func = mxc_mc13783_chrg_vbus; + tcd_ops.drv_vbus_func = mxc_mc13783_drv_vbus; + tcd_ops.dischrg_vbus_func = mxc_mc13783_dischrg_vbus; + tcd_ops.dp_pullup_func = mxc_mc13783_dp_pullup_func; + tcd_ops.dm_pullup_func = mxc_mc13783_dm_pullup_func; + tcd_ops.dp_pulldown_func = mxc_mc13783_dp_pulldown_func; + tcd_ops.dm_pulldown_func = mxc_mc13783_dm_pulldown_func; + + tcd_ops.overcurrent_func = NULL; + tcd_ops.dm_det_func = mxc_mc13783_dm_det_func; + tcd_ops.dp_det_func = mxc_mc13783_dp_det_func; + tcd_ops.cr_det_func = mxc_mc13783_cr_det_func; + //tcd_ops.charge_pump_func = mx2_charge_pump_func; + //tcd_ops.bdis_acon_func = mxc_mc13783_bdis_acon_func; + tcd_ops.peripheral_host_func = mxc_mc13783_peripheral_host_func; + tcd_ops.mx21_vbus_drain_func = mxc_mc13783_mx21_vbus_drain_func; + tcd_ops.id_pulldown_func = mxc_mc13783_id_pulldown_func; + tcd_ops.audio_func = mxc_mc13783_audio_func; + tcd_ops.uart_func = mxc_mc13783_uart_func; + tcd_ops.mono_func = mxc_mc13783_mono_func; + + tcd_ops.mod_init = mc13783_tcd_mod_init; + tcd_ops.mod_exit = mc13783_tcd_mod_exit; +} +#else /* !defined(OTG_C99) */ +struct tcd_ops tcd_ops = { + + //.id = mxc_mc13783_id, + //.vbus = mxc_mc13783_vbus, + //.tcd_init_func = mx2_tcd_init, + + .tcd_init_func = mxc_mc13783_tcd_init, + .tcd_en_func = mxc_mc13783_tcd_en, + .chrg_vbus_func = mxc_mc13783_chrg_vbus, + .drv_vbus_func = mxc_mc13783_drv_vbus, + .dischrg_vbus_func = mxc_mc13783_dischrg_vbus, + .dp_pullup_func = mxc_mc13783_dp_pullup_func, + .dm_pullup_func = mxc_mc13783_dm_pullup_func, + .dp_pulldown_func = mxc_mc13783_dp_pulldown_func, + .dm_pulldown_func = mxc_mc13783_dm_pulldown_func, + + .overcurrent_func = NULL, + .dm_det_func = mxc_mc13783_dm_det_func, + .dp_det_func = mxc_mc13783_dp_det_func, + .cr_det_func = mxc_mc13783_cr_det_func, + //.charge_pump_func = mx2_charge_pump_func, + //tcd_ops.bdis_acon_func = mxc_mc13783_bdis_acon_func, + .peripheral_host_func = mxc_mc13783_peripheral_host_func, + .mx21_vbus_drain_func = mxc_mc13783_mx21_vbus_drain_func, + .id_pulldown_func = mxc_mc13783_id_pulldown_func, + .audio_func = mxc_mc13783_audio_func, + .uart_func = mxc_mc13783_uart_func, + .mono_func = mxc_mc13783_mono_func, + + .mod_init = mc13783_tcd_mod_init, + .mod_exit = mc13783_tcd_mod_exit, +}; +#endif /* !defined(OTG_C99) */ + +/* ********************************************************************************************* */ +void mc13783_tcd_mod_exit(struct otg_instance *otg); + +/*! + * mc13783_tcd_mod_init() - initial tcd setup + * This performs the platform specific hardware setup for the MX2ADS. + * @param otg - otg_instance pointer + * + */ +int mc13783_tcd_mod_init(struct otg_instance *otg) +{ + int gpio = 1; + bool res; + unsigned int reg_value; + int i; + +#if 1 +#ifdef CONFIG_OTG_ZASEVB_DIFFERENTIAL_BIDIRECTIONAL + int hwmode = XCVR_D_D; + int newmode = XCVR_D_D; +#elif CONFIG_OTG_ZASEVB_DIFFERENTIAL_UNIDIRECTIONAL + int hwmode = XCVR_D_SE0_NEW; + int newmode = XCVR_D_D; +#elif CONFIG_OTG_ZASEVB_SINGLE_ENDED_UNIDIRECTIONAL + int hwmode = XCVR_SE0_D_NEW; + int newmode = XCVR_SE0_D_NEW; +#elif CONFIG_OTG_ZASEVB_SINGLE_ENDED_BIDIRECTIONAL + int hwmode = XCVR_SE0_SE0; + int newmode = XCVR_SE0_SE0; +#else +#error Please Configure Transceiver Mode +#endif /* CONFIG_OTG_ZASEVB_.... */ +#endif + + printk(KERN_INFO "%s: AAAA22\n", __FUNCTION__); + + TRACE_MSG0(REMOVE_TCD, "1. mc13783 Connectivity"); + + mxc_mc13783_mod_init(otg); + + TRACE_MSG0(REMOVE_TCD, "2. Transceiver setup"); + + switch (hwmode) { + case XCVR_D_D: + case XCVR_SE0_D_NEW: + case XCVR_D_SE0_NEW: + break; + + case XCVR_SE0_SE0: + // this works with XCVR_SE0_SE0 if AP_GPIO_AP_C16 not configured + //isp1301_configure(dat_se0_bidirectional, spd_susp_reg); // XCVR_SEO_SE0 + // XXX configure mc13783 transceiver here + break; + } + + //isp1301_configure(vp_vm_bidirectional, spd_susp_reg); // XCVR_D_D + + //TRACE_MSG0(REMOVE_TCD, "5. SET TCD OPS"); + //THROW_UNLESS(mc13783_tcd_instance = otg_set_tcd_ops(otg, &tcd_ops), error); + + mxc_iomux_gpio_mc13783_set(hwmode); + + switch (hwmode) { + case XCVR_D_SE0_NEW: + TRACE_MSG0(REMOVE_TCD, "D_D - vp_vm_bidirectional"); + printk(KERN_INFO "%s: D_D - Differential Unidirectional\n", + __FUNCTION__); + mc13783_convity_set_single_ended_mode(FALSE); + mc13783_convity_set_directional_mode(FALSE); + break; + case XCVR_SE0_D_NEW: + TRACE_MSG0(REMOVE_TCD, "SE0_D"); + printk(KERN_INFO "%s: SE0_D - Single Ended Unidirectional\n", + __FUNCTION__); + mc13783_convity_set_single_ended_mode(TRUE); + mc13783_convity_set_directional_mode(FALSE); + break; + case XCVR_D_D: + TRACE_MSG0(REMOVE_TCD, "D_SE0"); + printk(KERN_INFO "%s: D_SE0 - Differential Bidirectional\n", + __FUNCTION__); + mc13783_convity_set_single_ended_mode(FALSE); + mc13783_convity_set_directional_mode(TRUE); + break; + + case XCVR_SE0_SE0: + TRACE_MSG0(REMOVE_TCD, "SE0_SE0 - SEO_bidirectional"); + printk(KERN_INFO "%s: SE0_SE0 - Single Ended Bidirectional\n", + __FUNCTION__); + mc13783_convity_set_single_ended_mode(TRUE); + mc13783_convity_set_directional_mode(TRUE); + break; + } + + TRACE_MSG0(REMOVE_TCD, "7. SET HWMODE"); + mxc_set_transceiver_mode(newmode); + mc13783_convity_set_var_disconnect(TRUE); // variable 1k5 and UDP/UDM pull-down are disconnected. (PULLOVER) + mc13783_convity_set_usb_transceiver(TRUE); //USB transceiver is disabled (USBXCVREN) + mc13783_convity_set_udp_auto_connect(FALSE); //variable UDP is not automatically connected (SE0CONN) + mc13783_convity_set_pull_down_switch(PD_UDP_150, FALSE); //150K UDP pull-up switch is out (DP150KPU) + mc13783_convity_set_udp_pull(FALSE); //1.5K UDP pull-up and USB xcver is controlled by SPI bits.(USBCNTRL) + mc13783_convity_set_output(TRUE, FALSE); //disable vbus + mc13783_convity_set_output(FALSE, FALSE); //disable vusb + mc13783_convity_set_output(FALSE, TRUE); //enable vusb + +#if 1 + + for (i = 48; i < 51; i++) { + mc13783_read_reg(PRIO_CONN, i, ®_value); + printk(KERN_INFO "Register %d = %8X\n", i, reg_value); + } +#endif + + /* Success! */ + + TRACE_MSG0(REMOVE_TCD, "8. Success!"); + + CATCH(error) { + printk(KERN_INFO "%s: failed\n", __FUNCTION__); + //SHP + //UNLESS (gpio) gpio_free_irq (3, GPIO_PIN, GPIO_HIGH_PRIO); + return -EINVAL; + } + TRACE_MSG0(REMOVE_TCD, "MX2_MOD_TCD_INIT FINISHED"); + return 0; +} + +/*! + * mc13783cd_mod_exit() - de-initialize + * This is called from mx2-ocd.c + * @param otg - otg_instance pointer + */ +void mc13783_tcd_mod_exit(struct otg_instance *otg) +{ + //struct otg_instance *otg = tcd_instance->otg; + TRACE_MSG0(REMOVE_TCD, "MX2_MOD_TCD_EXIT"); + + mxc_iomux_gpio_mc13783_reset(); + + mxc_mc13783_mod_exit(otg); + mc13783_tcd_instance = otg_set_tcd_ops(otg, NULL); + +} +#endif /* CONFIG_OTG_ZASEVB_MC13783_CONNECTIVITY */ diff --git a/drivers/otg/hardware/zasevb-pmic.c b/drivers/otg/hardware/zasevb-pmic.c new file mode 100644 index 000000000000..23aefa2aa83d --- /dev/null +++ b/drivers/otg/hardware/zasevb-pmic.c @@ -0,0 +1,318 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/hardware/zasevb-mc13783.c -- Freescale mc13783 Connectivity driver + * @(#) balden@belcarra.com/seth2.rillanon.org|otg/platform/zasevb/zasevb-pmic.c|20070614183950|37596 + * + * Copyright (c) 2004-2005 Belcarra Technologies Corp + * + * By: + * Stuart Lynne <sl@lbelcarra.com>, + * Bruce Balden <balden@belcarra.com> + * + */ + +/*! + * @file otg/hardware/zasevb-pmic.c + * @brief mc13783 USB Transceiver Driver + * + * This is a wrapper for the Freescale MC13783 driver to support it on the ZASEVB. + * + * Notes + * + * 1. Ensure that S2.1 is ON and S2.2 is OFF. + * + * 2. Note that this driver has not been tested on the ZAS EVB. + * + * 3. The MC13783 Transceiver and USBOTG HWMODE must both be setup to match. Currently it + * appears that only the SEO-SEO / DAT-Bidirectional combination works correctly. + * + * @ingroup ZASEVB + * @ingroup TCD + * + */ + +#include <asm/delay.h> + +#include <otg/pcd-include.h> +#if defined(CONFIG_OTG_ZASEVB_MC13783_PMIC) || defined(_OTG_DOXYGEN) + +#include <asm/arch/gpio.h> + +#include "mxc-lnx.h" +#include "mxc-hardware.h" + +#include "isp1301.h" +#include "isp1301-hardware.h" + +/* + * These files are currently located in + * drivers/mxc/pmic + */ +#include <asm/arch/pmic_convity.h> /* For PMIC Connectivity driver interface. */ +#include <asm/arch/pmic_external.h> /* For PMIC Protocol driver interface. */ +//#include <core/pmic_event.h> + + + +/* ********************************************************************************************* */ +extern PMIC_CONVITY_HANDLE pmic_handle; +extern int pmic_tcd_mod_init(struct otg_instance *otg); +extern void pmic_tcd_mod_exit(struct otg_instance *otg); +struct tcd_instance *pmic_tcd_instance; +/* ********************************************************************************************* */ +int mxc_pmic_vbus(struct otg_instance *otg); +int mxc_pmic_id(struct otg_instance *otg); +void mxc_pmic_tcd_en(struct otg_instance *otg, u8 flag); +void mxc_pmic_tcd_init(struct otg_instance *otg, u8 flag); +void mxc_pmic_chrg_vbus(struct otg_instance *otg, u8 flag); +void mxc_pmic_drv_vbus(struct otg_instance *otg, u8 flag); +void mxc_pmic_dischrg_vbus(struct otg_instance *otg, u8 flag); +void mxc_pmic_mx21_vbus_drain_func(struct otg_instance *otg, u8 flag); +void mxc_pmic_dp_pullup_func(struct otg_instance *otg, u8 flag); +void mxc_pmic_dm_pullup_func(struct otg_instance *otg, u8 flag); +void mxc_pmic_dp_pulldown_func(struct otg_instance *otg, u8 flag); +void mxc_pmic_dm_pulldown_func(struct otg_instance *otg, u8 flag); +void mxc_pmic_peripheral_host_func(struct otg_instance *otg, u8 flag); +void mxc_pmic_dm_det_func(struct otg_instance *otg, u8 flag); +void mxc_pmic_dp_det_func(struct otg_instance *otg, u8 flag); +void mxc_pmic_cr_det_func(struct otg_instance *otg, u8 flag); +void mxc_pmic_bdis_acon_func(struct otg_instance *otg, u8 flag); +void mxc_pmic_id_pulldown_func(struct otg_instance *otg, u8 flag); +void mxc_pmic_audio_func(struct otg_instance *otg, u8 flag); +void mxc_pmic_uart_func(struct otg_instance *otg, u8 flag); +void mxc_pmic_mono_func(struct otg_instance *otg, u8 flag); +int mxc_pmic_mod_init(struct otg_instance *); +void mxc_pmic_mod_exit(struct otg_instance *); +void mxc_set_transceiver_mode(int); + +int mxc_iomux_gpio_mc13783_set(int); +int mxc_iomux_gpio_mc13783_reset(void); + +/* ********************************************************************************************* */ +#if !defined(OTG_C99) +struct tcd_ops tcd_ops; +/*! + * pmic_tcd_global_init() - non c99 global initializer + */ +void pmic_tcd_global_init(void) +{ + ZERO(tcd_ops); + + tcd_ops.id = mxc_pmic_id; + tcd_ops.vbus = mxc_pmic_vbus; + //tcd_ops.tcd_init_func = mx2_tcd_init; + + tcd_ops.tcd_init_func = mxc_pmic_tcd_init; + tcd_ops.tcd_en_func = mxc_pmic_tcd_en; + tcd_ops.chrg_vbus_func = mxc_pmic_chrg_vbus; + tcd_ops.drv_vbus_func = mxc_pmic_drv_vbus; + tcd_ops.dischrg_vbus_func = mxc_pmic_dischrg_vbus; + tcd_ops.dp_pullup_func = mxc_pmic_dp_pullup_func; + tcd_ops.dm_pullup_func = mxc_pmic_dm_pullup_func; + tcd_ops.dp_pulldown_func = mxc_pmic_dp_pulldown_func; + tcd_ops.dm_pulldown_func = mxc_pmic_dm_pulldown_func; + + tcd_ops.overcurrent_func = NULL; + tcd_ops.dm_det_func = mxc_pmic_dm_det_func; + tcd_ops.dp_det_func = mxc_pmic_dp_det_func; + tcd_ops.cr_det_func = mxc_pmic_cr_det_func; + //tcd_ops.charge_pump_func = mx2_charge_pump_func; + //tcd_ops.bdis_acon_func = mxc_pmic_bdis_acon_func; + tcd_ops.peripheral_host_func = mxc_pmic_peripheral_host_func; + tcd_ops.mx21_vbus_drain_func = mxc_pmic_mx21_vbus_drain_func; + tcd_ops.id_pulldown_func = mxc_pmic_id_pulldown_func; + tcd_ops.audio_func = mxc_pmic_audio_func; + tcd_ops.uart_func = mxc_pmic_uart_func; + tcd_ops.mono_func = mxc_pmic_mono_func; + + tcd_ops.mod_init = pmic_tcd_mod_init; + tcd_ops.mod_exit = pmic_tcd_mod_exit; +} +#else /* !defined(OTG_C99) */ +struct tcd_ops tcd_ops = { + + //.id = mxc_pmic_id, + //.vbus = mxc_pmic_vbus, + //.tcd_init_func = mx2_tcd_init, + + .tcd_init_func = mxc_pmic_tcd_init, + .tcd_en_func = mxc_pmic_tcd_en, + .chrg_vbus_func = mxc_pmic_chrg_vbus, + .drv_vbus_func = mxc_pmic_drv_vbus, + .dischrg_vbus_func = mxc_pmic_dischrg_vbus, + .dp_pullup_func = mxc_pmic_dp_pullup_func, + .dm_pullup_func = mxc_pmic_dm_pullup_func, + .dp_pulldown_func = mxc_pmic_dp_pulldown_func, + .dm_pulldown_func = mxc_pmic_dm_pulldown_func, + + .overcurrent_func = NULL, + .dm_det_func = mxc_pmic_dm_det_func, + .dp_det_func = mxc_pmic_dp_det_func, + .cr_det_func = mxc_pmic_cr_det_func, + //.charge_pump_func = mx2_charge_pump_func, + //tcd_ops.bdis_acon_func = mxc_pmic_bdis_acon_func, + .peripheral_host_func = mxc_pmic_peripheral_host_func, + .mx21_vbus_drain_func = mxc_pmic_mx21_vbus_drain_func, + .id_pulldown_func = mxc_pmic_id_pulldown_func, + .audio_func = mxc_pmic_audio_func, + .uart_func = mxc_pmic_uart_func, + .mono_func = mxc_pmic_mono_func, + + .mod_init = pmic_tcd_mod_init, + .mod_exit = pmic_tcd_mod_exit, +}; +#endif /* !defined(OTG_C99) */ + +/* ********************************************************************************************* */ +void pmic_tcd_mod_exit(struct otg_instance *otg); + +/*! + * pmic_tcd_mod_init() - initial tcd setup + * This performs the platform specific hardware setup for the MX2ADS. + * @param otg - otg_instance pointer + * + */ +int pmic_tcd_mod_init(struct otg_instance *otg) +{ + int gpio = 1; + bool res; + unsigned int reg_value, reg_mask = 0xfffff; + int i; +#if 1 +#ifdef CONFIG_OTG_ZASEVB_DIFFERENTIAL_BIDIRECTIONAL + int hwmode = XCVR_D_D; + int newmode = XCVR_D_D; +#elif CONFIG_OTG_ZASEVB_DIFFERENTIAL_UNIDIRECTIONAL + int hwmode = XCVR_D_SE0_NEW; + int newmode = XCVR_D_D; +#elif CONFIG_OTG_ZASEVB_SINGLE_ENDED_UNIDIRECTIONAL + int hwmode = XCVR_SE0_D_NEW; + int newmode = XCVR_SE0_D_NEW; +#elif CONFIG_OTG_ZASEVB_SINGLE_ENDED_BIDIRECTIONAL + int hwmode = XCVR_SE0_SE0; + int newmode = XCVR_SE0_SE0; +#else +#error Please Configure Transceiver Mode +#endif /* CONFIG_OTG_ZASEVB_.... */ +#endif + + printk(KERN_INFO "%s: AAAA22\n", __FUNCTION__); + + TRACE_MSG0(REMOVE_TCD, "1. mc13783 Connectivity"); + + mxc_pmic_mod_init(otg); + + TRACE_MSG0(REMOVE_TCD, "2. Transceiver setup"); + + switch (hwmode) { + case XCVR_D_D: + case XCVR_SE0_D_NEW: + case XCVR_D_SE0_NEW: + break; + + case XCVR_SE0_SE0: + // this works with XCVR_SE0_SE0 if AP_GPIO_AP_C16 not configured + //isp1301_configure(dat_se0_bidirectional, spd_susp_reg); // XCVR_SEO_SE0 + // XXX configure mc13783 transceiver here + break; + } + + //isp1301_configure(vp_vm_bidirectional, spd_susp_reg); // XCVR_D_D + + //TRACE_MSG0(REMOVE_TCD, "5. SET TCD OPS"); + //THROW_UNLESS(mc13783_tcd_instance = otg_set_tcd_ops(otg, &tcd_ops), error); + + mxc_iomux_gpio_mc13783_set(hwmode); + + switch (hwmode) { + case XCVR_D_SE0_NEW: + TRACE_MSG0(REMOVE_TCD, "D_D - vp_vm_bidirectional"); + printk(KERN_INFO "%s: D_D - Differential Unidirectional\n", + __FUNCTION__); + pmic_convity_usb_otg_set_config(pmic_handle, USB_OTG_SE0CONN); + pmic_convity_usb_set_xcvr(pmic_handle, USB_DIFFERENTIAL_UNIDIR); + break; + case XCVR_SE0_D_NEW: + TRACE_MSG0(REMOVE_TCD, "SE0_D"); + printk(KERN_INFO "%s: SE0_D - Single Ended Unidirectional\n", + __FUNCTION__); + pmic_convity_usb_set_xcvr(pmic_handle, USB_SINGLE_ENDED_UNIDIR); + break; + case XCVR_D_D: + TRACE_MSG0(REMOVE_TCD, "D_SE0"); + printk(KERN_INFO "%s: D_SE0 - Differential Bidirectional\n", + __FUNCTION__); + pmic_convity_usb_set_xcvr(pmic_handle, USB_DIFFERENTIAL_BIDIR); + break; + + case XCVR_SE0_SE0: + TRACE_MSG0(REMOVE_TCD, "SE0_SE0 - SEO_bidirectional"); + printk(KERN_INFO "%s: SE0_SE0 - Single Ended Bidirectional\n", + __FUNCTION__); + pmic_convity_usb_set_xcvr(pmic_handle, USB_SINGLE_ENDED_BIDIR); + break; + } + + TRACE_MSG0(REMOVE_TCD, "7. SET HWMODE"); + + mxc_set_transceiver_mode(newmode); + pmic_convity_usb_otg_set_config(pmic_handle, USB_PULL_OVERRIDE); // variable 1k5 and UDP/UDM pull-down are disconnected. (PULLOVER) + pmic_convity_usb_otg_set_config(pmic_handle, USBXCVREN); //USB transceiver is disabled (USBXCVREN) + pmic_convity_usb_otg_clear_config(pmic_handle, USB_OTG_SE0CONN); //variable UDP is not automatically connected (SE0CONN) + pmic_convity_usb_otg_clear_config(pmic_handle, USB_DP150K_PU); + pmic_convity_usb_otg_clear_config(pmic_handle, USB_USBCNTRL); + pmic_convity_set_output(pmic_handle, TRUE, FALSE); //disable vbus TBD + pmic_convity_set_output(pmic_handle, FALSE, FALSE); //disable vusb TBD + pmic_convity_set_output(pmic_handle, FALSE, TRUE); //enable vusb TBD + + +#if 1 + + for (i = 0; i < 3; i++) { + pmic_read_reg(REG_CHARGER + i, ®_value, reg_mask); + printk(KERN_INFO "Register %d = %8X\n", REG_CHARGER + i, reg_value); + } +#endif + + /* Success! */ + + TRACE_MSG0(REMOVE_TCD, "8. Success!"); + + CATCH(error) { + printk(KERN_INFO "%s: failed\n", __FUNCTION__); + //SHP + //UNLESS (gpio) gpio_free_irq (3, GPIO_PIN, GPIO_HIGH_PRIO); + return -EINVAL; + } + TRACE_MSG0(REMOVE_TCD, "MX2_MOD_TCD_INIT FINISHED"); + return 0; +} + +/*! + * pmic_tcd_mod_exit() - de-initialize + * This is called from mx2-ocd.c + * @param otg - otg_instance pointer + */ +void pmic_tcd_mod_exit(struct otg_instance *otg) +{ + //struct otg_instance *otg = tcd_instance->otg; + TRACE_MSG0(REMOVE_TCD, "MX2_MOD_TCD_EXIT"); + + mxc_iomux_gpio_mc13783_reset(); + + mxc_pmic_mod_exit(otg); + pmic_tcd_instance = otg_set_tcd_ops(otg, NULL); + +} +#endif /* CONFIG_OTG_ZASEVB_MC13783_CONNECTIVITY */ diff --git a/drivers/otg/otg-config-std.h b/drivers/otg/otg-config-std.h new file mode 100644 index 000000000000..a6ae61785ba6 --- /dev/null +++ b/drivers/otg/otg-config-std.h @@ -0,0 +1,95 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * @(#) balden@belcarra.com|otg/otg-config-std.h|20060419204305|26216 + * + */ + + + +/* + * config OTG_USB_PERIPHERAL + * bool "USB Peripheral (only)" + * Compile as a standard USB Peripheral. + */ +/* #define OTG_USB_PERIPHERAL */ + +/* + * config OTG_USB_HOST + * bool "USB Host (only)" + * Compile as a standard USB Host. + */ +/* #define OTG_USB_HOST */ + +/* + * config OTG_USB_PERIPHERAL_OR_HOST + * bool "USB Peripheral or Host" + * Compile as a standard USB Peripheral and Host. + * The transceiver driver must implement ID_GND to switch between + * host and peripheral roles. + */ +/* #define OTG_USB_PERIPHERAL_OR_HOST */ + +/* + * config OTG_BDEVICE_WITH_SRP + * bool "SRP Capable B-Device (Only)" + * Compile as a On-The-Go Peripheral-Only SRP capable device. This + * is similiar to a Traditional USB Peripheral but enables + * On-The-Go features such as SRP. + */ +/* #define OTG_BDEVICE_WITH_SRP */ + +/* + * config OTG_DEVICE + * bool "OTG Device - can act as A or B Device" + * Implement full On-The-Go Device support for a platform that + * supports implemenation of A and B Device. + */ +/* #define OTG_DEVICE */ + + +/* + * bool 'OTG Fast Tracing' + * This option implements register trace to support + * driver debugging. + */ +/* #define CONFIG_OTG_TRACE */ + + + +/* + * bool 'Built-in Minimal USB Device' + * Compile in minimal USB Device only firmware. + */ +/* #define CONFIG_OTG_FW_MN */ + +/* + * bool 'Enable Auto-Start' + * Automatically start and enable minimal USB Device. + */ +/* #define CONFIG_OTG_TR_AUTO */ + +/* + * bool 'Disable C99 initializers' + * If your compiler does not allow a structure to be initialized as .element_name=value + */ +/* #define CONFIG_OTG_NOC99 */ + +/* + * boolean "USB Host - OTG Support" + * The most notable feature of USB OTG is support for a + * "Dual-Role" device, which can act as either a device + * or a host. The initial role choice can be changed + * later, when two dual-role devices talk to each other. + */ +/* #define CONFIG_USB_OTG */ diff --git a/drivers/otg/otg/hcd-hw.h b/drivers/otg/otg/hcd-hw.h new file mode 100644 index 000000000000..0e110dda055f --- /dev/null +++ b/drivers/otg/otg/hcd-hw.h @@ -0,0 +1,114 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/hcd/hc_xfer_hw.h - Generic transfer level USBOTG aware Host Controller Driver (HCD) + * @(#) sl@belcarra.com/whiskey.enposte.net|otg/otg/hcd-hw.h|20061017072623|49375 + * + * Copyright (c) 2004-2005 Belcarra Technologies + * Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + * + * By: + * Stuart Lynne <sl@belcarra.com>, + * Bruce Balden <balden@belcarra.com> + * + */ +/*! + * @file otg/otg/hcd-hw.h + * @brief Implements Generic Host Controller Driver + * + * @ingroup HCD + */ +#ifndef HC_XFER_HW_H +#define HC_XFER_HW_H 1 + +/*===========================================================================* + * Functions provided by the hardware specific component (whatever HW it is). + * Each HW specific component provides these functions. + *===========================================================================*/ + +/*===========================================================================* + * For the generic transfer aware framework. + *===========================================================================*/ + +/* + * hcd_hw_max_active_transfers() + * Returns: the (constant) maximum number of concurrently active transfers the HW will handle. + */ +extern int hcd_hw_max_active_transfers(struct bus_hcpriv *bus_hcpriv); + +/* + * hcd_hw_start_transfer() + * Returns: tid >= 0 when the transfer starts - this is an index in [0..hcd_hw_max_active_transfers] + * -1 when the transfer is invalid, + * -2 when there are no resources for the transfer currently available. + */ +extern int hcd_hw_start_transfer(struct bus_hcpriv *bus_hcpriv, + int len, // length of data region + void *data, // virtual address of data region + int toggle, // toggle value to start the xfer with + int maxps, // max packet size of endpoint + int slow, // 1 ==> slow speed, 0 ==> full speed QQSV verify: (((urb->pipe) >> 26) & 1) + int endpoint, // endpoint number + int address, // USB device address + int pid, // {PID_OUT, PID_IN, PID_SETUP} + int format, // PIPE_{CONTROL,BULK,INTERRUPT,ISOCHRONOUS} + u32 other); // Values depending on format + +/* + * hcd_hw_unlink_urb() + * Called in irq_lock, calls back to xhc_transfer_complete(). + * Returns: 0 when transfer is unlinked, non-zero otherwise. + */ +extern int hcd_hw_unlink_urb(struct bus_hcpriv *bus_hcpriv, int tid); // tid is value returned by hcd_hw_start_transfer() + +extern u32 hcd_hw_frame_number(struct bus_hcpriv *bus_hcpriv); + +extern void hcd_hw_enable_interrupts(struct bus_hcpriv *bus_hcpriv); +extern void hcd_hw_disable_interrupts(struct bus_hcpriv *bus_hcpriv); + +extern void hcd_hw_get_ops(struct bus_hcpriv *bus_hcpriv); + +extern int hcd_hw_init(struct bus_hcpriv *bus_hcpriv); + +extern void hcd_hw_exit(struct bus_hcpriv *bus_hcpriv); + +/*===========================================================================* + * For the virtual root hub. + *===========================================================================*/ + +extern int hcd_hw_rh_num_ports(struct bus_hcpriv *bus_hcpriv); +extern u8 hcd_hw_rh_otg_capable_mask(struct bus_hcpriv *bus_hcpriv); + +extern char *hcd_hw_rh_string(struct bus_hcpriv *bus_hcpriv, int strno); +extern u16 hcd_hw_rh_idVendor(struct bus_hcpriv *bus_hcpriv); +extern u16 hcd_hw_rh_idProduct(struct bus_hcpriv *bus_hcpriv); +extern u16 hcd_hw_rh_bcdDevice(struct bus_hcpriv *bus_hcpriv); +extern u8 hcd_hw_rh_cfg_bmAttributes(struct bus_hcpriv *bus_hcpriv); +extern u8 hcd_hw_rh_cfg_MaxPower(struct bus_hcpriv *bus_hcpriv); +extern u16 hcd_hw_rh_hub_attributes(struct bus_hcpriv *bus_hcpriv); +extern u8 hcd_hw_rh_power_delay(struct bus_hcpriv *bus_hcpriv); +extern u8 hcd_hw_rh_hub_contr_current(struct bus_hcpriv *bus_hcpriv); +extern u8 hcd_hw_rh_DeviceRemovable(struct bus_hcpriv *bus_hcpriv); +extern u8 hcd_hw_rh_PortPwrCtrlMask(struct bus_hcpriv *bus_hcpriv); +extern u32 hcd_hw_rh_get_hub_change_status(struct bus_hcpriv *bus_hcpriv); +extern void hcd_hw_rh_hub_feature(struct bus_hcpriv *bus_hcpriv, int feat_selector, int set_flag); +extern void hcd_hw_hcd_en_func(struct otg_instance *oi, u8 on); +/* + * Note: in the following functions, portnum is 0-origin. + * (I.e., valid range is [0..(hcd_hw_rh_num_ports-1)].) + */ +extern u32 hcd_hw_rh_get_port_change_status(struct bus_hcpriv *bus_hcpriv, int portnum); +extern void hcd_hw_rh_port_feature(struct bus_hcpriv *bus_hcpriv, u16 wValue, u16 wIndex, int set_flag); + + +#endif diff --git a/drivers/otg/otg/hcd-l26.h b/drivers/otg/otg/hcd-l26.h new file mode 100644 index 000000000000..c11502ce969c --- /dev/null +++ b/drivers/otg/otg/hcd-l26.h @@ -0,0 +1,163 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/hcd/hc_xfer.h - Generic transfer level USBOTG aware Host Controller Driver (HCD) + * @(#) sl@belcarra.com/whiskey.enposte.net|otg/otg/hcd-l26.h|20061017072623|12456 + * + * Copyright (c) 2004-2005 Belcarra Technologies Corp + * Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + * + * By: + * Stuart Lynne <sl@belcarra.com>, + * Bruce Balden <balden@belcarra.com> + * + */ +/*! + * @file otg/otg/hcd-l26.h + * @brief Implements Linux version of Generic Host Controller driver + * + * @ingroup HCD + */ +#ifndef HC_XFER_H +#define HC_XFER_H 1 + +/*===========================================================================* + * Data structures and functions provided by the generic transfer aware + * host controller framework. + * + * The generic framework must be linked with a hardware specific component, + * whose functions are specified in hc_xfer_hw.h, and a generic root hub + * component, whose functions are specified in hc_xfer_rh.h. + * + * The resulting module provides a table of operations to the usb core layer, + * passed to the core layer by a call to usb_alloc_bus(). + *===========================================================================*/ + +/*! @name Descriptor sizes per descriptor type + */ + /*! @{ */ + +// XXX this should be fixed.... + +#define USB_DT_DEVICE_SIZE 18 +#define USB_DT_CONFIG_SIZE 9 +#define USB_DT_INTERFACE_SIZE 9 +#define USB_DT_ENDPOINT_SIZE 7 +#define USB_DT_ENDPOINT_AUDIO_SIZE 9 /* Audio extension */ +#define USB_DT_HUB_NONVAR_SIZE 7 +#define USB_DT_HID_SIZE 9 +/*! @} */ + + +#define XHC_RH_DESCRIPTORS_SIZE (USB_DT_DEVICE_SIZE+USB_DT_CONFIG_SIZE+USB_DT_INTERFACE_SIZE+USB_DT_ENDPOINT_SIZE+USB_DT_HUB_NONVAR_SIZE+2) + +/*! typedef struct rh_hcpriv rh_hcpriv_t + *@brief - encapsulation of root hub relatied variables + */ + +typedef struct rh_hcpriv { + struct urb *int_urb; + struct usbd_device_descriptor *dd; + struct usbd_configuration_descriptor *cd; + struct usbd_interface_descriptor *id; + struct usbd_endpoint_descriptor *ed; + struct hub_descriptor *hd; + struct timer_list poll_timer; + //int poll_jiffies; + struct usb_device *dev; + int dev_registered; + u32 hub_change_status; // For shadowing the HW + u32 *port_change_status; // An array of [num_ports] values for shadowing the HW + u32 hub_port_change_status; + struct WORK_STRUCT psc_bh; + u8 curr_cfg; + u8 curr_itf; + //u8 num_ports; + u8 otg_device_mask; // (1 << port_num) bit set iff port_num is acting as a device, not host, and so not usable + u8 descriptors[XHC_RH_DESCRIPTORS_SIZE]; + int suspended; +} rh_hcpriv_t; + +/*! typedef struct bus_hcpriv bus_hcpriv_t + * @brief -encapsualtion of bus related host controller variables + */ + +typedef struct bus_hcpriv { // XFER level Host Controller Info + struct usb_bus *usb_bus; +#if 1 //SHP + struct usb_hcd hcd; +#endif + void *hw_hci; // HW specific information for this HC + int root_hub_addr; // Initially 0, set by the virtual root hub when addressed. + struct rh_hcpriv *rh_hcpriv; // Generic root hub info (see hc_xfer_rh.[hc]) + int terminating; // Boolean, TRUE if shutting down. + //struct hcd_instance *hcd; // OTG Controller Info + int max_active_urbs; + struct urb **active_urbs; + int device_registered; // bus_device status + struct device bus_device; // Linux Driver Model info. + //struct hcd_ops otg_ops; // operations for OTG component. + struct usb_driver *usb_driver; + + u8 num_ports; + u8 otg_port; + u8 otg_capable_mask; + u16 rh_vendorid; + u16 rh_productid; + u16 rh_bcddevice; + char *rh_serial; + char *rh_product; + char *rh_manufacturer; + u8 rh_bmAttributes; + u8 rh_bMaxPower; + + struct usb_device *roothub_dev; + struct usb_device *first_dev; + + int max_active_transfers; + +} bus_hcpriv_t; + +/*! typedef struct dev_hcpriv dev_hcpriv_t + * @brief encapsulation about device related host controller globals + */ + +typedef struct dev_hcpriv { // XFER level HCI per attached device info + struct usb_device *dev; + struct list_head queued_urbs_both[2][16]; + u32 num_urbs_both[2][16]; + u8 epq_state_both[2][16]; +} dev_hcpriv_t; + +#define EPQ_EMPTY 0 +#define EPQ_RUNNING 1 +#define EPQ_WAITING 2 + +/*===========================================================================* + * For the hardware specific component. + *===========================================================================*/ + +extern void hcd_transfer_complete(struct bus_hcpriv *bus_hcpriv, int transfer_id, int format, + int cc, u32 remaining, int next_toggle); + +/*===========================================================================* + * For the virtual root hub. + *===========================================================================*/ + +extern void hcd_rh_urb_complete(struct bus_hcpriv *bus_hcpriv, struct urb *urb); + +#include "../otg/otg-trace.h" +extern otg_tag_t xfer_hci_trace_tag; + + +#endif diff --git a/drivers/otg/otg/hcd-rh.h b/drivers/otg/otg/hcd-rh.h new file mode 100644 index 000000000000..86248785a7e6 --- /dev/null +++ b/drivers/otg/otg/hcd-rh.h @@ -0,0 +1,68 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/hcd/hc_xfer_rh.h - Generic transfer level USBOTG aware Host Controller Driver (HCD) + * @(#) sl@belcarra.com/whiskey.enposte.net|otg/otg/hcd-rh.h|20061017072623|12102 + * + * Copyright (c) 2004-2005 Belcarra Technologies + * Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + * + * By: + * Stuart Lynne <sl@belcarra.com>, + * Bruce Balden <balden@belcarra.com> + * + */ +/*! + * @file otg/otg/hcd-rh.h + * @brief Implements Generic Root Hub function for Generic Host Controller Driver. + * + * @ingroup HCD + */ +#ifndef HC_XFER_RH_H +#define HC_XFER_RH_H 1 + +#define XHC_RH_STRING_CONFIGURATION 1 +#define XHC_RH_STRING_INTERFACE 2 +#define XHC_RH_STRING_SERIAL 3 +#define XHC_RH_STRING_PRODUCT 4 +#define XHC_RH_STRING_MANUFACTURER 5 + +/*===========================================================================* + * Functions provided by the generic virtual root hub. + *===========================================================================*/ + +/*===========================================================================* + * For the generic transfer aware framework. + *===========================================================================*/ + +/* + * hcd_rh_submit_urb() + * Returns: values as for the generic submit_urb() function. + */ +extern int hcd_rh_submit_urb(struct bus_hcpriv *bus_hcpriv, struct urb *urb, int mem_flags); + +extern int hcd_rh_unlink_urb(struct bus_hcpriv *bus_hcpriv, struct urb *urb); + +extern void hcd_rh_get_ops(struct bus_hcpriv *bus_hcpriv); + +extern int hcd_rh_init(struct bus_hcpriv *bus_hcpriv); + +extern void hcd_rh_exit(struct bus_hcpriv *bus_hcpriv); + +/*===========================================================================* + * For the hardware specific root hub component. + *===========================================================================*/ + +extern irqreturn_t hcd_rh_int_hndlr(int irq, void *dev_id, struct pt_regs *regs); + +#endif diff --git a/drivers/otg/otg/otg-api.h b/drivers/otg/otg/otg-api.h new file mode 100644 index 000000000000..8742296058a8 --- /dev/null +++ b/drivers/otg/otg/otg-api.h @@ -0,0 +1,375 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/otg-api.h + * @(#) sl@belcarra.com/whiskey.enposte.net|otg/otg/otg-api.h|20070125083535|47004 + * + * Copyright (c) 2004-2005 Belcarra Technologies Corp + * Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + * + * By: + * Stuart Lynne <sl@belcarra.com>, + * Bruce Balden <balden@belcarra.com> + * + */ +/* + * Doxygen group definitions - N.B. These much each be in + * their own separate comment... + */ + +/*! + * @defgroup FunctionDrivers USB Function Drivers + * @brief These are the USB Function Drivers for Belcarra USBOTG. + */ + /*! + * @defgroup CompositeFunctions Composite USB Functions + * @ingroup FunctionDrivers + * @brief These are the USB Composite Function Drivers for Belcarra USBOTG. + */ + /*! + * @defgroup InterfaceFunctions Interface USB Functions + * @ingroup FunctionDrivers + * @brief These are the USB Interface Function Drivers for Belcarra USBOTG. + */ + /*! + * @defgroup ClassFunctions Class USB Functions + * @ingroup FunctionDrivers + * @brief These are the USB Class Function Drivers for Belcarra USBOTG. + */ + /*! + * @defgroup SimpleFunctions Simple USB Functions + * @ingroup FunctionDrivers + * @brief These are the USB Simple Function Drivers for Belcarra USBOTG. + */ + + +/*! + * @defgroup USBDOTG USBOTG + */ + /*! + * @defgroup USBDAPI USBD Function Driver API + * @ingroup USBDOTG + * @brief This is the USBD API. + */ + /*! + * @defgroup OTGAPI OTG Driver API + * @ingroup USBDOTG + * @brief This is the OTG State Machine API. + */ + /*! + * @defgroup USBDCORE USBD Core + * @ingroup USBDOTG + * @brief This implements the USBD API. + */ + /*! + * @defgroup OTGCORE OTG State Machine + * @ingroup USBDOTG + * @brief This implements the OTG State Machine API. + */ + /*! + * @defgroup OTGINIT OTG Core Init + * @ingroup USBDOTG + * @brief This implements the OTG initialization. + */ + /*! + * @defgroup OTGMESG OTG Core Mesg + * @ingroup USBDOTG + * @brief This implements the OTG messaging facility. + */ + /*! + * @defgroup OTGTRACE OTG Core Trace + * @ingroup USBDOTG + * @brief This implements the OTG trace facility. + */ +#if 0 + /*! + * @defgroup OTGFW OTG Firmware + * @ingroup USBDOTG + * @brief This implements the OTG State Machine Firmware. + */ +#endif +/*! + * @defgroup OSPort OS Support + */ + /*! + * @defgroup OSAPI OTG OS Support API + * @ingroup OSPort + * @brief This defines the OTG OS API. + */ + /*! + * @defgroup LINUXAPI Linux OTG OS Support API + * @ingroup OSPort + * @brief This defines the Linux version of the OTG OS API. + */ + /*! + * @defgroup LINUXOS Linux OTG OS Support + * @ingroup OSPort + * @brief This implements the Linux OTG OS API. + */ + +/*! + * @defgroup Platform Platform + */ +/*! + * @defgroup Hardware Platform Specific Hardware + * @ingroup Platform + * @brief The Platform specific hardware drivers. + */ + /*! + * @defgroup PCD Peripheral Controller Driver + * @ingroup Platform + * @brief The Peripheral Controller Driver controls the USB Device Controller hardware. + */ + /*! + * @defgroup TCD Transceiver Controller Driver + * @ingroup Platform + * @brief The Transceiver Controller Driver controls the USB and/or OTG Transceiver Controller hardware. + */ + /*! + * @defgroup HCD Host Controller Driver + * @ingroup Platform + * @brief The Host Controller Driver controls the USB Host Controller hardware. + */ + /*! + * @defgroup OCD OTG Controller Driver + * @ingroup Platform + * @brief The OTG Controller Driver controls the OTG Transceiver Controller hardware that is common to + * both PCD and HCD. + */ + + +/*! + * @file otg/otg/otg-api.h + * @brief Core Defines for USB OTG Core Layaer + * + * @ingroup OTGAPI + */ + + + + +/*! + * @name OTGCORE OTG API Definitions + * This contains the OTG API structures and definitions. + * @{ + */ + +#include <otg/otg-fw.h> + +#if defined(CONFIG_OTG_USB_PERIPHERAL) || defined (CONFIG_OTG_USB_HOST) || defined(CONFIG_OTG_USB_PERIPHERAL_OR_HOST) +#include <otg/otg-fw-mn.h> +#elif defined(CONFIG_OTG_BDEVICE_WITH_SRP) || defined(CONFIG_OTG_DEVICE) +#include <otg/otg-fw-df.h> +#else /* error */ +//#abort "Missing USB or OTG configuration" +#endif + + +struct otg_instance; +typedef void (*otg_output_proc_t) (struct otg_instance *, u8); + +extern struct otg_firmware *otg_firmware_loaded; +extern struct otg_firmware *otg_firmware_orig; +extern struct otg_firmware *otg_firmware_loading; + + +char * otg_get_state_name(int state); + +typedef u64 otg_current_t; + +/*! + * @struct otg_instance otg-api.h "otg/otg-api.h" + * + * This tracks the current OTG configuration and state + */ +struct otg_instance { + + u16 state; /*!< current state */ + int previous; /*!< previous state */ + + int active; /*!< used as semaphore */ + + u32 current_inputs; /*!< input settings */ + otg_current_t outputs; /*!< output settings */ + + otg_tick_t tickcount; /*!< when we transitioned to current state */ + otg_current_t bconn; /*!< when b_conn was set */ + + struct otg_state *current_outputs; /*!< output table entry for current state */ + struct otg_state *previous_outputs; /*!< output table entry for current state */ + + otg_output_proc_t otg_output_ops[MAX_OUTPUTS]; /*!< array of functions mapped to output numbers */ + + int (*start_timer) (struct otg_instance *, int); /*!< OCD function to start timer */ + otg_tick_t (*ticks) (void); /*!< OCD function to return ticks */ + otg_tick_t (*elapsed) ( otg_tick_t *, otg_tick_t *); /*!< OCD function to return elapsed ticks */ + + struct hcd_instance* hcd; /*!< pointer to hcd instance */ + struct ocd_instance* ocd; /*!< pointer to ocd instance */ + struct pcd_instance* pcd; /*!< pointer to pcd instance */ + struct tcd_instance* tcd; /*!< pointer to tcd instance */ + + struct hcd_ops* hcd_ops; + struct ocd_ops* ocd_ops; + struct pcd_ops* pcd_ops; + struct tcd_ops* tcd_ops; + + + char function_name[OTGADMIN_MAXSTR]; /*!< current function */ + char serial_number[OTGADMIN_MAXSTR]; /*!< current serial number */ + + u32 interrupts; /*!< track number of interrupts */ + + //#if 0 + struct otg_tasklet *tasklet; + //#else + struct otg_task *task; + //otg_sem_t event; + //#endif + otg_sem_t command; + struct otg_task *message_task; + + otg_pthread_mutex_t mutex; + + otg_tag_t TAG; + + void *privdata; +}; + +typedef int (*otg_event_t) (struct otg_instance *, int, char *); +/*! + * @struct otg_event_info otg-api.h "otg/otg-api.h" + */ + +struct otg_event_info { + char *name; /*< otg event name */ + otg_event_t event; /*< otg event */ +}; + +//typedef u16 (*framenum_t)(struct otg_instance *); +typedef u16 (*framenum_t)(struct otg_instance *otg); + +/* 1 - used by external functions to pass in administrative commands as simple strings + */ +extern int otg_status(int, u32, u32, char *, int); + +/* 2 + */ +struct otg_instance *otg_create(void); +void otg_destroy(struct otg_instance *otg); + + +/* 3 - used internally by any OTG stack component to single event from non-interrupt context, + */ +extern void otg_queue_event(struct otg_instance *, otg_current_t, otg_tag_t, char *); +extern void otg_event(struct otg_instance *, otg_current_t, otg_tag_t, char *); +extern void otg_event_new(struct otg_instance *, otg_current_t, otg_tag_t, char *); + +/* 5 - used by PCD/TCD drivers to signal various OTG Transceiver events + */ +void otg_event_set_irq(struct otg_instance *, int, int, u32, otg_tag_t, char *); + + +extern void otg_serial_number(struct otg_instance *otg, char *serial_number_str); +extern void otg_init (struct otg_instance *otg); +extern void otg_exit (struct otg_instance *otg); + +/* message + */ +#if defined(CONFIG_OTG_LNX) +extern int otg_message_init_l24(struct otg_instance *); +extern void otg_message_exit_l24(struct otg_instance *); +#endif /* defined(OTG_LNX) */ + +#if defined(CONFIG_OTG_QNX) +extern int otg_message_init_qnx(struct otg_instance *); +extern void otg_message_exit_qnx(struct otg_instance *); +#endif /* defined(CONFIG_OTG_QNX) */ + +extern int otg_message_init(struct otg_instance *); +extern void otg_message_exit(void); + +//extern int otg_write_message_irq(char *buf, int size); +extern int otg_write_message(struct otg_instance *otg, char *buf, int size); +extern int otg_read_message(char *buf, int size); +extern int otg_data_queued(void); +extern unsigned int otg_message_block(void); +//extern unsigned int otg_message_poll(struct file *, struct poll_table_struct *); + + +/* trace + */ +extern int otg_trace_init (void); +extern void otg_trace_exit (void); +#if defined(OTG_LINUX) +extern int otg_trace_init_l24(void); +extern void otg_trace_exit_l24(void); +#endif /* defined(OTG_LINUX) */ + +#if defined(OTG_WINCE) +extern int otg_trace_init_w42(void); +extern void otg_trace_exit_w42(void); +#endif /* defined(OTG_WINCE) */ + +extern int otgtrace_init (void); +extern void otgtrace_exit (void); +extern int otg_trace_proc_read (char *page, int count, int * pos); +extern int otg_trace_proc_write (const char *buf, int count, int * pos); + +/* usbp + */ +extern int usbd_device_init (void); +extern void usbd_device_exit (void); + + +/* + * otgcore/otg-mesg.c + */ + +extern void otg_message(struct otg_instance *otg, char *buf); +extern void otg_mesg_get_status_update(struct otg_status_update *status_update); +extern void otg_mesg_get_firmware_info(struct otg_firmware_info *firmware_info); +extern int otg_mesg_set_firmware_info(struct otg_firmware_info *firmware_info); +extern struct otg_instance * mesg_otg_instance; + + +/* + * otgcore/otg.c + */ +extern char * otg_get_state_names(int i); + +/* + * ops + */ + +#define CORE core_trace_tag +extern otg_tag_t CORE; + +extern struct hcd_instance * otg_set_hcd_ops(struct otg_instance *otg, struct hcd_ops *); +extern struct ocd_instance * otg_set_ocd_ops(struct otg_instance *otg, struct ocd_ops *); +extern struct pcd_instance * otg_set_pcd_ops(struct otg_instance *otg, struct pcd_ops *); +extern struct tcd_instance * otg_set_tcd_ops(struct otg_instance *otg, struct tcd_ops *); +extern int otg_set_usbd_ops(void *); + +extern void otg_get_ocd_info(struct otg_instance *otg, otg_tick_t *ticks, u16 *d_framenum); +extern void otg_get_trace_info(struct otg_instance *otg, otg_trace_t *p); +extern int otg_tmr_id_gnd(void); +extern otg_tick_t otg_tmr_ticks(void); +extern otg_tick_t otg_tmr_elapsed(otg_tick_t *t1, otg_tick_t *t2); +extern u16 otg_tmr_framenum(void); +extern u32 otg_tmr_interrupts(void); + +//extern void otg_write_info_message(struct usbd_bus_instance *, char *msg); + + +/* @} */ diff --git a/drivers/otg/otg/otg-compat.h b/drivers/otg/otg/otg-compat.h new file mode 100644 index 000000000000..bea2bbb09696 --- /dev/null +++ b/drivers/otg/otg/otg-compat.h @@ -0,0 +1,93 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/otgcore/otg-compat.h + * @(#) sl@belcarra.com/whiskey.enposte.net|otg/otg/linux/otg-compat.h|20070425221118|15928 + * + * Copyright (c) 2004-2005 Belcarra Technologies Corp + * Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + * + */ +/*! + * @file otg/otg/otg-compat.h + * @brief Common include file for Linux to determine and include appropriate OS compatibility file. + * + * + * @ingroup LINUXAPI + */ +#ifndef _OTG_COMPAT_H +#define _OTG_COMPAT_H 1 + + +#include <otg/otg-utils.h> +//#include <otg/otg-trace.h> + + +#if defined(_WIN32_WCE) +#define OTG_WINCE _WIN32_WCE +#endif /* defined(_WIN32_WCE) */ + + /* What operating system are we running under? */ + + /* Recursively include enough information to determine which release */ + + #if (__GNUC__ >=3) + #define GCC3 + #else + #define GCC2 + #endif + #include <linux/kernel.h> + #include <linux/version.h> + +#if defined(__GNUC__) + #define OTG_LINUX + #ifndef CONFIG_OTG_NOC99 + #define OTG_C99 + #else + #endif + + #if LINUX_VERSION_CODE > KERNEL_VERSION(2,5,2) + #define LINUX26 + #elif LINUX_VERSION_CODE > KERNEL_VERSION(2,4,5) + #define LINUX24 + #else /* LINUX_VERSION_CODE > KERNEL_VERSION(2,4,5) */ + #define LINUX24 + #define LINUX_OLD + #warning "Early unsupported release of Linux kernel" + #endif /* LINUX_VERSION_CODE > KERNEL_VERSION(2,4,5) */ + + /* We are running under a supported version of Linux */ + #include <otg/otg-linux.h> + + +#elif defined(OTG_WINCE) + + /* We are running under a supported version of WinCE */ + #include <otg/otg-wince.h> + #include <otg/otg-wince-ex.h> + +#else /* defined(OTG_WINCE) */ + + #error "Operating system not recognized" + +#endif /* defined(OTG_WINCE) */ + +#if !defined(OTG_C99) +#else /* !defined(OTG_C99) */ +#endif /* !defined(OTG_C99) */ + +/* include otg-os.h - this will confirm that the otg-xxx.h file + * properly defined the os primitives. + */ +#include <otg/otg-os.h> +#endif /* _OTG_COMPAT_H */ diff --git a/drivers/otg/otg/otg-dev.h b/drivers/otg/otg/otg-dev.h new file mode 100644 index 000000000000..12ec8f3d41c7 --- /dev/null +++ b/drivers/otg/otg/otg-dev.h @@ -0,0 +1,191 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/otg/otg-dev.h -- Generic PCI driver + * @(#) sl@belcarra.com/whiskey.enposte.net|otg/otg/otg-dev.h|20070918212334|33755 + * + * Copyright (c) 2005 Belcarra Technologies Corp + * Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + * + * By: + * Stuart Lynne <sl@belcarra.com> + * + */ +/*! + * @file otg/otg/otg-dev.h + * @brief Generic PCI Driver. + * + * This is the base PCI driver for supporting the PCI devices. It is + * setup to allow the various OTG drivers to register and be handled + * separately. + * + * Multiple devices are supported. + * + * + * TCD PCD HCD OCD + * | | | | + * ------------------------- + * device PCI + * ------------------------- + * otg PCI + * ------------------------- + * OS PCI + * ------------------------- + * hardware + * + * + * It will probe the hardware and support registration for the sub-device + * drivers (ocd, hcd and pcd). + * + * otg_dev_register_driver() and otg_dev_unregister_driver() are used + * to register the base PCI hardware driver information. Which should + * result in the hardware getting probed. + * + * otg_register_driver() and otg_unregister_driver() are used to register + * the ocd, pcd, hcd, tcd drivers. + * + * The Device PCI driver should ensure that the basic hardware and interrupt + * setup is performed during it's probe. Then call otg_dev_probe(). + * + */ + +#define OTG_DRIVER_TCD 0 +#define OTG_DRIVER_PCD 1 +#define OTG_DRIVER_HCD 2 +#define OTG_DRIVER_OCD 3 +#define OTG_DRIVER_TYPES 4 + + +struct otg_dev; + +struct otg_device_driver; + +/*! otg_dev_driver + * Functions used to start an otg sub driver. + */ +struct otg_dev_driver { + char *name; + int id; + + u32 irqs; + irqreturn_t (*isr)(struct otg_dev *, void *, u32); + + int (*probe)(struct otg_dev *); + void (*remove)(struct otg_dev *); + +#ifdef CONFIG_PM + void (*suspend)(struct otg_dev *dev, u32 state); /* Device suspended */ + void (*resume)(struct otg_dev *dev); /* Device woken up */ +#endif /* CONFIG_PM */ + + void *ops; + +}; + +struct otg_interrupt { + struct device *device; + struct platform_device *platform_device; + int irq; +}; + +/*! otg_device_driver + */ +struct otg_device_driver { + + char *name; + + /* device driver support */ + //int (*device_otg_probe)(struct device *, struct otg_device_driver *); + //int (*device_otg_remove)(struct device *, struct otg_device_driver *); + + int (*probe)(struct device *, struct otg_device_driver *); + int (*remove)(struct device *, struct otg_device_driver *); + + /* platform device driver support */ + int (*platform_otg_probe)(struct platform_device *, struct otg_device_driver *); + int (*platform_otg_remove)(struct platform_device *, struct otg_device_driver *); + + /* interrupts */ + #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,19) + irqreturn_t (*isr) (int irq, void *data, struct pt_regs *r); + #else /* LINUX_VERSION_CODE < KERNEL_VERSION(2,6,19) */ + irqreturn_t (*isr) (int irq, void *data); + #endif /* LINUX_VERSION_CODE < KERNEL_VERSION(2,6,19) */ + + + /* sub-drivers supporting various OTG functions from the hardware */ + struct otg_dev_driver *drivers[OTG_DRIVER_TYPES]; + + char *serial_number; +}; + + +struct otg_dev { + /* base hardware PCI driver and interrupt handler */ + struct device *device; + struct platform_device *platform_device; + + struct otg_device_driver *otg_device_driver; + + struct otg_dev *next; + + spinlock_t lock; + + /* OTG Driver data */ + void *drvdata; + + /* interrupt support */ + int num_resources; + struct otg_interrupt *otg_interrupts; + + /* otg trace support */ + otg_tag_t DEV; + + char procfs[32]; + + struct otg_instance *otg_instance; + + struct ocd_instance *ocd_instance; + struct pcd_instance *pcd_instance; + struct tcd_instance *tcd_instance; + struct hcd_instance *hcd_instance; + + u32 interrupts; + + void *privdata; +}; + + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,19) +extern irqreturn_t otg_dev_isr(int irq, void *data, struct pt_regs *r); +extern irqreturn_t otg_pdev_isr(int irq, void *data, struct pt_regs *r); +#else /* LINUX_VERSION_CODE < KERNEL_VERSION(2,6,19) */ +extern irqreturn_t otg_dev_isr(int irq, void *data); +extern irqreturn_t otg_pdev_isr(int irq, void *data); +#endif /* LINUX_VERSION_CODE < KERNEL_VERSION(2,6,19) */ + +extern int otg_dev_probe (struct device *, struct otg_device_driver *, void *privdata); +extern void otg_dev_remove (struct device *, struct otg_device_driver *); + +extern int otg_pdev_probe (struct platform_device *, struct otg_device_driver *, void *privdata); +extern void otg_pdev_remove (struct platform_device *, struct otg_device_driver *); + +extern int otg_dev_register_driver(struct otg_device_driver*, struct otg_dev_driver *); +extern void otg_dev_unregister_driver(struct otg_device_driver*, struct otg_dev_driver *); + +extern void otg_dev_set_drvdata(struct otg_dev *, void *); +extern void * otg_dev_get_drvdata(struct otg_dev *); + + + +/* End of FILE */ diff --git a/drivers/otg/otg/otg-fw-df.h b/drivers/otg/otg/otg-fw-df.h new file mode 100644 index 000000000000..2bad1cfa0248 --- /dev/null +++ b/drivers/otg/otg/otg-fw-df.h @@ -0,0 +1,68 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/* Generated by otg-states-h.awk + * + * Do not Edit thie file + */ + +/* %Z %K */ + +/*! +* @file otg/otg/otg-fw-df.h +* @brief OTG Firmware - Firmware for df +* +* This file defines the OTG State Machine tests. +* +* @ingroup OTGFW +*/ + + +/*! +* @page OTGFW +* @section OTGFW_SECTION - otg-fw-df.h +* This contains the input, output and timout definitions for the OTG state machine firmware +*/ +extern char otg_fw_name_df[]; +extern int otg_test_max_df; +extern struct otg_test otg_tests_df[]; + + /* + * Copyright (c) 2005-2007 Belcarra Technologies 2005 Corp + * + */ + /*! + * This is the default Firmware. It is included in the + * compiled modules and supports the auto Traditional USB + * mode. No user inputs are implemented. + */ + +#define invalid_state 0 /* State machine has been initialized. */ + +#define otg_disabled 1 /* State machine has been initialized. */ + +#define terminator_state 2 /* terminator */ + /* + * This is not an OTG State. It is used internally to mark the end of the + * list of states and inputs. + */ + +#define OTG_STATES_DF 3 + +/* @} */ + +extern struct otg_state otg_states_df[OTG_STATES_DF + 1]; + +extern char *otg_get_state_name_df(int); + +extern struct otg_firmware otg_firmware_df; diff --git a/drivers/otg/otg/otg-fw-mn.h b/drivers/otg/otg/otg-fw-mn.h new file mode 100644 index 000000000000..a8140dd655e9 --- /dev/null +++ b/drivers/otg/otg/otg-fw-mn.h @@ -0,0 +1,352 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/* Generated by otg-states-h.awk + * + * Do not Edit thie file + */ + +/* %Z %K */ + +/*! +* @file otg/otg/otg-fw-mn.h +* @brief OTG Firmware - Firmware for mn +* +* This file defines the OTG State Machine tests. +* +* @ingroup OTGFW +*/ + + +/*! +* @page OTGFW +* @section OTGFW_SECTION - otg-fw-mn.h +* This contains the input, output and timout definitions for the OTG state machine firmware +*/ +extern char otg_fw_name_mn[]; +extern int otg_test_max_mn; +extern struct otg_test otg_tests_mn[]; + + /* + * Copyright (c) 2005-2007 Belcarra Technologies 2005 Corp + */ + /*! + * This is the initialization set for pcd, hcd and tcd. + */ + /*! + * @name Meta State - otg_init + * @{ + */ + +#define invalid_state 0 /* Un-initialized state. */ + /* + * This the initial state of the software when first loaded. + * It is not possible to return to this state. + */ + +#define otg_disabled 1 /* State Machine ready but disabled. */ + /* + * The USBOTG State Machine has been initialized but is inactive. + * This state may have arrived at from either the invalid_state or + * from the otg_disable state. + */ + +#define otg_disable_tcd 2 /* State Machine waiting for driver de-initialization. */ + /* + * The State Machine stops the device drivers and waits for them + * to signal that they have finished de-initializing. + */ + +#define otg_disable_hcd 3 /* State Machine waiting for driver de-initialization. */ + /* + * The State Machine stops the device drivers and waits for them + * to signal that they have finished de-initializing. + */ + +#define otg_disable_pcd 4 /* State Machine waiting for driver de-initialization. */ + /* + * The State Machine stops the device drivers and waits for them + * to signal that they have finished de-initializing. + */ + +#define otg_disable_ocd 5 /* State Machine waiting for driver de-initialization. */ + /* + * The State Machine stops the device drivers and waits for them + * to signal that they have finished de-initializing. + */ + +#define otg_enable_ocd 6 /* State Machine waiting for driver initialization. */ + /* + * The State Machine starts the device drivers and waits for them + * to signal that they have finished initializing. + */ + +#define otg_enable_pcd 7 /* State Machine waiting for driver initialization. */ + /* + * The State Machine starts the device drivers and waits for them + * to signal that they have finished initializing. + */ + +#define otg_enable_hcd 8 /* State Machine waiting for driver initialization. */ + /* + * The State Machine starts the device drivers and waits for them + * to signal that they have finished initializing. + */ + +#define otg_enable_tcd 9 /* State Machine waiting for driver initialization. */ + /* + * The State Machine starts the device drivers and waits for them + * to signal that they have finished initializing. + */ + /* @} */ + /* + * Copyright (c) 2005-2007 Belcarra Technologies 2005 Corp + * + */ + /*! + * This is the minimal firmware. It can be included in the + * compiled modules and supports the auto Traditional USB + * mode. No user inputs are required for normal operation. + * + * The b_bus_drop input can be optionally used to disconnect and re-connect. + * + * The enable_otg input can be optionally used to disable and re-enable. + * Note that disable/enable will reset b_bus_drop. + * + * + * B-Device Input Signals + * + * ID_GND/ + * B_SESS_VLD + * + * + * + * A-Device Input Signals + * + * ID_GND + * DM_HIGH + * VBUS_VLD + * HUB_PORT_CONNECT + * BUS_RESET + * ADDRESSED + * CONFIGURED + * A_SESS_VLD/ + * + * + * + */ + /*! + * @name Meta State - otg_init + * @{ + */ + +#define otg_enabled 10 /* State machine has been initialized. */ + /*! + * The State Machine has successfully started the device drivers and + * is waiting for an input event. Typically it will move from here to + * an idle state specific to the current conditions (peripheral_idle etc.) + * based on user request b_bus_drop. + * + */ + /* @} */ + /*! + * @name Meta State - b_idle + * @{ + */ + +#define peripheral_idle 11 /* Idle */ + /*! + * USB Peripheral is idle. + * Waiting for Vbus to indicate that it has been plugged into a USB Host. + * + */ + +#define peripheral_dropped 12 /* Dropped */ + /*! + * USB Peripheral, user has dropped the bus. + */ + /* @} */ + /*! + * @name Meta State - b_peripheral + * @{ + */ + +#define peripheral_wait 13 /* Signal a connection and wait for packet traffic. */ + /*! + * USB Peripheral, Vbus sensed, enabling pullup. + * The D+ pullup is enabled and we are waiting for a BUS_RESET to + * indicate that the USB Host has recognized that a USB Device is attached. + */ + +#define peripheral_bus_reset 14 /* The bus has been reset. */ + /*! + * USB Peripheral, waiting to be addressed. + * It is waiting to be enumerated and configured by the USB Host. + */ + +#define peripheral_addressed 15 /* The device has been addressed. */ + /*! + * The State Machine in the configured state for a Traditional USB Device. + * This means that there is an active session, there is packet traffic + * with this device. + */ + +#define peripheral_configured 16 /* The device has been configured. */ + /*! + * The State Machine in the configured state for a Traditional USB Device. + * This means that there is an active session, there is packet traffic + * with this device. + */ + +#define peripheral_discharge_vbus 17 /* Discharging Vbus */ + /*! + * The State Machine in the discharge state for a Traditional USB Device. + * The device has been unplugged. The Vbus discharge resistor will be enabled + * for the TLDISC_DSCHRG time period. + */ + /* @} */ + /*! + * @name Meta State - b_suspended + * @{ + */ + +#define peripheral_suspended 18 /* The bus has been suspended */ + /*! + * The State Machine in the suspend state for a Traditional USB Device. + */ + +#define peripheral_wakeup_enabled 19 /* Suspended with REMOTE WAKEUP enabled. */ + /*! + * The State Machine in the suspend state for a Traditional USB Device, + * prior to suspended the USB Host enabled Remote Wakeup by sending a + * set REMOTE WAKUP request. + */ + +#define peripheral_wakeup 20 /* Perform REMOTE WAKEUP procedure. */ + /*! + * The State Machine in the wakeup state for a Traditional USB Device, + * The REMOTE WAKEUP procedure will be performed. + */ + /* @} */ + +#define host_idle 21 /* A-device idle. */ + /*! + * A-Device idle state. An A-Plug is inserted in the Mini A-B Receptacle. + * This is the Host Only idle state. Waiting for user to allow + * the bus to be used. + * + * N.B. Reset all progress indicator inputs here. + */ + +#define host_idle_dropped 22 /* A-device idle. */ + /*! + * A-Device idle state. An A-Plug is inserted in the Mini A-B Receptacle. + * This is the Host Only idle state. Waiting for user to allow + * the bus to be used. + * + * N.B. Reset all progress indicator inputs here. + */ + +#define host_wait_vrise 23 /* Check if charge pump should be enabled. */ + /*! + * First check if external charge pump is required. + * + * XXX We force a wait of Ta_wait_vrise here, arriving in host wait + * port connect to quickly causes system hangs occasionally. + */ + +#define host_wait_vrise_overcurrent 24 /* Overcurrent error. */ + /*! + * A-Device Overcurrent condition (taking too long for Vbus to become valid.) + * + */ + +#define host_wait_port_connect 25 /* Wait for a B-device connect. */ + /*! + * Wait for a connection. + * + * A-Device wait for B-Device Connect. This is the normal route using the + * long debounce window. + * + * This state can optionally use the Ta_wait_bcon timeout (OTG mode) or + * wait forever (traditional USB Host mode.) + * + * XXX Arriving in this state to quickly from wait vrise can cause a system hang. + */ + +#define host_port_connected 26 /* Reset the bus */ + /*! + * A-Device host, the host controller driver has noticed a port status change, + * reset the bus and proceed. + */ + +#define host_bus_reset 27 /* Address the device. */ + /*! + * A-Device host, the bus has been reset, attempt to address the device. + */ + +#define host_addressed 28 /* Enumerate the device. */ + /*! + * A-Device host, the device has been addressed, attempt to enumerate, + * find the appropriate class driver and configure. + */ + +#define host_configured 29 /* The device is configured. */ + /*! + * A-Device host, the enumerated device is supported, and has been configured. + * + * XXX N.B. Currently assuming HNP enable feature is automatically performed. + * XXX N.B. Currently forcing HNP_CAPABLE and HNP_ENABLED. + */ + /*! + * @name Meta State - a_vbus_err + * @{ + */ + +#define host_vbus_err 30 /* Vbus error. */ + /*! + * A-Device Vbus error. + * The user must be informed and allowed to clear the problem. + */ + /* @} */ + /*! + * @name Meta State - host_wait_vfall + * @{ + */ + +#define host_wait_dischrg 31 /* Wait for Vbus to fall. */ + /*! + */ + +#define host_wait_vfall 32 /* Wait for Vbus to fall. */ + /*! + * A-Device wait for Vbus to fall. + * + * XXX Currently reseting a_bus_req on entry, require explicit a_bus_req to proceed. + */ + +#define terminator_state 33 /* terminator */ + /*! + * This is not an OTG State. It is used internally to mark the end of the + * list of states and inputs. + */ + +#define OTG_STATES_MN 34 + +/* @} */ + +extern struct otg_state otg_states_mn[OTG_STATES_MN + 1]; + +extern char *otg_get_state_name_mn(int); + +extern struct otg_firmware otg_firmware_mn; diff --git a/drivers/otg/otg/otg-fw.h b/drivers/otg/otg/otg-fw.h new file mode 100644 index 000000000000..a04ccada9b18 --- /dev/null +++ b/drivers/otg/otg/otg-fw.h @@ -0,0 +1,1337 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/* Generated by otg-h.awk + * + * Do not Edit, see otg-state.awk + */ + +/* %Z %K */ + + +/*! +* @defgroup OTGFW Firmware +* @ingroup onthegogroup +*/ + +/*! +* @file otg/otg/otg-fw.h +* @brief OTG Firmware - Input, Output and Timeout definitions +* This file defines the OTG State Machine input, output and timeout constants. +* +* +* @ingroup OTGFW +*/ + +/*! +* @name OTGFW +* @section OTGFW_SECTION - otg-fw.h +*/ + +/*! +* @name OTGVERSION OTG Version +* Version information +* @{ +*/ + +#define OTG_VERSION_FW 200704251606L + +/*! @} */ + +/*! +* @name BASIC +* These are the provided for application layer compatibility. +* @{ +*/ +#ifdef OTG_APPLICATION +typedef unsigned char u8; +typedef unsigned short u16; +typedef unsigned long u32; +typedef unsigned long long u64; +#include <stdio.h> +#include <sys/ioctl.h> +#endif /* OTG_APPLICATION */ + +/*! @} */ + +/*! +* @name OTGSTRUCT OTG Structures +* Basic OTG Structures +*/ +#define OTGADMIN_MAXSTR 48 + +/*! + * @struct otg_test + * + * This defines the tests that allow the state machine to move + * from state to state based on input events. + * + * Goto target: if ( + * (!test1 || (test1 & inputs)) && + * (!test2 || (test2 & inputs)) && + * + */ +typedef struct otg_test { + u16 test; /*!< Test number */ + u16 state; /*!< State Machine State */ + u32 target; /*!< Goto this target if */ + u64 test1; /*!< (!test1 || (test1 & inputs)) && */ + u64 test2; /*!< (!test2 || (test2 & inputs)) && */ + u64 test3; /*!< (!test3 || (test3 & inputs)) && */ + /* N.B. An empty test is ignored. */ +} otg_test_t; /*!< */ + + +/*! + * @struct otg_input_name + * This structure simply allows for a table of input names that can + * searched for by input mask + */ +typedef struct otg_input_name { + u32 value; /*!< Bit value */ + char name[OTGADMIN_MAXSTR]; /*!< Name of state. */ +} otg_input_name_t; + + +/*! + * @struct otg_state + * This defines an OTG State. Each state has a name, meta state, + * timeout, reset and outputs + * + * On entry to the state the reset mask is or'd into the current + * input mask, the settings defined in the output mask are used to. + * call the required output functions and then if present the timer + * is started with the timeout value (in uSec). + * + */ +typedef struct otg_state { + u16 state; /*!< State Machine State */ + u16 meta; /*!< Meta Machine State */ + char name[OTGADMIN_MAXSTR]; /*!< Name of state. */ + u32 tmout; /*!< Start timeout if non-zero */ + u64 reset; /*!< Or these inputs on entry to state */ + u64 outputs; /*!< Or these outputs on entry to state */ +} otg_state_t; + + +/*! + * @struct otg_ioctl_name + * This structure allows a table for a table of IOCTL event names that can be + * searched for by the ioctl command value. It also stores the actual + * input mask to set or reset when according to the ioctl arguement. + */ +typedef struct otg_ioctl_name { + u32 cmd; /*!< Ioctl cmd */ + u32 set; /*!< Signal to set/reset */ + char *name; /*!< Name of ioctl. */ +} otg_ioctl_name_t; + + +/*! + * @struct otg_admin_command + * This allows for a table of IOCTL admin commands. + */ +typedef struct otg_admin_command { + int n; /*!< Ioctl cmd */ + char string[OTGADMIN_MAXSTR]; /*!< Signal to set/reset */ +} otg_admin_command_t; + + +/*! + * @struct otg_status_update + * This is used by the OTG administrative programs to pass data to and received + * data from the OTG State Machine driver. + */ +typedef struct otg_status_update { + u16 state; /*!< current state */ + u16 meta; /*!< current meta state */ + u32 inputs; /*!< current inputs */ + u64 outputs; /*!< current outputs */ + u32 capabilities; /*!< */ + char fw_name[OTGADMIN_MAXSTR]; /*!< name of firmware */ + char state_name[OTGADMIN_MAXSTR]; /*!< name of current state */ + char meta_name[OTGADMIN_MAXSTR]; /*!< name of current meta-state */ + char function_name[OTGADMIN_MAXSTR]; /*!< currently selected function */ +} otg_status_update_t; + + +/*! + * @struct otg_firmware_info + * This is used by the OTG Administrative program to pass firmware information + * to the OTG State Machine./ + */ +typedef struct otg_firmware_info { + int number_of_states; /*!< number of states */ + int number_of_tests; /*!< number of tests */ + char fw_name[OTGADMIN_MAXSTR]; /*!< name of firmware */ +} otg_firmware_info_t; + + +/*! + * @struct otg_firmware + * This is used by the OTG Administrative program to pass firmware + * to the OTG State Machine./ + */ +typedef struct otg_firmware { + int number_of_states; /*!< number of states */ + int number_of_tests; /*!< number of tests */ + char fw_name[OTGADMIN_MAXSTR]; /*!< name of firmware */ + struct otg_state *otg_states; /*!< output information */ + struct otg_test *otg_tests; /*!< test information */ +} otg_firmware_t; + +/*! @} */ + +/*! +* @name OTGTIME Time Macros +* Basic OTG Time Macros +*/ + +#define US(n) (n) /*!< micro-seconds */ + +#define MS(n) (1000 * US(n)) /*!< milli-seconds */ + +#define SEC(n) (1000 * MS(n)) /*!< seconds */ + +/*! @} */ + +/*! +* @name OTGOUTPUTM Output Macros +* OTG Output Macros +*/ + +/* OTG State Outputs + * + * Each state that we transition to defines new settings for each of the defined + * outputs. Each output setting can have four settings: + * + * + * + * + * + */ +#define NC ((u64)0x0) /*!< NC no change */ +#define SET ((u64)0x1) /*!< SET the output should be set (i.e. enabled) */ +#define RESET ((u64)0x2) /*!< RESET the output should be reset (i.e. disabled) */ +#define PULSE ((u64)0x3) /*!< OTHER special setting, for example used for pulse vbus */ +#define POWER ((u64)0x3) /*!< POWER */ + +#define _MASK(n) (((u64) 1) << n) /*!< Set nth bit in 64 bit mask */ +#define _NOT(m) (((u64) m) << 32) /*!< Shift left mask 32 bits */ +#define _ncmask(n) (NC) /*!< No output change mask */ +#define _setmask(n) (SET << (n*2)) /*!< Output set mask */ +#define _resetmask(n) (RESET << (n*2)) /*!< Output reset mask */ +#define _pulsemask(n) (PULSE << (n*2)) /*!< Output pulse mask */ +#define _powermask(n) (POWER << (n*2)) /*!< Output poser mask */ + +/*! @} */ + +/*! +* @name OTGNAMES OTG Names +* Basic OTG Name Tables +*/ + +extern struct otg_input_name otg_input_names[]; +extern struct otg_ioctl_name otg_ioctl_names[]; +extern struct otg_firmware *otg_firmware_loaded; +extern char *otg_output_names[]; + + +/*! @} */ + +/* Generated by otg-inputs-h.awk + * + * Do not Edit this file. + */ + +/* %Z %K */ + + + /*! + * N.B. The OTG State Machine Documentation uses the syntax A and A/ + * to indicate an event being true or not true. Because a trailing + * slash is not legal in C we use A and A_ to indicate true and not true. + */ + + /*! + * @name OTG Transceiver Inputs + * These inputs reflect changes seen in the OTG Transceiver. These + * what are available in most common OTG transceivers, for example + * the ISP1301 or MAX3353. + * + * The following are typical Vbus comparators. + * + * ......................................ISP1301.........MAX3353E + * B-Session.end.threshold...............0.2V-0.8V.......0.5V.... + * Session.Valid.Comparator..............0.8V-2.0V.......1.4V.... + * B-Session.Valid.......................2.0V-4.0V.......N/A..... + * A-Device.Vbus.Valid.Comparator........4.4V............4.6V.... + * + * @{ + */ +#define ID_GND _MASK( 0) /*!< otg_ok - ID is grounded */ +#define ID_GND_ _NOT(ID_GND) +#define ID_FLOAT _MASK( 1) /*!< otg_ok - ID is floating */ +#define ID_FLOAT_ _NOT(ID_FLOAT) +#define DP_HIGH _MASK( 2) /*!< otg_ok - DP is pulled high */ +#define DP_HIGH_ _NOT(DP_HIGH) +#define DM_HIGH _MASK( 3) /*!< otg_ok - DM pullup is pulled high */ +#define DM_HIGH_ _NOT(DM_HIGH) +#define B_SESS_END _MASK( 4) /*!< otg_ok - Vbus less than B-Session end thresshold */ +#define B_SESS_END_ _NOT(B_SESS_END) +#define A_SESS_VLD _MASK( 5) /*!< otg_ok - Vbus greater than Session valid threshold */ +#define A_SESS_VLD_ _NOT(A_SESS_VLD) +#define B_SESS_VLD _MASK( 6) /*!< otg_ok - Vbus greater than B-Session Valid threshold */ +#define B_SESS_VLD_ _NOT(B_SESS_VLD) +#define VBUS_VLD _MASK( 7) /*!< otg_ok - Vbus greater than A-Device Vbus Valid threshold */ +#define VBUS_VLD_ _NOT(VBUS_VLD) +#define SRP_DET _MASK( 8) /*!< a_idle - SRP Detected (Vbus pulsed) */ +#define SRP_DET_ _NOT(SRP_DET) +#define SE0_DET _MASK( 9) /*!< b_idle - SE0 Detected (Single Ended Zeros, DM and DP both low) */ +#define SE0_DET_ _NOT(SE0_DET) +#define SE1_DET _MASK(10) /*!< b_idle - SE1 Detected (Single Ended Ones, DM and DP both high) */ +#define SE1_DET_ _NOT(SE1_DET) +#define BDIS_ACON _MASK(10) /*!< a_hnp_wait - Auto DP pullup high after B-disconnect */ +#define BDIS_ACON_ _NOT(BDIS_ACON) +#define CR_INT_DET _MASK(10) /*!< ph_audio - 0.4V < DP < 0.6V */ +#define CR_INT_DET_ _NOT(CR_INT_DET) + /* @} */ + /*! + * @name Peripheral and Host driver signals. + * + * + * @{ + */ +#define HUB_PORT_CONNECT _MASK(11) /*!< otg_both - Port Connection */ +#define HUB_PORT_CONNECT_ _NOT(HUB_PORT_CONNECT) +#define BUS_RESET _MASK(12) /*!< otg_both - Bus has reset. */ +#define BUS_RESET_ _NOT(BUS_RESET) +#define ADDRESSED _MASK(13) /*!< otg_both - Device has been addressed (received SET ADDRESS request.) */ +#define ADDRESSED_ _NOT(ADDRESSED) +#define DEVICE_REQUEST _MASK(14) /*!< otg_both - Device has received device request (received SETUP request.) */ +#define DEVICE_REQUEST_ _NOT(DEVICE_REQUEST) +#define CONFIGURED _MASK(15) /*!< otg_both - Device has been configured (received SET CONFIG request.) */ +#define CONFIGURED_ _NOT(CONFIGURED) +#define NOT_SUPPORTED _MASK(16) /*!< otg_both - Peripheral not supported (no class driver.) */ +#define NOT_SUPPORTED_ _NOT(NOT_SUPPORTED) +#define BUS_SUSPENDED _MASK(17) /*!< otg_both - Bus has been suspended. */ +#define BUS_SUSPENDED_ _NOT(BUS_SUSPENDED) + /* @} */ + + /*! + * @name Administrative Policy Inputs + * These are only valid in the state indicated and + * can be enabled or disabled. + * + * @{ + */ +#define a_bcon_no_tmout_req _MASK(18) /*!< otg_host - Application on A-host wants Ta_wait_bcon timeout disabled (non-OTG mode). */ +#define a_bcon_no_tmout_req_ _NOT(a_bcon_no_tmout_req) +#define a_hpwr_req _MASK(19) /*!< otg_host - Application on A-host wants external charge pump enabled. */ +#define a_hpwr_req_ _NOT(a_hpwr_req) +#define bus_drop _MASK(20) /*!< otg_ok - Application on Device needs to power down bus. */ +#define bus_drop_ _NOT(bus_drop) +#define a_bus_drop _MASK(20) /*!< otg_ok - Application on A-Device needs to power down bus. */ +#define a_bus_drop_ _NOT(a_bus_drop) +#define b_bus_drop _MASK(20) /*!< otg_ok - Application on B-Device needs to disconnect from bus. */ +#define b_bus_drop_ _NOT(b_bus_drop) +#define bus_req _MASK(21) /*!< otg_ok - Application on Device wants to use the bus. */ +#define bus_req_ _NOT(bus_req) +#define a_bus_req _MASK(21) /*!< otg_ok - Application on A-Device wants to act as host */ +#define a_bus_req_ _NOT(a_bus_req) +#define b_bus_req _MASK(21) /*!< otg_ok - Application on B-Device wants to act as host */ +#define b_bus_req_ _NOT(b_bus_req) +#define b_sess_req _MASK(21) /*!< otg_ok - Application on B-Device to perform SRP (alias for b_srp_req.) */ +#define b_sess_req_ _NOT(b_sess_req) +#define suspend_req _MASK(22) /*!< otg_host - Application on Device requests bus be suspended (alias for a_bus_req/.) */ +#define suspend_req_ _NOT(suspend_req) +#define a_suspend_req _MASK(22) /*!< otg_host - Application on A-host requests bus be suspended (alias for a_bus_req/.) */ +#define a_suspend_req_ _NOT(a_suspend_req) +#define b_suspend_req _MASK(22) /*!< otg_host - Application on B-host requests bus be suspended (alias for b_bus_req/.) */ +#define b_suspend_req_ _NOT(b_suspend_req) + /* @} */ + + /*! + * @name Administrative Action Inputs + * These are only valid in state indicated. The + * state machine tests must reset in states + * prior and after use. + * @{ + */ +#define set_remote_wakeup_cmd _MASK(24) /*!< a_host - A-Device will send Remote Wakeup Enable Request */ +#define set_remote_wakeup_cmd_ _NOT(set_remote_wakeup_cmd) +#define remote_wakeup_cmd _MASK(24) /*!< tr_configured - B-Device will perform Remote Wakeup. */ +#define remote_wakeup_cmd_ _NOT(remote_wakeup_cmd) +#define reset_remote_wakeup_cmd _MASK(25) /*!< a_host - A-Device will send Remote Wakeup Disable Request */ +#define reset_remote_wakeup_cmd_ _NOT(reset_remote_wakeup_cmd) +#define clr_err_cmd _MASK(25) /*!< a_vbus_err - A-Device ill clears Vbus overcurrent error. */ +#define clr_err_cmd_ _NOT(clr_err_cmd) +#define b_hnp_cmd _MASK(25) /*!< b_configured - B-Device will attempt HNP */ +#define b_hnp_cmd_ _NOT(b_hnp_cmd) +#define ph_int_cmd _MASK(25) /*!< ph_audio - B-Device will request Carkit interrupt */ +#define ph_int_cmd_ _NOT(ph_int_cmd) +#define ph_audio_cmd _MASK(25) /*!< ph_uart - Application on B-Device connected to Carkit requests audio mode. */ +#define ph_audio_cmd_ _NOT(ph_audio_cmd) +#define cr_int_cmd _MASK(25) /*!< cr_aud - Application on A-Device wants to emulate Carkit */ +#define cr_int_cmd_ _NOT(cr_int_cmd) +#define led_on_cmd _MASK(26) /*!< ph_uart - B-Device will enable Carkit LED */ +#define led_on_cmd_ _NOT(led_on_cmd) +#define led_off_cmd _MASK(27) /*!< ph_uart - B-Device will disable Carkit LED */ +#define led_off_cmd_ _NOT(led_off_cmd) + /* @} */ + + /*! + * @name Internal State + * Used to track status changes. + * @{ + */ +#define HNP_ENABLED _MASK(27) /*!< b_configured - B-HNP Enable Request sent (a-host) or received (b-peripheral). */ +#define HNP_ENABLED_ _NOT(HNP_ENABLED) +#define HNP_CAPABLE _MASK(28) /*!< otg_both - B-host Peripheral may do HNP */ +#define HNP_CAPABLE_ _NOT(HNP_CAPABLE) +#define HNP_SUPPORTED _MASK(28) /*!< otg_both - B-host can do HNP (A-Host Received HNP Supported SET FEATURE) */ +#define HNP_SUPPORTED_ _NOT(HNP_SUPPORTED) +#define REMOTE_WAKEUP_ENABLED _MASK(29) /*!< otg_configured - Remote Wakeup Enable Request received. */ +#define REMOTE_WAKEUP_ENABLED_ _NOT(REMOTE_WAKEUP_ENABLED) +#define REMOTE_CAPABLE _MASK(29) /*!< otg_both - Peripheral can do remote wakeup */ +#define REMOTE_CAPABLE_ _NOT(REMOTE_CAPABLE) + /* @} */ + + /*! + * @name Global Administration + * @{ + */ + /* @} */ + + /*! + * @name Driver Initialization Finished signals + * @{ + */ +#define PCD_OK _MASK(30) /*!< otg_driver - PCD Driver Initialization Finished. */ +#define PCD_OK_ _NOT(PCD_OK) +#define TCD_OK _MASK(30) /*!< otg_driver - TCD Driver Initialization Finished. */ +#define TCD_OK_ _NOT(TCD_OK) +#define HCD_OK _MASK(30) /*!< otg_driver - HCD Driver Initialization Finished. */ +#define HCD_OK_ _NOT(HCD_OK) +#define OCD_OK _MASK(30) /*!< otg_driver - OCD Driver Initialization Finished. */ +#define OCD_OK_ _NOT(OCD_OK) + /* @} */ + + /*! + * @name Timeout and enable + * @{ + */ +#define TMOUT _MASK(30) /*!< otg_all - Generic Timeout */ +#define TMOUT_ _NOT(TMOUT) +#define enable_otg _MASK(31) /*!< otg_all - Move State Machine otg_disabled state. */ +#define enable_otg_ _NOT(enable_otg) +#define AUTO _MASK(31) /*!< otg_all - Auto Return (enable_otg only true when active) */ +#define AUTO_ _NOT(AUTO) + /* @} */ + + /*! @name Timeouts C.f. OTG Table 5-2 A-Device Timing + * @{ + */ +#define Ta_wait_vrise _MASK(30) /*!< a_wait_vrise - Wait for Vbus Rise */ +#define Ta_wait_vrise_ _NOT(Ta_wait_vrise) +#define TA_WAIT_VRISE MS(100) +#define Ta_wait_vrise_200 _MASK(30) /*!< a_wait_vrise - Wait for Vbus Rise */ +#define Ta_wait_vrise_200_ _NOT(Ta_wait_vrise_200) +#define TA_WAIT_VRISE_200 MS(200) +#define Ta_wait_vrise_400 _MASK(30) /*!< a_wait_vrise - Wait for Vbus Rise */ +#define Ta_wait_vrise_400_ _NOT(Ta_wait_vrise_400) +#define TA_WAIT_VRISE_400 MS(400) +#define Ta_wait_vrise_500 _MASK(30) /*!< a_wait_vrise - Wait for Vbus Rise */ +#define Ta_wait_vrise_500_ _NOT(Ta_wait_vrise_500) +#define TA_WAIT_VRISE_500 MS(500) +#define Ta_wait_vrise_800 _MASK(30) /*!< a_wait_vrise - Wait for Vbus Rise */ +#define Ta_wait_vrise_800_ _NOT(Ta_wait_vrise_800) +#define TA_WAIT_VRISE_800 MS(800) +#define Ta_bcon_ldb _MASK(30) /*!< a_bcon_ldb - B-Connect Long Debounce */ +#define Ta_bcon_ldb_ _NOT(Ta_bcon_ldb) +#define TA_BCON_LDB MS(100) +#define Ta_wait_bcon _MASK(30) /*!< a_wait_bcon - Wait for 1 second for B-Connect */ +#define Ta_wait_bcon_ _NOT(Ta_wait_bcon) +#define TA_WAIT_BCON SEC(1) +#define Ta_wait_bcon_5 _MASK(30) /*!< a_wait_bcon - Wait for 5 second for B-Connect */ +#define Ta_wait_bcon_5_ _NOT(Ta_wait_bcon_5) +#define TA_WAIT_BCON_5 SEC(5) +#define Ta_wait_bcon_10 _MASK(30) /*!< a_wait_bcon - Wait for 10 second for B-Connect */ +#define Ta_wait_bcon_10_ _NOT(Ta_wait_bcon_10) +#define TA_WAIT_BCON_10 SEC(10) +#define Ta_aidl_bdis _MASK(30) /*!< a_hnp_wait - A-Idle to B-Disconnect */ +#define Ta_aidl_bdis_ _NOT(Ta_aidl_bdis) +#define TA_AIDL_BDIS MS(200) +#define Ta_bdis_acon _MASK(30) /*!< a_suspend - B-disconnect to A-Connect */ +#define Ta_bdis_acon_ _NOT(Ta_bdis_acon) +#define TA_BDIS_ACON MS(3) +#define Ta_bidl_adis_min _MASK(30) /*!< a_peripheral - B-Idle to A-Disconnect minimum (TODO) */ +#define Ta_bidl_adis_min_ _NOT(Ta_bidl_adis_min) +#define TA_BIDL_ADIS_MIN MS(3) +#define Ta_bcon_sdb _MASK(30) /*!< a_bcon_sdb - B-Connect Short Debounce */ +#define Ta_bcon_sdb_ _NOT(Ta_bcon_sdb) +#define TA_BCON_SDB US(2) +#define Ta_bcon_sdb_win _MASK(30) /*!< a_bcon_win - B-Connect Short Debounce Window */ +#define Ta_bcon_sdb_win_ _NOT(Ta_bcon_sdb_win) +#define TA_BCON_SDB_WIN MS(100) + /* @} */ + + /*! @name Timeouts C.f. OTG Table 5-3 B-Device Timing + * @{ + */ +#define Tb_se0_srp _MASK(30) /*!< b_srp_se0 - SE0 Time Before SRP */ +#define Tb_se0_srp_ _NOT(Tb_se0_srp) +#define TB_SE0_SRP MS(2) +#define Tb_data_pls _MASK(30) /*!< b_srp_init - Data-Line Pulse Time */ +#define Tb_data_pls_ _NOT(Tb_data_pls) +#define TB_DATA_PLS US(5500) +#define Tb_data_pls_min _MASK(30) /*!< a_srp_min - Data-Line Pulse minimum time (5mS-3mSidle) */ +#define Tb_data_pls_min_ _NOT(Tb_data_pls_min) +#define TB_DATA_PLS_MIN MS(4) +#define Tb_data_pls_max _MASK(30) /*!< a_srp_wait - Data-Line Pulse maximum time (10mS-3mS idle) */ +#define Tb_data_pls_max_ _NOT(Tb_data_pls_max) +#define TB_DATA_PLS_MAX MS(7) +#define Tb_srp_init _MASK(30) /*!< b_srp_init - SRP Initiate Time (TODO multi-state? Not-needed?) */ +#define Tb_srp_init_ _NOT(Tb_srp_init) +#define TB_SRP_INIT MS(100) +#define Tb_srp_fail_min _MASK(30) /*!< b_srp_wait - SRP Fail Time minimum (TODO) */ +#define Tb_srp_fail_min_ _NOT(Tb_srp_fail_min) +#define TB_SRP_FAIL_MIN SEC(5) +#define Tb_aidl_bdis_min _MASK(30) /*!< b_peripheral - A-idle to B-Disconnect minimum (TODO) */ +#define Tb_aidl_bdis_min_ _NOT(Tb_aidl_bdis_min) +#define TB_AIDL_BDIS_MIN MS(5) +#define Tb_aidl_bdis_max _MASK(30) /*!< b_peripheral - A-idle to B-Disconnect maximum (TODO) */ +#define Tb_aidl_bdis_max_ _NOT(Tb_aidl_bdis_max) +#define TB_AIDL_BDIS_MAX MS(150) +#define Tldisc_dschrg _MASK(30) /*!< b_dischrg - Local Disconnect to Data Line Discharge (TODO) */ +#define Tldisc_dschrg_ _NOT(Tldisc_dschrg) +#define TLDISC_DSCHRG US(25) +#define Tb_ase0_brst_min _MASK(30) /*!< b_wait_acon - A-SE0 to B-Reset minimum */ +#define Tb_ase0_brst_min_ _NOT(Tb_ase0_brst_min) +#define TB_ASE0_BRST_MIN US(3125) +#define Tb_acon_dbnc _MASK(30) /*!< b_acon_dbnc - A-Connect Debounce */ +#define Tb_acon_dbnc_ _NOT(Tb_acon_dbnc) +#define TB_ACON_DBNC US(2) +#define Tb_acon_bse0 _MASK(30) /*!< b_host_se0 - A-Connect to B-SE0 */ +#define Tb_acon_bse0_ _NOT(Tb_acon_bse0) +#define TB_ACON_BSE0 MS(1) +#define Tid_ldb _MASK(30) /*!< otg_enable - ID changes debounce */ +#define Tid_ldb_ _NOT(Tid_ldb) +#define TID_LDB MS(100) + /* @} */ + + /*! @name Timeouts for Carkit + * @{ + */ +#define Tph_bcon_ldb _MASK(30) /*!< ph_init - B-Connect Long Debounce */ +#define Tph_bcon_ldb_ _NOT(Tph_bcon_ldb) +#define TPH_BCON_LDB MS(100) +#define Tph_init_pls _MASK(30) /*!< ph_int - Timeout for Carkit interrupt. */ +#define Tph_init_pls_ _NOT(Tph_init_pls) +#define TPH_INIT_PLS US(500) +#define Tcr_uart_rsp _MASK(30) /*!< ph_int - Timeout for Carkit UART */ +#define Tcr_uart_rsp_ _NOT(Tcr_uart_rsp) +#define TCR_UART_RSP MS(30) +#define Tcr_aud_det _MASK(30) /*!< ph_audio_wait - Timeout waiting for Carkit Audio ack */ +#define Tcr_aud_det_ _NOT(Tcr_aud_det) +#define TCR_AUD_DET US(1000) +#define Tph_led_off _MASK(30) /*!< ph_uart - Timeout for LED off pulse */ +#define Tph_led_off_ _NOT(Tph_led_off) +#define TPH_LED_OFF US(10) +#define Tph_led_on _MASK(30) /*!< ph_uart - Timeout for LED on pulse */ +#define Tph_led_on_ _NOT(Tph_led_on) +#define TPH_LED_ON MS(4) +#define Tcr_led_off _MASK(30) /*!< ph_uart - Timeout for LED off pulse */ +#define Tcr_led_off_ _NOT(Tcr_led_off) +#define TCR_LED_OFF MS(4) +#define Tcr_led_on _MASK(30) /*!< ph_uart - Timeout for LED on pulse */ +#define Tcr_led_on_ _NOT(Tcr_led_on) +#define TCR_LED_ON MS(8) +#define Tcr_dm_disc _MASK(30) /*!< cr_aud - Timeout for DM disconnect */ +#define Tcr_dm_disc_ _NOT(Tcr_dm_disc) +#define TCR_DM_DISC MS(50) +#define Tph_mono_ack _MASK(30) /*!< cr_wait - Timeout for CR INT */ +#define Tph_mono_ack_ _NOT(Tph_mono_ack) +#define TPH_MONO_ACK MS(1) +#define Tph_aud_det _MASK(30) /*!< cr_ack - Timeout for CR ACK */ +#define Tph_aud_det_ _NOT(Tph_aud_det) +#define TPH_AUD_DET MS(100) + /* @} */ + + /*! @name Timeouts for timer test + * @{ + */ +#define Tzero _MASK(30) /*!< otg_enabled - Startup */ +#define Tzero_ _NOT(Tzero) +#define TZERO MS(2) +#define Tst_100_us _MASK(30) /*!< tm_start - Timer test */ +#define Tst_100_us_ _NOT(Tst_100_us) +#define TST_100_US US(100) +#define Tst_one_ms _MASK(30) /*!< tm_start - Timer test */ +#define Tst_one_ms_ _NOT(Tst_one_ms) +#define TST_ONE_MS MS(1) +#define Tst_ten_ms _MASK(30) /*!< tm_start - Timer test */ +#define Tst_ten_ms_ _NOT(Tst_ten_ms) +#define TST_TEN_MS MS(10) +#define Tst_one_second _MASK(30) /*!< tm_start - Timer test */ +#define Tst_one_second_ _NOT(Tst_one_second) +#define TST_ONE_SECOND SEC(1) +#define Tst_two_second _MASK(30) /*!< tm_start - Timer test */ +#define Tst_two_second_ _NOT(Tst_two_second) +#define TST_TWO_SECOND SEC(2) +#define Tst_four_second _MASK(30) /*!< tm_start - Timer test */ +#define Tst_four_second_ _NOT(Tst_four_second) +#define TST_FOUR_SECOND SEC(4) +#define Tst_eight_second _MASK(30) /*!< tm_start - Timer test */ +#define Tst_eight_second_ _NOT(Tst_eight_second) +#define TST_EIGHT_SECOND SEC(8) +#define Tst_ten_second _MASK(30) /*!< tm_start - Timer test */ +#define Tst_ten_second_ _NOT(Tst_ten_second) +#define TST_TEN_SECOND SEC(10) + /* @} */ + +/* Generated by otg-outputs-h.awk + * + * Do not Edit this file. + */ + +/* %Z %K */ + + + + /* State Machine Outputs + */ + + /*! @name Driver Initialization Outputs + * N.B. tcd_en is used for older devices, to check if Vbus + * already enabled. + * @{ + */ + +#define TCD_INIT_OUT 0 /*!< Initiate Transceiver Controller Driver Initialization (or De-initialization.) */ +#define tcd_init_out _setmask(TCD_INIT_OUT) +#define tcd_init_out_ _resetmask(TCD_INIT_OUT) +#define tcd_init_out_set _setmask(TCD_INIT_OUT) +#define tcd_init_out_reset _resetmask(TCD_INIT_OUT) +#define tcd_init_out_power _powermask(TCD_INIT_OUT) + +#define PCD_INIT_OUT 1 /*!< Initiate Peripheral Controller Driver Initialization (or De-initialization.) */ +#define pcd_init_out _setmask(PCD_INIT_OUT) +#define pcd_init_out_ _resetmask(PCD_INIT_OUT) +#define pcd_init_out_set _setmask(PCD_INIT_OUT) +#define pcd_init_out_reset _resetmask(PCD_INIT_OUT) +#define pcd_init_out_power _powermask(PCD_INIT_OUT) + +#define HCD_INIT_OUT 2 /*!< Initiate Host Controller Driver Initialization (or De-initialization). */ +#define hcd_init_out _setmask(HCD_INIT_OUT) +#define hcd_init_out_ _resetmask(HCD_INIT_OUT) +#define hcd_init_out_set _setmask(HCD_INIT_OUT) +#define hcd_init_out_reset _resetmask(HCD_INIT_OUT) +#define hcd_init_out_power _powermask(HCD_INIT_OUT) + +#define OCD_INIT_OUT 3 /*!< Initiate OTG Controller Driver Initialization (or De-initialization). */ +#define ocd_init_out _setmask(OCD_INIT_OUT) +#define ocd_init_out_ _resetmask(OCD_INIT_OUT) +#define ocd_init_out_set _setmask(OCD_INIT_OUT) +#define ocd_init_out_reset _resetmask(OCD_INIT_OUT) +#define ocd_init_out_power _powermask(OCD_INIT_OUT) + +#define TCD_EN_OUT 4 /*!< Enable Transceiver Controller Driver */ +#define tcd_en_out _setmask(TCD_EN_OUT) +#define tcd_en_out_ _resetmask(TCD_EN_OUT) +#define tcd_en_out_set _setmask(TCD_EN_OUT) +#define tcd_en_out_reset _resetmask(TCD_EN_OUT) +#define tcd_en_out_power _powermask(TCD_EN_OUT) + +#define PCD_EN_OUT 5 /*!< Enable Peripheral Controller Driver */ +#define pcd_en_out _setmask(PCD_EN_OUT) +#define pcd_en_out_ _resetmask(PCD_EN_OUT) +#define pcd_en_out_set _setmask(PCD_EN_OUT) +#define pcd_en_out_reset _resetmask(PCD_EN_OUT) +#define pcd_en_out_power _powermask(PCD_EN_OUT) + +#define HCD_EN_OUT 6 /*!< Enable Host Controller Driver */ +#define hcd_en_out _setmask(HCD_EN_OUT) +#define hcd_en_out_ _resetmask(HCD_EN_OUT) +#define hcd_en_out_set _setmask(HCD_EN_OUT) +#define hcd_en_out_reset _resetmask(HCD_EN_OUT) +#define hcd_en_out_power _powermask(HCD_EN_OUT) + /* @) */ + + /*! @name Transceiver Controller Driver Outputs + * @{ + */ + +#define DRV_VBUS_OUT 7 /*!< A-Device will Drive Vbus to 5V through charge pump. */ +#define drv_vbus_out _setmask(DRV_VBUS_OUT) +#define drv_vbus_out_ _resetmask(DRV_VBUS_OUT) +#define drv_vbus_out_set _setmask(DRV_VBUS_OUT) +#define drv_vbus_out_reset _resetmask(DRV_VBUS_OUT) +#define drv_vbus_out_power _powermask(DRV_VBUS_OUT) + +#define CHRG_VBUS_OUT 8 /*!< B-Device will charge Vbus to 3.3V through resistor (SRP.) */ +#define chrg_vbus_out _setmask(CHRG_VBUS_OUT) +#define chrg_vbus_out_ _resetmask(CHRG_VBUS_OUT) +#define chrg_vbus_out_set _setmask(CHRG_VBUS_OUT) +#define chrg_vbus_out_reset _resetmask(CHRG_VBUS_OUT) +#define chrg_vbus_out_power _powermask(CHRG_VBUS_OUT) + +#define DISCHRG_VBUS_OUT 9 /*!< B-Device will discharge Vbus (enable dischage resistor.) */ +#define dischrg_vbus_out _setmask(DISCHRG_VBUS_OUT) +#define dischrg_vbus_out_ _resetmask(DISCHRG_VBUS_OUT) +#define dischrg_vbus_out_set _setmask(DISCHRG_VBUS_OUT) +#define dischrg_vbus_out_reset _resetmask(DISCHRG_VBUS_OUT) +#define dischrg_vbus_out_power _powermask(DISCHRG_VBUS_OUT) + +#define DM_PULLUP_OUT 10 /*!< DM pullup control - aka loc_carkit */ +#define dm_pullup_out _setmask(DM_PULLUP_OUT) +#define dm_pullup_out_ _resetmask(DM_PULLUP_OUT) +#define dm_pullup_out_set _setmask(DM_PULLUP_OUT) +#define dm_pullup_out_reset _resetmask(DM_PULLUP_OUT) +#define dm_pullup_out_power _powermask(DM_PULLUP_OUT) + +#define DM_PULLDOWN_OUT 11 /*!< DM pulldown control */ +#define dm_pulldown_out _setmask(DM_PULLDOWN_OUT) +#define dm_pulldown_out_ _resetmask(DM_PULLDOWN_OUT) +#define dm_pulldown_out_set _setmask(DM_PULLDOWN_OUT) +#define dm_pulldown_out_reset _resetmask(DM_PULLDOWN_OUT) +#define dm_pulldown_out_power _powermask(DM_PULLDOWN_OUT) + +#define DP_PULLUP_OUT 12 /*!< DP pullup control - aka loc_conn */ +#define dp_pullup_out _setmask(DP_PULLUP_OUT) +#define dp_pullup_out_ _resetmask(DP_PULLUP_OUT) +#define dp_pullup_out_set _setmask(DP_PULLUP_OUT) +#define dp_pullup_out_reset _resetmask(DP_PULLUP_OUT) +#define dp_pullup_out_power _powermask(DP_PULLUP_OUT) + +#define DP_PULLDOWN_OUT 13 /*!< DP pulldown control */ +#define dp_pulldown_out _setmask(DP_PULLDOWN_OUT) +#define dp_pulldown_out_ _resetmask(DP_PULLDOWN_OUT) +#define dp_pulldown_out_set _setmask(DP_PULLDOWN_OUT) +#define dp_pulldown_out_reset _resetmask(DP_PULLDOWN_OUT) +#define dp_pulldown_out_power _powermask(DP_PULLDOWN_OUT) + +#define CLR_OVERCURRENT_OUT 14 /*!< Clear overcurrent indication */ +#define clr_overcurrent_out _setmask(CLR_OVERCURRENT_OUT) +#define clr_overcurrent_out_ _resetmask(CLR_OVERCURRENT_OUT) +#define clr_overcurrent_out_set _setmask(CLR_OVERCURRENT_OUT) +#define clr_overcurrent_out_reset _resetmask(CLR_OVERCURRENT_OUT) +#define clr_overcurrent_out_power _powermask(CLR_OVERCURRENT_OUT) + +#define DM_DET_OUT 15 /*!< Enable B-Device D- High detect */ +#define dm_det_out _setmask(DM_DET_OUT) +#define dm_det_out_ _resetmask(DM_DET_OUT) +#define dm_det_out_set _setmask(DM_DET_OUT) +#define dm_det_out_reset _resetmask(DM_DET_OUT) +#define dm_det_out_power _powermask(DM_DET_OUT) + +#define DP_DET_OUT 16 /*!< Enable B-Device D+ High detect */ +#define dp_det_out _setmask(DP_DET_OUT) +#define dp_det_out_ _resetmask(DP_DET_OUT) +#define dp_det_out_set _setmask(DP_DET_OUT) +#define dp_det_out_reset _resetmask(DP_DET_OUT) +#define dp_det_out_power _powermask(DP_DET_OUT) + +#define CR_DET_OUT 17 /*!< Enable D+ CR detect */ +#define cr_det_out _setmask(CR_DET_OUT) +#define cr_det_out_ _resetmask(CR_DET_OUT) +#define cr_det_out_set _setmask(CR_DET_OUT) +#define cr_det_out_reset _resetmask(CR_DET_OUT) +#define cr_det_out_power _powermask(CR_DET_OUT) + +#define CHARGE_PUMP_OUT 18 /*!< Enable external charge pump. */ +#define charge_pump_out _setmask(CHARGE_PUMP_OUT) +#define charge_pump_out_ _resetmask(CHARGE_PUMP_OUT) +#define charge_pump_out_set _setmask(CHARGE_PUMP_OUT) +#define charge_pump_out_reset _resetmask(CHARGE_PUMP_OUT) +#define charge_pump_out_power _powermask(CHARGE_PUMP_OUT) + +#define BDIS_ACON_OUT 19 /*!< Enable auto A-connect after B-disconnect. */ +#define bdis_acon_out _setmask(BDIS_ACON_OUT) +#define bdis_acon_out_ _resetmask(BDIS_ACON_OUT) +#define bdis_acon_out_set _setmask(BDIS_ACON_OUT) +#define bdis_acon_out_reset _resetmask(BDIS_ACON_OUT) +#define bdis_acon_out_power _powermask(BDIS_ACON_OUT) + +#define ID_PULLDOWN_OUT 20 /*!< Enable the ID to ground pulldown ( (CEA-936 - 5 wire carkit.) */ +#define id_pulldown_out _setmask(ID_PULLDOWN_OUT) +#define id_pulldown_out_ _resetmask(ID_PULLDOWN_OUT) +#define id_pulldown_out_set _setmask(ID_PULLDOWN_OUT) +#define id_pulldown_out_reset _resetmask(ID_PULLDOWN_OUT) +#define id_pulldown_out_power _powermask(ID_PULLDOWN_OUT) + +#define UART_OUT 21 /*!< Enable Transparent UART mode (CEA-936.) */ +#define uart_out _setmask(UART_OUT) +#define uart_out_ _resetmask(UART_OUT) +#define uart_out_set _setmask(UART_OUT) +#define uart_out_reset _resetmask(UART_OUT) +#define uart_out_power _powermask(UART_OUT) + +#define AUDIO_OUT 22 /*!< Enable Audio mode (CEA-936 CarKit interrupt detector.) */ +#define audio_out _setmask(AUDIO_OUT) +#define audio_out_ _resetmask(AUDIO_OUT) +#define audio_out_set _setmask(AUDIO_OUT) +#define audio_out_reset _resetmask(AUDIO_OUT) +#define audio_out_power _powermask(AUDIO_OUT) + +#define MONO_OUT 23 /*!< Enable Mono-Audio mode (CEA-936.) */ +#define mono_out _setmask(MONO_OUT) +#define mono_out_ _resetmask(MONO_OUT) +#define mono_out_set _setmask(MONO_OUT) +#define mono_out_reset _resetmask(MONO_OUT) +#define mono_out_power _powermask(MONO_OUT) + /* @) */ + + /*! @name Peripheral Controller Driver Outputs + * @{ + */ + +#define REMOTE_WAKEUP_OUT 24 /*!< Peripheral will perform remote wakeup. */ +#define remote_wakeup_out _setmask(REMOTE_WAKEUP_OUT) +#define remote_wakeup_out_ _resetmask(REMOTE_WAKEUP_OUT) +#define remote_wakeup_out_set _setmask(REMOTE_WAKEUP_OUT) +#define remote_wakeup_out_reset _resetmask(REMOTE_WAKEUP_OUT) +#define remote_wakeup_out_power _powermask(REMOTE_WAKEUP_OUT) + /* @) */ + + /*! @name Host Controller Driver Outputs + * @{ + */ + +#define HCD_RH_OUT 25 /*!< Host will enable root hub */ +#define hcd_rh_out _setmask(HCD_RH_OUT) +#define hcd_rh_out_ _resetmask(HCD_RH_OUT) +#define hcd_rh_out_set _setmask(HCD_RH_OUT) +#define hcd_rh_out_reset _resetmask(HCD_RH_OUT) +#define hcd_rh_out_power _powermask(HCD_RH_OUT) + +#define LOC_SOF_OUT 26 /*!< Host will enable packet traffic. */ +#define loc_sof_out _setmask(LOC_SOF_OUT) +#define loc_sof_out_ _resetmask(LOC_SOF_OUT) +#define loc_sof_out_set _setmask(LOC_SOF_OUT) +#define loc_sof_out_reset _resetmask(LOC_SOF_OUT) +#define loc_sof_out_power _powermask(LOC_SOF_OUT) + +#define LOC_SUSPEND_OUT 27 /*!< Host will suspend bus. */ +#define loc_suspend_out _setmask(LOC_SUSPEND_OUT) +#define loc_suspend_out_ _resetmask(LOC_SUSPEND_OUT) +#define loc_suspend_out_set _setmask(LOC_SUSPEND_OUT) +#define loc_suspend_out_reset _resetmask(LOC_SUSPEND_OUT) +#define loc_suspend_out_power _powermask(LOC_SUSPEND_OUT) + +#define REMOTE_WAKEUP_EN_OUT 28 /*!< Host will send remote wakeup enable or disable request. */ +#define remote_wakeup_en_out _setmask(REMOTE_WAKEUP_EN_OUT) +#define remote_wakeup_en_out_ _resetmask(REMOTE_WAKEUP_EN_OUT) +#define remote_wakeup_en_out_set _setmask(REMOTE_WAKEUP_EN_OUT) +#define remote_wakeup_en_out_reset _resetmask(REMOTE_WAKEUP_EN_OUT) +#define remote_wakeup_en_out_power _powermask(REMOTE_WAKEUP_EN_OUT) + +#define HNP_EN_OUT 29 /*!< Host will send HNP enable request. */ +#define hnp_en_out _setmask(HNP_EN_OUT) +#define hnp_en_out_ _resetmask(HNP_EN_OUT) +#define hnp_en_out_set _setmask(HNP_EN_OUT) +#define hnp_en_out_reset _resetmask(HNP_EN_OUT) +#define hnp_en_out_power _powermask(HNP_EN_OUT) + +#define HPWR_OUT 30 /*!< Host will enable high power (external charge pump.) */ +#define hpwr_out _setmask(HPWR_OUT) +#define hpwr_out_ _resetmask(HPWR_OUT) +#define hpwr_out_set _setmask(HPWR_OUT) +#define hpwr_out_reset _resetmask(HPWR_OUT) +#define hpwr_out_power _powermask(HPWR_OUT) + /* @) */ +#define MAX_OUTPUTS 31 + +/* Generated by otg-ioctls-h.awk + * + * Do not Edit this file, + */ + +/* %Z %K */ + + +#if defined(OTG_LINUX) + +#define OTGADMIN_MAGIC 'O' +#define OTGADMIN_VERSION _IOR(OTGADMIN_MAGIC, 1, u64) +#define OTGADMIN_STATUS _IOR(OTGADMIN_MAGIC, 3, struct otg_status_update) +#define OTGADMIN_SET_FUNCTION _IOW(OTGADMIN_MAGIC, 4, struct otg_admin_command) +#define OTGADMIN_GET_FUNCTION _IOR(OTGADMIN_MAGIC, 4, struct otg_admin_command) +#define OTGADMIN_SET_INFO _IOW(OTGADMIN_MAGIC, 5, struct otg_firmware_info) +#define OTGADMIN_GET_INFO _IOR(OTGADMIN_MAGIC, 5, struct otg_firmware_info) +#define OTGADMIN_SET_STATE _IOW(OTGADMIN_MAGIC, 6, struct otg_state) +#define OTGADMIN_GET_STATE _IOR(OTGADMIN_MAGIC, 6, struct otg_state) +#define OTGADMIN_SET_TEST _IOW(OTGADMIN_MAGIC, 7, struct otg_test) +#define OTGADMIN_GET_TEST _IOR(OTGADMIN_MAGIC, 7, struct otg_test) +#define OTGADMIN_SET_SERIAL _IOW(OTGADMIN_MAGIC, 8, struct otg_admin_command) +#define OTGADMIN_GET_SERIAL _IOR(OTGADMIN_MAGIC, 8, struct otg_admin_command) + + + + /*! + * Signal Sent: a_bcon_no_tmout_req + * State Valid: otg_host + * Application on A-host wants Ta_wait_bcon timeout disabled (non-OTG mode). + */ +#define OTGADMIN_A_BCON_NO_TMOUT_REQ _IOW(OTGADMIN_MAGIC, 10, int) + + /*! + * Signal Sent: a_hpwr_req + * State Valid: otg_host + * Application on A-host wants external charge pump enabled. + */ +#define OTGADMIN_A_HPWR_REQ _IOW(OTGADMIN_MAGIC, 11, int) + + /*! + * Signal Sent: bus_drop + * State Valid: otg_ok + * Application on Device needs to power down bus. + */ +#define OTGADMIN_BUS_DROP _IOW(OTGADMIN_MAGIC, 12, int) + + /*! + * Signal Sent: a_bus_drop + * State Valid: otg_ok + * Application on A-Device needs to power down bus. + */ +#define OTGADMIN_A_BUS_DROP _IOW(OTGADMIN_MAGIC, 13, int) + + /*! + * Signal Sent: b_bus_drop + * State Valid: otg_ok + * Application on B-Device needs to disconnect from bus. + */ +#define OTGADMIN_B_BUS_DROP _IOW(OTGADMIN_MAGIC, 14, int) + + /*! + * Signal Sent: bus_req + * State Valid: otg_ok + * Application on Device wants to use the bus. + */ +#define OTGADMIN_BUS_REQ _IOW(OTGADMIN_MAGIC, 15, int) + + /*! + * Signal Sent: a_bus_req + * State Valid: otg_ok + * Application on A-Device wants to act as host + */ +#define OTGADMIN_A_BUS_REQ _IOW(OTGADMIN_MAGIC, 16, int) + + /*! + * Signal Sent: b_bus_req + * State Valid: otg_ok + * Application on B-Device wants to act as host + */ +#define OTGADMIN_B_BUS_REQ _IOW(OTGADMIN_MAGIC, 17, int) + + /*! + * Signal Sent: b_sess_req + * State Valid: otg_ok + * Application on B-Device to perform SRP (alias for b_srp_req.) + */ +#define OTGADMIN_B_SESS_REQ _IOW(OTGADMIN_MAGIC, 18, int) + + /*! + * Signal Sent: suspend_req + * State Valid: otg_host + * Application on Device requests bus be suspended (alias for a_bus_req/.) + */ +#define OTGADMIN_SUSPEND_REQ _IOW(OTGADMIN_MAGIC, 19, int) + + /*! + * Signal Sent: a_suspend_req + * State Valid: otg_host + * Application on A-host requests bus be suspended (alias for a_bus_req/.) + */ +#define OTGADMIN_A_SUSPEND_REQ _IOW(OTGADMIN_MAGIC, 20, int) + + /*! + * Signal Sent: b_suspend_req + * State Valid: otg_host + * Application on B-host requests bus be suspended (alias for b_bus_req/.) + */ +#define OTGADMIN_B_SUSPEND_REQ _IOW(OTGADMIN_MAGIC, 21, int) + + /*! + * Signal Sent: set_remote_wakeup_cmd + * State Valid: a_host + * A-Device will send Remote Wakeup Enable Request + */ +#define OTGADMIN_SET_REMOTE_WAKEUP_CMD _IOW(OTGADMIN_MAGIC, 22, int) + + /*! + * Signal Sent: remote_wakeup_cmd + * State Valid: tr_configured + * B-Device will perform Remote Wakeup. + */ +#define OTGADMIN_REMOTE_WAKEUP_CMD _IOW(OTGADMIN_MAGIC, 23, int) + + /*! + * Signal Sent: reset_remote_wakeup_cmd + * State Valid: a_host + * A-Device will send Remote Wakeup Disable Request + */ +#define OTGADMIN_RESET_REMOTE_WAKEUP_CMD _IOW(OTGADMIN_MAGIC, 24, int) + + /*! + * Signal Sent: clr_err_cmd + * State Valid: a_vbus_err + * A-Device ill clears Vbus overcurrent error. + */ +#define OTGADMIN_CLR_ERR_CMD _IOW(OTGADMIN_MAGIC, 25, int) + + /*! + * Signal Sent: b_hnp_cmd + * State Valid: b_configured + * B-Device will attempt HNP + */ +#define OTGADMIN_B_HNP_CMD _IOW(OTGADMIN_MAGIC, 26, int) + + /*! + * Signal Sent: ph_int_cmd + * State Valid: ph_audio + * B-Device will request Carkit interrupt + */ +#define OTGADMIN_PH_INT_CMD _IOW(OTGADMIN_MAGIC, 27, int) + + /*! + * Signal Sent: ph_audio_cmd + * State Valid: ph_uart + * Application on B-Device connected to Carkit requests audio mode. + */ +#define OTGADMIN_PH_AUDIO_CMD _IOW(OTGADMIN_MAGIC, 28, int) + + /*! + * Signal Sent: cr_int_cmd + * State Valid: cr_aud + * Application on A-Device wants to emulate Carkit + */ +#define OTGADMIN_CR_INT_CMD _IOW(OTGADMIN_MAGIC, 29, int) + + /*! + * Signal Sent: led_on_cmd + * State Valid: ph_uart + * B-Device will enable Carkit LED + */ +#define OTGADMIN_LED_ON_CMD _IOW(OTGADMIN_MAGIC, 30, int) + + /*! + * Signal Sent: led_off_cmd + * State Valid: ph_uart + * B-Device will disable Carkit LED + */ +#define OTGADMIN_LED_OFF_CMD _IOW(OTGADMIN_MAGIC, 31, int) + + /*! + * Signal Sent: enable_otg + * State Valid: otg_all + * Move State Machine otg_disabled state. + */ +#define OTGADMIN_ENABLE_OTG _IOW(OTGADMIN_MAGIC, 32, int) + +#define OTGADMIN_MAXNR 33 + + +#endif /* defined(OTG_LINUX) */ + +/* Generated by otg-iocontrol-h.awk + * + * Do not Edit this file, + */ + +/* %Z %K */ + + +#if defined(OTG_WINCE) + +#define OTGADMIN_BASE$ 0x401 +#define _USBLAN_CTL_CODE(_Function, _Method, _Access) \ + CTL_CODE(OTGADMIN_BASE, _Function, _Method, _Access) + +#define OTGADMIN_VERSION OTGADMIN_CTL_CODE(0x401, METHOD_BUFFERRED, FILE_READ_ACCESS) +#define OTGADMIN_STATUS OTGADMIN_CTL_CODE(0x403, METHOD_BUFFERRED, FILE_READ_ACCESS) +#define OTGADMIN_SET_FUNCTION OTGADMIN_CTL_CODE(0x404, METHOD_BUFFERRED, FILE_WRITE_ACCESS) +#define OTGADMIN_GET_FUNCTION OTGADMIN_CTL_CODE(0x404, METHOD_BUFFERRED, FILE_READ_ACCESS) +#define OTGADMIN_SET_INFO OTGADMIN_CTL_CODE(0x405, METHOD_BUFFERRED, FILE_WRITE_ACCESS) +#define OTGADMIN_GET_INFO OTGADMIN_CTL_CODE(0x405, METHOD_BUFFERRED, FILE_READ_ACCESS) +#define OTGADMIN_SET_STATE OTGADMIN_CTL_CODE(0x406, METHOD_BUFFERRED, FILE_WRITE_ACCESS) +#define OTGADMIN_GET_STATE OTGADMIN_CTL_CODE(0x406, METHOD_BUFFERRED, FILE_READ_ACCESS) +#define OTGADMIN_SET_TEST OTGADMIN_CTL_CODE(0x407, METHOD_BUFFERRED, FILE_WRITE_ACCESS) +#define OTGADMIN_GET_TEST OTGADMIN_CTL_CODE(0x407, METHOD_BUFFERRED, FILE_READ_ACCESS) + + + + /*! + * Signal Sent: a_bcon_no_tmout_req + * State Valid: otg_host + * Application on A-host wants Ta_wait_bcon timeout disabled (non-OTG mode). + */ +#define OTGADMIN_A_BCON_NO_TMOUT_REQ OTGADMIN_CTL_CODE(, 0x40a, METHOD_BUFFERRED, FILE_READ_ACCESS) + + /*! + * Signal Sent: a_hpwr_req + * State Valid: otg_host + * Application on A-host wants external charge pump enabled. + */ +#define OTGADMIN_A_HPWR_REQ OTGADMIN_CTL_CODE(, 0x40b, METHOD_BUFFERRED, FILE_READ_ACCESS) + + /*! + * Signal Sent: bus_drop + * State Valid: otg_ok + * Application on Device needs to power down bus. + */ +#define OTGADMIN_BUS_DROP OTGADMIN_CTL_CODE(, 0x40c, METHOD_BUFFERRED, FILE_READ_ACCESS) + + /*! + * Signal Sent: a_bus_drop + * State Valid: otg_ok + * Application on A-Device needs to power down bus. + */ +#define OTGADMIN_A_BUS_DROP OTGADMIN_CTL_CODE(, 0x40d, METHOD_BUFFERRED, FILE_READ_ACCESS) + + /*! + * Signal Sent: b_bus_drop + * State Valid: otg_ok + * Application on B-Device needs to disconnect from bus. + */ +#define OTGADMIN_B_BUS_DROP OTGADMIN_CTL_CODE(, 0x40e, METHOD_BUFFERRED, FILE_READ_ACCESS) + + /*! + * Signal Sent: bus_req + * State Valid: otg_ok + * Application on Device wants to use the bus. + */ +#define OTGADMIN_BUS_REQ OTGADMIN_CTL_CODE(, 0x40f, METHOD_BUFFERRED, FILE_READ_ACCESS) + + /*! + * Signal Sent: a_bus_req + * State Valid: otg_ok + * Application on A-Device wants to act as host + */ +#define OTGADMIN_A_BUS_REQ OTGADMIN_CTL_CODE(, 0x410, METHOD_BUFFERRED, FILE_READ_ACCESS) + + /*! + * Signal Sent: b_bus_req + * State Valid: otg_ok + * Application on B-Device wants to act as host + */ +#define OTGADMIN_B_BUS_REQ OTGADMIN_CTL_CODE(, 0x411, METHOD_BUFFERRED, FILE_READ_ACCESS) + + /*! + * Signal Sent: b_sess_req + * State Valid: otg_ok + * Application on B-Device to perform SRP (alias for b_srp_req.) + */ +#define OTGADMIN_B_SESS_REQ OTGADMIN_CTL_CODE(, 0x412, METHOD_BUFFERRED, FILE_READ_ACCESS) + + /*! + * Signal Sent: suspend_req + * State Valid: otg_host + * Application on Device requests bus be suspended (alias for a_bus_req/.) + */ +#define OTGADMIN_SUSPEND_REQ OTGADMIN_CTL_CODE(, 0x413, METHOD_BUFFERRED, FILE_READ_ACCESS) + + /*! + * Signal Sent: a_suspend_req + * State Valid: otg_host + * Application on A-host requests bus be suspended (alias for a_bus_req/.) + */ +#define OTGADMIN_A_SUSPEND_REQ OTGADMIN_CTL_CODE(, 0x414, METHOD_BUFFERRED, FILE_READ_ACCESS) + + /*! + * Signal Sent: b_suspend_req + * State Valid: otg_host + * Application on B-host requests bus be suspended (alias for b_bus_req/.) + */ +#define OTGADMIN_B_SUSPEND_REQ OTGADMIN_CTL_CODE(, 0x415, METHOD_BUFFERRED, FILE_READ_ACCESS) + + /*! + * Signal Sent: set_remote_wakeup_cmd + * State Valid: a_host + * A-Device will send Remote Wakeup Enable Request + */ +#define OTGADMIN_SET_REMOTE_WAKEUP_CMD OTGADMIN_CTL_CODE(, 0x416, METHOD_BUFFERRED, FILE_READ_ACCESS) + + /*! + * Signal Sent: remote_wakeup_cmd + * State Valid: tr_configured + * B-Device will perform Remote Wakeup. + */ +#define OTGADMIN_REMOTE_WAKEUP_CMD OTGADMIN_CTL_CODE(, 0x417, METHOD_BUFFERRED, FILE_READ_ACCESS) + + /*! + * Signal Sent: reset_remote_wakeup_cmd + * State Valid: a_host + * A-Device will send Remote Wakeup Disable Request + */ +#define OTGADMIN_RESET_REMOTE_WAKEUP_CMD OTGADMIN_CTL_CODE(, 0x418, METHOD_BUFFERRED, FILE_READ_ACCESS) + + /*! + * Signal Sent: clr_err_cmd + * State Valid: a_vbus_err + * A-Device ill clears Vbus overcurrent error. + */ +#define OTGADMIN_CLR_ERR_CMD OTGADMIN_CTL_CODE(, 0x419, METHOD_BUFFERRED, FILE_READ_ACCESS) + + /*! + * Signal Sent: b_hnp_cmd + * State Valid: b_configured + * B-Device will attempt HNP + */ +#define OTGADMIN_B_HNP_CMD OTGADMIN_CTL_CODE(, 0x41a, METHOD_BUFFERRED, FILE_READ_ACCESS) + + /*! + * Signal Sent: ph_int_cmd + * State Valid: ph_audio + * B-Device will request Carkit interrupt + */ +#define OTGADMIN_PH_INT_CMD OTGADMIN_CTL_CODE(, 0x41b, METHOD_BUFFERRED, FILE_READ_ACCESS) + + /*! + * Signal Sent: ph_audio_cmd + * State Valid: ph_uart + * Application on B-Device connected to Carkit requests audio mode. + */ +#define OTGADMIN_PH_AUDIO_CMD OTGADMIN_CTL_CODE(, 0x41c, METHOD_BUFFERRED, FILE_READ_ACCESS) + + /*! + * Signal Sent: cr_int_cmd + * State Valid: cr_aud + * Application on A-Device wants to emulate Carkit + */ +#define OTGADMIN_CR_INT_CMD OTGADMIN_CTL_CODE(, 0x41d, METHOD_BUFFERRED, FILE_READ_ACCESS) + + /*! + * Signal Sent: led_on_cmd + * State Valid: ph_uart + * B-Device will enable Carkit LED + */ +#define OTGADMIN_LED_ON_CMD OTGADMIN_CTL_CODE(, 0x41e, METHOD_BUFFERRED, FILE_READ_ACCESS) + + /*! + * Signal Sent: led_off_cmd + * State Valid: ph_uart + * B-Device will disable Carkit LED + */ +#define OTGADMIN_LED_OFF_CMD OTGADMIN_CTL_CODE(, 0x41f, METHOD_BUFFERRED, FILE_READ_ACCESS) + + /*! + * Signal Sent: enable_otg + * State Valid: otg_all + * Move State Machine otg_disabled state. + */ +#define OTGADMIN_ENABLE_OTG OTGADMIN_CTL_CODE(, 0x420, METHOD_BUFFERRED, FILE_READ_ACCESS) + +#define OTGADMIN_MAXNR 33 + + +#endif /* defined(OTG_WINCE) */ + +/* Generated by otg-metas-h.awk + * + * Do not Edit thie file + */ + +/* %Z %K */ + + + /*! @name OTG Figure 6-2 Meta States + * + * These are the Meta States defined in the 2.0 OTG Specification + * Figure 6.2 - Dual-Role A-Device. + * @{ + */ + +#define m_a_idle 0 /* */ + +#define m_a_wait_vrise 1 /* */ + +#define m_a_wait_bcon 2 /* */ + +#define m_a_host 3 /* */ + +#define m_a_suspend 4 /* */ + +#define m_a_peripheral 5 /* */ + +#define m_a_wait_vfall 6 /* */ + +#define m_a_vbus_err 7 /* */ + /* @} */ + /*! @name OTG Figure 6-3 Meta States + * + * These are the Meta States defined in the 2.0 OTG Specification + * Figure 6.3 - Dual-Role B-Device. + * @{ + */ + +#define m_b_idle 8 /* */ + +#define m_b_srp_init 9 /* */ + +#define m_b_peripheral 10 /* */ + +#define m_b_suspend 11 /* */ + +#define m_b_wait_acon 12 /* */ + +#define m_b_host 13 /* */ + +#define m_b_suspended 14 /* */ + /* @} */ + /*! @name Carkit Meta States Figure 7-7 + * + * @{ + */ + +#define m_ph_disc 15 /* Equivalent to b_peripheral */ + +#define m_ph_init 16 /* */ + +#define m_ph_uart 17 /* */ + +#define m_ph_aud 18 /* */ + +#define m_ph_wait 19 /* */ + +#define m_ph_exit 20 /* */ + +#define m_cr_init 21 /* */ + +#define m_cr_uart 22 /* */ + +#define m_cr_aud 23 /* */ + +#define m_cr_ack 24 /* */ + +#define m_cr_wait 25 /* */ + +#define m_cr_disc 26 /* */ + /* @} */ + /*! @name Additional states used locally. + * + * @{ + */ + +#define m_otg_init 27 /* */ + +#define m_usb_accessory 28 /* */ + +#define m_usb_factory 29 /* */ + +#define m_unknown 30 /* */ + /* @} */ + +#define OTG_METAS_FW 31 + +extern char *otg_meta_names[]; diff --git a/drivers/otg/otg/otg-hcd.h b/drivers/otg/otg/otg-hcd.h new file mode 100644 index 000000000000..9f0412d457d1 --- /dev/null +++ b/drivers/otg/otg/otg-hcd.h @@ -0,0 +1,102 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/otg-hcd.h - OTG Host Controller Driver + * @(#) sl@belcarra.com/whiskey.enposte.net|otg/otg/otg-hcd.h|20061218212925|63115 + * + * Copyright (c) 2004-2005 Belcarra Technologies Corp + * Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + * + * By: + * Stuart Lynne <sl@belcarra.com>, + * Bruce Balden <balden@belcarra.com> + * + * + */ +/*! + * @file otg/otg/otg-hcd.h + * @brief Defines common to On-The-Go Host Controller Support + * + * This file defines the hcd_ops and hcd_instance structures. + * + * The hcd_ops structure contains all of the output functions that will + * be called as required by the OTG event handler when changing states. + * + * The hcd_instance structure is used to maintain the global data + * required by the host controller drivers. + * + * @ingroup OTGAPI + * @ingroup HCD + */ + +/*! + * @name HCD Host Controller Driver + * @{ + */ +struct hcd_instance; + +/*! + * @struct hcd_ops + * The pcd_ops structure contains pointers to all of the functions implemented for the + * linked in driver. Having them in this structure allows us to easily determine what + * functions are available without resorting to ugly compile time macros or ifdefs + * + * There is only one instance of this, defined in the device specific lower layer. + */ +struct hcd_ops { + + /* Driver Initialization - by degrees + */ + int (*mod_init) (struct otg_instance *); /*!< HCD Module Initialization */ + void (*mod_exit) (struct otg_instance *); /*!< HCD Module Exit */ + + + /* mandatory */ + int max_ports; /*!< maximum number of ports available */ + u32 capabilities; /*!< UDC Capabilities - see usbd-bus.h for details */ + char *name; /*!< name of controller */ + + + otg_output_proc_t hcd_init_func; /*!< OTG calls to initialize or de-initialize the HCD */ + otg_output_proc_t hcd_en_func; /*!< OTG calls to enable or disable the HCD */ + otg_output_proc_t hcd_rh_func; /*!< OTG calls to enable HCD Root Hub */ + otg_output_proc_t loc_sof_func; /*!< OTG calls to into a_host or b_host state - attempt to use port */ + otg_output_proc_t loc_suspend_func; /*!< OTG calls to suspend bus */ + otg_output_proc_t remote_wakeup_en_func; /*!< OTG calls to issue SET FEATURE REMOTE WAKEUP */ + otg_output_proc_t hnp_en_func; /*!< OTG calls to issues SET FEATURE B_HNP_ENABLE */ + + framenum_t framenum; /*!< OTG calls to get current USB Frame number */ +}; + +/*! + * @struct hcd_instance otg-hcd.h "otg/otg-hcd.h" + */ +struct hcd_instance { + struct otg_instance *otg; /*!< pointer to OTG Instance */ + otg_tag_t TAG; + void * privdata; /*!< pointer to private data for PCD */ + //struct WORK_STRUCT bh; /*!< work structure for bottom half handler */ + int active; +}; + +#define HCD hcd_trace_tag +extern otg_tag_t HCD; +extern struct hcd_ops hcd_ops; +extern struct hcd_instance *hcd_instance; +#if !defined(OTG_C99) +extern void fs_hcd_global_init(void); +#endif /* !defined(OTG_C99) */ +extern void hcd_init_func(struct otg_instance *, u8 ); +extern void hcd_en_func(struct otg_instance *, u8 ); + +/* @} */ diff --git a/drivers/otg/otg/otg-linux.h b/drivers/otg/otg/otg-linux.h new file mode 100644 index 000000000000..48c2a336bd39 --- /dev/null +++ b/drivers/otg/otg/otg-linux.h @@ -0,0 +1,1384 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/otg/otg-linux.h + * @(#) balden@belcarra.com/seth2.rillanon.org|otg/otg/linux/otg-linux.h|20070711184304|55012 + * + * Copyright (c) 2004-2005 Belcarra + * Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + * + */ + +/*! + * @file otg/otg/otg-linux.h + * @brief Linux OS Compatibility defines + * + * See otg-os.h for API definitions. + * + * @ingroup OSAPI + * @ingroup LINUXAPI + */ +#ifndef _OTG_LINUX_H +#define _OTG_LINUX_H 1 + +#if !defined(_OTG_MODULE_H) +#include <otg/otg-module.h> +#endif + + +#include <linux/kernel.h> +#include <linux/version.h> +#include <linux/init.h> +#include <linux/ctype.h> +#include <linux/slab.h> +#include <asm/types.h> +#include <asm/uaccess.h> +#include <asm/semaphore.h> +#include <asm/atomic.h> +#include <linux/proc_fs.h> +#include <linux/interrupt.h> +#include <linux/random.h> +#include <linux/sched.h> +#include <linux/workqueue.h> + +#if !defined(LINUX26) && !defined(LINUX24) +#error "One of LINUX24 and LINUX26 needs to be defined here" +#endif + +#if defined(LINUX26) +#include <linux/device.h> +#endif + + +#undef OTG_SKYE_LED +#ifdef OTG_SKYE_LED +#include <asm/arch/gpio.h> +#define LED1 SP_GP_SP_A26 +#define LED2 SP_GP_SP_A7 +void otg_led(int led, int flag); +void otg_led_init(int led); + +//#define LEDI LED1 /* define this to get local_irq_save/restore */ +#define LEDI 0 + +#else +#define LED1 0 +#define LED2 1 +#define LEDI 0 +void otg_led(int led, int flag); +void otg_led_init(int led); +#endif + +/* ********************************************************************************************** */ + +#define CONFIG_OTG_LNX 1 + +/*! @name Compilation Related + */ + +/*@{*/ +#undef PRAGMAPACK +/** Macros to support packed structures **/ +#define PACKED1 __attribute__((packed)) +#define PACKED2 +#define PACKED0 + +/** Macros to support packed enum's **/ +#define PACKED_ENUM enum +#define PACKED_ENUM_EXTRA + +#define INLINE __inline__ + +/*@}*/ + +/*! @name Lock Interrupts + */ + +/*@{*/ + +static inline void otg_disable_interrupts(void) +{ + /* do nothing */ +} + +static inline void otg_enable_interrupts(void) +{ + /* do nothing */ +} + + +/*@}*/ + + +/* ********************************************************************************************** */ +/* otg-trace - included so that TRACE_MSG can be used if desired to debug + * the otg-xxx implementation + */ + +typedef u32 otg_tick_t; +#include <otg/otg-trace.h> + + +/* ********************************************************************************************** */ +/*! + * @name Semaphore + * + * Posix compatible Semaphores. + * @{ + */ +//typedef struct semaphore otg_sem_t; + +typedef struct xotg_sem { + char *name; + BOOL semdebug; + struct semaphore sem; +} otg_sem_t; + + +static int inline otg_sem_init(char *name, otg_sem_t *sem, int pshared, unsigned value) { + sem->name = name; + sem->semdebug = FALSE; + sema_init(&sem->sem, value); + return 0; +} + +static int inline otg_sem_init_locked(char *name, otg_sem_t *sem) { + otg_sem_init(name, sem, 1, 0); + //sema_init(sem, 0); + return 0; +} + +static int inline otg_sem_init_unlocked(char *name, otg_sem_t *sem) { + otg_sem_init(name, sem, 1, 1); + //sema_init(sem, 1); + return 0; +} + +static int inline otg_sem_destroy(otg_sem_t *sem) { + return 0; +} +static int inline otg_sem_wait(otg_sem_t *sem) { + return down_interruptible(&sem->sem); +} +static int inline otg_sem_trywait(otg_sem_t *sem) { + return down_trylock(&sem->sem); +} +static int inline otg_sem_post(otg_sem_t *sem) { + up(&sem->sem); + return 0; +} + +/*! @} */ + + +/* ********************************************************************************************** */ +/*! + * @name Mutex + * + * Posix compatible Mutexes. + * @{ + */ +typedef unsigned long otg_pthread_mutex_t; + +static int inline otg_pthread_mutex_lock(otg_pthread_mutex_t *mutex) { + local_irq_save(*mutex); + return 0; +} + +static int inline otg_pthread_mutex_trylock(otg_pthread_mutex_t *mutex) { + local_irq_save(*mutex); + return 0; +} +static int inline otg_pthread_mutex_unlock(otg_pthread_mutex_t *mutex) { + local_irq_restore(*mutex); + return 0; +} + + +/* ********************************************************************************************** */ +/*! @} */ + +/*! + * @name Atomic + * + * Basic Atomic operations. + * @{ + */ +typedef atomic_t otg_atomic_t; + +static void inline otg_atomic_set(otg_atomic_t *a, int b) { + atomic_set(a, b); +} +static void inline otg_atomic_add(int a, otg_atomic_t *b) { + atomic_add(a, b); +} +static void inline otg_atomic_sub(int a, otg_atomic_t *b) { + atomic_sub(a, b); +} +static void inline otg_atomic_clr(otg_atomic_t *a) { + atomic_set(a, 0); +} +static void inline otg_atomic_inc(otg_atomic_t *a) { + return atomic_inc(a); +} +static void inline otg_atomic_dec(otg_atomic_t *a) { + return atomic_dec(a); +} +static int inline otg_atomic_read(otg_atomic_t *a) { + return atomic_read(a); +} + +static __inline__ int atomic_post_inc(volatile otg_atomic_t *v) { + unsigned long flags; + int result; + local_irq_save(flags); + result = (v->counter)++; + local_irq_restore(flags); + return(result); +} + +static __inline__ int atomic_pre_dec(volatile otg_atomic_t *v) { + unsigned long flags; + int result; + local_irq_save(flags); + result = --(v->counter); + local_irq_restore(flags); + return(result); +} + +/*! @} */ + +/* ********************************************************************************************** */ +/*! + * @name Time of Day + * gettimeofday + * @{ + */ +static int inline otg_gettimeofday(struct timeval *tv) +{ + do_gettimeofday(tv); + return 0; +} + + +static void inline otg_get_random_bytes(u8 *p, int n) +{ + get_random_bytes(p, n); +} + +/*! @} */ + +/* ********************************************************************************************** */ +/*! + * @name Memory Allocation + * + * Simple allocated memory operations. + * @{ + */ + +void * otg_cmalloc(int n); +void otg_free(void *); + +#define KMALLOC(n) _kmalloc(__FUNCTION__, __LINE__, n, GFP_ATOMIC) +#define CKMALLOC(n) _ckmalloc(__FUNCTION__, __LINE__, n, GFP_ATOMIC) +#define LSTRDUP(str) _lstrdup(__FUNCTION__, __LINE__, str) +#define LKFREE(p) _lkfree(__FUNCTION__, __LINE__, p) + +//#define kmalloc(n) _kmalloc(__FUNCTION__, __LINE__, n, GFP_ATOMIC) +#define ckmalloc(n) _ckmalloc(__FUNCTION__, __LINE__, n, GFP_ATOMIC) +#define lstrdup(str) _lstrdup(__FUNCTION__, __LINE__, str) +#define lkfree(p) _lkfree(__FUNCTION__, __LINE__, p) + +#define OTG_MALLOC_TEST +#undef OTG_MALLOC_DEBUG +//#define OTG_MALLOC_DEBUG +#ifdef OTG_MALLOC_TEST + extern int otg_mallocs; +#endif + + +static inline void *_kmalloc (const char *func, int line, int n, int f) +{ + void *p; + if ((p = kmalloc (n, f)) == NULL) { + return NULL; + } + #ifdef OTG_MALLOC_TEST + ++otg_mallocs; + #endif + #ifdef OTG_MALLOC_DEBUG + printk(KERN_INFO"%s: %p %s %d %d\n", __FUNCTION__, p, func, line, otg_mallocs); + #endif + return p; +} + +static inline void *_ckmalloc (const char *func, int line, int n, int f) +{ + void *p; + if ((p = kmalloc (n, f)) == NULL) { + return NULL; + } + memset (p, 0, n); + #ifdef OTG_MALLOC_TEST + ++otg_mallocs; + #endif + #ifdef OTG_MALLOC_DEBUG + printk(KERN_INFO"%s: %p %s %d %d\n", __FUNCTION__, p, func, line, otg_mallocs); + #endif + return p; +} + +static inline char *_lstrdup (const char *func, int line, char *str) +{ + int n; + char *s; + if (str && (n = strlen (str) + 1) && (s = kmalloc (n, GFP_ATOMIC))) { +#ifdef OTG_MALLOC_TEST + ++otg_mallocs; +#endif +#ifdef OTG_MALLOC_DEBUG + printk(KERN_INFO"%s: %p %s %d %d\n", __FUNCTION__, s, func, line, otg_mallocs); +#endif + return strcpy (s, str); + } + return NULL; +} + +static inline void _lkfree (const char *func, int line, void *p) +{ + if (p) { +#ifdef OTG_MALLOC_TEST + --otg_mallocs; +#endif +#ifdef OTG_MALLOC_DEBUG + printk(KERN_INFO"%s:--1 %p %s %d %d\n", __FUNCTION__, p, func, line, otg_mallocs); +#endif + kfree (p); +#ifdef MALLOC_TEST + if (otg_mallocs < 0) { + printk(KERN_INFO"%s: %p %s %d %d otg_mallocs less zero!\n", __FUNCTION__, p, func, line, otg_mallocs); + } +#endif +#ifdef OTG_MALLOC_DEBUG + if (otg_mallocs >= 0) { + printk(KERN_INFO"%s:--2 %s %d NULL\n", __FUNCTION__, func, line); + } +#endif + } +} + + + +/*! @} */ + +/* ********************************************************************************************** */ +/*! + * @name Task Allocation + * + * Long lived, low priority, high latency tasks. + * + * Tasks may use semaphores, mutexes, atomic operations and memory allocations. + * @{ + * + * TASKS are long lived, MAY HAVE low priority and high latency, the typical + * implementation has the work item wait in a semaphore waiting for work. :w + * + * The TASK work functions are structured to run until terminated, using + * a semaphore to wait for work. + * + * TASK creation: + * struct otg_task *task = otg_task_init("taskname", task_work, TAG); + * if (task) + * otg_task_start(task); + * + * TASK termination: + * otg_task_exit(task); + * + * TASK work notification + * otg_up_work(task); + * + * TASK work process + * + * void task_work(void *data) + * { + * struct otg_task *task = (struct otg_task *)data; + * void *mydata = task->data; + * otg_up_admin(task); + * do { + * // wait for work + * otg_down_work(task); + * // do work + * } while (!task->terminating); + * task->terminated = TRUE; + * otg_up_admin(task); + * } + * + * static struct otg_task *otg_task_init(char *name, void (*work) (void *), void *data, otg_tag_t tag) + * static void otg_up_work(struct otg_task *task) + * static void otg_up_admin(struct otg_task *task) + * static void otg_down_work(struct otg_task *task) + * static void otg_down_admin(struct otg_task *task) + * static void otg_task_start(struct otg_task *task) + * static void otg_task_exit(struct otg_task *task) + * + */ + +/* XXX CONFIG_OTG_TASK_WORK + * + * Linux Implementation Configuration + * + * #define CONFIG_OTG_TASK_WORK + * Use work items only for TASK implemenation + */ + +#if defined(LINUX26) + #define SCHEDULE_WORK(item) schedule_work(&item) +#else /* LINUX26 */ + #define SCHEDULE_WORK(item) schedule_work(&item) +#endif /* LINUX26 */ + +/*! struct otg_task + */ +typedef void *otg_task_arg_t; +typedef void *(* otg_task_proc_t) (otg_task_arg_t data); +struct otg_task { + #if !defined(CONFIG_OTG_TASK_WORK) + struct workqueue_struct *work_queue; + otg_sem_t admin_sem; + otg_sem_t work_sem; + #endif /* defined(CONFIG_OTG_TASK_WORK) */ + + #if defined(LINUX26) + struct work_struct work; + #else /* LINUX26 */ + struct work_struct work; + #endif /* LINUX26 */ + otg_task_proc_t proc; + BOOL terminate; + BOOL terminated; + otg_task_arg_t *data; + BOOL taskdebug; + otg_tag_t tag; + char *name; +}; + +static void inline otg_sleep(int n) +{ + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout( n * HZ ); +} + +/*! otg_up_admin + * @brief Signal start task to re-start. + * @param task - otg_task instance pointer + */ +static void inline otg_up_admin(struct otg_task *task) +{ + #if defined(CONFIG_OTG_TASK_WORK) + /* NOTHING */ + #else /* defined(CONFIG_OTG_TASK_WORK) */ + //TRACE_STRING(task->tag, "UP ADMIN: %s", task->name); + if (task->taskdebug) + printk(KERN_INFO"%s: %s\n", __FUNCTION__, task->name); + otg_sem_post(&task->admin_sem); + #endif /* defined(CONFIG_OTG_TASK_WORK) */ +} + +/*! otg_down_work + *@brief Used by work task to wait. + *@param task - otg_task instance pointer + */ +static void inline otg_down_work(struct otg_task *task) +{ + #if defined(CONFIG_OTG_TASK_WORK) + /* NOTHING */ + #else /* defined(CONFIG_OTG_TASK_WORK) */ + //TRACE_STRING(task->tag, "DOWN WORK: %s", task->name); + if (task->taskdebug) + printk(KERN_INFO"%s: %s\n", __FUNCTION__, task->name); + + while (otg_sem_wait(&task->work_sem)); + #endif /* defined(CONFIG_OTG_TASK_WORK) */ +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20) +/*! otg_task_proc - otg task handler + * @param data - otg_task instance pointer + */ +static void inline otg_task_proc(otg_task_arg_t data) +#else /* LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20) */ +/*! otg_task_proc - otg task handler + * @param work - otg_task instance pointer + */ +static void inline otg_task_proc(struct work_struct *work) +#endif /* LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20) */ +{ + #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20) + struct otg_task *task = (struct otg_task *)data; + #else /* LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20) */ + struct otg_task *task = (struct otg_task *)container_of(work, struct otg_task, work); + #endif /* LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20) */ + otg_up_admin(task); + do { + // wait for work + if (task->taskdebug) + printk(KERN_INFO"%s: DOWN %s\n", __FUNCTION__, task->name); + + otg_down_work(task); + + if (task->taskdebug) + printk(KERN_INFO"%s: WORKING %s\n", __FUNCTION__, task->name); + + task->proc(task->data); + } while (!task->terminate); + if (task->taskdebug) + printk(KERN_INFO"%s: TERMINATING %s\n", __FUNCTION__, task->name); + task->terminated = TRUE; + otg_up_admin(task); +} + +/*! otg_task_init + *@brief Create otg task structure, create workqueue, initialize it. + *@param name - name of task or workqueue + *@param proc - handler + *@param data - parameter pointer for handler + *@param tag- + *@return initialized otg_task instance pointer + */ +static inline struct otg_task *otg_task_init2(char *name, otg_task_proc_t proc, otg_task_arg_t data, otg_tag_t tag) +{ + struct otg_task *task; + + //TRACE_STRING(tag, "INIT: %s", name); + + RETURN_NULL_UNLESS((task = CKMALLOC(sizeof (struct otg_task)))); + + task->tag = tag; + task->data = data; + task->name = name; + task->proc = proc; + + #if defined(CONFIG_OTG_TASK_WORK) + task->terminated = task->terminate = TRUE; + #else /* defined(CONFIG_OTG_TASK_WORK) */ + task->terminated = task->terminate = FALSE; + #if defined(LINUX26) + THROW_UNLESS((task->work_queue = create_singlethread_workqueue(name)), error); + #else /* LINUX26 */ + THROW_UNLESS((task->work_queue = create_workqueue(name)), error); + #endif /* LINUX26 */ + //init_MUTEX_LOCKED(&task->admin_sem); + //init_MUTEX_LOCKED(&task->work_sem); + otg_sem_init_locked(name, &task->admin_sem); + otg_sem_init_locked(name, &task->work_sem); + #endif /* defined(CONFIG_OTG_TASK_WORK) */ + + #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20) + INIT_WORK(&task->work, otg_task_proc, task); + #else /* LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20) */ + INIT_WORK(&task->work, otg_task_proc); + #endif /* LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20) */ + + return task; + + CATCH(error) { + printk(KERN_INFO"%s: ERROR\n", __FUNCTION__); + if (task) LKFREE(task); + return NULL; + } +} + + +/*! otg_up_work + * @brief Signal work param taskto re-start. + * @param task - otg_task instance pointer + */ +static void inline otg_up_work(struct otg_task *task) +{ + //TRACE_STRING(task->tag, "UP WORK: %s", task->name); + + if (task->taskdebug) + printk(KERN_INFO"%s: %s\n", __FUNCTION__, task->name); + + #if defined(CONFIG_OTG_TASK_WORK) + //printk(KERN_INFO"%s: AAAA\n", __FUNCTION__); + task->terminated = FALSE; + SCHEDULE_WORK(task->work); + #else /* defined(CONFIG_OTG_TASK_WORK) */ + //printk(KERN_INFO"%s: BBBB\n", __FUNCTION__); + otg_sem_post(&task->work_sem); + #endif /* defined(CONFIG_OTG_TASK_WORK) */ +} + +/*! otg_down_admin + * @brief Used by admin task to wait. + * @param task - otg_task instance pointer + */ +static void inline otg_down_admin(struct otg_task *task) +{ + #if defined(CONFIG_OTG_TASK_WORK) + /* NOTHING */ + #else /* defined(CONFIG_OTG_TASK_WORK) */ + //TRACE_STRING(task->tag, "DOWN ADMIN: %s", task->name); + if (task->taskdebug) + printk(KERN_INFO"%s: %s\n", __FUNCTION__, task->name); + while (otg_sem_wait(&task->admin_sem)); + //DOWN(&task->admin_sem); + #endif /* defined(CONFIG_OTG_TASK_WORK) */ +} + +/*! otg_task_start + * @brief Start and wait for otg task. + * @param task - otg_task instance pointer + */ +static void inline otg_task_start(struct otg_task *task) +{ + #if defined(CONFIG_OTG_TASK_WORK) + /* NOTHING */ + #else /* defined(CONFIG_OTG_TASK_WORK) */ + //TRACE_STRING(task->tag, "START: %s", task->name); + if (task->taskdebug) + printk(KERN_INFO"%s: %s\n", __FUNCTION__, task->name); + + /* schedule work item and wait until it starts */ + queue_work(task->work_queue, &task->work); + otg_down_admin(task); + #endif /* defined(CONFIG_OTG_TASK_WORK) */ +} + +/*! otg_task_exit + * @brief Terminate and wait for otg task. + * @param task - otg_task instance pointer + */ +static void inline otg_task_exit(struct otg_task *task) +{ + TRACE_STRING(task->tag, "EXIT: %s", task->name); + if (task->taskdebug) + printk(KERN_INFO"%s: %s\n", __FUNCTION__, task->name); + + #if defined(CONFIG_OTG_TASK_WORK) + while (!task->terminated) { + otg_sleep(1); + } + #else /* defined(CONFIG_OTG_TASK_WORK) */ + /* signal termination */ + task->terminate = TRUE; + otg_up_work(task); + otg_down_admin(task); + + /* destroy workqueue */ + flush_workqueue(task->work_queue); + destroy_workqueue(task->work_queue); + #endif /* defined(CONFIG_OTG_TASK_WORK) */ + + LKFREE(task); +} + + +/* ********************************************************************************************** */ +/*! + * @name WorkItem Allocation + * + * Short lived, low priority, high latency task. + * + * @{ + * + * Work items are similar to tasks except that they are not expected + * to run for long periods of time or wait. + * + * A Work item function is run once and exits when the work + * is finished. + * + * static struct otg_workitem *otg_workitem_init(char *name, void (*work) (unsigned long), unsigned long data, otg_tag_t tag) + * static void otg_workitem_start(struct otg_workitem *tasklet) + * static void otg_workitem_exit(struct otg_workitem *tasklet) + * + * WORK creation: + * struct otg_workitem *tasklet = otg_workitem_init("taskletname", tasklet_work, TAG); + * + * WORK start: + * otg_workitem_start(tasklet); + * + * WORK termination: + * otg_workitem_exit(tasklet); + * + * WORK work process + * + * void task_work(void *data) + * { + * struct otg_workitem *tasklet = (struct otg_workitem *)data; + * void *mydata = task->data; + * + * // do work + * + * } + * + */ + +/*! struct otg_workitem + */ +typedef void *otg_workitem_arg_t; +typedef void *(* otg_workitem_proc_t) (otg_task_arg_t data); + +struct otg_workitem { + + #if defined(LINUX26) + struct workqueue_struct *work_queue; + #endif /* defined(LINUX26) */ + + struct work_struct work; + otg_workitem_proc_t proc; + BOOL terminate; + BOOL terminated; + otg_task_arg_t *data; + BOOL workdebug; + otg_tag_t tag; + char *name; +}; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20) +/*! otg_workitem_run + * @param data - workitem poiner + */ +static inline void otg_workitem_run(void *data) +#else /* LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20) */ +/*! otg_workitem_run + * @param work - work struct poiner + */ +static inline void otg_workitem_run(struct work_struct *work) +#endif /* LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20) */ +{ + #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20) + struct otg_workitem *workitem = (struct otg_workitem *)data; + #else /* LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20) */ + struct otg_workitem *workitem = (struct otg_workitem *)container_of(work, struct otg_workitem, work); + #endif /* LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20) */ + + if (workitem->workdebug) + printk(KERN_INFO"%s: %s\n", __FUNCTION__, workitem->name); + + workitem->proc(workitem->data); // XXX convert to workitem->data + workitem->terminated = TRUE; + + if (workitem->workdebug) + printk(KERN_INFO"%s: %s finished\n", __FUNCTION__, workitem->name); + +} + +/*! otg_workitem_init + * @brief Create otg work structure, create workqueue, initialize it. + * @param name - workitem name + * @param proc - workitem handler + * @param data - workitem data + * @param tag - otg tag + * @return otg_workitem instance pointer + */ +static inline struct otg_workitem *otg_workitem_init(char *name, otg_workitem_proc_t proc, otg_workitem_arg_t data, otg_tag_t tag) +{ + struct otg_workitem *workitem; + + //TRACE_STRING(tag, "INIT: %s", name); + + //printk(KERN_INFO"%s: %s\n", __FUNCTION__, name); + + RETURN_NULL_UNLESS((workitem = CKMALLOC(sizeof (struct otg_workitem)))); + + workitem->data = data; + workitem->tag = tag; + workitem->name = name; + workitem->proc = proc; + //workitem->workdebug = TRUE; + + workitem->terminated = workitem->terminate = TRUE; + + #if defined(LINUX26) + THROW_UNLESS((workitem->work_queue = create_singlethread_workqueue(name)), error); + #endif /* LINUX26 */ + #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20) + INIT_WORK(&workitem->work, otg_workitem_run, workitem); + #else /* LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20) */ + INIT_WORK(&workitem->work, otg_workitem_run); + #endif /* LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20) */ + + return workitem; + + CATCH(error) { + printk(KERN_INFO"%s: ERROR\n", __FUNCTION__); + if (workitem) LKFREE(workitem); + return NULL; + } +} + +/*! otg_workitem_start + * @brief Signal work work to run. + * @param workitem - workitem pointer + */ +static void inline otg_workitem_start(struct otg_workitem *workitem) +{ + //TRACE_STRING(workitem->tag, "START: %s", workitem->name); + + if (workitem->workdebug) + printk(KERN_INFO"%s: %s\n", __FUNCTION__, workitem->name); + + //workitem->proc(workitem->data); + workitem->terminated = FALSE; + #if defined(LINUX26) + queue_work(workitem->work_queue,&workitem->work); + #else /* defined(LINUX26) */ + SCHEDULE_WORK(workitem->work); + #endif /* defined(LINUX26) */ +} + +/*! otg_workitem_exit + * @brief Terminate and wait for otg work. + * @param workitem - workitem pointer + */ +static void inline otg_workitem_exit(struct otg_workitem *workitem) +{ + //TRACE_STRING(workitem->tag, "EXIT: %s", workitem->name); + if (workitem->workdebug) + printk(KERN_INFO"%s: %s terminating\n", __FUNCTION__, workitem->name); + + while (!workitem->terminated) { + otg_sleep(1); + if (workitem->workdebug) + printk(KERN_INFO"%s: %s while loop terminating\n", __FUNCTION__, workitem->name); + } + + if (workitem->workdebug) + printk(KERN_INFO"%s: %s terminated\n", __FUNCTION__, workitem->name); +#if defined(LINUX26) + /* destroy workqueue */ + flush_workqueue(workitem->work_queue); + destroy_workqueue(workitem->work_queue); +#endif /* LINUX26 */ + LKFREE(workitem); +} + + +/*! @} */ + +/* ********************************************************************************************** */ +/*! + * @name Tasklet Allocation + * + * Short lived, high priority, low latency tasklets. + * + * Tasklets MAY NOT use memory allocation or wait on semaphores. + * + * Taskslets may use signal semaphores, mutexes and atomic operations. + * @{ + * + * TASKLETS are short lived, MUST run with low latency and SHOULD have high + * priority. + * + * The TASKLET work function is structure to run once and exit when the work + * is finished. + * + * static struct otg_tasklet *otg_tasklet_init(char *name, void (*work) (unsigned long), unsigned long data, otg_tag_t tag) + * static void otg_tasklet_start(struct otg_tasklet *tasklet) + * static void otg_tasklet_exit(struct otg_tasklet *tasklet) + * + * TASKLET creation: + * struct otg_tasklet *tasklet = otg_tasklet_init("taskletname", tasklet_work, TAG); + * + * TASKLET start: + * otg_tasklet_start(tasklet); + * + * TASKLET termination: + * otg_tasklet_exit(tasklet); + * + * TASKLET work process + * + * void task_work(otg_tasklet_arg_t data) + * { + * void *mydata = data; + * + * // do work + * + * } + * + */ +/* XXX CONFIG_OTG_TASKLET_WORK + * + * Linux Implementation Configuration + * + * #define CONFIG_OTG_TASKLET_WORK + * Use work items only for TASK implemenation + * + * N.B. the Linux work item implementation for tasklets is not suitable + * for full OTG Dual-Role implemenation. + * + */ + +/*! struct otg_tasklet + */ +#ifdef CONFIG_OTG_TASKLET_WORK +typedef void *otg_tasklet_arg_t; +#else /* CONFIG_OTG_TASKLET_WORK */ +typedef unsigned long otg_tasklet_arg_t; +#endif /* CONFIG_OTG_TASKLET_WORK */ +typedef void *(* otg_tasklet_proc_t) (otg_tasklet_arg_t data); +struct otg_tasklet { + #ifdef CONFIG_OTG_TASKLET_WORK + struct work_struct work; + #else /* CONFIG_OTG_TASKLET_WORK */ + struct tasklet_struct tasklet; + #endif /* CONFIG_OTG_TASKLET_WORK */ + BOOL terminated; + otg_tasklet_proc_t proc; + otg_tasklet_arg_t data; + BOOL taskdebug; + otg_tag_t tag; + char *name; +}; + +/*! otg_tasklet_run + * @param data - otg_tasklet pointer + */ +#ifdef CONFIG_OTG_TASKLET_WORK +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20) +static inline void otg_tasklet_run(otg_tasklet_arg_t data) +#else /* LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20) */ +static inline void otg_tasklet_run(struct work_struct *work) +#endif /* LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20) */ +#else /* CONFIG_OTG_TASKLET_WORK */ +static inline void otg_tasklet_run(otg_tasklet_arg_t data) +#endif /* CONFIG_OTG_TASKLET_WORK */ +{ + #ifdef CONFIG_OTG_TASKLET_WORK + #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20) + struct otg_tasklet *tasklet = (struct otg_tasklet *)data; + #else /* LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20) */ + struct otg_tasklet *tasklet = (struct otg_tasklet *)container_of(work, struct otg_tasklet, work); + #endif /* LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20) */ + #else /* CONFIG_OTG_TASKLET_WORK */ + struct otg_tasklet *tasklet = (struct otg_tasklet *)data; + #endif /* CONFIG_OTG_TASKLET_WORK */ + + if (tasklet->taskdebug) + printk(KERN_INFO"%s: %s\n", __FUNCTION__, tasklet->name); + + tasklet->proc(tasklet->data); + tasklet->terminated = TRUE; + + if (tasklet->taskdebug) + printk(KERN_INFO"%s: %s finished\n", __FUNCTION__, tasklet->name); +} + +/*! otg_tasklet_init + * @brief Create otg task structure, create workqueue, initialize it. + * @param name - otg_tasklet name + * @param proc - otg_tasklet process + * @param data - otg_taskle data, argument for proc + * @param tag - otg_tasklet tag + * @return initialized otg_tasklet instance pointer + */ +static inline struct otg_tasklet *otg_tasklet_init(char *name, otg_tasklet_proc_t proc, otg_tasklet_arg_t data, otg_tag_t tag) +{ + struct otg_tasklet *tasklet; + + //TRACE_STRING(tag, "INIT: %s", name); + //printk(KERN_INFO"%s: %s\n", __FUNCTION__, name); + + RETURN_NULL_UNLESS((tasklet = CKMALLOC(sizeof (struct otg_tasklet)))); + + tasklet->tag = tag; + tasklet->name = name; + tasklet->terminated = TRUE; + tasklet->proc = proc; + tasklet->data = data; + + #ifdef CONFIG_OTG_TASKLET_WORK + INIT_WORK(&tasklet->work, otg_tasklet_run, tasklet); + #else /* CONFIG_OTG_TASKLET_WORK */ + tasklet_init(&tasklet->tasklet, otg_tasklet_run, (otg_tasklet_arg_t) tasklet); + #endif /* CONFIG_OTG_TASKLET_WORK */ + + return tasklet; + + CATCH(error) { + if (tasklet) LKFREE(tasklet); + return NULL; + } +} + +/*! otg_tasklet_start + * @brief Signal work task to re-start. + * @param tasklet - otg_tasklet instance pointer + */ +static void inline otg_tasklet_start(struct otg_tasklet *tasklet) +{ + //TRACE_STRING(tag, "START: %s", name); + if (tasklet->taskdebug) + printk(KERN_INFO"%s: %s\n", __FUNCTION__, tasklet->name); + + tasklet->terminated = FALSE; + #ifdef CONFIG_OTG_TASKLET_WORK + SCHEDULE_WORK(tasklet->work); + #else /* CONFIG_OTG_TASKLET_WORK */ + tasklet_schedule(&tasklet->tasklet); + #endif /* CONFIG_OTG_TASKLET_WORK */ +} + +/*! otg_tasklet_exit + * @brief Terminate and wait for otg task. + * @param tasklet - otg_tasklet instance pointer + */ +static void inline otg_tasklet_exit(struct otg_tasklet *tasklet) +{ + //TRACE_STRING(tag, "EXIT: %s", name); + if (tasklet->taskdebug) + printk(KERN_INFO"%s: %s\n", __FUNCTION__, tasklet->name); + + #ifdef CONFIG_OTG_TASKLET_WORK + while (!tasklet->terminated) { + if (tasklet->taskdebug) + printk(KERN_INFO"%s: SLEEPING\n", __FUNCTION__); + otg_sleep(1); + if (tasklet->taskdebug) + printk(KERN_INFO"%s: RUNNING\n", __FUNCTION__); + } + #else /* CONFIG_OTG_TASKLET_WORK */ + tasklet_kill(&tasklet->tasklet); + #endif /* CONFIG_OTG_TASKLET_WORK */ + + LKFREE(tasklet); +} + + +/*! @} */ + +/* ********************************************************************************************** */ +/*! + * @name DMA Cache + * + * @{ + * + * + * #define CACHE_SYNC_RCV(buf, len) pci_map_single (NULL, (void *) buf, len, PCI_DMA_FROMDEVICE) + * #define CACHE_SYNC_TX(buf, len) consistent_sync (NULL, buf, len, PCI_DMA_TODEVICE) + */ +/*! @} */ +#define CACHE_SYNC_RCV(buf, len) pci_map_single (NULL, (void *) buf, len, PCI_DMA_FROMDEVICE) +// XXX define CONFIG_OTG_LD_TX_CACHE if you are on an older kernel that uses the +// old consistent_sync() api +// +//#define CONFIG_OTG_OLD_TX_CACHE 0 +// +#if defined(CONFIG_OTG_OLD_TX_CACHE) +#define CACHE_SYNC_TX(buf, len) consistent_sync (NULL, buf, len, PCI_DMA_TODEVICE) +#else +#define CACHE_SYNC_TX(buf, len) consistent_sync (buf, len, PCI_DMA_TODEVICE) +#endif + +/*@}*/ + + + +/* ********************************************************************************************** */ +/*! @name likely and unlikely + * + * Under linux these can be used to provide a hint to GCC that + * the expression being evaluated is probably going to evaluate + * to true (likely) or false (unlikely). The intention is that + * GCC can then provide optimal code for that. + * @{ + */ +#ifndef likely +#define likely(x) x +#endif +#define otg_likely(x) likely(x) + +#ifndef unlikely +#define unlikely(x) x +#endif +#define otg_unlikely(x) unlikely(x) +/* @} */ + + + + +/* ********************************************************************************************** */ +/* + * XXX Deprecated and/or move to platform or architecture specific + */ + + + +// Common to all supported versions of Linux ?? + +#if 0 +#include <linux/list.h> +#define otg_list_node list_head +#define LIST_NODE struct list_head +#define LIST_NODE_INIT(name) struct list_head name = {&name, &name} +#define INIT_LIST_NODE(ptr) INIT_LIST_HEAD(ptr) +#define LIST_ENTRY(pointer, type, member) list_entry(pointer, type, member) +#define LIST_FOR_EACH(cursor, head) list_for_each(cursor, head) +#define LIST_ADD_TAIL(n,h) list_add_tail(n,h) +#define LIST_DEL(h) list_del(&(h)) +#else +#include <otg/otg-list.h> +#endif + +/*! @} */ + + +/*!@name Scheduling Primitives + * + * WORK_STRUCT + * WORK_ITEM + * + * SET_WORK_ARG()\n + * SCHEDULE_IMMEDIATE_WORK()\n + * NO_WORK_DATA()\n + * MOD_DEC_USE_COUNT\n + * MOD_INC_USE_COUNT\n + */ + +/*! @{ */ + + +/* Separate Linux 2.4 and 2.6 versions of scheduling primitives */ +#if defined(LINUX26) + #include <linux/workqueue.h> + + #define OLD_WORK_STRUCT work_struct + #define WORK_ITEM work_struct + #define OLD_WORK_ITEM work_struct + typedef struct OLD_WORK_ITEM OLD_WORK_ITEM; + #if 0 + #define PREPARE_WORK_ITEM(__item,__routine,__data) INIT_WORK((__item),(__routine),(__data)) + #else + #include <linux/interrupt.h> + #define PREPARE_WORK_ITEM(__item,__routine,__data) __prepare_work(&(__item),(__routine),(__data)) + static inline void __prepare_work(struct work_struct *_work, + void (*_routine), + void * _data){ + INIT_LIST_HEAD(&_work->entry); + _work->func = _routine; + #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20) + _work->data = _data; + _work->pending = 0; + init_timer(&_work->timer); + #else /* LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20) */ + *work_data_bits(_work) = (long)_data; + #endif /* LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20) */ + } + #endif + //#undef PREPARE_WORK + typedef void (* WORK_PROC)(void *); + + #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20) + #define SET_WORK_ARG(__item, __data) (__item).data = __data + #define PENDING_WORK_ITEM(item) (item.pending != 0) + #define NO_WORK_DATA(item) (!item.data) + #else /* LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20) */ + #define SET_WORK_ARG(__item, __data) (*work_data_bits(&(__item)) = (long)__data) + #define NO_WORK_DATA(item) (!(*work_data_bits(&(item)))) + #define WORK_DATA(item) (*work_data_bits(&(item))) + #define PENDING_WORK_ITEM(item) (work_pending(&(item))) + #endif /* LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20) */ + + + #define SCHEDULE_DELAYED_WORK(item) schedule_delayed_work(&item, 0) + #define SCHEDULE_IMMEDIATE_WORK(item) SCHEDULE_WORK((item)) + + + //#define _MOD_DEC_USE_COUNT //Not used in 2.6 + //#define _MOD_INC_USE_COUNT //Not used in 2.6 + + //#define MODPARM(a) _##a + + //#define MOD_PARM_BOOL(a, d, v) static int _##a = v; module_param_named(a, _##a, bool, 0); MODULE_PARM_DESC(a, d) + //#define MOD_PARM_INT(a, d, v) static int _##a = v; module_param_named(a, _##a, uint, 0); MODULE_PARM_DESC(a, d) + //#define MOD_PARM_STR(a, d, v) static char * _##a = v; module_param_named(a, _##a, charp, 0); MODULE_PARM_DESC(a, d) + #define OTG_INTERRUPT 0 /* SA_INTERRUPT in some environments */ + + + + +#else /* LINUX26 */ + + #define OLD_WORK_STRUCT tq_struct + #define OLD_WORK_ITEM tq_struct + typedef struct OLD_WORK_ITEM OLD_WORK_ITEM; + #define PREPARE_WORK_ITEM(item,work_routine,work_data) { item.routine = work_routine; item.data = work_data; } + #define SET_WORK_ARG(__item, __data) (__item).data = __data + #define NO_WORK_DATA(item) (!(item).data) + #define PENDING_WORK_ITEM(item) (item.sync != 0) + //#define _MOD_DEC_USE_COUNT MOD_DEC_USE_COUNT + //#define _MOD_INC_USE_COUNT MOD_INC_USE_COUNT + + //#define MODPARM(a) a + + //#define MOD_PARM_BOOL(a, d, v) static int a = v; MOD_PARM(a, "i"); MOD_PARM_DESC(a, d); + //#define MOD_PARM_INT(a, d, v) static int a = v; MOD_PARM(a, "i"); MOD_PARM_DESC(a, d); + //#define MOD_PARM_STR(a, d, v) static char * a = v; MOD_PARM(a, "s"); MOD_PARM_DESC(a, d); + + typedef void (* WORK_PROC)(void *); + + #define OTG_INTERRUPT 0 /* SA_INTERRUPT in some environments */ +#endif /* LINUX26 */ + +/*! @} */ + +/*! @name Linux Module support + */ +/*@{*/ +#if !defined(_LINUX_MODULE_H) + #include <linux/module.h> +#endif /* _LINUX_MODULE_H */ + +#if defined(MODULE) + + #if defined(OTG_EXTRA_INFO) + #warning MODULE DEFINED + #endif + + #define MOD_EXIT(exit_routine) module_exit(exit_routine) + #define MOD_INIT(init_routine) module_init(init_routine) + #define MOD_PROC(proc) (proc) + #define MOD_AUTHOR(string) MODULE_AUTHOR(string) + #define MOD_PARM(param, type) MODULE_PARM(param, type) + #define MOD_PARM_DESC(param,desc) MODULE_PARM_DESC(param, desc) + #define MOD_DESCRIPTION(description) MODULE_DESCRIPTION(description) + #define OTG_EXPORT_SYMBOL(symbol) EXPORT_SYMBOL(symbol) + #define OTG_EPILOGUE 1 /* EPILOGUE ROUTINE NEEDED */ + + #if defined(LINUX26) + #define MODPARM(a) _##a + #define MOD_PARM_BOOL(a, d, v) static int _##a = v; module_param_named(a, _##a, bool, 0); MODULE_PARM_DESC(a, d) + #define MOD_PARM_INT(a, d, v) static int _##a = v; module_param_named(a, _##a, uint, 0); MODULE_PARM_DESC(a, d) + #define MOD_PARM_STR(a, d, v) static char * _##a = v; module_param_named(a, _##a, charp, 0); MODULE_PARM_DESC(a, d) + #else /* defined(LINUX26) */ + #define MODPARM(a) a + #define MOD_PARM_BOOL(a, d, v) static int a = v; MOD_PARM(a, "i"); MOD_PARM_DESC(a, d); + #define MOD_PARM_INT(a, d, v) static int a = v; MOD_PARM(a, "i"); MOD_PARM_DESC(a, d); + #define MOD_PARM_STR(a, d, v) static char * a = v; MOD_PARM(a, "s"); MOD_PARM_DESC(a, d); + #endif /* defined(LINUX26) */ + +#else /* defined(MODULE) */ + #if defined(OTG_EXTRA_INFO) + #warning "Modules are not enabled for this kernel" + #endif + + + #define MOD_EXIT(exit_routine) + #define MOD_INIT(init_routine) module_init(init_routine) + #define MOD_PROC(proc) NULL + #define MOD_AUTHOR(string) + #define MOD_PARM(param, type) + #define MOD_PARM_DESC(param,desc) + #define MOD_DESCRIPTION(description) + #define OTG_EXPORT_SYMBOL(symbol) //EXPORT_SYMBOL(symbol) + #undef EXPORT_SYMBOL + #define OTG_EPILOGUE 0 /* EPILOGUE ROUTINE NOT NEEDED */ + + #define MODPARM(a) a + #define MOD_PARM_BOOL(a, d, v) static int a = v; MOD_PARM(a, "i"); MOD_PARM_DESC(a, d); + #define MOD_PARM_INT(a, d, v) static int a = v; MOD_PARM(a, "i"); MOD_PARM_DESC(a, d); + #define MOD_PARM_STR(a, d, v) static char * a = v; MOD_PARM(a, "s"); MOD_PARM_DESC(a, d); + +#endif /* defined(MODULE) */ + +//#undef MODULE_AUTHOR +//#undef MODULE_DESCRIPTION +//#undef MODULE_PARM_DESC +//#undef MODULE_PARM +#if defined(LINUX24) || defined(LINUX26) + #include <linux/version.h> + #if defined(MODULE) && (LINUX_VERSION_CODE >= KERNEL_VERSION (2,4,17)) + //"GPL License applies under certain cirumstances; consult your vendor for details" + #define EMBED_LICENSE() MODULE_LICENSE ("GPL") + #else + #define EMBED_LICENSE() //Operation not supported for earlier Linux kernels + #endif + +#else /* defined(LINUX24) || defined(LINUX26) */ + #error "Need to define EMBED_LICENSE for the current operating system" +#endif /* defined(LINUX24) || defined(LINUX26) */ +#define EMBED_MODULE_INFO(section,moduleinfo) static char __##section##_module_info[] = moduleinfo "tt/root@belcarra.com/debian286.bbb" +#define EMBED_USBD_INFO(moduleinfo) EMBED_MODULE_INFO(usbd,moduleinfo) +#define GET_MODULE_INFO(section) __##section##_module_info + +/*! @} */ + +/* ********************************************************************************************* */ +/* Linux specific - not part of otg-os.h implementation + */ + + +#if defined(LINUX26) +/*! @name OTG Bus Type + */ +/*@{*/ +extern struct bus_type otg_bus_type; +/*@}*/ +#endif /* defined(LINUX26) */ + + + +#if defined(LINUX26) + #include <linux/gfp.h> +#define GET_KERNEL_PAGE() __get_free_page(GFP_KERNEL) + +#else /* LINUX26 */ + + #include <linux/mm.h> + #define GET_KERNEL_PAGE() get_free_page(GFP_KERNEL) + #if !defined(IRQ_HANDLED) + // Irq's + typedef void irqreturn_t; + #define IRQ_NONE + #define IRQ_HANDLED + #define IRQ_RETVAL(x) + #endif +#endif /* LINUX26 */ + + +#define LINUX_VERSION(a,b,c) (LINUX_VERSION_CODE >= KERNEL_VERSION(a,b,c)) + +static otg_tick_t inline otg_do_div(otg_tick_t a, otg_tick_t b) +{ + // XXX au1x00 has u64 ticks for example + + #ifdef TICKS_IS_U64 + + #if defined(LINUX26) + return do_div(a, b); + #else /* defined(LINUX26) */ + return (a / b); + #endif /* defined(LINUX26) */ + + #else /* TICKS_IS_U64 */ + return (a / b); + #endif /* TICKS_IS_U64 */ +} + + + +#endif /* _OTG_LINUX_H */ diff --git a/drivers/otg/otg/otg-list.h b/drivers/otg/otg/otg-list.h new file mode 100644 index 000000000000..3c4cc6f627af --- /dev/null +++ b/drivers/otg/otg/otg-list.h @@ -0,0 +1,111 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/otg/otg-list.h + * @(#) sl@belcarra.com/whiskey.enposte.net|otg/otg/otg-list.h|20061218212925|37335 + * + * Copyright (c) 2004 Belcarra Technologies Corp + * Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + * + */ +#ifndef _OTG_LIST_H +#define _OTG_LIST_H 1 + +/* + * + * This file defines doubly linked list capabilities + * needed. Many operating systems have built in capabilities + * which match and may possibly be borrowed. + * In that case this file will not be needed. + * + * The implementation here is for demonstration purposes. + * Notes: the operations here may require protection from + * interruption. This is OS-dependent and not shown here. +*/ + +/* A list node structure is the anchor of a list + * A pointer to a node is used to identify items in the list + * A node structure is embedded in the items + */ +/*! @struct otg_list_node otg-list.h "otg/otg-list.h" ++ */ +struct otg_list_node { + struct otg_list_node *previous, *next; +}; +typedef struct otg_list_node otg_list_node_t, *otg_list_node_ptr; +//#define LIST_NODE struct list_head + +//A macro to define an list node within a structure +#define OTG_LIST_NODE struct otg_list_node +#define OTG_LIST_NODE_INIT(name) struct otg_list_node name = {&name, &name}; + +/* Each item in the list is a C structure. Each item is a CONTAINER. Within + * each container is a member of type OTG_LIST_NODE. A CONTAINER is also + * called an ENTRY + */ + +/* Assume that an OTG_LIST_NODE pointer is embedded as structure + * Recover numerical offset of member "member" within a pointer to a "type" + */ +#define __OTG_PTR_OFFSET(type, member) (u32) ( &(((type *) 0)->member) ) + +/* Recover typed pointer from pointer to interior of type at the given offset + */ +#define __OTG_PTR_CONTAINER(ptr, type, member) (type *) ((char *) ptr - __OTG_PTR_OFFSET(type,member)) + +/* From a pointer to the embedded NODE within an ENTRY, calculate a pointer to the + * ENTRY itself, properly typecast + */ +#define OTG_LIST_ENTRY(pointer, type, member) __OTG_PTR_CONTAINER(pointer, type, member) + +/* Recover list header embedded in structure which is a member of a list + */ +#define OTG_LIST_MEMBER(pointer, member) &(ptr->member) + + +/* Add an entry at the logical end of the list + */ +#define OTG_LIST_ADD_TAIL(new_entry, list_head) __otg_list_add_tail(new_entry, list_head) +static INLINE void __otg_list_add_tail(struct otg_list_node *new_entry, struct otg_list_node *list_head) +{ + otg_list_node_t *old_previous = list_head->previous; + old_previous->next = new_entry; + new_entry->next = list_head; + new_entry->previous = old_previous; + list_head -> previous = new_entry; + +} + +/* Delete an entry from the surrounding list + */ +#define OTG_LIST_DEL_ENTRY(del_entry) __otg_list_del_entry(&(del_entry)) + +static void INLINE __otg_list_del_entry(struct otg_list_node *del_entry){ + //Remove from list + del_entry->previous->next = del_entry->next; + del_entry->next->previous = del_entry->previous; + //Invalidate the entry + del_entry->previous = NULL; + del_entry->next = NULL; +} + +#define OTG_LIST_FOR_EACH(cursor, list) for(cursor=(list)->next; cursor != (list); cursor=cursor->next) + +#define LIST_NODE_INIT(name) OTG_LIST_NODE_INIT(name) +#define LIST_ENTRY(pointer,type,member) OTG_LIST_ENTRY(pointer,type,member) +#define LIST_ADD_TAIL(new_entry, list_head) OTG_LIST_ADD_TAIL(new_entry, list_head) +#define LIST_DEL_ENTRY(del_entry) OTG_LIST_DEL_ENTRY(del_entry) +#define LIST_DEL(del_entry) OTG_LIST_DEL_ENTRY(del_entry) +#define LIST_FOR_EACH(cursor, list) OTG_LIST_FOR_EACH(cursor, list) + +#endif /*_OTG_LIST_H */ diff --git a/drivers/otg/otg/otg-module.h b/drivers/otg/otg/otg-module.h new file mode 100644 index 000000000000..cdac7221e4a1 --- /dev/null +++ b/drivers/otg/otg/otg-module.h @@ -0,0 +1,32 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/otg/otg-module.h + * @(#) sl@belcarra.com|otg/otg/otg-module.h|20060725042911|22922 + * + * Copyright (c) 2004 Belcarra + * Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + * + */ +/*! + * @file otg/otg/otg-module.h + * @brief Linux Module OS Compatibility defines + * + * + * @ingroup OTGCore + */ +#ifndef _OTG_MODULE_H +#define _OTG_MODULE_H 1 + + +#endif diff --git a/drivers/otg/otg/otg-ocd.h b/drivers/otg/otg/otg-ocd.h new file mode 100644 index 000000000000..d3b9ba27c35e --- /dev/null +++ b/drivers/otg/otg/otg-ocd.h @@ -0,0 +1,110 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/otg-ocd.h - OTG Controller Driver + * @(#) sl@belcarra.com/whiskey.enposte.net|otg/otg/otg-ocd.h|20061218212925|27282 + * + * Copyright (c) 2004-2005 Belcarra Technologies Corp + * Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + * + * By: + * Stuart Lynne <sl@belcarra.com>, + * Bruce Balden <balden@belcarra.com> + * + * + */ +/*! + * @file otg/otg/otg-ocd.h + * @brief Defines common to On-The-Go OTG Controller Support + * + * This file defines the ocd_ops and ocd_instance structures. + * + * The ocd_ops structure contains all of the output functions that will + * be called as required by the OTG event handler when changing states. + * + * The ocd_instance structure is used to maintain the global data + * required by the OTG controller drivers. + * + * @ingroup OTGAPI + * @ingroup OCD + */ + +/*! + * @name OCD OTG Controller Driver + * @{ + */ +/*! @struct ocd_instance otg-ocd.h "otg/otg-ocd.h" + */ + +struct ocd_instance { + struct otg_instance *otg; + otg_tag_t TAG; + void * privdata; +}; + + +typedef int (*otg_timer_callback_proc_t) (void *); + +#define OCD_CAPABILITIES_DR 1 << 0 +#define OCD_CAPABILITIES_PO 1 << 1 +#define OCD_CAPABILITIES_TR 1 << 2 +#define OCD_CAPABILITIES_HOST 1 << 3 + +#define OCD_CAPABILITIES_AUTO 1 << 4 + + +/*! + * @struct ocd_ops + * The ocd_ops structure contains pointers to all of the functions implemented for the + * linked in driver. Having them in this structure allows us to easily determine what + * functions are available without resorting to ugly compile time macros or ifdefs + * + * There is only one instance of this, defined in the device specific lower layer. + */ +struct ocd_ops { + + u32 capabilities; /* OCD Capabilities */ + + /* Driver Initialization - by degrees + */ + int (*mod_init) (struct otg_instance *); /*!< OCD Module Initialization */ + void (*mod_exit) (struct otg_instance *); /*!< OCD Module Exit */ + + otg_output_proc_t ocd_init_func; /*!< OTG calls to initialize or de-initialize the OCD */ + + int (*start_timer) (struct otg_instance *, int);/*!< called by OTG to start timer */ + otg_tick_t (*ticks) (void); /*!< called by OTG to fetch current ticks, typically micro-seconds when available */ + otg_tick_t (*elapsed) ( otg_tick_t *, otg_tick_t *); + /*!< called by OTG to get micro-seconds elapsed between two ticks */ + //u32 interrupts; /*!< called by OTG to get number of interrupts */ + // + + void *privdata; +}; + + +#if 0 +struct ocd_instance { + struct otg_instance *otg; + void * privdata; +}; +#endif + +#ifndef OTG_APPLICATION + +#define REMOVE_OCD ocd_trace_tag +extern otg_tag_t REMOVE_OCD; +extern struct ocd_instance *REMOVE_ocd_instance; +extern struct ocd_ops ocd_ops; +#endif +/* @} */ diff --git a/drivers/otg/otg/otg-os.h b/drivers/otg/otg/otg-os.h new file mode 100644 index 000000000000..42ccf6395d1d --- /dev/null +++ b/drivers/otg/otg/otg-os.h @@ -0,0 +1,329 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/otg/otg-os.h - USB Device Bus Interface Driver Interface + * @(#) sl@belcarra.com/whiskey.enposte.net|otg/otg/otg-os.h|20061101065829|45204 + * + * Copyright (c) 2006 Belcarra Technologies 2005 Corp + * + * By: + * Stuart Lynne <sl@belcarra.com>, + * Bruce Balden <balden@belcarra.com> + * + */ + + +/*! + * @name OTGCORE OTG OS Definitions + * This contains the OTG OS structures and definitions. + * @{ + * + * This file should be included after an os specific library implementation + * to ensure that the API is correctly implemneted. + * + * OTG Task + * Long lived, low priority, high latency tasks. + * + * OTG WorkItem + * Short lived, low priority, high latency task. + * + * OTG Tasklet + * Short lived, high priority, low latency tasklets. + * + */ + +/*! + * @name Compilation Support + * + * @{ + * + * Macros to support packed structures + * #define PACKED1 __attribute__((packed)) + * #define PACKED2 + * #define PACKED0 + * + * Macros to support packed enum's + * #define PACKED_ENUM enum + * #define PACKED_ENUM_EXTRA + * + * #define INLINE __inline__ + * + * @ingroup OSAPI + * + */ + + +/* @} */ + +/*! + * @name Semaphore + * + * Posix compatible Semaphores. + * + * @{ + * + * typedef sem_t otg_sem_t; + * + */ + +extern int otg_sem_init(char *, otg_sem_t *sem, int pshared, unsigned value); + +extern int otg_sem_init_locked(char *, otg_sem_t *sem); +extern int otg_sem_init_unlocked(char *, otg_sem_t *sem); + +extern int otg_sem_destroy(otg_sem_t *sem); +extern int otg_sem_wait(otg_sem_t *sem); +extern int otg_sem_trywait(otg_sem_t *sem); +extern int otg_sem_post(otg_sem_t *sem); + +/*! @} */ + + +/*! + * @name Mutex + * + * Posix compatible Mutexes. + * + * @{ + * + * typedef pthread_mutex_t otg_pthread_mutex_t; + * + */ + +//extern int otg_pthread_mutex_init(otg_pthread_mutex_t *mutex, const phtread_mutexattr_t *attr); +//extern int otg_pthread_mutex_destroy(otg_pthread_mutex_t *mutex); + +extern int otg_pthread_mutex_lock(otg_pthread_mutex_t *mutex); +extern int otg_pthread_mutex_trylock(otg_pthread_mutex_t *mutex); +extern int otg_pthread_mutex_unlock(otg_pthread_mutex_t *mutex); + + +/*! @} */ + +/*! + * @name Atomic + * + * Basic Atomic operations. + * @{ + * + * typedef int otg_atomic_t; + * + * }; + */ + +extern void otg_atomic_set(otg_atomic_t *a, int b); +extern void otg_atomic_add(int a, otg_atomic_t *b); +extern void otg_atomic_sub(int a, otg_atomic_t *b); +extern void otg_atomic_clr(otg_atomic_t *a); +extern void otg_atomic_inc(otg_atomic_t *a); +extern void otg_atomic_dec(otg_atomic_t *a); +extern int otg_atomic_read(otg_atomic_t *a); + +/*! @} */ + +/*! + * @name Memory Allocation + * + * Simple allocated memory operations. + * @{ + */ + +extern void * otg_cmalloc(int n); +extern void otg_free(void *); + + +/*! @} */ + +/*! + * @name Task Allocation + * + * Long lived, low priority, high latency tasks. + * + * Tasks may use semaphores, mutexes, atomic operations and memory allocations. + * + * @{ + * + * TASKS are long lived, MAY HAVE low priority and high latency, the typical + * implementation has the work item wait in a semaphore waiting for work. :w + * + * The TASK work functions are structured to run until terminated, using + * a semaphore to wait for work. + * + * TASK creation: + * struct otg_task *task = otg_task_init("taskname", task_work, TAG); + * if (task) + * otg_task_start(task); + * + * TASK termination: + * otg_task_exit(task); + * + * TASK work notification + * otg_up_work(task); + * + * TASK work process + * + * void task_work(void *data) + * { + * struct otg_task *task = (struct otg_task *)data; + * void *mydata = task->data; + * otg_up_admin(task); + * do { + * // wait for work + * otg_down_work(task); + * // do work + * } while (!task->terminating); + * task->terminated = TRUE; + * otg_up_admin(task); + * } + * + * static struct otg_task *otg_task_init(char *name, void (*work) (void *), void *data, otg_tag_t tag) + * static void otg_up_work(struct otg_task *task) + * static void otg_up_admin(struct otg_task *task) + * static void otg_down_work(struct otg_task *task) + * static void otg_down_admin(struct otg_task *task) + * static void otg_task_start(struct otg_task *task) + * static void otg_task_exit(struct otg_task *task) + * + */ + +extern struct otg_task *otg_task_init2(char *name, otg_task_proc_t work, otg_task_arg_t data, otg_tag_t tag); +extern void otg_up_work(struct otg_task *task); +extern void otg_up_admin(struct otg_task *task); +extern void otg_down_work(struct otg_task *task); +extern void otg_down_admin(struct otg_task *task); +extern void otg_task_start(struct otg_task *task); +extern void otg_task_exit(struct otg_task *task); + +extern void otg_sleep(int n); + + +/*! @} */ + +/*! + * @name Work Allocation + * + * Short lived, low priority, high latency task. + * + * @{ + * + * Work items are similar to tasks except that they are not expected + * to run for long periods of time or wait. + * + * The Work item function is run once and exit when the work + * is finished. + * + * static struct otg_workitem *otg_workitem_init(char *name, void (*work) (unsigned long), unsigned long data, otg_tag_t tag) + * static void otg_workitem_start(struct otg_workitem *tasklet) + * static void otg_workitem_exit(struct otg_workitem *tasklet) + * + * WORK creation: + * struct otg_workitem *tasklet = otg_workitem_init("taskletname", tasklet_work, TAG); + * + * WORK start: + * otg_workitem_start(tasklet); + * + * WORK termination: + * otg_workitem_exit(tasklet); + * + * WORK work process + * + * void task_work(void *data) + * { + * struct otg_workitem *tasklet = (struct otg_workitem *)data; + * void *mydata = task->data; + * + * // do work + * + * } + * + */ + +extern struct otg_workitem *otg_workitem_init(char *name, otg_workitem_proc_t proc, otg_workitem_arg_t data, otg_tag_t tag);; +extern void otg_workitem_start(struct otg_workitem *work); +extern void otg_workitem_exit(struct otg_workitem *work); + + +/*! @} */ + +/*! + * @name Tasklet Allocation + * + * Short lived, high priority, low latency tasklets. + * + * Tasklets MAY NOT use memory allocation or wait on semaphores. + * + * Taskslets may use signal semaphores, mutexes and atomic operations. + * @{ + * + * TASKLETS are short lived, MUST run with low latency and SHOULD have high + * priority. + * + * The TASKLET work function is structure to run once and exit when the work + * is finished. + * + * static struct otg_tasklet *otg_tasklet_init(char *name, void (*work) (unsigned long), unsigned long data, otg_tag_t tag) + * static void otg_tasklet_start(struct otg_tasklet *tasklet) + * static void otg_tasklet_exit(struct otg_tasklet *tasklet) + * + * TASKLET creation: + * struct otg_tasklet *tasklet = otg_tasklet_init("taskletname", tasklet_work, TAG); + * + * TASKLET start: + * otg_tasklet_start(tasklet); + * + * TASKLET termination: + * otg_tasklet_exit(tasklet); + * + * TASKLET work process + * + * void task_work(unsigned long data) + * { + * struct otg_tasklet *tasklet = (struct otg_tasklet *)data; + * void *mydata = task->data; + * + * // do work + * + * } + * + */ + +extern struct otg_tasklet *otg_tasklet_init(char *name, otg_tasklet_proc_t proc, otg_tasklet_arg_t data, otg_tag_t tag); +extern void otg_tasklet_start(struct otg_tasklet *tasklet); +extern void otg_tasklet_exit(struct otg_tasklet *tasklet); + + +/*! @} */ + +/*! + * @name DMA Cache + * + * @{ + * + * + * #define CACHE_SYNC_RCV(buf, len) pci_map_single (NULL, (void *) buf, len, PCI_DMA_FROMDEVICE) + * #define CACHE_SYNC_TX(buf, len) consistent_sync (NULL, buf, len, PCI_DMA_TODEVICE) + */ +/*! @} */ + + +/*! + * @name GCC Optimize + * + * @{ + * + * + * otg_likely() + * otg_unlikely() + * + */ +/*! @} */ diff --git a/drivers/otg/otg/otg-pcd.h b/drivers/otg/otg/otg-pcd.h new file mode 100644 index 000000000000..0fbfc36a6c69 --- /dev/null +++ b/drivers/otg/otg/otg-pcd.h @@ -0,0 +1,120 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/otg-pcd.h - OTG Peripheral Controller Driver + * @(#) sl@belcarra.com/whiskey.enposte.net|otg/otg/otg-pcd.h|20070819221238|02897 + * + * Copyright (c) 2004-2005 Belcarra Technologies Corp + * Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + * + * By: + * Stuart Lynne <sl@belcarra.com>, + * Bruce Balden <balden@belcarra.com> + * + */ + +/*! + * @file otg/otg/otg-pcd.h + * @brief Defines common to On-The-Go Peripheral Controller Support + * + * This file defines the pcd_ops and pcd_instance structures. + * + * The pcd_ops structure contains all of the output functions that will + * be called as required by the OTG event handler when changing states. + * + * The pcd_instance structure is used to maintain the global data + * required by the peripheral controller drivers. + * + * @ingroup OTGAPI + * @ingroup PCD + */ + +/*! + * @name PCD Peripheral Controller Driver + * @{ + */ + +/*! + * @struct pcd_ops otg-pcd.h "otg/otg-pcd.h" + * + * The pcd_ops structure contains pointers to all of the functions implemented for the + * linked in driver. Having them in this structure allows us to easily determine what + * functions are available without resorting to ugly compile time macros or ifdefs + * + * There is only one instance of this, defined in the device specific lower layer. + */ +struct pcd_ops { + + int (*mod_init) (struct otg_instance *); /*!< PCD Module Initialization */ + void (*mod_exit) (struct otg_instance *); /*!< PCD Module Exit */ + + otg_output_proc_t pcd_init_func; /*!< OTG calls to initialize or de-initialize the PCD */ + otg_output_proc_t pcd_en_func; /*!< OTG calls to enable or disable the PCD */ + otg_output_proc_t remote_wakeup_func; /*!< OTG calls to have PCD perform remote wakeup */ + + framenum_t framenum; /*!< OTG calls to get current USB Frame number */ + + otg_output_proc_t tcd_en_func; /*!< OTG calls to enable or disable the TCD */ + otg_output_proc_t dp_pullup_func; /*!< OTG calls to enable or disable D+ pullup (aka loc_conn) */ + otg_tick_t (*ticks) (void); /*!< called by OTG to get ticks, typically micro-seconds when available */ + otg_tick_t (*elapsed) ( otg_tick_t *, otg_tick_t *); + + + u8 max_endpoints; + //u32 ep_in_mask; + //u32 ep_out_mask; +}; + +#define REMOVE_PCD pcd_trace_tag +extern otg_tag_t REMOVE_PCD; +extern struct pcd_instance *REMOVE_pcd_instance; +extern struct pcd_ops pcd_ops; + +/*! + * @struct pcd_instance otg-pcd.h "otg/otg-pcd.h" + */ +struct pcd_instance { + struct otg_instance *otg; /*!< pointer to OTG Instance */ + otg_tag_t TAG; + struct usbd_bus_instance *bus; /*!< pointer to usb bus instance */ + void * privdata; /*!< pointer to private data for PCD */ + int pcd_exiting; /*!< non-zero if OTG is unloading */ + //struct WORK_STRUCT bh; /*!< work structure for bottom half handler */ + struct otg_workitem *register_bh; /*!< work structure for bottom half handler */ + struct otg_workitem *deregister_bh; /*!< work structure for bottom half handler */ + struct otg_task *task; + otg_pthread_mutex_t mutex; + int active; + u8 address; + u8 new_address; +}; + + +#if !defined(OTG_C99) +extern void pcd_global_init(void); +#endif /* !defined(OTG_C99) */ +extern void pcd_init_func(struct otg_instance *, u8 ); +extern void pcd_en_func(struct otg_instance *, u8 ); +extern void pcd_remote_wakeup(struct otg_instance *, u8 ); +extern void pcd_tcd_en_func(struct otg_instance *, u8 ); +extern void pcd_dp_pullup_func(struct otg_instance *, u8 ); +extern u16 pcd_framenum(struct otg_instance *); +extern otg_tick_t pcd_ticks(void); +extern otg_tick_t pcd_elapsed(otg_tick_t *, otg_tick_t *); + +extern int pcd_request_endpoints(struct pcd_instance *, struct usbd_endpoint_map *, int, struct usbd_endpoint_request *); + +extern struct pcd_ops pcd_ops; + + +/* @} */ diff --git a/drivers/otg/otg/otg-pci.h b/drivers/otg/otg/otg-pci.h new file mode 100644 index 000000000000..ee2be345b319 --- /dev/null +++ b/drivers/otg/otg/otg-pci.h @@ -0,0 +1,282 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/otg/otg-pci.h -- Generic PCI driver + * @(#) sl@belcarra.com/whiskey.enposte.net|otg/otg/otg-pci.h|20061218212925|55364 + * + * Copyright (c) 2005 Belcarra Technologies Corp + * Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + * + * By: + * Stuart Lynne <sl@belcarra.com> + * + */ +/*! + * @file otg/otg/otg-pci.h + * @brief Generic PCI Driver. + * + * This is the base PCI driver for supporting the PCI devices. It is + * setup to allow the various OTG drivers to register and be handled + * separately. + * + * Multiple devices are supported. + * + * + * TCD PCD HCD OCD + * | | | | + * ------------------------- + * device PCI + * ------------------------- + * otg PCI + * ------------------------- + * OS PCI + * ------------------------- + * hardware + * + * + * It will probe the hardware and support registration for the sub-device + * drivers (ocd, hcd and pcd). + * + * otg_pci_register_driver() and otg_pci_unregister_driver() are used + * to register the base PCI hardware driver information. Which should + * result in the hardware getting probed. + * + * otg_register_driver() and otg_unregister_driver() are used to register + * the ocd, pcd, hcd, tcd drivers. + * + * The Device PCI driver should ensure that the basic hardware and interrupt + * setup is performed during it's probe. Then call otg_pci_prove(). + * + */ + +#define OTG_DRIVER_TCD 0 +#define OTG_DRIVER_PCD 1 +#define OTG_DRIVER_HCD 2 +#define OTG_DRIVER_OCD 3 +#define OTG_DRIVER_TYPES 4 + + +struct otg_pci; + + +/*! @struct otg_pci_driver otg-pci.h "otg/otp-pci.h" + * + * @brief base pci hardware driver struct providing interface + * to OS pci + */ +struct otg_pci_driver { + char *name; + int id; + + + irqreturn_t (*isr)(struct otg_pci *, void *); + + int (*probe)(struct otg_pci *dev, int id); + void (*remove)(struct otg_pci *dev, int id); + +#ifdef CONFIG_PM + void (*suspend)(struct otg_pci *dev, u32 state, int id); /* Device suspended */ + void (*resume)(struct otg_pci *dev, int id); /* Device woken up */ +#endif /* CONFIG_PM */ + +}; + +/*! otg_pci_device_driver + */ +struct otg_pci_device_driver { + + int pci_regions; + char *name; + irqreturn_t (*isr)(struct otg_pci *, void *); + + /* sub-drivers supporting various OTG functions from the hardware */ + struct otg_pci_driver *drivers[OTG_DRIVER_TYPES]; +}; + + +struct otg_pci { + /* base hardware PCI driver and interrupt handler */ + struct pci_dev *pci_dev; + + struct otg_pci_device_driver *otg_pci_device_driver; + + struct otg_pci *next; + + spinlock_t lock; + + + /* PCI mapping information */ + //unsigned long resource_start; + //unsigned long resource_len; + //void *base; + + void *regs[DEVICE_COUNT_RESOURCE]; + + int pci_regions; + + int id; + + /* OTG Driver data */ + void *drvdata; + + otg_tag_t PCI; + + char procfs[32]; + u32 chiprev; + + struct otg_instance *otg_instance; + + //struct ocd_instance *ocd_instance; + //struct pcd_instance *pcd_instance; + //struct tcd_instance *tcd_instance; + //struct hcd_instance *hcd_instance; + + u32 interrupts; +}; + + + +int __devinit otg_pci_probe (struct pci_dev *pci_dev, const struct pci_device_id *id, struct otg_pci_device_driver *otg_pci_device_driver); +void __devexit otg_pci_remove (struct pci_dev *pci_dev); + +extern int otg_pci_register_driver(struct otg_pci_device_driver*, struct otg_pci_driver *); +extern void otg_pci_unregister_driver(struct otg_pci_device_driver*, struct otg_pci_driver *); + +extern void otg_pci_set_drvdata(struct otg_pci *, void *); +extern void * otg_pci_get_drvdata(struct otg_pci *); + + +/* ********************************************************************************************** */ + +static u32 inline otg_readl(struct otg_pci *dev, int region, u32 reg) +{ + return readl(dev->regs[region] + reg); +} + +static u32 inline otg_readlv(struct otg_pci *dev, int region, u32 reg, char *msg) +{ + u32 data = otg_readl(dev, region, reg); + TRACE_MSG4(dev->PCI, "[%08x:%04x] %08x %s", dev->regs[region], reg, data, msg); + return data; +} + +static void inline otg_writel(struct otg_pci *dev, int region, u32 data, u32 reg) +{ + writel(data, dev->regs[region] + reg); +} + +static void inline otg_writelv(struct otg_pci *dev, int region, u32 data, u32 reg, char *msg) +{ + otg_writel(dev, region, data, reg); + TRACE_MSG4(dev->PCI, "[%08x:%04x] %08x %s", dev->regs[region], reg, data, msg); +} + + +static u16 inline otg_readw(struct otg_pci *dev, int region, u32 reg) +{ + u16 data = readw(dev->regs[region] + reg); + TRACE_MSG2(dev->PCI, "[%04x] %04x", reg, data); + //printk(KERN_INFO"%s: [%04x] %04x\n", __FUNCTION__, reg, data); + return data; +} + +static void inline otg_writew(struct otg_pci *dev, int region, u16 data, u32 reg) +{ + TRACE_MSG2(dev->PCI, "[%04x] %04x", reg, data); + //printk(KERN_INFO"%s: [%04x] %04x\n", __FUNCTION__, reg, data); + writew(data, dev->regs[region] + reg); +} + +static u8 inline otg_readb(struct otg_pci *dev, int region, u32 reg) +{ + u8 data = readb(dev->regs[region] + reg); + TRACE_MSG2(dev->PCI, "[%04x] %02x", reg, data); + //printk(KERN_INFO"%s: [%04x] %02x\n", __FUNCTION__, reg, data); + return data; +} + +static void inline otg_writeb(struct otg_pci *dev, int region, u8 data, u32 reg) +{ + TRACE_MSG2(dev->PCI, "[%04x] %02x", reg, data); + //printk(KERN_INFO"%s: [%04x] %02x\n", __FUNCTION__, reg, data); + writeb(data, dev->regs[region] + reg); +} + +/* ********************************************************************************************* */ + +static u32 inline otg_readl0(struct otg_pci *dev, u32 reg) +{ + return otg_readl(dev, 0, reg); +} +static void inline otg_writel0(struct otg_pci *dev, u32 data, u32 reg) +{ + otg_writel(dev, 0, data, reg); +} + +static u32 inline otg_readl1(struct otg_pci *dev, u32 reg) +{ + return otg_readl(dev, 1, reg); +} +static void inline otg_writel1(struct otg_pci *dev, u32 data, u32 reg) +{ + otg_writel(dev, 1, data, reg); +} + +static u32 inline otg_readl2(struct otg_pci *dev, u32 reg) +{ + return otg_readl(dev, 2, reg); +} +static void inline otg_writel2(struct otg_pci *dev, u32 data, u32 reg) +{ + otg_writel(dev, 2, data, reg); +} + +static u32 inline otg_readl3(struct otg_pci *dev, u32 reg) +{ + return otg_readl(dev, 3, reg); +} +static void inline otg_writel3(struct otg_pci *dev, u32 data, u32 reg) +{ + otg_writel(dev, 3, data, reg); +} + +static u32 inline otg_readl3v(struct otg_pci *dev, u32 reg) +{ + return otg_readlv(dev, 3, reg, ""); +} + +static u32 inline otg_readl3m(struct otg_pci *dev, u32 reg, char *msg) +{ + return otg_readlv(dev, 3, reg, msg); +} + +static void inline otg_writel3m(struct otg_pci *dev, u32 data, u32 reg, char *msg) +{ + otg_writelv(dev, 3, data, reg, msg); +} + +static void inline otg_writel3v(struct otg_pci *dev, u32 data, u32 reg) +{ + otg_writelv(dev, 3, data, reg, ""); +} + +static u32 inline otg_readl4(struct otg_pci *dev, u32 reg) +{ + return otg_readl(dev, 4, reg); +} +static void inline otg_writel4(struct otg_pci *dev, u32 data, u32 reg) +{ + otg_writel(dev, 4, data, reg); +} + +/* End of FILE */ diff --git a/drivers/otg/otg/otg-task.h b/drivers/otg/otg/otg-task.h new file mode 100644 index 000000000000..dcec7d50287f --- /dev/null +++ b/drivers/otg/otg/otg-task.h @@ -0,0 +1,338 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +xxxxx +xxxx +#if 0 +/* + * otg/otg/otg-task.h + * @(#) sl@belcarra.com/whiskey.enposte.net|otg/otg/otg-task.h|20061017072623|50255 + * + * Copyright (c) 2006 Belcarra Technologies 2005 Corp + * + */ + +/*! + * @file otg/otg/otg-linux.h + * @brief Linux OS Compatibility defines + * + * @ingroup OTGCore + */ +#ifndef _OTG_TASK_H +#define _OTG_TASK_H 1 + +#if !defined(_OTG_MODULE_H) +#include <otg/otg-module.h> +#endif + + +/*! @name OTG Task API + * + */ + +/*@{*/ +/* + */ + + +/* XXX OTG_TASK_WORK + * + * Linux Implementation Configuration + * + * #define OTG_TASK_WORK + * Use work items only for TASK implemenation + * #define OTG_TASKLET_WORK + * Use work items only for TASK implemenation + * + * N.B. the work item implementation for tasklets is not suitable + * for full OTG Dual-Role implemenation. + * + */ + +#define OTG_TASK_WORK +//#undef OTG_TASK_WORK + +//#define OTG_TASKLET_WORK +#undef OTG_TASKLET_WORK + + + +/*! struct otg_task + */ +typedef void *otg_task_arg_t; +typedef void (* otg_task_proc_t) (otg_task_arg_t data); +struct otg_task { + #if !defined(OTG_TASK_WORK) + struct workqueue_struct *work_queue; + struct semaphore admin_sem; + struct semaphore work_sem; + #endif /* defined(OTG_TASK_WORK) */ + struct work_struct work; + BOOL terminate; + BOOL terminated; + otg_task_arg_t *data; + BOOL debug; + otg_tag_t tag; + char *name; +}; + + +/*! otg_task_init + * Create otg task structure, create workqueue, initialize it. + */ +static inline struct otg_task *otg_task_init(char *name, otg_task_proc_t work, otg_task_arg_t data, otg_tag_t tag) +{ + struct otg_task *task; + + //TRACE_STRING(tag, "INIT: %s", name); + + printk(KERN_INFO"%s: %s\n", __FUNCTION__, name); + + RETURN_NULL_UNLESS((task = CKMALLOC(sizeof (struct otg_task), GFP_KERNEL))); + + task->data = data; + task->tag = tag; + task->name = name; + + #if defined(OTG_TASK_WORK) + task->terminated = task->terminate = TRUE; + + #else /* defined(OTG_TASK_WORK) */ + + task->terminated = task->terminate = FALSE; + #if defined(LINUX26) + THROW_UNLESS((task->work_queue = create_singlethread_workqueue(name)), error); + #else /* LINUX26 */ + THROW_UNLESS((task->work_queue = create_workqueue(name)), error); + #endif /* LINUX26 */ + init_MUTEX_LOCKED(&task->admin_sem); + init_MUTEX_LOCKED(&task->work_sem); + #endif /* defined(OTG_TASK_WORK) */ + + INIT_WORK(&task->work, work, task); + + return task; + + CATCH(error) { + printk(KERN_INFO"%s: ERROR\n", __FUNCTION__); + if (task) LKFREE(task); + return NULL; + } +} + +/*! otg_up_work + * Signal work task to re-start. + */ +static void inline otg_up_work(struct otg_task *task) +{ + //TRACE_STRING(task->tag, "UP WORK: %s", task->name); + + if (task->debug) + printk(KERN_INFO"%s: %s\n", __FUNCTION__, task->name); + + #if defined(OTG_TASK_WORK) + task->terminated = FALSE; + SCHEDULE_WORK(task->work); + #else /* defined(OTG_TASK_WORK) */ + UP(&task->work_sem); + #endif /* defined(OTG_TASK_WORK) */ +} + +/*! otg_up_admin + * Signal start task to re-start. + */ +static void inline otg_up_admin(struct otg_task *task) +{ + #if defined(OTG_TASK_WORK) + /* NOTHING */ + #else /* defined(OTG_TASK_WORK) */ + //TRACE_STRING(task->tag, "UP ADMIN: %s", task->name); + if (task->debug) + printk(KERN_INFO"%s: %s\n", __FUNCTION__, task->name); + UP(&task->admin_sem); + #endif /* defined(OTG_TASK_WORK) */ +} + +/*! otg_down_work + * Used by work task to wait. + */ +static void inline otg_down_work(struct otg_task *task) +{ + #if defined(OTG_TASK_WORK) + /* NOTHING */ + #else /* defined(OTG_TASK_WORK) */ + //TRACE_STRING(task->tag, "DOWN WORK: %s", task->name); + if (task->debug) + printk(KERN_INFO"%s: %s\n", __FUNCTION__, task->name); + + while (down_interruptible(&task->work_sem)); + #endif /* defined(OTG_TASK_WORK) */ +} + +/*! otg_down_admin + * Used by admin task to wait. + */ +static void inline otg_down_admin(struct otg_task *task) +{ + #if defined(OTG_TASK_WORK) + /* NOTHING */ + #else /* defined(OTG_TASK_WORK) */ + //TRACE_STRING(task->tag, "DOWN ADMIN: %s", task->name); + if (task->debug) + printk(KERN_INFO"%s: %s\n", __FUNCTION__, task->name); + while (down_interruptible(&task->admin_sem)); + //DOWN(&task->admin_sem); + #endif /* defined(OTG_TASK_WORK) */ +} + +/*! otg_task_start + * Start and wait for otg task. + */ +static void inline otg_task_start(struct otg_task *task) +{ + #if defined(OTG_TASK_WORK) + /* NOTHING */ + #else /* defined(OTG_TASK_WORK) */ + //TRACE_STRING(task->tag, "START: %s", task->name); + if (task->debug) + printk(KERN_INFO"%s: %s\n", __FUNCTION__, task->name); + + /* schedule work item and wait until it starts */ + queue_work(task->work_queue, &task->work); + otg_down_admin(task); + #endif /* defined(OTG_TASK_WORK) */ +} + +/*! otg_task_exit + * Terminate and wait for otg task. + */ +static void inline otg_task_exit(struct otg_task *task) +{ + TRACE_STRING(task->tag, "EXIT: %s", task->name); + if (task->debug) + printk(KERN_INFO"%s: %s\n", __FUNCTION__, task->name); + + #if defined(OTG_TASK_WORK) + while (!task->terminated) { + SCHEDULE_TIMEOUT(10 * HZ); + } + #else /* defined(OTG_TASK_WORK) */ + /* signal termination */ + task->terminate = TRUE; + otg_up_work(task); + otg_down_admin(task); + + /* destroy workqueue */ + flush_workqueue(task->work_queue); + destroy_workqueue(task->work_queue); + #endif /* defined(OTG_TASK_WORK) */ + + LKFREE(task); +} + +/*! struct otg_tasklet + */ +#ifdef OTG_TASKLET_WORK +typedef void *otg_tasklet_arg_t; +#else /* OTG_TASKLET_WORK */ +typedef unsigned long otg_tasklet_arg_t; +#endif /* OTG_TASKLET_WORK */ +typedef void (* otg_tasklet_proc_t) (otg_tasklet_arg_t data); +struct otg_tasklet { + #ifdef OTG_TASKLET_WORK + struct work_struct work; + #else /* OTG_TASKLET_WORK */ + struct tasklet_struct tasklet; + #endif /* OTG_TASKLET_WORK */ + BOOL terminated; + otg_task_arg_t data; + BOOL debug; + otg_tag_t tag; + char *name; +}; + + + +/*! otg_tasklet_init + * Create otg task structure, create workqueue, initialize it. + */ +static inline struct otg_tasklet *otg_tasklet_init(char *name, otg_tasklet_proc_t work, otg_tasklet_arg_t data, otg_tag_t tag) +{ + struct otg_tasklet *tasklet; + + //TRACE_STRING(tag, "INIT: %s", name); + printk(KERN_INFO"%s: %s\n", __FUNCTION__, name); + + RETURN_NULL_UNLESS((tasklet = CKMALLOC(sizeof (struct otg_tasklet), GFP_KERNEL))); + + tasklet->tag = tag; + tasklet->name = name; + tasklet->terminated = TRUE; + + #ifdef OTG_TASKLET_WORK + INIT_WORK(&tasklet->work, work, data); + #else /* OTG_TASKLET_WORK */ + tasklet_init(&tasklet->tasklet, work, data); + #endif /* OTG_TASKLET_WORK */ + + return tasklet; + + CATCH(error) { + if (tasklet) LKFREE(tasklet); + return NULL; + } +} + +/*! otg_tasklet_start + * Signal work task to re-start. + */ +static void inline otg_tasklet_start(struct otg_tasklet *tasklet) +{ + //TRACE_STRING(tag, "START: %s", name); + if (tasklet->debug) + printk(KERN_INFO"%s: %s\n", __FUNCTION__, tasklet->name); + + tasklet->terminated = FALSE; + #ifdef OTG_TASKLET_WORK + SCHEDULE_WORK(tasklet->work); + #else /* OTG_TASKLET_WORK */ + tasklet_schedule(&tasklet->tasklet); + #endif /* OTG_TASKLET_WORK */ +} + +/*! otg_tasklet_exit + * Terminate and wait for otg task. + */ +static void inline otg_tasklet_exit(struct otg_tasklet *tasklet) +{ + //TRACE_STRING(tag, "EXIT: %s", name); + if (tasklet->debug) + printk(KERN_INFO"%s: %s\n", __FUNCTION__, tasklet->name); + + #ifdef OTG_TASKLET_WORK + while (!tasklet->terminated) { + printk(KERN_INFO"%s: SLEEPING\n", __FUNCTION__); + SCHEDULE_TIMEOUT(10 * HZ); + printk(KERN_INFO"%s: RUNNING\n", __FUNCTION__); + } + #else /* OTG_TASKLET_WORK */ + tasklet_kill(&tasklet->tasklet); + #endif /* OTG_TASKLET_WORK */ + + LKFREE(tasklet); +} + + +/*! @} */ + +#endif /* _OTG_TASK_H */ +#endif diff --git a/drivers/otg/otg/otg-tcd.h b/drivers/otg/otg/otg-tcd.h new file mode 100644 index 000000000000..8864a34175fe --- /dev/null +++ b/drivers/otg/otg/otg-tcd.h @@ -0,0 +1,143 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/otg-tcd.h + * @(#) sl@belcarra.com/whiskey.enposte.net|otg/otg/otg-tcd.h|20061218212925|42044 + * + * Copyright (c) 2004-2005 Belcarra Technologies Corp + * Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + * + * By: + * Stuart Lynne <sl@belcarra.com>, + * Bruce Balden <balden@belcarra.com> + * + */ + +/*! + * @addgroup OTGTCD Transceiver Controller Driver Support + * @ingroup OTGOCD + */ +/*! + * @file otg/otg/otg-tcd.h + * @brief Defines common to On-The-Go Transceiver Controller Support + * + * This file defines the tcd_ops and tcd_instance structures. + * + * The tcd_ops structure contains all of the output functions that will + * be called as required by the OTG event handler when changing states. + * + * The tcd_instance structure is used to maintain the global data + * required by the transceiver controller drivers. + * + * @ingroup OTGAPI + * @ingroup TCD + */ + +/*! + * @name TCD Transceiver Controller Driver + * @{ + */ + + +/*! + * @struct tcd_ops otg-tcd.h "otg/otg-tcd.h" + * The pcd_ops structure contains pointers to all of the functions implemented for the + * linked in driver. Having them in this structure allows us to easily determine what + * functions are available without resorting to ugly compile time macros or ifdefs + * + * There is only one instance of this, defined in the device specific lower layer. + */ +struct tcd_ops { + /* Driver Initialization - by degrees + */ + int (*mod_init) (struct otg_instance *); /*!< TCD Module Initialization */ + void (*mod_exit) (struct otg_instance *); /*!< TCD Module Exit */ + + otg_current_t initial_state; + + void * privdata; + + + /* 3. called for usbd_vbus() if defined */ + //int (*vbus) (struct otg_instance *); /* return non-zero if the Vbus valid */ + //int (*id) (struct otg_instance *); /* return non-zero if the ID valid */ + + /* 4. called by otg event to control outputs + */ + + otg_output_proc_t tcd_init_func; /*!< OTG calls to initialize or de-initialize the HCD */ + otg_output_proc_t tcd_en_func; /*!< OTG calls to enable or disable the TCD */ + otg_output_proc_t chrg_vbus_func; /*!< OTG calls to enable or disable charging Vbus */ + otg_output_proc_t drv_vbus_func; /*!< OTG calls to enable or disable Vbus */ + otg_output_proc_t dischrg_vbus_func; /*!< OTG calls to enable or disable discharging Vbus */ + otg_output_proc_t dp_pullup_func; /*!< OTG calls to enable or disable D+ pullup (aka loc_conn) */ + otg_output_proc_t dm_pullup_func; /*!< OTG calls to enable or disable D- pullup (aka loc_carkit) */ + otg_output_proc_t dp_pulldown_func; /*!< OTG calls to enable or disable D+ pulldown (aka loc_conn) */ + otg_output_proc_t dm_pulldown_func; /*!< OTG calls to enable or disable D- pulldown (aka loc_carkit) */ + otg_output_proc_t peripheral_host_func; /*!< OTG calls to enable or disable D- pulldown (aka loc_carkit) */ + otg_output_proc_t overcurrent_func; /*!< OTG calls to clear overcurrent indication */ + otg_output_proc_t dm_det_func; /*!< OTG calls to enable or disable D+ pullup detection */ + otg_output_proc_t dp_det_func; /*!< OTG calls to enable or disable D- pullup detection */ + otg_output_proc_t cr_det_func; /*!< OTG calls to enable or disable D+ CRINT detection */ + otg_output_proc_t charge_pump_func; /*!< OTG calls to enable or disable external charge pump */ + otg_output_proc_t bdis_acon_func; /*!< OTG calls to enable or disable the BDIS ACON feature */ + otg_output_proc_t mx21_vbus_drain_func; /*!< OTG calls to enable or disable the mx21 vbus drain feature */ + otg_output_proc_t id_pulldown_func; /*!< OTG calls to enable or disable ID pulldown to ground */ + otg_output_proc_t audio_func; /*!< OTG calls to enable or disable audio function */ + otg_output_proc_t uart_func; /*!< OTG calls to enable or disable uart function */ + otg_output_proc_t mono_func; /*!< OTG calls to enable or disable mono function */ +}; + +/*! + * @struct hcd_instance otg-tcd.h "otg/otg-tcd.h" + */ +struct tcd_instance { + struct otg_instance *otg; /*!< pointer to OTG Instance */ + otg_tag_t TAG; + + BOOL vbus; + BOOL id; + + void *privdata; + + BOOL inputs_valid; + otg_current_t inputs; +}; + + +//extern struct trace_ops trace_ops; +extern struct tcd_ops tcd_ops; + + +// XXX The following will be REMOVED in the near future +// XXX If you need to use them, at REMOVE_ to your code +// XXX to allow use. But plan on removing, use the otg-> +// XXX equivalents. +// XXX +extern struct tcd_instance *REMOVE_tcd_instance; +#define REMOVE_TCD tcd_trace_tag +extern otg_tag_t REMOVE_TCD; + + +extern int tcd_vbus (struct otg_instance *otg); + +/* pcd_cable_event[_irq] - these can be called in a traditional PCD implementation + * to set b_sess_vld appropriately when vbus is sensed (pcd must implement ocd_ops.vbus) + */ +extern void tcd_cable_event_irq (struct otg_instance *otg, u8 connect); + +extern void tcd_cable_event (struct otg_instance *otg, u8 connect); + +//extern void tcd_init(struct otg_instance *, u8 ); + +/* @} */ diff --git a/drivers/otg/otg/otg-trace.h b/drivers/otg/otg/otg-trace.h new file mode 100644 index 000000000000..61305e5534e8 --- /dev/null +++ b/drivers/otg/otg/otg-trace.h @@ -0,0 +1,415 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/otgcore/otg-trace.h + * @(#) sl@belcarra.com/whiskey.enposte.net|otg/otg/otg-trace.h|20061218212938|61040 + * + * Copyright (c) 2004-2005 Belcarra Technologies Corp + * Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + * + * By: + * Stuart Lynne <sl@belcarra.com> + * + */ +/*! + * @file otg/otg/otg-trace.h + * @brief Core Defines for USB OTG Core Layaer + * + * Fast Trace Utility + * This set of definitions and code is meant to provide a _fast_ debugging facility + * (much faster than printk) so that time critical code can be debugged by looking + * at a trace of events provided by reading a file in the procfs without affecting + * the timing of events in the critical code. + * + * The mechanism used it to allocate a (large) ring buffer of relatively small structures + * that include location and high-res timestamp info, and up to 8 bytes of optional + * data. Values are stored and timestamps are taken as the critical code runs, but + * data formatting and display are done during the procfs read, when more time is + * available :). + * + * Note that there is usually some machine dependent code involved in getting the + * high-res timestamp, and there may be other bits used just to keep the overall + * time impact as low as possible. + * + * Varargs up to 9 arguments are now supported, but you have to supply the number + * of args, since examining the format string for the number at trace event time + * was deemed too expensive time-wise. + * + * + * @ingroup OTGCore + */ + +#ifndef OTG_TRACE_H +#define OTG_TRACE_H 1 + +/*! + * @var typedef struct otg_tag otg_tag_t + */ +typedef struct otg_tag { + void *otg; + //char msg[16]; + char *msg; + u8 tag; +} *otg_tag_t; + + +#ifdef PRAGMAPACK +#pragma pack(push,1) +#endif /* PRAGMAPACK */ + +/*!create a type for otg_trace_types */ +typedef PACKED_ENUM /*enum*/ otg_trace_types { + otg_trace_msg_invalid_n, + otg_trace_msg_va_start_n, + otg_trace_send_n, + otg_trace_recv_n, + otg_trace_nsend_n, + otg_trace_nrecv_n, + otg_trace_setup_n, + otg_trace_string_n, + otg_trace_elapsed_n, + +} PACKED_ENUM_EXTRA otg_trace_types_t; + +#ifdef PRAGMAPACK +#pragma pack(pop) +#endif /* PRAGMAPACK */ + +/*! @name otg_trace_message otg trace message type defines */ + +/*!@{ */ + +#define OTG_TRACE_MAX_IN_VA 8 + + +/*! @typedef struct trace otg_trace_t + * @brief define an alias for struct trace + * + */ +#define OTG_TRACE_IN_INTERRUPT (1 << 0) +#define OTG_TRACE_ID_GND (1 << 1) +#define OTG_TRACE_HCD_PCD (1 << 2) + +#if !defined(OTG_TRACE_DISABLE) && ( defined(CONFIG_OTG_TRACE) || defined(CONFIG_OTG_TRACE_MODULE)) + +#if defined(CONFIG_OTG_TRACE_PACKED) +#define TRACE_PACKED0 PACKED0 +#define TRACE_PACKED1 PACKED1 +#define TRACE_PACKED2 PACKED2 +#else +#define TRACE_PACKED0 +#define TRACE_PACKED1 +#define TRACE_PACKED2 +#endif + +/*! @typedef struct trace otg_trace_t + * @brief define an alias for struct trace + * + */ +typedef TRACE_PACKED0 struct TRACE_PACKED1 trace { + otg_trace_types_t otg_trace_type; + u8 tag; + u8 va_num_args; + u8 flags; + + const char *function; + u32 interrupts; + otg_tick_t ticks; + u16 h_framenum; + u16 p_framenum; + + char *fmt; + + union { + u8 dump[32]; + u8 setup[8]; + u8 string[32]; + u32 val[OTG_TRACE_MAX_IN_VA]; + struct { + u32 id; + otg_tick_t ticks; + }; + + } trace; /*< share space for different message */ + +} TRACE_PACKED2 otg_trace_t; + +#else +//#warning TRACE DISABLED +/*! create a type a trace */ +typedef struct trace { + int a; +} otg_trace_t; +#endif + +/*! + * create an alias for otg_trace_snapshot + */ + +typedef struct otg_trace_snapshot { + int first; + int next; + int total; + otg_trace_t *traces; +} otg_trace_snapshot; + +/* @} */ + + #define TRACE_MAX 0x00008000 + #define TRACE_MASK 0x00007FFF +//#define TRACE_MAX 0x0000800 +//#define TRACE_MASK 0x00007FF +//#define TRACE_MAX 0x000080 +//#define TRACE_MASK 0x00007F + + +#if !defined(OTG_TRACE_DISABLE) && ( defined(CONFIG_OTG_TRACE) || defined(CONFIG_OTG_TRACE_MODULE)) + +/*Don't produce unrelated TRACE MSG */ + + +#ifdef XXXCONFIG_OTG_LATENCY_CHECK + +#if !defined(OLD_TRACE_API) + +extern void otg_trace_dump(otg_tag_t tag, const char *fn, otg_trace_types_t trace_type, int len, void *dump); +extern void otg_trace_setup(otg_tag_t tag, const char *fn, void *setup); +extern void otg_trace_string(otg_tag_t tag, const char *fn, char *fmt, void *setup); +extern void otg_trace_elapsed(otg_tag_t tag, const char *fn, char *fmt, u32 id, otg_tick_t ticks); + +#define TRACE_RECV(tag,len,dump) +#define TRACE_SEND(tag,len,dump) +#define TRACE_NRECV(tag,len,dump) +#define TRACE_NSEND(tag,len,dump) +#define TRACE_SETUP(tag,setup) +#define TRACE_STRING(tag,fmt,setup) +#define TRACE_ELAPSED(tag,fmt,ticks,id) otg_trace_elapsed(tag,__FUNCTION__,fmt,id, ticks) +#define TRACE_MSG0(tag, msg) + +#else + +extern int otg_trace_first; +extern int otg_trace_last_read; +extern int otg_trace_next; + + +#endif + +extern void otg_trace_msg( + otg_tag_t tag, + const char *fn, + u8 nargs, + char *fmt, + u32 a1, u32 a2, u32 a3, u32 a4, u32 a5, u32 a6, u32 a7, u32 a8); + +#define TRACE_MSG1(tag, fmt, a1) +#define TRACE_MSG2(tag, fmt, a1, a2) +#define TRACE_MSG3(tag, fmt, a1, a2, a3) +#define TRACE_MSG4(tag, fmt, a1, a2, a3, a4) +#define TRACE_MSG5(tag, fmt, a1, a2, a3, a4, a5) +#define TRACE_MSG6(tag, fmt, a1, a2, a3, a4, a5, a6) +#define TRACE_MSG7(tag, fmt, a1, a2, a3, a4, a5, a6, a7) +#define TRACE_MSG8(tag, fmt, a1, a2, a3, a4, a5, a6, a7, a8) + +/*Latency TRACE*/ + +#define LTRACE_MSG0(tag, msg) otg_trace_msg(tag, __FUNCTION__, 0, msg, 0, 0, 0, 0, 0, 0, 0, 0) + +#define LTRACE_MSG1(tag, fmt, a1) \ + otg_trace_msg(tag, __FUNCTION__, 1, fmt, (u32)(a1), 0, 0, 0, 0, 0, 0, 0) + +#define LTRACE_MSG2(tag, fmt, a1, a2) \ + otg_trace_msg(tag, __FUNCTION__, 2, fmt, (u32)(a1), (u32)(a2), 0, 0, 0, 0, 0, 0) + +#define LTRACE_MSG3(tag, fmt, a1, a2, a3) \ + otg_trace_msg(tag, __FUNCTION__, 3, fmt, (u32)(a1), (u32)(a2), (u32)(a3), 0, 0, 0, 0, 0) + + +#define LTRACE_MSG4(tag, fmt, a1, a2, a3, a4) \ + otg_trace_msg(tag, __FUNCTION__, 4, fmt, (u32)(a1), (u32)(a2), (u32)(a3), (u32)(a4), 0, 0, 0, 0) + + +#define LTRACE_MSG5(tag, fmt, a1, a2, a3, a4, a5) \ + otg_trace_msg(tag, __FUNCTION__, 5, fmt, (u32)(a1), (u32)(a2), (u32)(a3), (u32)(a4), (u32)(a5), 0, 0, 0) + +#define LTRACE_MSG6(tag, fmt, a1, a2, a3, a4, a5, a6) \ + otg_trace_msg(tag, __FUNCTION__, 6, fmt, (u32)(a1), (u32)(a2), (u32)(a3), (u32)(a4), (u32)(a5), (u32)(a6), 0, 0) + +#define LTRACE_MSG7(tag, fmt, a1, a2, a3, a4, a5, a6, a7) \ + otg_trace_msg(tag, __FUNCTION__, 7, fmt, (u32)(a1), (u32)(a2), (u32)(a3),\ + (u32)(a4), (u32)(a5), (u32)(a6), (u32)(a7), 0) + +#define LTRACE_MSG8(tag, fmt, a1, a2, a3, a4, a5, a6, a7, a8) \ + otg_trace_msg(tag, __FUNCTION__, 8, fmt, (u32)(a1), (u32)(a2), (u32)(a3), (u32)(a4), \ + (u32)(a5), (u32)(a6), (u32)(a7), (u32)a8 ) + +extern otg_tag_t otg_trace_obtain_tag(void *, char *); +extern otg_tag_t otg_trace_invalidate_tag(otg_tag_t tag); + + +#else + +#if !defined(OLD_TRACE_API) + +extern void otg_trace_dump(otg_tag_t tag, const char *fn, otg_trace_types_t trace_type, int len, void *dump); +extern void otg_trace_setup(otg_tag_t tag, const char *fn, void *setup); +extern void otg_trace_string(otg_tag_t tag, const char *fn, char *fmt, void *setup); +extern void otg_trace_elapsed(otg_tag_t tag, const char *fn, char *fmt, u32 id, otg_tick_t ticks); + +#define TRACE_RECV(tag,len,dump) otg_trace_dump(tag,__FUNCTION__,otg_trace_recv_n,len,dump) +#define TRACE_SEND(tag,len,dump) otg_trace_dump(tag,__FUNCTION__,otg_trace_send_n,len,dump) +#define TRACE_NRECV(tag,len,dump) otg_trace_dump(tag,__FUNCTION__,otg_trace_nrecv_n,len,dump) +#define TRACE_NSEND(tag,len,dump) otg_trace_dump(tag,__FUNCTION__,otg_trace_nsend_n,len,dump) +#define TRACE_SETUP(tag,setup) otg_trace_setup(tag,__FUNCTION__,setup) +#define TRACE_STRING(tag,fmt,setup) otg_trace_string(tag,__FUNCTION__,fmt,setup) +#define TRACE_ELAPSED(tag,fmt,ticks,id) otg_trace_elapsed(tag,__FUNCTION__,fmt,id, ticks) +#define TRACE_MSG0(tag, msg) otg_trace_msg(tag, __FUNCTION__, 0, msg, 0, 0, 0, 0, 0, 0, 0, 0) + +#else + +extern int otg_trace_first; +extern int otg_trace_last_read; +extern int otg_trace_next; + + +#endif + +extern void otg_trace_msg( + otg_tag_t tag, + const char *fn, + u8 nargs, + char *fmt, + u32 a1, u32 a2, u32 a3, u32 a4, u32 a5, u32 a6, u32 a7, u32 a8); + +#define TRACE_MSG1(tag, fmt, a1) \ + otg_trace_msg(tag, __FUNCTION__, 1, fmt, (u32)(a1), 0, 0, 0, 0, 0, 0, 0) + +#define TRACE_MSG2(tag, fmt, a1, a2) \ + otg_trace_msg(tag, __FUNCTION__, 2, fmt, (u32)(a1), (u32)(a2), 0, 0, 0, 0, 0, 0) + +#define TRACE_MSG3(tag, fmt, a1, a2, a3) \ + otg_trace_msg(tag, __FUNCTION__, 3, fmt, (u32)(a1), (u32)(a2), (u32)(a3), 0, 0, 0, 0, 0) + + +#define TRACE_MSG4(tag, fmt, a1, a2, a3, a4) \ + otg_trace_msg(tag, __FUNCTION__, 4, fmt, (u32)(a1), (u32)(a2), (u32)(a3), (u32)(a4), 0, 0, 0, 0) + + +#define TRACE_MSG5(tag, fmt, a1, a2, a3, a4, a5) \ + otg_trace_msg(tag, __FUNCTION__, 5, fmt, (u32)(a1), (u32)(a2), (u32)(a3), (u32)(a4), (u32)(a5), 0, 0, 0) + +#define TRACE_MSG6(tag, fmt, a1, a2, a3, a4, a5, a6) \ + otg_trace_msg(tag, __FUNCTION__, 6, fmt, (u32)(a1), (u32)(a2), (u32)(a3), (u32)(a4), (u32)(a5), (u32)(a6), 0, 0) + +#define TRACE_MSG7(tag, fmt, a1, a2, a3, a4, a5, a6, a7) \ + otg_trace_msg(tag, __FUNCTION__, 7, fmt, (u32)(a1), (u32)(a2), (u32)(a3),\ + (u32)(a4), (u32)(a5), (u32)(a6), (u32)(a7), 0) + +#define TRACE_MSG8(tag, fmt, a1, a2, a3, a4, a5, a6, a7, a8) \ + otg_trace_msg(tag, __FUNCTION__, 8, fmt, (u32)(a1), (u32)(a2), (u32)(a3), (u32)(a4), \ + (u32)(a5), (u32)(a6), (u32)(a7), (u32)a8 ) + +#define LTRACE_MSG1(tag, fmt, a1) +#define LTRACE_MSG2(tag, fmt, a1, a2) +#define LTRACE_MSG3(tag, fmt, a1, a2, a3) +#define LTRACE_MSG4(tag, fmt, a1, a2, a3, a4) +#define LTRACE_MSG5(tag, fmt, a1, a2, a3, a4, a5) +#define LTRACE_MSG6(tag, fmt, a1, a2, a3, a4, a5, a6) +#define LTRACE_MSG7(tag, fmt, a1, a2, a3, a4, a5, a6, a7) +#define LTRACE_MSG8(tag, fmt, a1, a2, a3, a4, a5, a6, a7, a8) + +extern otg_tag_t otg_trace_obtain_tag(void *, char *); +extern otg_tag_t otg_trace_invalidate_tag(otg_tag_t tag); + +#endif + +#elif defined(OTG_WINCE) + +#define TRACE_MSG0(tag, msg) \ + DEBUGMSG(ZONE_INIT,(_T("OTGCORE - CORE - %s"), _T(msg))) + +#define TRACE_MSG1(tag, fmt, a1) \ + DEBUGMSG(ZONE_INIT,(_T("OTGCORE - CORE - %s %d"), _T(fmt), a1)) + +#define TRACE_MSG2(tag, fmt, a1, a2) \ + DEBUGMSG(ZONE_INIT,(_T("OTGCORE - CORE - %s %d %d"), _T(fmt), a1, a2)) + +#define TRACE_MSG3(tag, fmt, a1, a2, a3) \ + DEBUGMSG(ZONE_INIT,(_T("OTGCORE - CORE - %s %d %d"), _T(fmt), a1, a2, a3)); + +#define TRACE_MSG4(tag, fmt, a1, a2, a3, a4) \ + DEBUGMSG(ZONE_INIT,(_T("OTGCORE - CORE - %s %d %d %d %d"), _T(fmt), a1, a2, a3, a4)); + +#define TRACE_MSG5(tag, fmt, a1, a2, a3, a4, a5) \ + DEBUGMSG(ZONE_INIT,(_T("OTGCORE - CORE - %s %d %d %d %d %d"), _T(fmt), a1, a2, a3, a4, a5)); + +#define TRACE_MSG6(tag, fmt, a1, a2, a3, a4, a5, a6) \ + DEBUGMSG(ZONE_INIT,(_T("OTGCORE - CORE - %s %d %d %d %d %d %d %d"), _T(fmt), a1, a2, a3, a4, a5, a6)); + +#define TRACE_MSG7(tag, fmt, a1, a2, a3, a4, a5, a6, a7) \ + DEBUGMSG(ZONE_INIT,(_T("OTGCORE - CORE - %s %d %d %d %d %d %d %d %d"), _T(fmt), a1, a2, a3, a4, a5, a6, a7)); + +#define TRACE_MSG8(tag, fmt, a1, a2, a3, a4, a5, a6, a7, a8) \ + DEBUGMSG(ZONE_INIT,(_T("OTGCORE - CORE - %s %d %d %d %d %d %d %d %d %d"), _T(fmt), a1, a2, a3, a4, a5, a6, a7, a8)); + +extern otg_tag_t otg_trace_obtain_tag(void *, char *); +extern otg_tag_t otg_trace_invalidate_tag(otg_tag_t tag); + + +extern otg_tag_t otg_trace_obtain_tag(void *, char *); +extern otg_tag_t otg_trace_invalidate_tag(otg_tag_t tag); + +#define TRACE_TMP + +#else /* defined(CONFIG_OTG_TRACE) || defined(CONFIG_OTG_TRACE_MODULE) */ + +#define TRACE_RECV(tag,len,dump) +#define TRACE_SEND(tag,len,dump) +#define TRACE_NRECV(tag,len,dump) +#define TRACE_NSEND(tag,len,dump) +#define TRACE_SETUP(tag,setup) +#define TRACE_STRING(tag,fmt,setup) +#define TRACE_ELAPSED(tag,fmt,id,ticks) + +#define TRACE_MSG0(tag, fmt) \ + while (0) { int x; x = (int) (tag); } +#define TRACE_MSG1(tag, fmt, a1) \ + while (0) { int x; x = (int) (tag); x = (int)(a1); } +#define TRACE_MSG2(tag, fmt, a1, a2) \ + while (0) { int x; x = (int) (tag); x = (int)(a1); x = (int)(a2); } +#define TRACE_MSG3(tag, fmt, a1, a2, a3) \ + while (0) { int x; x = (int) (tag); x = (int)(a1); x = (int)(a2); x = (int)(a3); } +#define TRACE_MSG4(tag, fmt, a1, a2, a3, a4) \ + while (0) { int x; x = (int) (tag); x = (int)(a1); x = (int)(a2); x = (int)(a3); x = (int)(a4); } +#define TRACE_MSG5(tag, fmt, a1, a2, a3, a4, a5) \ + while (0) { int x; x = (int) (tag); x = (int)(a1); x = (int)(a2); x = (int)(a3); x = (int)(a4); \ + x = (int)(a5); } +#define TRACE_MSG6(tag, fmt, a1, a2, a3, a4, a5, a6) \ + while (0) { int x; x = (int) (tag); x = (int)(a1); x = (int)(a2); x = (int)(a3); x = (int)(a4); \ + x = (int)(a5); x = (int)(a6); } +#define TRACE_MSG7(tag, fmt, a1, a2, a3, a4, a5, a6, a7) \ + while (0) { int x; x = (int) (tag); x = (int)(a1); x = (int)(a2); x = (int)(a3); x = (int)(a4); \ + x = (int)(a5); x = (int)(a6); x = (int)(a7); } +#define TRACE_MSG8(tag, fmt, a1, a2, a3, a4, a5, a6, a7, a8) \ + while (0) { int x; x = (int) (tag); x = (int)(a1); x = (int)(a2); x = (int)(a3); x = (int)(a4); \ + x = (int)(a5); x = (int)(a6); x = (int)(a7); x = (int)(a8); } + +extern otg_tag_t otg_trace_obtain_tag(void *, char *); +extern otg_tag_t otg_trace_invalidate_tag(otg_tag_t tag); + +#define TRACE_TMP // + +#endif /* defined(CONFIG_OTG_TRACE) || defined(CONFIG_OTG_TRACE_MODULE) */ + + +#endif /* OTG_TRACE_H */ diff --git a/drivers/otg/otg/otg-utils.h b/drivers/otg/otg/otg-utils.h new file mode 100644 index 000000000000..916bdca027a1 --- /dev/null +++ b/drivers/otg/otg/otg-utils.h @@ -0,0 +1,161 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/otg/otg-utils.h + * @(#) sl@belcarra.com/whiskey.enposte.net|otg/otg/otg-utils.h|20070612174152|35048 + * + * Copyright (c) 2004-2005 Belcarra + * Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + * + * Basic utilities for OTG package + */ +/*! + * @file otg/otg/otg-utils.h + * @brief Useful macros. + * + * + * @ingroup OTGCore + */ + + +#if !defined(_OTG_UTILS_H) +#define _OTG_UTILS_H 1 + +/*! @name Min, Max and zero + * @{ + */ +#ifndef MIN +#define MIN(a,b) ((a) < (b) ? (a) : (b)) +#endif +#ifndef MAX +#define MAX(a,b) ((a) > (b) ? (a) : (b)) +#endif + +#define ZERO(a) memset(&a, 0, sizeof(a)) + +/* @} */ + +/*! @name TRUE and FALSE + * + * @{ + */ +#if !defined(TRUE) +#define TRUE 1 +#endif + +#if !defined(FALSE) +#define FALSE 0 +#endif + +#define BOOL int +#define BOOLEAN(e)((e)?TRUE:FALSE) + +/* @} */ + + +/*! @name CATCH and THROW + * + * These macros implement a conceptually clean way to use + * C's goto to implement a standard error handler. The typical forms are: + * + * THROW(error); // continue execution in CATCH statement + * THROW_IF(test, error); // continue execution in CATCH statement if test true + * THROW_UNLESS(test, error); // continue execution in CATCH statement if test false + * + * // program execution will continue after the CATCH statement; + * CATCH(error) { + * // handle error here + * } + * + * @{ + */ +#define CATCH(x) while(0) x: +#define THROW(x) goto x +#define THROW_IF(e, x) if (e) goto x; +#define THROW_UNLESS(e, x) UNLESS (e) goto x; +/* @} */ + + +/*! @name UNLESS + * + * This implements a simple equivalent to negated if (expression). + * + * Unless expression then statement else statement. + * + * @{ + */ +#define unless(x) if(!(x)) +#define UNLESS(x) if(!(x)) +/* @} */ + +/*! @name BREAK_ and CONTINUE_ + * + * These implement a conditional break and continue statement. + * + * @{ + */ +#define BREAK_UNLESS(x) UNLESS (x) break; +#define BREAK_IF(x) if (x) break; +#define CONTINUE_IF(x) if (x) continue; +#define CONTINUE_UNLESS(x) UNLESS (x) continue; + +/* @} */ + + +/*! @name RETURN_ + * + * These implement conditional return statement. + * @{ + */ +#define RETURN_IF(x) if (x) return; +#define RETURN_ZERO_IF(x) if (x) return 0; +#define RETURN_NULL_IF(x) if (x) return NULL; +#define RETURN_EBUSY_IF(x) if (x) return -EBUSY; +#define RETURN_EFAULT_IF(x) if (x) return -EFAULT; +#define RETURN_EINVAL_IF(x) if (x) return -EINVAL; +#define RETURN_ENOMEM_IF(x) if (x) return -ENOMEM; +#define RETURN_ENODEV_IF(x) if (x) return -ENODEV; +#define RETURN_EAGAIN_IF(x) if (x) return -EAGAIN; +#define RETURN_ETIMEDOUT_IF(x) if (x) return -ETIMEDOUT; +#define RETURN_EPIPE_IF(x) if (x) return -EPIPE; +#define RETURN_EUNATCH_IF(x) if (x) return -EUNATCH; +#define RETURN_IRQ_HANDLED_IF(x) if (x) return IRQ_HANDLED; +#define RETURN_IRQ_HANDLED_IF_IRQ_HANDLED(x) if (IRQ_HANDLED == x) return IRQ_HANDLED; +#define RETURN_IRQ_NONE_IF(x) if (x) return IRQ_NONE; +#define RETURN_TRUE_IF(x) if (x) return TRUE; +#define RETURN_FALSE_IF(x) if (x) return FALSE; + +#define RETURN_UNLESS(x) UNLESS (x) return; +#define RETURN_NULL_UNLESS(x) UNLESS (x) return NULL; +#define RETURN_ZERO_UNLESS(x) UNLESS (x) return 0; +#define RETURN_EBUSY_UNLESS(x) UNLESS (x) return -EBUSY; +#define RETURN_EFAULT_UNLESS(x) UNLESS (x) return -EFAULT; +#define RETURN_EINVAL_UNLESS(x) UNLESS (x) return -EINVAL; +#define RETURN_ENOMEM_UNLESS(x) UNLESS (x) return -ENOMEM; +#define RETURN_ENODEV_UNLESS(x) UNLESS (x) return -ENODEV; +#define RETURN_EAGAIN_UNLESS(x) UNLESS (x) return -EAGAIN; +#define RETURN_ETIMEDOUT_UNLESS(x) UNLESS (x) return -ETIMEDOUT; +#define RETURN_EPIPE_UNLESS(x) UNLESS (x) return -EPIPE; +#define RETURN_EUNATCH_UNLESS(x) UNLESS (x) return -EUNATCH; +#define RETURN_IRQ_HANDLED_UNLESS(x) UNLESS (x) return IRQ_HANDLED; +#define RETURN_IRQ_NONE_UNLESS(x) UNLESS (x) return IRQ_NONE; +#define RETURN_TRUE_UNLESS(x) UNLESS (x) return TRUE; +#define RETURN_FALSE_UNLESS(x) UNLESS (x) return FALSE; + +#define UNTIL(x) while (!(x)) + +/* @} */ + + + +#endif /* _OTG_UTILS_H */ diff --git a/drivers/otg/otg/pcd-include.h b/drivers/otg/otg/pcd-include.h new file mode 100644 index 000000000000..797302d3bdfa --- /dev/null +++ b/drivers/otg/otg/pcd-include.h @@ -0,0 +1,49 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/udc.h + * @(#) sl@belcarra.com/whiskey.enposte.net|otg/otg/pcd-include.h|20061017072623|59852 + * + * Copyright (c) 2004-2005 Belcarra + * Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + * + * By: + * Stuart Lynne <sl@belcarra.com>, + * Bruce Balden <balden@belcarra.com> + * + */ +/*! + * @file otg/otg/pcd-include.h + * @brief Linux OS Precompiled Headers + * + * @ingroup OTGCore + */ + +#include <otg/otg-compat.h> +#include <otg/otg-module.h> + +//#include <asm/irq.h> +//#include <asm/system.h> +//#include <asm/io.h> + +#include <otg/usbp-chap9.h> +//#include <otg/usbp-func.h> +#include <otg/usbp-bus.h> +#include <otg/otg-trace.h> +#include <otg/otg-api.h> +//#include <otg/otg-task.h> +#include <otg/otg-tcd.h> +#include <otg/otg-hcd.h> +#include <otg/otg-pcd.h> +#include <otg/otg-ocd.h> +#include <otg/usbp-pcd.h> diff --git a/drivers/otg/otg/usbp-audio.h b/drivers/otg/otg/usbp-audio.h new file mode 100644 index 000000000000..e2b815e28156 --- /dev/null +++ b/drivers/otg/otg/usbp-audio.h @@ -0,0 +1,357 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/otg/usbp-audio.h + * @(#) sl@belcarra.com/whiskey.enposte.net|otg/otg/usbp-audio.h|20061218212925|42686 + * + * Copyright (c) 2004-2005 Belcarra + * Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + * By: + * Stuart Lynne <sl@belcarra.com>, + * Bruce Balden <balden@belcarra.com> + * + */ + +/* + * @file otg/otg/usbp-audio.h + * @brief Audio Class class descriptor structure definitions. + * + * + * @ingroup USBDAPI + */ + +/*! + * @name Audio + */ + /*! @{ */ + +#define CS_AUDIO_UNDEFINED 0x20 +#define CS_AUDIO_DEVICE 0x21 +#define CS_AUDIO_CONFIGURATION 0x22 +#define CS_AUDIO_STRING 0x23 +#define CS_AUDIO_INTERFACE 0x24 +#define CS_AUDIO_ENDPOINT 0x25 + +#define AUDIO_HEADER 0x01 +#define AUDIO_INPUT_TERMINAL 0x02 +#define AUDIO_OUTPUT_TERMINAL 0x03 +#define AUDIO_MIXER_UNIT 0x04 +#define AUDIO_SELECTOR_UNIT 0x05 +#define AUDIO_FEATURE_UNIT 0x06 +#define AUDIO_PROCESSING_UNIT 0x07 +#define AUDIO_EXTENSION_UNIT 0x08 +/*! @} */ + +#ifdef PRAGMAPACK +#pragma pack(push,1) +#endif + + +/*! @name c.f. CDC 5.2.3 Table 24 + * c.f. Audio Appendix A.4 + */ + /*! @{ */ +#define CS_UNDEFINED 0x20 +#define CS_DEVICE 0x21 +#define CS_CONFIG 0x22 +#define CS_STRING 0x23 +#define CS_INTERFACE 0x24 +#define CS_ENDPOINT 0x25 +/*! @} */ + +/*! + * @name Audio Interface Class - c.f Appendix A.1 + */ + /*! @{ */ +#define AUDIO_INTERFACE_CLASS 0x01 +/*! @} */ + +/*! @name Audio Interface Subclass - c.f Appendix A.2 + */ + /*! @{ */ +#define AUDIO_SUBCLASS_UNDEFINED 0x00 +#define AUDIO_AUDIOCONTROL 0x01 +#define AUDIO_AUDIOSTREAMING 0x02 +#define AUDIO_MIDISTREAMING 0x03 +/*! @} */ + +/*! @name Audio Interface Proctol - c.f. Appendix A.3 + */ + /*! @{ */ +#define AUDIO_PR_PROTOCOL_UNDEFINED 0x00 +/*! @} */ + +/*! @name Audio Class-Specific AC Interface Descriptor Subtypes - c.f. A.5 + */ + /*! @{ */ +#define AUDIO_AC_DESCRIPTOR_UNDEFINED 0x00 +#define AUDIO_AC_HEADER 0x01 +#define AUDIO_AC_INPUT_TERMINAL 0x02 +#define AUDIO_AC_OUTPUT_TERMINAL 0x03 +#define AUDIO_AC_MIXER_UNIT 0x04 +#define AUDIO_AC_SELECTOR_UNIT 0x05 +#define AUDIO_AC_FEATURE_UNIT 0x06 +#define AUDIO_AC_PROCESSING_UNIT 0x07 +#define AUDIO_AC_EXTENSION_UNIT 0x08 +/*! @} */ + +/*! @name Audio Class-Specific AS Interface Descriptor Subtypes - c.f. A.6 + */ + /*! @{ */ +#define AUDIO_AS_DESCRIPTOR_UNDEFINED 0x00 +#define AUDIO_AS_GENERAL 0x01 +#define AUDIO_AS_FORMAT_TYPE 0x02 +#define AUDIO_AS_FORMAT_UNSPECFIC 0x03 +/*! @} */ + +/*! @name Audio Class Processing Unit Processing Types - c.f. A.7 + */ + /*! @{ */ +#define AUDIO_PROCESS_UNDEFINED 0x00 +#define AUDIO_UP_DOWN_MUIX_PROCESS 0x01 +#define AUDIO_DOLBY_PROLOGIC_PROCESS 0x02 +#define AUDIO_3D_STEREO_EXTENDER_PROCESS 0x03 +#define AUDIO_REVERBERATION_PROCESS 0x04 +#define AUDIO_CHORUS_PROCESS 0x05 +#define AUDIO_DYN_RANGE_COMP_PROCESS 0x06 +/*! @} */ + +/*! @name Audio Class-Specific Endpoint Descriptor Subtypes - c.f. A.8 + */ + /*! @{ */ +#define AUDIO_DESCRIPTOR_UNDEFINED 0x00 +#define AUDIO_EP_GENERAL 0x01 +/*! @} */ + +/*! @name Audio Class-Specific Request Codes - c.f. A.9 + */ + /*! @{ */ +#define AUDIO_REQUEST_CODE_UNDEFINED 0x00 +#define AUDIO_SET_CUR 0x01 +#define AUDIO_GET_CUR 0x81 +#define AUDIO_SET_MIN 0x02 +#define AUDIO_GET_MIN 0x82 +#define AUDIO_SET_MAX 0x03 +#define AUDIO_GET_MAX 0x83 +#define AUDIO_SET_RES 0x04 +#define AUDIO_GET_RES 0x84 +#define AUDIO_SET_MEM 0x05 +#define AUDIO_GET_MEM 0x85 +#define AUDIO_GET_STAT 0xff +/*! @} */ + +/*! + * @name Audio Status Word Format - c.f. Table 3.1 + */ + /*! @{ */ + +#define AUDIO_STATUS_INTERRUPT_PENDING 1<<7 +#define AUDIO_STATUS_MEMORY_CONTENT_CHANGED 1<<6 +#define AUDIO_STATUS_ORIGINATOR_AUDIO_CONTROL_INTERFACE 0 +#define AUDIO_STATUS_ORIGINATOR_AUDIO_STREAMING_INTERFACE 1 +#define AUDIO_STATUS_ORIGINATOR_AUDIO_STREAMING_ENDPOINT 2 +#define AUDIO_STATUS_ORIGINATOR_AUDIOCONTROL_INTERFACE 0 +/*! @struct usbd_audio_status_word usbp-audio.h "otg/usbp-audio.h" + * @brief audio status wrapper + */ +struct usbd_audio_status_word { + u8 bStatusType; + u8 bOriginator; +}; +/*! @struct usbd_audio_ac_interface_header_descriptor usbp-audio.h "otg/usbp-audio.h" + * + * @brief audio AC sub interface header descriptor wrapper + */ +struct usbd_audio_ac_interface_header_descriptor { + u8 bFunctionLength; + u8 bDescriptorType; + u8 bDescriptorSubtype; + u16 bcdADC; + u16 wTotalLength; + u8 binCollection; + u8 bainterfaceNr[1]; +}; +/*! @struct usbd_audio_input_terminal_descriptor usbp-audio.h "otg/usbp-audio.h" + * + * @brief audio input terminal descriptor wrapper + */ +struct usbd_audio_input_terminal_descriptor { + u8 bFunctionLength; + u8 bDescriptorType; + u8 bDescriptorSubtype; + u8 bTerminalID; + u16 wTerminalType; + u8 bAssocTerminal; + u8 bNrChannels; + u16 wChannelConfig; + u8 iChannelNames; + u8 iTerminal; +}; +/*! @struct usbd_audio_input_terminal_description usbp-audio.h "otg/usbp-audio.h" + * + * @brief audio input terminal description wrapper + */ +struct usbd_audio_input_terminal_description { + struct usbd_audio_input_terminal_descriptor *audio_input_terminal_descriptor; + u16 wTerminalType; + u16 wChannelConfig; + char * iChannelNames; + char * iTerminal; +}; +/*! @struct usbd_audio_output_terminal_descriptor usbp-audio.h "otg/usbp-audio.h" + * + * @brief audio output terminal descriptor + */ +struct usbd_audio_output_terminal_descriptor { + u8 bFunctionLength; + u8 bDescriptorType; + u8 bDescriptorSubtype; + u8 bTerminalID; + u16 wTerminalType; + u8 bAssocTerminal; + u8 bSourceID; + u8 iTerminal; +}; +/*! @struct usbd_audio_output_terminal_description usbp-audio.h "otg/usbp-audio.h" + * + * @brief audio output terminal description wrapper + */ +struct usbd_audio_output_terminal_description { + struct usbd_audio_output_terminal_descriptor *audio_output_terminal_descriptor; + u16 wTerminalType; + char * iTerminal; +}; +/*! @struct usbd_audio_mixer_unit_descriptor_a usbp-audio.h "otg/usbp-audio.h" + * + * @brief audio mixer unit A descriptor + */ +struct usbd_audio_mixer_unit_descriptor_a { + u8 bFunctionLength; + u8 bDescriptorType; + u8 bDescriptorSubtype; + u8 bUniteID; + u8 bNrinPins; +}; +/*! @struct usbd_audio_mixer_unit_descriptor_b usbp-audio.h "otg/usbp-audio.h" + * + * @brief audio mixer unit B descriptor + */ +struct usbd_audio_mixer_unit_descriptor_b { + u8 baSourceID; + u8 bNrChannels; + u16 wChannelConfig; + u8 iChannelNames; + u8 bmControls; + u8 iMixer; +}; +/*! @struct usbd_audio_mixer_unit_description usbp-audio.h "otg/usbp-audio.h" + * + * @brief audio mixer unit description wrapper + */ +struct usbd_audio_mixer_unit_description { + struct usbd_audio_mixer_unit_descriptor *audio_mixer_unit_descriptor; + u16 wChannelConfig; + char * iMixer; +}; + +/*! @struct usbd_audio_as_general_interface_descriptor usbp-audio.h "otg/usbp-audio.h" + * + * @brief audio AS interface subclass descriptior + */ +struct usbd_audio_as_general_interface_descriptor { + u8 bLength; + u8 bDescriptorType; + u8 bDescriptorSubtype; + u8 bTerminalLink; + u8 bDelay; + u16 wFormatTag; +}; +/*! @struct usbd_audio_as_general_interface_description usbp-audio.h "otg/usbp-audio.h" ++ * ++ */ +struct usbd_audio_as_general_interface_description { + struct usbd_audio_as_general_interface_descriptor *audio_as_general_interface_descriptor; + u16 wFormatTag; +}; +/*! @struct usbd_audio_as_format_type_descriptor usbp-audio.h "otg/usbp-audio.h" + * + * @brief audio AS interface subclass format type descriptor + */ +struct usbd_audio_as_format_type_descriptor { + u8 bLength; + u8 bDescriptorType; + u8 bDescriptorSubtype; + u8 bFormatType; + u8 bNrChannels; + u8 bSubFrameSize; + u8 bBitResolution; + u8 bSamFreqType; + u8 iSamFreq[3]; +}; +/*! @struct usbd_audio_as_format_type_description usbp-audio.h "otg/usbp-audio.h" + * + * @brief audio AS interface subclass format type description wrapper + */ +struct usbd_audio_as_format_type_description { + struct usbd_audio_as_format_type_descriptor *audio_as_format_type_descriptor; + u16 wFormatTag; +}; + + + +/*! @struct usbd_audio_descriptor usbp-audio.h "otg/usbp-audio.h" + * + * @brief audio descriptor wrapper + */ +struct usbd_audio_descriptor { + union { + struct usbd_audio_ac_interface_header_descriptor header; + struct usbd_audio_input_terminal_descriptor input; + struct usbd_audio_output_terminal_descriptor output; + } descriptor; +}; + + +/*! @struct usbd_ac_endpoint_descriptor usbp-audio.h "otg/usbp-audio.h" + * + * @brief audio control endpoint descriptor wrapper + */ +struct usbd_ac_endpoint_descriptor { + u8 bLength; + u8 bDescriptorType; + u8 bEndpointAddress; + u8 bmAttributes; + u16 wMaxPacketSize; + u8 bInterval; + u8 bRefresh; + u8 bSynchAddress; +}; + +/*! @struct usbd_as_iso_endpoint_descriptor usbp-audio.h "otg/usbp-audio.h" + * + * @brief as_iso_endpoint descriptor + */ +struct usbd_as_iso_endpoint_descriptor { + u8 bLength; + u8 bDescriptorType; + u8 bDescriptorSubtype; + u8 bmAttributes; + u8 bLockDelayUnits; + u16 wLockDelay; +}; +/*! @}*/ + + +#ifdef PRAGMAPACK +#pragma pack(pop) +#endif diff --git a/drivers/otg/otg/usbp-bus.h b/drivers/otg/otg/usbp-bus.h new file mode 100644 index 000000000000..3d371995c69c --- /dev/null +++ b/drivers/otg/otg/usbp-bus.h @@ -0,0 +1,363 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/otg/usbp-bus.h - USB Device Bus Interface Driver Interface + * @(#) balden@belcarra.com/seth2.rillanon.org|otg/otg/usbp-bus.h|20070810200302|45685 + * + * Copyright (c) 2004-2005 Belcarra Technologies Corp + * Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + * + * By: + * Stuart Lynne <sl@belcarra.com>, + * Bruce Balden <balden@belcarra.com> + * + */ + +/*! + * @file otg/otg/usbp-bus.h + * @brief Bus interface Defines for USB Device Core Layaer + * + * This file contains the USB Peripheral Driver definitions. + * + * This is the interface between the bottom of the USB Core and the top of + * the Bus Interace Drivers and is comprised of: + * + * o public functions exported by the USB Core layer + * + * o structures and functions passed by the Function Driver to the USB + * Core layer + * + * USB Peripheral Controller Drivers are structured such that the upper edge + * implements interfaces to the USB Peripheral Core layer to provide low + * level USB services and the lower edge interfaces to the actual USB + * Hardware (typically called the USB Device Controller or UDC.) + * + * The peripheral controller layer primarily deals with endpoints. + * There is one control endpoint and one or more data endpoints. + * + * The control endpoint is special, with received data processed with + * the built in EP0 function. Which in turn will pass requests to + * interface functions as appropriate. + * + * Data endpoints are controlled by interface driver and have a + * function callback specified by the interface driver to perform + * whatever needs to be done when data is sent or received. + * + * + * @ingroup USBDAPI + */ + +/* +struct usbd_endpoint_map; +struct usbd_endpoint_instance; +struct usbd_bus_instance; +struct usbd_urb; +*/ + +#define USBD usbd_trace_tag +extern otg_tag_t USBD; + +/*! + * USB Bus Interface Driver structures + * + * Driver description: + * + * struct usbd_bus_operations + * struct usbd_bus_driver + * + */ + +extern int usbd_maxstrings; + + +/*! + * exported to otg + */ +struct usbd_ops { + int (*admin) (char *); + char * (*function_name) (int); +}; + + + +/*! @struct usbd_bus_operations usbp-bus.h "otg/usbp-bus.h" + * + * @brief Operations that the slave layer or function driver can use to interact with the bus interface driver. + * The send_urb() function is used by the usb-device endpoint 0 driver and + * function drivers to submit data to be sent. + * + * The cancel_urb() function is used by the usb-device endpoint 0 driver and + * function drivers to remove previously queued data to be sent. + * + * The endpoint_halted() function is used by the ep0 control function to + * check if an endpoint is halted. + * + * The endpoint_feature() function is used by the ep0 control function to + * set/reset device features on an endpoint. + * + * The device_event() function is used by the usb device core to tell the + * bus interface driver about various events. + */ +struct usbd_bus_operations { + //int (*xbus_serial_number) (struct usbd_bus_instance *, char *); + + int (*start_endpoint_in) (struct usbd_bus_instance *, struct usbd_endpoint_instance *); + int (*start_endpoint_out) (struct usbd_bus_instance *, struct usbd_endpoint_instance *); + int (*cancel_urb_irq) (struct usbd_urb *); + int (*device_feature) (struct usbd_bus_instance *, int, int); + int (*endpoint_halted) (struct usbd_bus_instance *, int); + int (*halt_endpoint) (struct usbd_bus_instance *, int, int); + int (*set_configuration) (struct usbd_bus_instance *, int); + int (*set_address) (struct usbd_bus_instance *, int); + int (*event_handler) (struct usbd_bus_instance *, usbd_device_event_t, int); + int (*request_endpoints) (struct usbd_bus_instance *, struct usbd_endpoint_map *, + int, struct usbd_endpoint_request *); + int (*set_endpoints) (struct usbd_bus_instance *, int , struct usbd_endpoint_map *); + int (*framenum) (struct usbd_bus_instance *); + //u64 (*ticks) (void); + //u64 (*elapsed) (u64 *, u64 *); + //u64 (*interrupts) (void); +}; + +/*! @struct usbd_endpoint_instance usbp-bus.h "otg/usbp-bus.h" + * @brief Endpoint operation related data wrapper + * + * Per endpoint configuration data. Used to track which actual physical + * endpoints. + * + * The bus layer maintains an array of these to represent all physical + * endpoints. + * + */ +struct usbd_endpoint_instance { + int interface_wIndex; // which interface owns this endpoint + int physicalEndpoint[2]; // physical endpoint address - bus interface specific + u8 bEndpointAddress[2]; // logical endpoint address + u8 bmAttributes[2]; // endpoint type + u16 wMaxPacketSize[2]; // packet size for requested endpoint + u16 transferSize[2]; // packet size for requested endpoint + + u32 feature_setting; // save set feature information + + // control + int state; // available for use by bus interface driver + + + otg_pthread_mutex_t mutex; + struct urb_link rdy; // empty urbs ready to receive + + union { + struct usbd_urb *active_urb; // active urb + struct usbd_urb *rcv_urb; // alias + struct usbd_urb *tx_urb; // alias + }; + + u32 rcv_transferSize; // maximum transfer size from function driver + int rcv_error; // current bulk-in has an error + u32 tx_transferSize; // maximum transfer size from function driver + + u32 sent; // data already sent + u32 last; // data sent in last packet XXX do we need this + struct timer_list pcd_hr_timer; + void *privdata; + + struct usbd_bus_instance *bus; + + void *buffer; + dma_addr_t dma; + +}; + +/*! @name endpoint zero states + * @{ + */ +#define WAIT_FOR_SETUP 0 +#define DATA_STATE_XMIT 1 +#define DATA_STATE_NEED_ZLP 2 +#define WAIT_FOR_OUT_STATUS 3 +#define DATA_STATE_RECV 4 +#define DATA_STATE_PENDING_XMIT 5 +#define WAIT_FOR_IN_STATUS 6 +/*! @} */ + +/*! @struct usbd_bus_driver usbp-bus.h "otg/usbp-bus.h" + * @brief Bus Interface data structure + * + * Keep track of specific bus interface. + * + * This is passed to the usb-device layer when registering. It contains all + * required information about each real bus interface found such that the + * usb-device layer can create and maintain a usb-device structure. + * + * Note that bus interface registration is incumbent on finding specific + * actual real bus interfaces. There will be a registration for each such + * device found. + * + * The max_tx_endpoints and max_rx_endpoints are the maximum number of + * possible endpoints that this bus interface can support. The default + * endpoint 0 is not included in these counts. + * + */ +struct usbd_bus_driver { + char *name; + u8 max_endpoints; // maximimum number of rx enpoints + u8 maxpacketsize; + u8 high_speed_capable; + struct usbd_device_description *device_description; + struct usbd_bus_operations *bops; + void *privdata; + //u32 capabilities; + u8 bmAttributes; + u8 bMaxPower; + u8 ports; +}; + +#if 0 +/*! + * Bus state + * + * enabled or disabled + */ +typedef enum usbd_bus_state { + usbd_bus_state_unknown, + usbd_bus_state_disabled, + usbd_bus_state_enabled +} usbd_bus_state_t; +#endif + +/*! + * @name UDC Capabilities + * @{ + */ +#define HAVE_CABLE_IRQ 0x0001 +#define REMOTE_WAKEUP_SUPPORTED 0x0002 +#define ROOT_HUB 0x0004 + +#define USB_SPEED_FULL 0x0 +#define USB_SPEED_HIGH 0x1 +#define USB_SPEED_UNKNOWN 0x2 + +/*! @} */ + +/*! @struct usbd_bus_instance usbp-bus.h "otg/usbp-bus.h" + * @brief Bus Interface configuration structure + * + * This is allocated for each configured instance of a bus interface driver. + * + * It contains a pointer to the appropriate bus interface driver. + * + * The privdata pointer may be used by the bus interface driver to store private + * per instance state information. + */ +struct usbd_bus_instance { + + struct otg_instance *otg; + struct usbd_bus_driver *driver; + + int endpoints; + struct usbd_endpoint_instance *endpoint_array; // array of available configured endpoints + + struct urb_link finished; // processed urbs + + usbd_device_status_t status; // device status + //usbd_bus_state_t bus_state; + usbd_device_state_t device_state; // current USB Device state + usbd_device_state_t suspended_state; // previous USB Device state + + struct usbd_interface_instance *ep0; // ep0 configuration + struct usbd_function_instance *function_instance; + + u8 high_speed; + + u32 device_feature_settings;// save set feature information + u8 bmAttributes; + u8 bMaxPower; + + //struct WORK_STRUCT reset_bh; // runs as bottom half, equivalent to interrupt time + //struct WORK_STRUCT resume_bh; // runs as bottom half, equivalent to interrupt time + //struct WORK_STRUCT suspend_bh; // runs as bottom half, equivalent to interrupt time + + struct otg_workitem *reset_bh; // runs as bottom half, equivalent to interrupt time + struct otg_workitem *resume_bh; // runs as bottom half, equivalent to interrupt time + struct otg_workitem *suspend_bh; // runs as bottom half, equivalent to interrupt time + + //struct otg_task *task; + + void *privdata; // private data for the bus interface + char *arg; + + int usbd_maxstrings; + struct usbd_string_descriptor **usb_strings; + struct usbd_urb *ep0_urb; +}; + + +/*! bus driver registration + * + * Called by bus interface drivers to register themselves when loaded + * or de-register when unloading. + */ +struct usbd_bus_instance *usbd_register_bus (struct usbd_bus_driver *, int); +void usbd_deregister_bus (struct usbd_bus_instance *); + + +/*! Enable/Disable Function + * + * Called by a bus interface driver to select and enable a specific function + * driver. + */ +int usbd_enable_function (struct usbd_bus_instance *, char *, char *); +void usbd_disable_function (struct usbd_bus_instance *); + + +/*! + * usbd_device_request - process a received urb + * @urb: pointer to an urb structure + * + * Used by a USB Bus interface driver to pass received data in a URB to the + * appropriate USB Function driver. + * + * This function must return 0 for success and -EINVAL if the request + * is to be stalled. + * + * Not that if the SETUP is Host to Device with a non-zero wLength then there + * *MUST* be a valid receive urb queued OR the request must be stalled. + */ +int usbd_device_request_irq (struct usbd_bus_instance*, struct usbd_device_request *); +int usbd_device_request (struct usbd_bus_instance*, struct usbd_device_request *); + +/*! + * Device I/O + */ +void usbd_urb_finished (struct usbd_urb *, int ); +void usbd_urb_finished_irq (struct usbd_urb *, int ); +//struct usbd_urb *usbd_first_urb_detached_irq (urb_link * hd); +//struct usbd_urb *usbd_first_urb_detached (urb_link * hd); +void usbd_do_urb_callback (struct usbd_urb *urb, int rc); + + +/*! flush endpoint + */ +void usbd_flush_endpoint_irq (struct usbd_endpoint_instance *); +void usbd_flush_endpoint (struct usbd_endpoint_instance *); +int usbd_find_endpoint_index(struct usbd_bus_instance *bus, int bEndpointAddress); + +/* + * urb lists + */ +//struct usbd_urb *usbd_first_urb (urb_link * hd); +//void usbd_unlink_urb (struct usbd_urb * urb); + +struct usbd_urb *usbd_first_urb_detached (struct usbd_endpoint_instance *endpoint, urb_link * hd); +struct usbd_urb *usbd_first_finished_urb_detached (struct usbd_endpoint_instance *endpoint, urb_link * hd); +struct usbd_urb *usbd_first_ready_urb(struct usbd_endpoint_instance *endpoint, urb_link * hd); diff --git a/drivers/otg/otg/usbp-cdc.h b/drivers/otg/otg/usbp-cdc.h new file mode 100644 index 000000000000..13abe33218ba --- /dev/null +++ b/drivers/otg/otg/usbp-cdc.h @@ -0,0 +1,689 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/otg/usbp-cdc.h + * @(#) sl@belcarra.com/whiskey.enposte.net|otg/otg/usbp-cdc.h|20061218212925|24522 + * + * Copyright (c) 2004-2005 Belcarra Technologies Corp + * Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + * By: + * Stuart Lynne <sl@belcarra.com>, + * Bruce Balden <balden@belcarra.com> + * + */ + +/*! + * @file otg/otg/usbp-cdc.h + * @brief CDC class descriptor structure definitions. + * + * @ingroup USBDAPI + */ + +#ifdef PRAGMAPACK +#pragma pack(push,1) +#endif + +/*! + * @name Class-Specific Request Codes + * C.f. CDC Table 46 + */ + /*! @{ */ + +#define CDC_CLASS_REQUEST_SEND_ENCAPSULATED 0x00 +#define CDC_CLASS_REQUEST_GET_ENCAPSULATED 0x01 + +#define CDC_CLASS_REQUEST_SET_COMM_FEATURE 0x02 +#define CDC_CLASS_REQUEST_GET_COMM_FEATURE 0x03 +#define CDC_CLASS_REQUEST_CLEAR_COMM_FEATURE 0x04 + +#define CDC_CLASS_REQUEST_SET_LINE_CODING 0x20 +#define CDC_CLASS_REQUEST_GET_LINE_CODING 0x21 + +#define CDC_CLASS_REQUEST_SET_CONTROL_LINE_STATE 0x22 +#define CDC_CLASS_REQUEST_SEND_BREAK 0x23 +/*! @} */ + +/*! + * @name Notification codes + * c.f. CDC Table 68 + */ + /*! @{ */ + +#define CDC_NOTIFICATION_NETWORK_CONNECTION 0x00 +#define CDC_NOTIFICATION_RESPONSE_AVAILABLE 0x01 +#define CDC_NOTIFICATION_AUX_JACK_HOOK_STATE 0x08 +#define CDC_NOTIFICATION_RING_DETECT 0x09 +#define CDC_NOTIFICATION_SERIAL_STATE 0x20 +#define CDC_NOTIFICATION_CALL_STATE_CHANGE 0x28 +#define CDC_NOTIFICATION_LINE_STATE_CHANGE 0x29 +#define CDC_NOTIFICATION_CONNECTION_SPEED_CHANGE 0x2a +/*! @} */ + + +/*! + * @name ACM - Line Coding structure + * C.f. CDC Table 50 + */ + /*! @{ */ + +/*! @struct cdc_acm_line_coding usbp-cdc.h "otg/usbp-cdc.h" + * + * @brief acm line code wrapper + * + */ +PACKED0 struct PACKED1 cdc_acm_line_coding { + u32 dwDTERate; + u8 bCharFormat; + u8 bParityType; + u8 bDataBits; +}; + + /*!@} */ + +/*! + * @name ACM - Line State + * C.f. CDC Table 51 + */ + /*! @{ */ +/*! create a type for u16 */ +typedef u16 cdc_acm_bmLineState; +#define CDC_LINESTATE_D1_RTS (1 << 1) +#define CDC_LINESTATE_D0_DTR (1 << 0) +/*! @} */ + +/*! + * Serial State - C.f. 6.3.5 + */ +/*! @struct acm_serial_state usbp-cdc.h "otg/usbp-cdc.h" + * + * @brief acm serial state wrapper + */ +PACKED0 struct PACKED1 acm_serial_state { + u16 uart_state_bitmap; +}; + + +/*! +* @name ACM - UART State + * C.f. Table 69 - UART State Bitmap Values + */ +/*! @{ */ +/*! create a type for u16 */ +typedef u16 cdc_acm_bmUARTState; +#define CDC_UARTSTATE_BOVERRUN (1 << 6) +#define CDC_UARTSTATE_BPARITY (1 << 5) +#define CDC_UARTSTATE_BFRAMING (1 << 4) +#define CDC_UARTSTATE_BRINGSIGNAL (1 << 3) +#define CDC_UARTSTATE_BBREAK (1 << 2) +#define CDC_UARTSTATE_BTXCARRIER_DSR (1 << 1) +#define CDC_UARTSTATE_BRXCARRIER_DCD (1 << 0) +/*! @} */ + +/*! @struct cdc_notification_descriptor usbp-cdc.h "otg/usbp-cdc.h" + * + * @brief USB Notification descriptor struct + */ +PACKED0 struct PACKED1 cdc_notification_descriptor { + u8 bmRequestType; + u8 bNotification; + u16 wValue; + u16 wIndex; + u16 wLength; + u8 data[2]; +}; + + + + +/*! + * @name Communications Class Types + * + * c.f. CDC USB Class Definitions for Communications Devices + * c.f. WMCD USB CDC Subclass Specification for Wireless Mobile Communications Devices + * + */ +/*! @{ */ + +#define CLASS_BCD_VERSION 0x0110 +/*! @} */ + +/*! @name c.f. CDC 4.1 Table 14 */ + +/*! @{ */ + +#define AUDIO_CLASS 0x01 +#define COMMUNICATIONS_DEVICE_CLASS 0x02 +/*! @} */ + +/*! @name c.f. CDC 4.2 Table 15 + */ + /*! @{ */ +#define COMMUNICATIONS_INTERFACE_CLASS 0x02 +/*! @} */ + +/*! @name c.f. CDC 4.3 Table 16 + */ + /*! @{ */ +#define COMMUNICATIONS_NO_SUBCLASS 0x00 +#define COMMUNICATIONS_DLCM_SUBCLASS 0x01 +#define COMMUNICATIONS_ACM_SUBCLASS 0x02 +#define COMMUNICATIONS_TCM_SUBCLASS 0x03 +#define COMMUNICATIONS_MCCM_SUBCLASS 0x04 +#define COMMUNICATIONS_CCM_SUBCLASS 0x05 +#define COMMUNICATIONS_ENCM_SUBCLASS 0x06 +#define COMMUNICATIONS_ANCM_SUBCLASS 0x07 + +#define AUDIO_CONTROL_SUBCLASS 0x01 +#define AUDIO_STREAMING_SUBCLASS 0x02 +/*! @} */ + +/*! @name c.f. WMCD 5.1 + */ + /*! @{ */ +#define COMMUNICATIONS_WHCM_SUBCLASS 0x08 +#define COMMUNICATIONS_DMM_SUBCLASS 0x09 +#define COMMUNICATIONS_MDLM_SUBCLASS 0x0a +#define COMMUNICATIONS_OBEX_SUBCLASS 0x0b +#define COMMUNICATIONS_EEM_SUBCLASS 0x0c +/*! @} */ + +/*! @name c.f. CDC 4.6 Table 18 + */ + /*! @{ */ +#define DATA_INTERFACE_CLASS 0x0a +/*! @} */ + +/*! @name c.f. CDC 4.7 Table 19 + */ + /*! @{ */ +#define COMMUNICATIONS_NO_PROTOCOL 0x00 +#define COMMUNICATIONS_EEM_PROTOCOL 0x07 +/*! @} */ + +/*! + * @name bDescriptorSubtypes + * + * c.f. CDC 5.2.3 Table 25 + * c.f. WMCD 5.3 Table 5.3 + */ + /*! @{ */ + +#define USB_ST_HEADER 0x00 +#define USB_ST_CMF 0x01 +#define USB_ST_ACMF 0x02 +#define USB_ST_DLMF 0x03 +#define USB_ST_TRF 0x04 +#define USB_ST_TCLF 0x05 +#define USB_ST_UF 0x06 +#define USB_ST_CSF 0x07 +#define USB_ST_TOMF 0x08 +#define USB_ST_USBTF 0x09 +#define USB_ST_NCT 0x0a +#define USB_ST_PUF 0x0b +#define USB_ST_EUF 0x0c +#define USB_ST_MCMF 0x0d +#define USB_ST_CCMF 0x0e +#define USB_ST_ENF 0x0f +#define USB_ST_ATMNF 0x10 + +#define USB_ST_WHCM 0x11 +#define USB_ST_MDLM 0x12 +#define USB_ST_MDLMD 0x13 +#define USB_ST_DMM 0x14 +#define USB_ST_OBEX 0x15 +#define USB_ST_CS 0x16 +#define USB_ST_CSD 0x17 +#define USB_ST_TCM 0x18 +/*! @} */ + +/*! + * @name Call Management - bmCapabilities + * + * c.f. CDC 5.2.3.2 Table 27 + * c.f. WMCD + */ +#define USB_CMFD_DATA_CLASS 0x02 +#define USB_CMFD_CALL_MANAGEMENT 0x01 +/*! @} */ + +/*! + * @name Abstract Control Management - bmCapabilities + * + * c.f. CDC 5.2.3.2 Table 28 + * c.f. WMCD + */ +#define USB_ACMFD_NETWORK 0x08 +#define USB_ACMFD_SEND_BREAK 0x04 +#define USB_ACMFD_CONFIG 0x02 +#define USB_ACMFD_COMM_FEATURE 0x01 +/*! @} */ + +/*! + * @name Class Descriptor Description Structures... + * c.f. CDC 5.1 + * c.f. WCMC 6.7.2 + * + * XXX add the other dozen class descriptor description structures.... + * + */ + /*! @{ */ +/*! @struct usbd_header_functional_descriptor usbp-cdc.h "otg/usbp-cdc.h" + * + * @brief header functional descriptor + */ +PACKED0 struct PACKED1 usbd_header_functional_descriptor { + u8 bFunctionLength; + u8 bDescriptorType; + u8 bDescriptorSubtype; + u16 bcdCDC; +}; +/*! @struct usbd_call_management_functional_descriptor usbp-cdc.h "otg/usbp-cdc.h" + * + * @brief call management functional descriptor + */ +PACKED0 struct PACKED1 usbd_call_management_functional_descriptor { + u8 bFunctionLength; + u8 bDescriptorType; + u8 bDescriptorSubtype; + u8 bmCapabilities; + u8 bDataInterface; +}; +/*! @struct usbd_abstract_control_functional_descriptor usbp-cdc.h "otg/usbp-cdc.h" + * + * @brief abstract control functional descriptor + */ +PACKED0 struct PACKED1 usbd_abstract_control_functional_descriptor { + u8 bFunctionLength; + u8 bDescriptorType; + u8 bDescriptorSubtype; + u8 bmCapabilities; +}; +/*! @struct usbd_union_functional_descriptor usbp-cdc.h "otg/usbp-cdc.h" + * + * @brief union functional descriptor + */ +PACKED0 struct PACKED1 usbd_union_functional_descriptor { + u8 bFunctionLength; + u8 bDescriptorType; + u8 bDescriptorSubtype; + u8 bMasterInterface; + u8 bSlaveInterface[4]; +}; +/*! @struct usbd_ethernet_networking_descriptor usbp-cdc.h "otg/usbp-cdc.h" + * + * @brief ethernet networking descriptor + */ +PACKED0 struct PACKED1 usbd_ethernet_networking_descriptor { + u8 bFunctionLength; + u8 bDescriptorType; + u8 bDescriptorSubtype; + char *iMACAddress; + u8 bmEthernetStatistics; + u16 wMaxSegmentSize; + u16 wNumberMCFilters; + u8 bNumberPowerFilters; +}; +/*! @struct usbd_mobile_direct_line_model_descriptor usbp-cdc.h "otg/usbp-cdc.h" + * + * @brief mobile direct line model descriptor struct + */PACKED0 struct PACKED1 usbd_mobile_direct_line_model_descriptor { + u8 bFunctionLength; + u8 bDescriptorType; + u8 bDescriptorSubtype; + u16 bcdVersion; + u8 bGUID[16]; +}; +/*! @struct usbd_mobile_direct_line_model_detail_descriptor usbp-cdc.h "otg/usbp-cdc.h" + * + * @brief usbd_mobile direct line model detail descriptor struct + */ +PACKED0 struct PACKED1 usbd_mobile_direct_line_model_detail_descriptor { + u8 bFunctionLength; + u8 bDescriptorType; + u8 bDescriptorSubtype; + u8 bGuidDescriptorType; + u8 bDetailData[4]; +}; +/*! @} */ + + + + +/*! + * @name Communications Class Dscriptor Structures + * c.f. CDC 5.2 Table 25c + */ + +/*! @{*/ +/*! @struct usbd_class_function_descriptor usbp-cdc.h "otg/usbp-cdc.h" + * + * @brief CDC class function descriptor + */ +PACKED0 struct PACKED1 usbd_class_function_descriptor { + u8 bFunctionLength; + u8 bDescriptorType; + u8 bDescriptorSubtype; +}; +/*! @struct usbd_class_function_descriptor_generic usbp-cdc.h "otg/usbp-cdc.h" + * + * @brief CDC calss function descriptor generic struct + */ +PACKED0 struct PACKED1 usbd_class_function_descriptor_generic { + u8 bFunctionLength; + u8 bDescriptorType; + u8 bDescriptorSubtype; + u8 bmCapabilities; +}; +/*! @struct usbd_class_header_function_descriptor usbp-cdc.h "otg/usbp-cdc.h" + * + * @brief class header function descriptor + */ +PACKED0 struct PACKED1 usbd_class_header_function_descriptor { + u8 bFunctionLength; + u8 bDescriptorType; + u8 bDescriptorSubtype; // 0x00 + u16 bcdCDC; +}; +/*! @struct usbd_class_call_management_descriptor usbp-cdc.h "otg/usbp-cdc.h" + * + * @brief class call management descriptor + */ +PACKED0 struct PACKED1 usbd_class_call_management_descriptor { + u8 bFunctionLength; + u8 bDescriptorType; + u8 bDescriptorSubtype; // 0x01 + u8 bmCapabilities; + u8 bDataInterface; +}; +/*! @struct usbd_class_abstract_control_descriptor usbp-cdc.h "otg/usbp-cdc.h" + * + * @brief class abstract control descriptor struct + */ +PACKED0 struct PACKED1 usbd_class_abstract_control_descriptor { + u8 bFunctionLength; + u8 bDescriptorType; + u8 bDescriptorSubtype; // 0x02 + u8 bmCapabilities; +}; +/*! @struct usbd_class_direct_line_descriptor usbp-cdc.h "otg/usbp-cdc.h" + * + * @brief class direct line descriptor struct + */ +PACKED0 struct PACKED1 usbd_class_direct_line_descriptor { + u8 bFunctionLength; + u8 bDescriptorType; + u8 bDescriptorSubtype; // 0x03 +}; +/*! @struct usbd_class_telephone_ringer_descriptor usbp-cdc.h "otg/usbp-cdc.h" + * + * @brief class telephone ringer descriptor struct + */ +PACKED0 struct PACKED1 usbd_class_telephone_ringer_descriptor { + u8 bFunctionLength; + u8 bDescriptorType; + u8 bDescriptorSubtype; // 0x04 + u8 bRingerVolSeps; + u8 bNumRingerPatterns; +}; +/*! @struct usbd_class_telephone_call_descriptor usbp-cdc.h "otg/usbp-cdc.h" + * + * @brief class telephone call descriptor struct + */ +PACKED0 struct PACKED1 usbd_class_telephone_call_descriptor { + u8 bFunctionLength; + u8 bDescriptorType; + u8 bDescriptorSubtype; // 0x05 + u8 bmCapabilities; +}; +/*! @struct usbd_class_union_function_descriptor usbp-cdc.h "otg/usbp-cdc.h" + * + * @brief class union function descriptor struct + */ +PACKED0 struct PACKED1 usbd_class_union_function_descriptor { + u8 bFunctionLength; + u8 bDescriptorType; + u8 bDescriptorSubtype; // 0x06 + u8 bMasterInterface; + u8 bSlaveInterface[4]; +}; +/*! @struct usbd_class_country_selection_descriptor usbp-cdc.h "otg/usbp-cdc.h" + * + * @brief class country selection descriptor struct + */ +PACKED0 struct PACKED1 usbd_class_country_selection_descriptor { + u8 bFunctionLength; + u8 bDescriptorType; + u8 bDescriptorSubtype; // 0x07 + u8 iCountryCodeRelDate; + u16 wCountryCode0[4]; +}; + +/*! @struct usbd_class_telephone_operational_descriptor usbp-cdc.h "otg/usbp-cdc.h" + * + * @brief class telephone operational descriptor struct + */ +PACKED0 struct PACKED1 usbd_class_telephone_operational_descriptor { + u8 bFunctionLength; + u8 bDescriptorType; + u8 bDescriptorSubtype; // 0x08 + u8 bmCapabilities; +}; + +/*! @struct usbd_class_usbd_terminal_descriptor usbp-cdc.h "otg/usbp-cdc.h" + * + * @brief class usbd terminal descriptor struct + * + */ +PACKED0 struct PACKED1 usbd_class_usbd_terminal_descriptor { + u8 bFunctionLength; + u8 bDescriptorType; + u8 bDescriptorSubtype; // 0x09 + u8 bEntityId; + u8 bInterfaceNo; + u8 bOutInterfaceNo; + u8 bmOptions; + u8 bChild0[4]; +}; +/*! @struct usbd_class_network_channel_descriptor usbp-cdc.h "otg/usbp-cdc.h" + * + * @brief class network channel descriptor struct + */ +PACKED0 struct PACKED1 usbd_class_network_channel_descriptor { + u8 bFunctionLength; + u8 bDescriptorType; + u8 bDescriptorSubtype; // 0x0a + u8 bEntityId; + u8 iName; + u8 bChannelIndex; + u8 bPhysicalInterface; +}; +/*! @struct usbd_class_protocol_unit_function_descriptor usbp-cdc.h "otg/usbp-cdc.h" + * + * @brief class protocol unit function descriptor + */ +PACKED0 struct PACKED1 usbd_class_protocol_unit_function_descriptor { + u8 bFunctionLength; + u8 bDescriptorType; + u8 bDescriptorSubtype; // 0x0b + u8 bEntityId; + u8 bProtocol; + u8 bChild0[4]; +}; +/*! @struct usbd_class_extension_unit_descriptor usbp-cdc.h "otg/usbp-cdc.h" + * + * @brief class extension unit descriptor struct + */ +PACKED0 struct PACKED1 usbd_class_extension_unit_descriptor { + u8 bFunctionLength; + u8 bDescriptorType; + u8 bDescriptorSubtype; // 0x0c + u8 bEntityId; + u8 bExtensionCode; + u8 iName; + u8 bChild0[4]; +}; +/*! @struct usbd_class_multi_channel_descriptor usbp-cdc.h "otg/usbp-cdc.h" + * + * @brief class multi_channel descriptor struct + */ +PACKED0 struct PACKED1 usbd_class_multi_channel_descriptor { + u8 bFunctionLength; + u8 bDescriptorType; + u8 bDescriptorSubtype; // 0x0d + u8 bmCapabilities; +}; +/*! @struct usbd_class_capi_control_descriptor usbp-cdc.h "otg/usbp-cdc.h" + * + * @brief class capi control descriptor + */ +PACKED0 struct PACKED1 usbd_class_capi_control_descriptor { + u8 bFunctionLength; + u8 bDescriptorType; + u8 bDescriptorSubtype; // 0x0e + u8 bmCapabilities; +}; +/*! @struct usbd_class_ethernet_networking_descriptor usbp-cdc.h "otg/usbp-cdc.h" + * + * @brief class ethernet networking descriptor + */ +PACKED0 struct PACKED1 usbd_class_ethernet_networking_descriptor { + u8 bFunctionLength; + u8 bDescriptorType; + u8 bDescriptorSubtype; // 0x0f + u8 iMACAddress; + u32 bmEthernetStatistics; + u16 wMaxSegmentSize; + u16 wNumberMCFilters; + u8 bNumberPowerFilters; +}; +/*! @struct usbd_class_atm_networking_descriptor usbp-cdc.h "otg/usbp-cdc.h" + * + * @brief class atm networking descriptor struct + */ +PACKED0 struct PACKED1 usbd_class_atm_networking_descriptor { + u8 bFunctionLength; + u8 bDescriptorType; + u8 bDescriptorSubtype; // 0x10 + u8 iEndSystermIdentifier; + u8 bmDataCapabilities; + u8 bmATMDeviceStatistics; + u16 wType2MaxSegmentSize; + u16 wType3MaxSegmentSize; + u16 wMaxVC; +}; + +/*! @struct usbd_class_mdlm_descriptor usbp-cdc.h "otg/usbp-cdc.h" + * + * @brief class mdlm descriptor struct + */ +PACKED0 struct PACKED1 usbd_class_mdlm_descriptor { + u8 bFunctionLength; + u8 bDescriptorType; + u8 bDescriptorSubtype; // 0x12 + u16 bcdVersion; + u8 bGUID[16]; +}; + +/* +PACKED0 struct PACKED1 usbd_class_mdlmd_descriptor { + u8 bFunctionLength; + u8 bDescriptorType; + u8 bDescriptorSubtype; // 0x13 + u8 bGuidDescriptorType; + u8 bDetailData[4]; +}; +*/ +/*! @struct usbd_class_blan_descriptor usbp-cdc.h "otg/usbp-cdc.h" + * + * @brief class blan descriptor struct + */ +PACKED0 struct PACKED1 usbd_class_blan_descriptor { + u8 bFunctionLength; // 0x7 + u8 bDescriptorType; + u8 bDescriptorSubtype; // 0x13 + u8 bGuidDescriptorType; + u8 bmNetworkCapabilities; + u8 bmDataCapabilities; + u8 bPad; +}; +/*! @struct usbd_class_safe_descriptor usbp-cdc.h "otg/usbp-cdc.h" + * + * @brief class safe descriptor struct + */ +PACKED0 struct PACKED1 usbd_class_safe_descriptor { + u8 bFunctionLength; // 0x6 + u8 bDescriptorType; + u8 bDescriptorSubtype; // 0x13 + u8 bGuidDescriptorType; + u8 bmNetworkCapabilities; + u8 bmDataCapabilities; +}; + +/*! @} */ + + + + +/*! + * @name EEM + * c.f. EEM 1.0 + */ +/*! @{*/ + +/*! @struct usbd_eem_packet_header usbp-cdc.h "otg/usbp-cdc.h" + * + * @brief eem packet header wrapper + */ +PACKED0 struct PACKED1 usbd_eem_packet_header { + u8 bmType:1; + u16 eem: 15; +}; +/*! @struct usbd_eem_data_packet usbp-cdc.h "otg/usbp-cdc.h" + * + * @brief eem data packet wrapper + */ +PACKED0 struct PACKED1 usbd_eem_data_packet { + u8 bmType:1; + u8 bmCRC:1; + u16 length:14; +}; + +/*! @struct usbd_eem_command_packet usbp-cdc.h "otg/usbp-cdc.h" + * + * @brief eem command packet wrapper + */ +PACKED0 struct PACKED1 usbd_eem_command_packet { + u8 bmType:1; + u8 bmReserved:4; + u16 bmEEMCmdParam:11; +}; + +#define EEM_TYPE_DATA 0x0 +#define EEM_TYPE_COMMAND 0x1 + +#define EEM_ECHO 0x0 +#define EEM_ECHO_RESPONSE 0x1 +#define EEM_SUSPEND_HINT 0x2 +#define EEM_RESPONSE_HINT 0x3 +#define EEM_RESPONSE_COMPLETE_HINT 0x4 +#define EEM_TICKLE 0x5 + + + + +/*@}*/ + + +#ifdef PRAGMAPACK +#pragma pack(pop) +#endif diff --git a/drivers/otg/otg/usbp-chap9.h b/drivers/otg/otg/usbp-chap9.h new file mode 100644 index 000000000000..7f2411b837ff --- /dev/null +++ b/drivers/otg/otg/usbp-chap9.h @@ -0,0 +1,849 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/otg/usbp-chap9.h + * @(#) sl@belcarra.com/whiskey.enposte.net|otg/otg/usbp-chap9.h|20070814003511|14784 + * + * Copyright (c) 2004-2005 Belcarra Technologies Corp + * Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + * By: + * Stuart Lynne <sl@belcarra.com>, + * Bruce Balden <balden@belcarra.com> + * + */ +/*! + * @file otg/otg/usbp-chap9.h + * @brief Chapter 9 and other class descriptor structure definitions. + * + * USB Descriptors are used to build a configuration database for each USB + * Function driver. + * + * @ingroup USBDAPI + */ + +/*! + * @name Device and/or Interface Class Codes + */ + + /*! @{ */ + + +#define USB_CLASS_PER_INTERFACE 0 /* for DeviceClass */ +#define USB_CLASS_AUDIO 1 +#define USB_CLASS_COMM 2 +#define USB_CLASS_HID 3 +#define USB_CLASS_PHYSICAL 5 +#define USB_CLASS_PRINTER 7 +#define USB_CLASS_MASS_STORAGE 8 +#define USB_CLASS_HUB 9 +#define USB_CLASS_DATA 10 +#define USB_CLASS_MISC 0xef +#define USB_CLASS_APP_SPEC 0xfe +#define USB_CLASS_VENDOR_SPEC 0xff + +/*! @} */ + + +/*! @name USB Device Request Types (bmRequestType) + *C.f. USB 2.0 Table 9-2 +*/ + +/*! @{ */ + +#define USB_TYPE_STANDARD (0x00 << 5) +#define USB_TYPE_CLASS (0x01 << 5) +#define USB_TYPE_VENDOR (0x02 << 5) +#define USB_TYPE_RESERVED (0x03 << 5) + +#define USB_RECIP_DEVICE 0x00 +#define USB_RECIP_INTERFACE 0x01 +#define USB_RECIP_ENDPOINT 0x02 +#define USB_RECIP_OTHER 0x03 + +#ifndef __LINUX_USB_CH9_H +#define USB_RECIP_HUB 0x00 +#define USB_RECIP_PORT 0x03 +#endif /* __LINUX_USB_CH9_H */ + +#define USB_DIR_OUT 0 +#define USB_DIR_IN 0x80 +#define USB_ENDPOINT_OPT 0x40 +/*! @} */ + +/*! @name Descriptor types + * C.f. USB Table 9-5 + */ + +/*! @{ */ +#ifndef __LINUX_USB_CH9_H +#define USB_DT_DEVICE 1 +#define USB_DT_CONFIGURATION 2 +#define USB_DT_STRING 3 +#define USB_DT_INTERFACE 4 +#define USB_DT_ENDPOINT 5 +#define USB_DT_DEVICE_QUALIFIER 6 +#define USB_DT_OTHER_SPEED_CONFIGURATION 7 +#define USB_DT_INTERFACE_POWER 8 +#define USB_DT_OTG 9 +#define USB_DT_DEBUG 10 +#define USB_DT_INTERFACE_ASSOCIATION 11 +#define USB_DT_CLASS_SPECIFIC 0x24 +#endif /* __LINUX_USB_CH9_H */ + + +#define USB_DT_HID (USB_TYPE_CLASS | 0x01) +#define USB_DT_REPORT (USB_TYPE_CLASS | 0x02) +#define USB_DT_PHYSICAL (USB_TYPE_CLASS | 0x03) +#define USB_DT_HUB (USB_TYPE_CLASS | 0x09) +/*! @} */ + +#if 0 +/*! @name Descriptor sizes per descriptor type + */ + /*! @{ */ + +#define USB_DT_DEVICE_SIZE 18 +#define USB_DT_CONFIG_SIZE 9 +#define USB_DT_INTERFACE_SIZE 9 +#define USB_DT_ENDPOINT_SIZE 7 +#define USB_DT_ENDPOINT_AUDIO_SIZE 9 /* Audio extension */ +#define USB_DT_HUB_NONVAR_SIZE 7 +#define USB_DT_HID_SIZE 9 +/*! @} */ +#endif + +/*! @name Endpoints + */ +/*! @{ */ +#define USB_ENDPOINT_NUMBER_MASK 0x0f /* in bEndpointAddress */ +#define USB_ENDPOINT_DIR_MASK 0x80 + +#define USB_ENDPOINT_MASK 0x03 /* in bmAttributes */ +#define USB_ENDPOINT_CONTROL 0x00 +#define USB_ENDPOINT_ISOCHRONOUS 0x01 +#define USB_ENDPOINT_BULK 0x02 +#define USB_ENDPOINT_INTERRUPT 0x03 +/*! @} */ + +/*! + * @name USB Packet IDs (PIDs) + */ +/*! @{ */ +#define USB_PID_UNDEF_0 0xf0 +#define USB_PID_OUT 0xe1 +#define USB_PID_ACK 0xd2 +#define USB_PID_DATA0 0xc3 +#define USB_PID_PING 0xb4 /* USB 2.0 */ +#define USB_PID_SOF 0xa5 +#define USB_PID_NYET 0x96 /* USB 2.0 */ +#define USB_PID_DATA2 0x87 /* USB 2.0 */ +#define USB_PID_SPLIT 0x78 /* USB 2.0 */ +#define USB_PID_IN 0x69 +#define USB_PID_NAK 0x5a +#define USB_PID_DATA1 0x4b +#define USB_PID_PREAMBLE 0x3c /* Token mode */ +#define USB_PID_ERR 0x3c /* USB 2.0: handshake mode */ +#define USB_PID_SETUP 0x2d +#define USB_PID_STALL 0x1e +#define USB_PID_MDATA 0x0f /* USB 2.0 */ +/*! @} */ + +/*! * @name Standard requests + */ + + /*! @{ */ + +#define USB_REQ_GET_STATUS 0x00 +#define USB_REQ_CLEAR_FEATURE 0x01 +#define USB_REQ_SET_FEATURE 0x03 +#define USB_REQ_SET_ADDRESS 0x05 +#define USB_REQ_GET_DESCRIPTOR 0x06 +#define USB_REQ_SET_DESCRIPTOR 0x07 +#define USB_REQ_GET_CONFIGURATION 0x08 +#define USB_REQ_SET_CONFIGURATION 0x09 +#define USB_REQ_GET_INTERFACE 0x0A +#define USB_REQ_SET_INTERFACE 0x0B +#define USB_REQ_SYNCH_FRAME 0x0C +/*! @} */ + +/*! + * @name USB Spec Release number + */ + /*! @{ */ + +#define USB_BCD_VERSION_11 0x0110 +#define USB_BCD_VERSION_20 0x0200 + +#if defined(CONFIG_OTG_HIGH_SPEED) || defined(CONFIG_OTG_BDEVICE_WITH_SRP) || defined(CONFIG_OTG_DEVICE) +#define USB_BCD_VERSION USB_BCD_VERSION_20 +#else /* CONFIG_OTG_HIGH_SPEED */ +#define USB_BCD_VERSION USB_BCD_VERSION_11 +#endif /* CONFIG_OTG_HIGH_SPEED */ + +/*! @} */ + + +/*! + * @name Device Requests (c.f Table 9-2) + */ + /*! @{ */ + +#define USB_REQ_DIRECTION_MASK 0x80 +#define USB_REQ_TYPE_MASK 0x60 +#define USB_REQ_RECIPIENT_MASK 0x1f + +#define USB_REQ_DEVICE2HOST 0x80 +#define USB_REQ_HOST2DEVICE 0x00 + +#define USB_REQ_TYPE_STANDARD 0x00 +#define USB_REQ_TYPE_CLASS 0x20 +#define USB_REQ_TYPE_VENDOR 0x40 + +#define USB_REQ_RECIPIENT_DEVICE 0x00 +#define USB_REQ_RECIPIENT_INTERFACE 0x01 +#define USB_REQ_RECIPIENT_ENDPOINT 0x02 +#define USB_REQ_RECIPIENT_OTHER 0x03 +/*! @} */ + + +/*! + * @name get status bits + */ + /*! @{ */ + +#define USB_STATUS_SELFPOWERED 0x01 +#define USB_STATUS_REMOTEWAKEUP 0x02 + +#define USB_STATUS_HALT 0x01 +/*! @} */ + +/*! + * @name Convert Feature Selector to a Status Flag Setting + */ + /*! @{ */ +#define FEATURE(f) (1 << f) +/*! @} */ + +/*! + * @name Standard Feature Selectors + */ + /*! @{ */ +#ifndef __LINUX_USB_CH9_H +#define USB_ENDPOINT_HALT 0x00 +#define USB_DEVICE_REMOTE_WAKEUP 0x01 +#endif /* __LINUX_USB_CH9_H */ +#define USB_TEST_MODE 0x02 +/*! @} */ + +/*! + * @name Test Mode Selectors + */ + /*! @{ */ +#define USB_TEST_J 0x1 +#define USB_TEST_K 0x2 +#define USB_TEST_SE0_NAK 0x3 +#define USB_TEST_PACKET 0x4 +#define USB_TEST_FORCE_ENABLE 0x5 +/*! @} */ + +#ifdef PRAGMAPACK +#pragma pack(push,1) +#endif + +/* Note: +The various structs declared here need 1-byte +alignment. These is achieved in various ways +with assorted C compilers. +First of all, some compilers need the pack() +#pragma. This is enabled by defining the PRAGMAPACK +macro + +Secondly the structure definitions themselves +may require specialized additions to their declarations. +The following model seems to accommodate all compilers +known so far: + + +PACKED0 struct PACKED1 { + various items +}; + +Define appropriate values for PACKED0, PACKED1, PACKED2 +suited to your compiler. In most cases at least two of +these values will be empty strings. + +*/ + +/*! @struct usbd_device_request usbp-chap9.h "otg/usbp-chap9.h" + * + * @brief usb device request wrapper struct + */ +PACKED0 struct PACKED1 usbd_device_request { + u8 bmRequestType; + u8 bRequest; + u16 wValue; + u16 wIndex; + u16 wLength; +}; + + +/*! @struct usbd_otg_descriptor usbp-chap9.h "otg/osbp-chap9.h" + * + * @brief otg descriptor wrapper + */ +PACKED0 struct PACKED1 usbd_otg_descriptor { + u8 bLength; + u8 bDescriptorType; + u8 bmAttributes; +}; + +/*! + * @name OTG + */ + /*! @{ */ +#define USB_OTG_HNP_SUPPORTED 0x02 +#define USB_OTG_SRP_SUPPORTED 0x01 +/*! @} */ + +/*! + * @name OTG Feature selectors + */ + /*! @{ */ +#define USB_OTG_B_HNP_ENABLE 0x03 +#define USB_OTG_A_HNP_SUPPORT 0x04 +#define USB_OTG_A_ALT_HNP_ENABLE 0x05 +/*! @} */ + + +/*! + * @name Endpoint Modifiers + * static struct usbd_endpoint_description function_default_A_1[] = { + * + * {this_endpoint: 0, attributes: CONTROL, max_size: 8, polling_interval: 0 }, + * {this_endpoint: 1, attributes: BULK, max_size: 64, polling_interval: 0, direction: IN}, + * {this_endpoint: 2, attributes: BULK, max_size: 64, polling_interval: 0, direction: OUT}, + * {this_endpoint: 3, attributes: INTERRUPT, max_size: 8, polling_interval: 0}, + * + * + */ + /*! @{ */ + +#define CONTROL 0x00 +#define ISOCHRONOUS 0x01 +#define BULK 0x02 +#define INTERRUPT 0x03 +/*! @} */ + + +/*! @name configuration modifiers + */ + +/* @{ */ +#define USB_BMATTRIBUTE_RESERVED 0x80 +#define USB_BMATTRIBUTE_SELF_POWERED 0x40 +#define USB_BMATTRIBUTE_REMOTE_WAKEUP 0x20 + +#if 0 +/* + * The UUT tester specifically tests for MaxPower to be non-zero (> 0). + */ +#if !defined(CONFIG_OTG_MAXPOWER) || (CONFIG_OTG_MAXPOWER == 0) + #define BMATTRIBUTE BMATTRIBUTE_RESERVED | BMATTRIBUTE_SELF_POWERED + #define BMAXPOWER 1 +#else + #define BMATTRIBUTE BMATTRIBUTE_RESERVED + #define BMAXPOWER CONFIG_USBD_MAXPOWER +#endif +#endif +/*! @} */ + + + +/*! + * @name Standard Usb Descriptor Structures + */ + /*! @{ */ +/*! @struct usbd_endpoint_descriptor usbp-chap9.h "otg/usbp-chap9.h" + * + * @brief endpoint descriptor encapsulation + */ +PACKED0 struct PACKED1 usbd_endpoint_descriptor { + u8 bLength; + u8 bDescriptorType; // 0x5 + u8 bEndpointAddress; + u8 bmAttributes; + u16 wMaxPacketSize; + u8 bInterval; +}; +/*! @struct usbd_interface_descriptor usbp-chap9.h "otg/usbp-chap9.h" + * + * @brief interface descriptor structure + */ +PACKED0 struct PACKED1 usbd_interface_descriptor { + u8 bLength; + u8 bDescriptorType; // 0x04 + u8 bInterfaceNumber; + u8 bAlternateSetting; + u8 bNumEndpoints; + u8 bInterfaceClass; + u8 bInterfaceSubClass; + u8 bInterfaceProtocol; + u8 iInterface; +}; +/*! @struct usbd_configuration_descriptor usbp-chap9.h "otg/usbp-chap9.h" + * + * @brief configuration descriptor structure + */ +PACKED0 struct PACKED1 usbd_configuration_descriptor { + u8 bLength; + u8 bDescriptorType; // 0x2 + u16 wTotalLength; + u8 bNumInterfaces; + u8 bConfigurationValue; + u8 iConfiguration; + u8 bmAttributes; + u8 bMaxPower; +}; +/*! @struct usbd_device_descriptor usbp-chap9.h "otg/usbp-chap9.h" + * + * @brief device descriptor structure + */ +PACKED0 struct PACKED1 usbd_device_descriptor { + u8 bLength; + u8 bDescriptorType; // 0x01 + u16 bcdUSB; + u8 bDeviceClass; + u8 bDeviceSubClass; + u8 bDeviceProtocol; + u8 bMaxPacketSize0; + u16 idVendor; + u16 idProduct; + u16 bcdDevice; + u8 iManufacturer; + u8 iProduct; + u8 iSerialNumber; + u8 bNumConfigurations; +}; + +/* Define according to the whims of the compiler */ +#define USBD_WDATA_MIN 1 /* Some compilers will allow 0 */ +/*! @struct usbd_string_descriptor usbp-chap9.h "otg/usbp-chap9.h" + * + * @brief string descriptor structure + */ +PACKED0 struct PACKED1 usbd_string_descriptor { + u8 bLength; + u8 bDescriptorType; // 0x03 + u16 wData[USBD_WDATA_MIN]; +}; +/*! @struct usbd_langid_descriptor usbp-chap9.h "otg/usbp-chap9.h" + * + * @brief language ID descriptor + */ +PACKED0 struct PACKED1 usbd_langid_descriptor { + u8 bLength; + u8 bDescriptorType; // 0x03 + u8 bData[2]; +}; + +#if (USBD_WDATA_MIN > 0) +#define USBD_STRING_SIZEOF(x) (sizeof(x) - 2*(USBD_WDATA_MIN)) +#else +#define USBD_STRING_SIZEOF(x) (sizeof(x)) +#endif + +/*! @struct usbd_device_qualifier_descriptor usbp-chap9.h "otg/usbp-chap9.h" + * + * @brief device qualifier descriptor + */ +struct usbd_device_qualifier_descriptor { + u8 bLength; + u8 bDescriptorType; + u16 bcdUSB; + u8 bDeviceClass; + u8 bDeviceSubClass; + u8 bDeviceProtocol; + u8 bMaxPacketSize0; + u8 bNumConfigurations; + u8 bReserved; +}; + +/*! @struct usbd_generic_descriptor usbp-chap9.h "otg/usbp-chap9.h" + * + * @brief usb generic descriptor structure + */ +struct usbd_generic_descriptor { + u8 bLength; + u8 bDescriptorType; +}; +/*! @struct usbd_generic_calss_descriptor usbp-chap9.h "otg/usbp-chap9.h" + * + * @brief generic class descriptor structure + */ +struct usbd_generic_class_descriptor { + u8 bLength; + u8 bDescriptorType; + u8 bDescriptorSubtype; +}; +/*! @} */ + +/*! + * @name Interface Association Descriptor + */ + /*! @{ */ +/*! @struct usbd_interface_association_descriptor usbp-chap9.h "otg/usbp-chap9.h" + * + * @brief interface association descriptor + */ +PACKED0 struct PACKED1 usbd_interface_association_descriptor { + u8 bLength; + u8 bDescriptorType; + u8 bFirstInterface; + u8 bInterfaceCount; + u8 bFunctionClass; + u8 bFunctionSubClass; + u8 bFunctionProtocol; + u8 iFunction; +}; + +/*! @} */ + +#if 0 +/*! + * @name Descriptor Union Structures + */ + /*! @{ */ + +struct usbd_descriptor { + union { + struct usbd_generic_descriptor generic_descriptor; + struct usbd_generic_class_descriptor generic_class_descriptor; + struct usbd_endpoint_descriptor endpoint_descriptor; + struct usbd_interface_descriptor interface_descriptor; + struct usbd_configuration_descriptor configuration_descriptor; + struct usbd_device_descriptor device_descriptor; + struct usbd_string_descriptor string_descriptor; + } descriptor; + +}; + +struct usbd_class_descriptor { + union { + struct usbd_class_function_descriptor function; + struct usbd_class_function_descriptor_generic generic; + struct usbd_class_header_function_descriptor header_function; + struct usbd_class_call_management_descriptor call_management; + struct usbd_class_abstract_control_descriptor abstract_control; + struct usbd_class_direct_line_descriptor direct_line; + struct usbd_class_telephone_ringer_descriptor telephone_ringer; + struct usbd_class_telephone_operational_descriptor telephone_operational; + struct usbd_class_telephone_call_descriptor telephone_call; + struct usbd_class_union_function_descriptor union_function; + struct usbd_class_country_selection_descriptor country_selection; + struct usbd_class_usbd_terminal_descriptor usb_terminal; + struct usbd_class_network_channel_descriptor network_channel; + struct usbd_class_extension_unit_descriptor extension_unit; + struct usbd_class_multi_channel_descriptor multi_channel; + struct usbd_class_capi_control_descriptor capi_control; + struct usbd_class_ethernet_networking_descriptor ethernet_networking; + struct usbd_class_atm_networking_descriptor atm_networking; + struct usbd_class_mdlm_descriptor mobile_direct; + //struct usbd_class_mdlmd_descriptor mobile_direct_detail; + struct usbd_class_blan_descriptor mobile_direct_blan_detail; + struct usbd_class_safe_descriptor mobile_direct_safe_detail; + } descriptor; + +}; +/*! @} */ +#endif + + +/*! + * Device Events + * + * These are defined in the USB Spec (c.f USB Spec 2.0 Figure 9-1). + * + * There are additional events defined to handle some extra actions we need to have handled. + * + */ +/*! create a type for usbd_device_event */ +typedef enum usbd_device_event { + + DEVICE_UNKNOWN, // 0 - bi - unknown event + DEVICE_INIT, // 1 - bi - initialize + DEVICE_CREATE, // 2 - bi - + DEVICE_HUB_CONFIGURED, // 3 - bi - bus has been plugged int + DEVICE_RESET, // 4 - bi - hub has powered our port + + DEVICE_ADDRESS_ASSIGNED, // 5 - ep0 - set address setup received + DEVICE_CONFIGURED, // 6 - ep0 - set configure setup received + DEVICE_SET_INTERFACE, // 7 - ep0 - set interface setup received + + DEVICE_SET_FEATURE, // 8 - ep0 - set feature setup received + DEVICE_CLEAR_FEATURE, // 9 - ep0 - clear feature setup received + + DEVICE_DE_CONFIGURED, // 10 - ep0 - set configure setup received for ?? + + DEVICE_BUS_INACTIVE, // 11 - bi - bus in inactive (no SOF packets) + DEVICE_BUS_ACTIVITY, // 12 - bi - bus is active again + + DEVICE_POWER_INTERRUPTION, // 13 - bi - hub has depowered our port + DEVICE_HUB_RESET, // 14 - bi - bus has been unplugged + DEVICE_DESTROY, // 15 - bi - device instance should be destroyed + DEVICE_CLOSE, // 16 - bi - device instance should be destroyed + +} usbd_device_event_t; + +/*! USB Request Block structure + * + * This is used for both sending and receiving data. + * + * The callback function is used to let the function driver know when + * transmitted data has been sent. + * + * The callback function is set by the alloc_recv function when an urb is + * allocated for receiving data for an endpoint and used to call the + * function driver to inform it that data has arrived. + * + * Note that for OUT urbs the buffer is always allocated to a multiple of + * the packetsize that is 1 larger than the requested size. This prevents + * overflow if the host unexpectedly sends a full sized packet when we are + * expecting a short one (the host is always right..) + * + * USBD_URB_SENDZLP - set this flag if a ZLP (zero length packet) should be + * sent after the data is sent. This is required if sending data that is a + * multiple of the packetsize but less than the maximum transfer size for + * the protocol in use. A ZLP is required to ensure that the host + * recognizes a short transfer. + * + */ +/*!create a type for urb_link */ +typedef struct urb_link { + u32 total; + struct urb_link *next; + struct urb_link *prev; +} urb_link; + + +/*! URB Status + * + * This defines the current status of a pending or finshed URB. + * + */ +/*! create a type for usbd_urb_status + * @brief typedef enum usbd_urb_status usbd_urb_status_t + */ +typedef enum usbd_urb_status { + USBD_URB_OK = 0, + USBD_URB_QUEUED, + USBD_URB_ACTIVE, + USBD_URB_CANCELLED, + USBD_URB_ERROR, + USBD_URB_STALLED, + USBD_URB_RESET, + USBD_URB_NOT_READY, + USBD_URB_DISABLED, +} usbd_urb_status_t; + +#define USBD_URB_SENDZLP (1 << 0) /* send a Zero Length Packet when urb is finished */ +#define USBD_URB_ZLPSENT (1 << 1) /* send a Zero Length Packet when urb is finished */ +#define USBD_URB_SOF (1 << 2) /* terminate a receive transfer at SOF if any data received */ +#define USBD_URB_SOF2 (1 << 3) /* terminate a receive transfer at SOF if no data since previous SOF + (some data was received) */ + +#define USBD_URB_FAST_RETURN (1 << 4) /* process finished urb in interrupt */ +#define USBD_URB_FAST_FINISH (1 << 5) /* finished tx_urb as soon as loaded into FIFO */ + +#define USBD_URB_OUT (1 << 6) +#define USBD_URB_IN (1 << 7) + +/* + * These are used by the PCD hardware layer to indicate current + * status of the urb. The lifecycle of the urb is: + * + * 1. start urb - set USBD_URB_READY, add to endpoint queue + * + * 2. pcd - set to USBD_URB_ACTIVE when starting to process + * + * 3. pcd - set to USBD_URB_FINISHED when finished processing + * + */ +#define USBD_URB_READY (1 << 0) /* urb is ready */ +#define USBD_URB_ACTIVE (1 << 1) /* urb is active */ +#define USBD_URB_FINISHED (1 << 2) /* urb is finished */ + +//#define USBD_URB_OK (1 << 5) +//#define USBD_URB_CANCELLED (1 << 6) +//#define USBD_URB_ERROR (1 << 7) + + + +/*! @struct usbd_urb usbp-chap9.h "otg/usbp-chap9.h" + * + * @brief definition of the structure itself + */ +struct usbd_urb { + + struct usbd_bus_instance *bus; + struct usbd_function_instance *function_instance; + struct usbd_endpoint_instance *endpoint; + + + //usbd_urb_notification *notify; + int (*notify) (struct usbd_urb *, int); + + u8 *buffer; // data received (OUT) or being sent (IN) + dma_addr_t dma_addr; + u32 buffer_length; // maximum data expected for OUT + u32 alloc_length; // allocated size of buffer + u32 actual_length; // actual data received (OUT or being sent (IN) + u32 flags; + u32 irq_flags; + + int endpoint_index; + u16 wMaxPacketSize; + + void *function_privdata; + void *bus_privdata; + + struct urb_link link; + usbd_urb_status_t status; // what is the current status of the urb + otg_tick_t ticks; + u16 framenum; // SOF framenum when urb was finished + +#ifdef CONFIG_OTG_LATENCY_CHECK + otg_tick_t urb_rcved_ticks; // the queued time + otg_tick_t bh_start_ticks; // The process finished time + otg_tick_t goto_net_ticks; // the queued time + otg_tick_t usbd_ticks_start; // The process finished time + otg_tick_t usbd_ticks_end; // The process finished time +#endif +}; + + +/*! typedef int usbd_urb_notification(struct usbd_urb, int) + * + * @brief Standard notification callback typedef + + * This function, if present, is called to notify the function that the low layer + * is finished with the URB. + * If the function is absent or returns nonzero, then the URB is deallocated + * Otherwise the URB is preserved (for possible reuse by the function) + * The notification function is supplied an argument equal to the urb->status field. + */ +typedef int usbd_urb_notification(struct usbd_urb *urb, int rc); + +/*! @struct usbd_endpoint_request usbp-chap9.h "otg/usbp-chap9.h" + * + * @brief Endpoint Request struct * + * An array of these structures is initialized by each function driver to specify the endpoints + * it requires. + * + * The bus interface driver will attempt to fulfill these requests with the actual endpoints it + * has available. + * + * Note that in most cases the bEndpointAddress should be left as zero except for specialized + * function drivers that both require a specific value and know ahead of time that the specified + * value is legal for the bus interface driver being used. + */ +struct usbd_endpoint_request { + u8 index; /* function specific index */ + u8 configuration; /* configuration endpoint will be in */ + u8 interface_num; /* interface endpoint will be in */ + u8 alternate; /* altsetting for this request */ + u8 bmAttributes; /* endpoint type AND direction */ + u16 fs_requestedTransferSize; /* max full speed transfer size for this endpoint */ + u16 hs_requestedTransferSize; /* max high speed transfer size for this endpoint */ + u8 bInterval; + u8 bEndpointAddress; /* specific bEndpointAddress function driver requires */ + u8 physical; /* physical endpoint used */ +}; + +/*!@struct usbd_endpoint_map usbp-chap9.h "otg/usbp-chap9.h" + * + * @brief Endpoint Map * + * An array of these structures is created by the bus interface driver to + * show what endpoints have been configured for the function driver. + * + * This is the logical array of endpoints that are indexed into by the + * function layer using the logical endpoint numbers defined by the function + * drivers. It maps these numbers into a real physical endpoint. + * + * Fields that can be different for Full speed versus High speed are + * represented with an array both values are available. + * + * For interfaces with multiple alternate settings, each separate + * combinantion of interface_num/altsetting/endpoint must be specified. + * It is up to the lower layers to determine if / when overlapped + * endpoints can be re-used. + * + */ +struct usbd_endpoint_map { + u8 index; + u8 configuration; + u8 interface_num; + u8 alternate; + u8 bEndpointAddress[2]; // logical endpoint address + u16 wMaxPacketSize[2]; // packetsSize for requested endpoint + u8 bmAttributes[2]; // requested endpoint type + u16 transferSize[2]; // transferSize for bulk transfers + u8 physicalEndpoint[2]; // physical endpoint number + u8 bInterval[2]; + struct usbd_endpoint_instance *endpoint; +}; + + +/*! + * create a type for enum usbd_device_status + * @brief Device status enumeration * + * Overall state, we use this to show when we are suspended. + * This is required because when resumed we need to go back + * to previous state. + */ +typedef enum usbd_device_status { + USBD_OPENING, // 0. we are currently opening + USBD_RESETING, // 1. we are currently opening + USBD_OK, // 2. ok to use + USBD_SUSPENDED, // 3. we are currently suspended + USBD_CLOSING, // 4. we are currently closing + USBD_CLOSED, // 5. we are currently closing + USBD_UNKNOWN, +} usbd_device_status_t; + + +/*! @var typedef enum usbd_device_state usbd_device_state_t + * Device State (c.f USB Spec 2.0 Figure 9-1) + * + * What state the usb device is in. + * + * Note the state does not change if the device is suspended, we simply set a + * flag to show that it is suspended. + * + */ +typedef enum usbd_device_state { + STATE_INIT, // 0. just initialized + STATE_CREATED, // 1. just created + STATE_ATTACHED, // 2. we are attached + STATE_POWERED, // 3. we have seen power indication (electrical bus signal) + STATE_DEFAULT, // 4. we been reset + STATE_ADDRESSED, // 5. we have been addressed (in default configuration) + STATE_CONFIGURED, // 6. we have seen a set configuration device command + STATE_SUSPENDED, // 7. device has been suspended + STATE_UNKNOWN, // 8. destroyed +} usbd_device_state_t; + + + +#ifdef PRAGMAPACK +#pragma pack(pop) +#endif diff --git a/drivers/otg/otg/usbp-func.h b/drivers/otg/otg/usbp-func.h new file mode 100644 index 000000000000..9c51dff73056 --- /dev/null +++ b/drivers/otg/otg/usbp-func.h @@ -0,0 +1,737 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/otg/usbp-func.h - USB Device Function Driver Interface + * @(#) sl@belcarra.com/whiskey.enposte.net|otg/otg/usbp-func.h|20070220211522|09278 + * + * Copyright (c) 2004-2005 Belcarra + * Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + * + * By: + * Stuart Lynne <sl@belcarra.com>, + * Bruce Balden <balden@belcarra.com> + * + * + */ + +/*! + * @file otg/otg/usbp-func.h + * @brief Function Driver related structures and definitions. + * + * This file contains the USB Function Driver and USB Interface Driver + * definitions. + * + * The USBOTG support allows for implementation of composite devices. + * + * From USB 2.0, C.f. 5.2.3: + * + * A Device that has multiple interfaces controlled independantly of + * each other is referred to as a composite device. A composite device + * has only a single device. + * + * In this implemenation the function portion of the device is split into + * two types of drivers: + * + * - USB Interface Driver + * - USB Function Driver + * + * + * USB Interface Driver + * + * An USB Interface Driver specifies the Interface related descriptors and + * implements the data handling processes for each of the data endpoints + * associated with the interface (or interfaces) required for the function. + * It typically will also implement the upper edge interface to the OS. + * + * The USB Interface Driver will also handle non-standard device requests + * and feature requests that are for the interface or an endpoint associated + * with one of the interfaces associated with the driver. + * + * Each interface implemented by an USB Interface Driver will have a + * interface instance that stores a pointer to the parent USB Function + * Driver and has an array of pointers to endpoint instances for the data + * endpoints associated with the interface. + * + * + * USB Function Driver + * + * A USB Function Driver specifies the device and configuration descriptors. + * A configuration descriptor is assembled from the interface descriptors + * from the USB Interface Driver (or drivers) that it is making available to + * the USB Host. + * + * The USB Function Driver handles non-standard device requests and feature + * requests for the device. + * + * Each function driver has a function instance that maintains an array + * of pointers to configuration instances and a pointer to the device + * descriptor. + * + * Each configuration instance maintains an array of pointers to the + * interface instances for that configuration and a pointer to the + * complete configuration descriptor. + * + * @ingroup USBDAPI + */ + +#ifndef USBP_FUNC_H +#define USBP_FUNC_H + +/*! + * This file contains the USB Device Core Common definitions. It also contains definitions for + * functions that are commonly used by both Function Drivers and Bus Interface Drivers. + * + * Specifically: + * + * o public functions exported by the USB Core layer + * + * o common structures and definitions + * + */ + + +/*! + * @var typedef enum usbd_function_types usbd_function_types_t + * + * @brief USB Function Driver types + */ +typedef enum usbd_function_types { + function_ep0, // combined driver + function_simple, // combined driver + function_interface, // supports interface + function_class, // supports class + function_composite, // supports configuration +} usbd_function_types_t; + + +/*!@struct usbd_function_operations usbp-func.h "otg/usbp-func.h" + * USB Function Driver structures + * + * Descriptors: + * struct usbd_endpoint_description + * struct usbd_interface_description + * struct usbd_configuration_description + * + * Driver description: + * struct usbd_function_driver + * struct usbd_function_operations + * + */ + +struct usbd_function_operations { + + int (*function_enable) (struct usbd_function_instance *); + void (*function_disable) (struct usbd_function_instance *); + + /* + * All of the following may be called from an interrupt context + */ + void (*event_handler) (struct usbd_function_instance *, usbd_device_event_t, int); + int (*device_request) (struct usbd_function_instance *, struct usbd_device_request *); + + // XXX should these return anything? + int (*set_configuration) (struct usbd_function_instance *, int configuration); + int (*set_interface) (struct usbd_function_instance *, int wIndex, int altsetting); + int (*suspended) (struct usbd_function_instance *); + int (*resumed) (struct usbd_function_instance *); + int (*reset) (struct usbd_function_instance *); + + void * (*get_descriptor)(struct usbd_function_instance *, int descriptor_type, int descriptor_index); + int * (*set_descriptor)(struct usbd_function_instance *, int descriptor_type, int descriptor_index, void *); + + void (*endpoint_cleared)(struct usbd_function_instance*, int wIndex); + +}; + + +/*!@struct xusbd_alternate_instance usbp-func.h "otg/usbp-func.h" + * function driver definitions + */ +struct xusbd_alternate_instance { + struct usbd_interface_descriptor *interface_descriptor; + int classes; + struct usbd_generic_class_descriptor **class_list; + int endpoints; +}; + + +/*! + * @struct usbd_alternate_description usbp-func.h "otg/usbp-func.h" + * + * @brief usb device description structures + */ + +struct usbd_alternate_description { + //struct usbd_interface_descriptor *interface_descriptor; + char *iInterface; + u8 bInterfaceClass; + u8 bInterfaceSubClass; + u8 bInterfaceProtocol; + + // list of CDC class descriptions for this alternate interface + u8 classes; + struct usbd_generic_class_descriptor **class_list; + + // list of endpoint descriptions for this alternate interface + u8 endpoints; + + // list of indexes into endpoint request map for each endpoint descriptor + u8 *endpoint_index; + + // XXX This should be here + u8 new_endpointsRequested; + struct usbd_endpoint_request *new_requestedEndpoints; +}; + +/*! create a type for usbd_generic_class_defscriptor + * @brief typedef struct usbd_generic_class_descriptor usbd_calss_descriptor_t + * + */ + +typedef struct usbd_generic_class_descriptor usbd_class_descriptor_t; + +/*!@var typedef struct usbd_endpoint_descriptor usbd_endpoint_descriptor_t; + * + * @brief create a type for usbd_endpoint_descriptor + */ +typedef struct usbd_endpoint_descriptor usbd_endpoint_descriptor_t; + +/*! @struct usbd_interface_description usbp-func.h "otg/usbp-func.h" + */ + +struct usbd_interface_description { + // list of alternate interface descriptions for this interface + u8 alternates; /*< number of alternates */ + struct usbd_alternate_description *alternate_list; /*< alternate description list */ +}; + +/*! @struct usbd_configuration_description usbp-func.h "otg/usbp-func.h"*/ + +struct usbd_configuration_description { + char *iConfiguration; /*< configuration index string */ + u8 bmAttributes; /*< bmAttributes */ + u8 bMaxPower; /*< bMaxPower - maximum power allowed */ +}; + +/*! @struct usbd_device_description usbp-func.h "otg/usbp-func.h" + */ + +struct usbd_device_description { + //struct usbd_device_descriptor *device_descriptor; + //struct usbd_otg_descriptor *otg_descriptor; + + u16 idVendor; + u16 idProduct; + u16 bcdDevice; + u8 bDeviceClass; + u8 bDeviceSubClass; + u8 bDeviceProtocol; + + u8 bMaxPacketSize0; + + u8 *iManufacturer; + u8 *iProduct; + u8 *iSerialNumber; +}; +/*! @struct usbd_instances_instance usbp-func.h "otg/usbp-func.h"*/ + +struct usbd_interfaces_instance { + u8 alternates; + struct usbd_alternate_instance *alternates_instance_array; +}; + +/*! @struct usbd_function_driver usbp-func.h "otg/usbp-func.h" + * + * @brief Function Driver data structure * Function driver and its configuration descriptors. + * + * This is passed to the usb-device layer when registering. It contains all + * required information about the function driver for the usb-device layer + * to use the function drivers configuration data and to configure this + * function driver an active configuration. + * + * Note that each function driver registers itself on a speculative basis. + * Whether a function driver is actually configured will depend on the USB + * HOST selecting one of the function drivers configurations. + * + * This may be done multiple times WRT to either a single bus interface + * instance or WRT to multiple bus interface instances. In other words a + * multiple configurations may be selected for a specific bus interface. Or + * the same configuration may be selected for multiple bus interfaces. + * + */ +#define FUNCTION_REGISTERED 0x1 // set in flags if function is registered +#define FUNCTION_ENABLED 0x2 // set in flags if function is enabled +struct usbd_function_driver { + struct otg_list_node drivers; // linked list (was drivers in usbd_function_driver) + const char *name; + struct usbd_function_operations *fops; // functions + usbd_function_types_t function_type; + u32 flags; + void *privdata; + const char **usb_strings; + u8 *usb_string_indexes; +}; + +/*!@struct usbd_function_instance usbp-func.h "otg/usbp-func.h" + * + * @brief function configuration structure + * + * This is allocated for each configured instance of a function driver. + * + * It stores pointers to the usbd_function_driver for the appropriate function, + * and pointers to the USB HOST requested usbd_configuration_description and + * usbd_interface_description. + * + * The privdata pointer may be used by the function driver to store private + * per instance state information. + * + * The endpoint map will contain a list of all endpoints for the configuration + * driver, and only related endpoints for an interface driver. + * + * The interface driver array will be NULL for an interface driver. + */ +struct usbd_function_instance { + const char *name; + struct usbd_bus_instance *bus; + usbd_function_types_t function_type; + struct usbd_function_driver *function_driver; + void *privdata; // private data for the function + + //int usbd_maxstrings; + //struct usbd_string_descriptor **usb_strings; +}; + +/*! @struct usbd_simple_driver usbp-func.h "otg/usbp-func.h" + * + * @brief Function Driver data structure * + * Function driver and its configuration descriptors. + * + * This is passed to the usb-device layer when registering. It contains all + * required information about the function driver for the usb-device layer + * to use the function drivers configuration data and to configure this + * function driver an active configuration. + * + * Note that each function driver registers itself on a speculative basis. + * Whether a function driver is actually configured will depend on the USB + * HOST selecting one of the function drivers configurations. + * + * This may be done multiple times WRT to either a single bus interface + * instance or WRT to multiple bus interface instances. In other words a + * multiple configurations may be selected for a specific bus interface. Or + * the same configuration may be selected for multiple bus interfaces. + * + */ +struct usbd_simple_driver { + + struct usbd_function_driver driver; + + // device & configuration descriptions + struct usbd_device_description *device_description; + struct usbd_configuration_description *configuration_description; + int bNumConfigurations; + + u8 interfaces; // XXX should be interfaces + struct usbd_interface_description *interface_list; + + u8 endpointsRequested; + struct usbd_endpoint_request *requestedEndpoints; + + // constructed descriptors + //struct usbd_device_descriptor *device_descriptor; + + u8 iManufacturer; + u8 iProduct; + u8 iSerialNumber; + + + //struct usbd_otg_descriptor *otg_descriptor; + //struct usbd_configuration_instance *configuration_instance_array; + +}; + +/*! @struct usbd_simple_instance usbp-func.h "otg/usbp-func.h" + * + * @brief Simple configuration structure * + * This is allocated for each configured instance of a function driver. + * + * It stores pointers to the usbd_function_driver for the appropriate function, + * and pointers to the USB HOST requested usbd_configuration_description and + * usbd_interface_description. + * + * The privdata pointer may be used by the function driver to store private + * per instance state information. + * + * The endpoint map will contain a list of all endpoints for the configuration + * driver, and only related endpoints for an interface driver. + * + * The interface driver array will be NULL for an interface driver. + */ +struct usbd_simple_instance { + // common to all types + struct usbd_function_instance function; + + // composite driver only + int configuration; // saved value from set configuration + + int interface_functions; // number of names in interfaces_list + int interfaces; // accumulated total of all bNumInterfaces + + + u8 *altsettings; // array[0..interfaces-1] of alternate settings + + struct usbd_endpoint_map *endpoint_map_array; + + int configuration_size; + struct usbd_configuration_descriptor *configuration_descriptor[2]; + u8 ConfigurationValue; // current set configuration (zero is default) + +}; + +/*!@struct usbd_class_instance usbp-func.h "otg/usbp-func.h" + * @brief class configuration structure + * + * This is allocated for each configured instance of a function driver. + * + * It stores pointers to the usbd_function_driver for the appropriate function, + * and pointers to the USB HOST requested usbd_configuration_description and + * usbd_interface_description. + * + * The privdata pointer may be used by the function driver to store private + * per instance state information. + * + * The endpoint map will contain a list of all endpoints for the configuration + * driver, and only related endpoints for an interface driver. + * + * The interface driver array will be NULL for an interface driver. + */ +struct usbd_class_instance { + struct usbd_function_instance function; + +}; + +/*!@struct usbd_class_driver usbp-func.h "otg/usbp-func.h" */ + +struct usbd_class_driver { + struct usbd_function_driver driver; +}; + + + +/*! @struct usbd_interface_driver usbp-func.h "otg/usbp-func.h" + * @brief interface configuration structure + * + * This is allocated for each configured instance of a function driver. + * + * It stores pointers to the usbd_function_driver for the appropriate function, + * and pointers to the USB HOST requested usbd_configuration_description and + * usbd_interface_description. + * + * The privdata pointer may be used by the function driver to store private + * per instance state information. + * + * The endpoint map will contain a list of all endpoints for the configuration + * driver, and only related endpoints for an interface driver. + * + * The interface driver array will be NULL for an interface driver. + */ + +struct usbd_interface_driver { + struct usbd_function_driver driver; + + u8 interfaces; + struct usbd_interface_description *interface_list; + + // XXX This should moved to interface description + u8 endpointsRequested; + struct usbd_endpoint_request *requestedEndpoints; + + u8 bFunctionClass; + u8 bFunctionSubClass; + u8 bFunctionProtocol; + char *iFunction; +}; + +/*!@struct usbd_interface_instance usbp-func.h "otg/usbp-func.h"*/ + +struct usbd_interface_instance { + struct usbd_function_instance function; + + int wIndex; // allocated interface number + int lIndex; // logical interface number + int altsetting; + + int endpoints; + struct usbd_endpoint_map *endpoint_map_array; +}; + +/*! @struct usbd_composite_driver usbp-func.h "otg/usbp-func.h" + * @brief Composite configuration structure + * + * This is allocated for each configured instance of a function driver. + * + * It stores pointers to the usbd_function_driver for the appropriate function, + * and pointers to the USB HOST requested usbd_configuration_description and + * usbd_interface_description. + * + * The privdata pointer may be used by the function driver to store private + * per instance state information. + * + * The endpoint map will contain a list of all endpoints for the configuration + * driver, and only related endpoints for an interface driver. + * + * The interface driver array will be NULL for an interface driver. + */ +struct usbd_composite_driver { + struct usbd_function_driver driver; + + // device & configuration descriptions + struct usbd_device_description *device_description; + struct usbd_configuration_description *configuration_description; + int bNumConfigurations; + + const char **interface_function_names; // list of interface function names + const char *class_name; + const char *windows_os; // windows 0xee string + + u8 iManufacturer; + u8 iProduct; + u8 iSerialNumber; + + //struct usbd_otg_descriptor *otg_descriptor; // XXX unused + +}; + +/*! @struct usbd_composite_instance usbp-func.h "otg/usbp-func.h"*/ +struct usbd_composite_instance { + // common to all types + struct usbd_function_instance function; + // composite driver only + int configuration; // saved value from set configuration + + int interface_functions; // number of interface functions available + int interfaces; // accumulated total of all bNumInterfaces + + struct usbd_interface_description *class_list; + struct usbd_class_instance *class_instance; + + struct usbd_interface_instance *interfaces_array; // array of interface instances, one per interface function + struct usbd_interface_description *interface_list; + + int endpoints; + struct usbd_endpoint_map *endpoint_map_array; + u8 endpointsRequested; + struct usbd_endpoint_request *requestedEndpoints; + + int configuration_size; + struct usbd_configuration_descriptor *configuration_descriptor[2]; + u8 ConfigurationValue; // current set configuration (zero is default) +}; +/* +union usbd_instance_union { + struct usbd_function_instance *function_instance; + struct usbd_simple_instance *simple_instance; + struct usbd_composite_instance *composite_instance; +}; +*/ +/*! + * @name Function Driver Registration + * + * Called by function drivers to register themselves when loaded + * or de-register when unloading. + * @{ + */ +int usbd_register_simple_function (struct usbd_simple_driver *, const char *, void *); + +int usbd_register_interface_function (struct usbd_interface_driver *, const char *, void *); +int usbd_register_class_function (struct usbd_class_driver *function_driver, const char*, void *); +int usbd_register_composite_function (struct usbd_composite_driver *function_driver, const char*, const char*, const char **, void *); + +void usbd_deregister_simple_function (struct usbd_simple_driver *); +void usbd_deregister_interface_function (struct usbd_interface_driver *); +void usbd_deregister_class_function (struct usbd_class_driver *); +void usbd_deregister_composite_function (struct usbd_composite_driver *); + +struct usbd_function_driver *usbd_find_function (const char *name); +struct usbd_interface_driver *usbd_find_interface_function(const char *name); +struct usbd_class_driver *usbd_find_class_function(const char *name); +struct usbd_composite_driver *usbd_find_composite_function(const char *name); + +//struct usbd_function_instance * usbd_alloc_function (struct usbd_function_driver *, char *, void *); +//struct usbd_function_instance * usbd_alloc_interface_function (struct usbd_function_driver *, char *, void *); +//struct usbd_function_instance * usbd_alloc_class_function (struct usbd_function_driver *function_driver, char*, void *); +//struct usbd_function_instance * usbd_alloc_composite_function (struct usbd_function_driver *function_driver, char*, char*, +// char **, void *); + +//void usbd_dealloc_function (struct usbd_function_instance *); +//void usbd_dealloc_interface (struct usbd_function_instance *); +//void usbd_dealloc_class (struct usbd_function_instance *); + +/*! @} */ + + +/*! + * @name String Descriptor database + * + * @{ + */ + +struct usbd_string_descriptor *usbd_get_string_descriptor (struct usbd_function_instance *, u8); +u8 usbd_realloc_string (struct usbd_function_instance *, u8, const char *); +u8 usbd_alloc_string (struct usbd_function_instance *, const char *); +//void usbd_free_string_descriptor(struct usbd_function_instance *, u8 ); + + + +//extern struct usbd_string_descriptor **usb_strings; +//void usbd_free_descriptor_strings (struct usbd_descriptor *); + +/*! @} */ + +/*! + * @name LANGID's + * + * @{ + */ +#define LANGID_ENGLISH "\011" +#define LANGID_US_ENGLISH "\004" +#define LANGIDs LANGID_US_ENGLISH LANGID_ENGLISH +/*! @} */ + + +/*! + * @name Well Known string indices + * + * Used by function drivers to set certain strings + * @{ + */ +#define STRINDEX_LANGID 0 +#define STRINDEX_IPADDR 1 +#define STRINDEX_PRODUCT 2 +/*! @} */ + + + + +/*! + * @name Device Information + * + * @{ + */ +BOOL usbd_high_speed(struct usbd_function_instance *instance); +int usbd_bmaxpower(struct usbd_function_instance *instance); +int usbd_endpoint_wMaxPacketSize(struct usbd_function_instance *instance, int index, BOOL hs); +int usbd_endpoint_zero_wMaxPacketSize(struct usbd_function_instance *instance, BOOL hs); +int usbd_endpoint_bEndpointAddress(struct usbd_function_instance *instance, int index, BOOL hs); +int usbd_get_bMaxPower(struct usbd_function_instance *); +/*! @} */ + + +/*! + * @name Device Control + * + * @{ + */ +usbd_device_state_t usbd_get_device_state(struct usbd_function_instance *); +usbd_device_status_t usbd_get_device_status(struct usbd_function_instance *); +#if 0 +int usbd_framenum(struct usbd_function_instance *); +otg_tick_t usbd_ticks(struct usbd_function_instance *); +otg_tick_t usbd_elapsed(struct usbd_function_instance *, otg_tick_t *, otg_tick_t *); +#endif +/*! @} */ + +/*! + * @name Endpoint I/O + * + * @{ + */ + +//typedef int usbd_urb_notification(struct usbd_urb *urb, int rc); +struct usbd_urb *usbd_alloc_urb (struct usbd_function_instance *, int endpoint_index, int length, usbd_urb_notification *notify); +struct usbd_urb *usbd_alloc_urb_ep0 (struct usbd_function_instance *, int length, usbd_urb_notification *notify); +void usbd_free_urb (struct usbd_urb *urb); +int usbd_start_in_urb (struct usbd_urb *urb); +int usbd_start_out_urb (struct usbd_urb *); +int usbd_cancel_urb(struct usbd_urb *); +int usbd_halt_endpoint (struct usbd_function_instance *function, int endpoint_index); +int usbd_endpoint_halted (struct usbd_function_instance *function, int endpoint); + +/*! + * @brief _usbd_unlink_urb() - return first urb in list + * + * Return the first urb in a list with a distinguished + * head "hd", or NULL if the list is empty. + * + * Called from interrupt. + * + * @param urb linked list of urbs + * @return pointer to urb + */ +static void INLINE usbd_unlink_urb(struct usbd_urb *urb) +{ + urb_link *ul = &urb->link; + ul->next->prev = ul->prev; + ul->prev->next = ul->next; + ul->prev = ul->next = ul; +} + + + +/*! @} */ + + +/*! + * @name endpoint information + * + * Used by function drivers to get specific endpoint information + * @{ + */ + +int usbd_endpoint_transferSize(struct usbd_function_instance *, int, int); +int usbd_endpoint_interface(struct usbd_function_instance *, int); +int usbd_interface_AltSetting(struct usbd_function_instance *, int); +int usbd_ConfigurationValue(struct usbd_function_instance *); +void usbd_endpoint_update(struct usbd_function_instance *, int , struct usbd_endpoint_descriptor *, int); +/*! @} */ + +//int usbd_remote_wakeup_enabled(struct usbd_function_instance *); + +/*! + * @name device information + * + * Used by function drivers to get device feature information + * @{ + */ +int usbd_otg_bmattributes(struct usbd_function_instance *); +/*! @} */ + +/*! + * @name usbd_feature_enabled - return non-zero if specified feature has been enabled + * @{ + */ +int usbd_feature_enabled(struct usbd_function_instance *function, int f); +/*! @} */ + +/* DEPRECATED */ +/*! + * @name Access to function privdata (DEPRECATED) + * @{ + */ +void *usbd_function_get_privdata(struct usbd_function_instance *function); +void usbd_function_set_privdata(struct usbd_function_instance *function, void *privdata); +void usbd_flush_endpoint_index (struct usbd_function_instance *, int ); +int usbd_endpoint_urb_num (struct usbd_function_instance *function, int endpoint_index); +int usbd_get_descriptor (struct usbd_function_instance *function, u8 *buffer, int max, int descriptor_type, int index); +/*! @} */ + +#endif /* USBP_FUNC_H */ diff --git a/drivers/otg/otg/usbp-hid.h b/drivers/otg/otg/usbp-hid.h new file mode 100644 index 000000000000..c1aeff599f2f --- /dev/null +++ b/drivers/otg/otg/usbp-hid.h @@ -0,0 +1,98 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/otg/usbp-hid.h + * @(#) sl@belcarra.com/whiskey.enposte.net|otg/otg/usbp-hid.h|20070810225414|35914 + * + * Copyright (c) 2004-2005 Belcarra + * Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + * By: + * Stuart Lynne <sl@belcarra.com>, + * Bruce Balden <balden@belcarra.com> + * + */ + +#ifdef PRAGMAPACK +#pragma pack(push,1) +#endif + +/*! + * @file otg/otg/usbp-hid.h + * @brief HID class descriptor structure definitions. + * + * @ingroup USBDAPI + */ + +/*! + * @name HID requests + */ + /*! @{ */ +#define USB_REQ_GET_REPORT 0x01 +#define USB_REQ_GET_IDLE 0x02 +#define USB_REQ_GET_PROTOCOL 0x03 +#define USB_REQ_SET_REPORT 0x09 +#define USB_REQ_SET_IDLE 0x0A +#define USB_REQ_SET_PROTOCOL 0x0B +/*! @} */ + + +/*! + * @name HID - Class Descriptors + * C.f. 7.1.1 + */ + /*! @{ */ + +#define HID_DT_HID 0x21 +#define HID_DT_REPORT 0x22 +#define HID_DT_PHYSICAL 0x23 + +/*! @} */ + +/*! + * @name HID - Report Types + * C.f. 7.1.1 + */ + /*! @{ */ + +#define HID_INPUT 0x01 +#define HID_OUTPUT 0x02 +#define HID_FEATURE 0x03 + +/*! @} */ + +/*! + * @name HID Descriptor + * C.f. E.8 + */ + /*! @{ */ + +/*! @struct hid_descriptor usbp-hid.h "otg/usbp-hid.h" + * + * @brief hid class descriptor struct + */ +PACKED0 struct PACKED1 hid_descriptor { + u8 bLength; + u8 bDescriptorType; + u16 bcdHID; + u8 bCountryCode; + u8 bNumDescriptors; + u8 bReportType; + u16 wItemLength; +}; + +/*! @} */ + + +#ifdef PRAGMAPACK +#pragma pack(pop) +#endif diff --git a/drivers/otg/otg/usbp-hub.h b/drivers/otg/otg/usbp-hub.h new file mode 100644 index 000000000000..87b54089d22d --- /dev/null +++ b/drivers/otg/otg/usbp-hub.h @@ -0,0 +1,126 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/otg/usbp-hub.h + * @(#) balden@belcarra.com|otg/otg/usbp-hub.h|20070326205353|47221 + * + * Copyright (c) 2004-2005 Belcarra + * Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + * By: + * Stuart Lynne <sl@belcarra.com>, + * Bruce Balden <balden@belcarra.com> + * + */ +/*! + * @file otg/otg/usbp-hub.h + * @brief USB Hub class descriptor structure definitions. + * + * @ingroup USBDAPI + */ + + +/*! + * @name Hub requests + */ + /*! @{ */ +#define USB_REQ_CLEAR_TT_BUFFER 0x08 +#define USB_REQ_RESET_TT 0x09 +#define USB_REQ_GET_TT_STATE 0x0A +#define USB_REQ_STOP_TT 0x0B +/*! @} */ + +/*! + * @name Hub Recipients + */ + /*! @{ */ +#define USB_REQ_RECIPIENT_HUB 0x00 +#define USB_REQ_RECIPIENT_PORT 0x03 +/*! @} */ + +/*! + * @name Hub Feature Selectors C.f. Table 11-17 + * + * + */ + /*! @{ */ +#if !defined(C_HUB_LOCAL_POWER) +#define C_HUB_LOCAL_POWER 0x0 +#endif +#if !defined(C_HUB_OVER_CURRENT) +#define C_HUB_OVER_CURRENT 0x1 +#endif + +#define PORT_CONNECTION 0 // 0x01 +#define PORT_ENABLE 1 // 0x02 +#define PORT_SUSPEND 2 // 0x04 +#define PORT_OVER_CURRENT 3 // 0x08 +#define PORT_RESET 4 // 0x10 + +#define PORT_POWER 8 // 0x100 +#define PORT_LOW_SPEED 9 // 0x200 +#define PORT_HIGH_SPEED 10 // 0x400 + +#define C_PORT_CONNECTION 16 +#define C_PORT_ENABLE 17 +#define C_PORT_SUSPEND 18 +#define C_PORT_OVER_CURRENT 19 +#define C_PORT_RESET 20 + +#define PORT_TEST 21 +#define PORT_INDICATOR 22 + +/*! @} */ + +#ifdef PRAGMAPACK +#pragma pack(push,1) +#endif + + + +/*! + * @name Hub Descriptor C.f. 11.23.2.1 Table 11-13 + * N.B. This assumes 1-8 ports, DeviceRemovable and PortPwrCtrlMask + * must be sized for the number of if ports > 8. + */ +/*! @struct hub_descriptor usbp-hub.h "otg/usbp-hub.h" + */ +PACKED0 struct PACKED1 hub_descriptor { + u8 bDescLength; + u8 bDescriptorType; + u8 bNbrPorts; + u16 wHubCharacteristics; + u8 bPwrOn2PwrGood; + u8 bHubContrCurrent; + u8 DeviceRemovable; + u8 PortPwrCtrlMask; +}; +/*! @name HUB status defines + @{ */ + +#define HUB_GANGED_POWER 0x00 +#define HUB_INDIVIDUAL_POWER 0x01 +#define HUB_COMPOUND_DEVICE 0x04 +#define HUB_GLOBAL_OVERCURRENT 0x00 +#define HUB_INDIVIDUAL_OVERCURRENT 0x08 +#define HUB_NO_OVERCURRENT 0x10 +#define HUB_TT_8 0x00 +#define HUB_TT_16 0x20 +#define HUB_TT_24 0x40 +#define HUB_TT_32 0x60 +#define HUB_INDICATORS_SUPPORTED 0x80 +/*! @} */ + + +#ifdef PRAGMAPACK +#pragma pack(pop) +#endif diff --git a/drivers/otg/otg/usbp-mcpc.h b/drivers/otg/otg/usbp-mcpc.h new file mode 100644 index 000000000000..2b7437a53102 --- /dev/null +++ b/drivers/otg/otg/usbp-mcpc.h @@ -0,0 +1,217 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/otg/usbp-mcpc.h + * @(#) sl@belcarra.com/whiskey.enposte.net|otg/otg/usbp-mcpc.h|20061218212925|13063 + * + * Copyright (c) 2004-2005 Belcarra + * Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + * By: + * Stuart Lynne <sl@belcarra.com>, + * Bruce Balden <balden@belcarra.com> + * + */ + + +/*! + * @file otg/otg/usbp-mcpc.h + * @brief MCPC class descriptor structure definitions. + * + * @ingroup USBDCORE + */ + +#ifdef PRAGMAPACK +#pragma pack(push,1) +#endif + + +/*! + * @name Management Element Requests + * C.f. Table 11 - bRequest Value + */ + /*! @{ */ +#define MCPC_REQ_ACTIVATE_MODE 0x60 +#define MCPC_REQ_GET_MODETABLE 0x61 +#define MCPC_REQ_SET_LINK 0x62 +#define MCPC_REQ_CLEAR_LINK 0x63 +#define MCPC_REQ_MODE_SPECIFIC_REQUEST 0x64 + /*! @} */ + + +/*! + * @name MCPC Vendor Requests + * C.f. + */ + /*! @{ */ +#define MCPC_VENDOR_REQUEST 0x44 +#define MCPC_VENDOR_SUBREQUEST_MACM_AB 0x11 +#define MCPC_VENDOR_SUBREQUEST_MACM_DL 0x12 + /*! @} */ + + + +/*! +* @name C.f. Table 16 - Connection Model selector + */ + /*! @{ */ + +#define MCPC_MOBILE_ASTRACT_CONTROL_MODEL 0x00 +#define MCPC_MOBILE_DIRECT_LINE_MODEL 0x01 + /*! @} */ + + +/*! + * @name Notification codes + * C.f. Table 20 bNotification value + */ + /*! @{ */ +#define MCPC_REQUEST_MODE 0x30 +#define MCPC_REQUEST_ACKNOWLEDGE 0x31 +#define MCPC_MODE_SPECIFIC_NOTIFICATION 0x30 + /*! @} */ + +/*! + * @name c.f. MCPC Table 20 + */ + /*! @{ */ + +#define MCPC_NOTIFICATION_REQUEST_MODE 0x30 +#define MCPC_NOTIFICATION_REQUEST_ACKNOWLEDGE 0x31 +#define MCPC_NOTIFICATION_MODE_SPECIFIC 0x32 + +#define MCPC_MODE_SELECTOR_OK 0x00 +#define MCPC_MODE_SELECTOR_NG 0x01 + +/*! @} */ + + +/*! + * @name C.f. Table 25 Mobile Abstract Control Model Specific Function Descriptor + */ + /*! @{ */ +/*! @struct mpcp_abstract_control_model_specific_functional_descriptor usbp-mcpc.h "otg/usbp-mcpc.h" + */ +struct mpcp_abstract_control_model_specific_functional_descriptor { + u8 bFunctionLength; + u8 bDescriptorType; + u8 bDescriptorSubType; + u8 bType; + u8 bMode_n[1]; +}; + + /*! @} */ + +/*! + * @name bType pipe groups + * C.f. MCPC Table 25 + * C.f. MCPC Table 26 + */ +#define MCPC_PIPE_GROUP_AB_1 0x01 +#define MCPC_PIPE_GROUP_AB_2 0x02 +#define MCPC_PIPE_GROUP_AB_3 0x03 +#define MCPC_PIPE_GROUP_AB_4 0x04 +#define MCPC_PIPE_GROUP_AB_5 0x05 +#define MCPC_PIPE_GROUP_AB_6 0x06 +#define MCPC_PIPE_GROUP_DL_1 0x01 +#define MCPC_PIPE_GROUP_DL_2 0x02 +/*! @} */ + + +/*! + * @name C.f. Table 26 Mobile Direct Line Model Specific Function Descriptor + */ + /*! @{ */ +/*! @struct mpcp_direct_line_model_specific_functional_descriptor usbp-mcpc.h "otg/usbp-mcpc.h" + */ +struct mpcp_direct_line_model_specific_functional_descriptor { + u8 bFunctionLength; + u8 bDescriptorType; + u8 bDescriptorSubType; + u8 bType; + u8 bMode_n[1]; +}; + +#define MCPC_PIPE_GROUP_DL_1 0x01 +#define MCPC_PIPE_GROUP_DL_2 0x02 + /*! @} */ + +/*! + * @name C.f. Table 27 bDescriptorSubType in Functional Descriptor + */ + /*! @{ */ +#define MCPC_MACM_FUNCTIONAL_DESCRIPTOR 0x11 +#define MCPC_MDLM_FUNCTIONAL_DESCRIPTOR 0x12 + /*! @} */ + + +/*! + * @name C.f. Table 28 Mode (bMode_x) Value + */ + /*! @{ */ + +#define MCPC_MODE_DEACTIVATE 0x00 +#define MCPC_MODE_MODEM_MODE 0x01 +#define MCPC_MODE_AT_COMMAND_CONTROL_MODE 0x02 +#define MCPC_MODE_LAN_MODE 0x03 +#define MCPC_MODE_VOICE_COMMUNICATION_MODE 0x30 +#define MCPC_MODE_OBJECT_EXCHANGE_MODE 0x60 +#define MCPC_MODE_MDLM_FULL_SUPPORT 0x90 +#define MCPC_MODE_MDLM_ASYNC_ONLY 0x91 +#define MCPC_MODE_UNLINKED_STATE 0xff + /*! @} */ + +/*! + * @name MCPC + */ + /*! @{ */ +/*! @struct usbd_mobile_abstract_control_model_specific_desciptor usbp-mcpc.h "otg/usbp-mcpc.h" + */ + +PACKED0 struct PACKED1 usbd_mobile_abstract_control_model_specific_desciptor { + u8 bFunctionLength; + u8 bDescriptorType; + u8 bDescriptorSubtype; + u8 bType; + u8 bMode_N[4]; +}; +/*! @struct usbd_mobile_direct_line_model_specific_desciptor usbp-mcpc.h "otg/usbp-mcpc.h" + */ +PACKED0 struct PACKED1 usbd_mobile_direct_line_model_specific_desciptor { + u8 bFunctionLength; + u8 bDescriptorType; + u8 bDescriptorSubType; + u8 bType; + u8 bMode_N[4]; +}; + + + +/*! @var typedef enum mcpc_mode mcpc_mode_t + * + * @brief MCPC mode + */ +typedef enum mcpc_mode { + mcpc_unlinked, + mcpc_linked, + mcpc_activated +} mcpc_mode_t; + + +/*! @} */ + +#ifdef PRAGMAPACK +#pragma pack(pop) +#endif + + +/* End of FILE */ diff --git a/drivers/otg/otg/usbp-pcd.h b/drivers/otg/otg/usbp-pcd.h new file mode 100644 index 000000000000..94f767797e14 --- /dev/null +++ b/drivers/otg/otg/usbp-pcd.h @@ -0,0 +1,296 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/otg/usbp-pcd.h - OTG Peripheral Controller Driver + * @(#) sl@belcarra.com/whiskey.enposte.net|otg/otg/usbp-pcd.h|20070820053712|09860 + * + * Copyright (c) 2004-2005 Belcarra Technologies Corp + * Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + * + * By: + * Stuart Lynne <sl@belcarra.com>, + * Bruce Balden <balden@belcarra.com> + * + */ + +/*! + * @file otg/otg/usbp-pcd.h + * @brief Peripheral Controller Driver Related Structures and Definitions. + * + * @ingroup PCD + */ + +/*! + * The pcd_ops structure contains pointers to all of the functions implemented for the + * linked in driver. Having them in this structure allows us to easily determine what + * functions are available without resorting to ugly compile time macros or ifdefs + * + * There is only one instance of this, defined in the device specific lower layer. + */ +struct usbd_pcd_ops { + + /* mandatory */ + u8 bmAttributes; + int max_endpoints; + int ep0_packetsize; + u8 high_speed_capable; + u32 capabilities; /* UDC Capabilities - see usbd-bus.h for details */ + u8 bMaxPower; + char *name; + u8 ports; + + + /* 3. called by ot_init() if defined */ + int (* serial_init) (struct pcd_instance *); /* get device serial number if available */ + + /* 4. called from usbd_enable_function_irq() - REQUIRED */ + int (*request_endpoints) /* process endpoint_request list and return endpoint_map */ + (struct pcd_instance *, + struct usbd_endpoint_map *, /* showing allocated endpoints with attributes, addresses */ + int, struct usbd_endpoint_request*); /* and other associated information */ + + /* 5. called from usbd_enable_function_irq() - REQUIRED */ + int (*set_endpoints) /* setup required endpoints in hardware */ + (struct pcd_instance *, int , struct usbd_endpoint_map *); + + /* 6. called by xxx() if defined */ + void (*enable) (struct pcd_instance *); /* enable the UDC */ + + /* 7. called by xxx() if defined */ + void (*disable) (struct pcd_instance *); /* disable the UDC */ + + /* 8. called by xxx() if defined */ + void (*start) (struct pcd_instance *); /* called for DEVICE_CREATE to start the UDC */ + void (*stop) (struct pcd_instance *); /* called for DEVICE_DESTROY to stop the UDC */ + void (*disable_ep) + (struct pcd_instance *, + unsigned int ep, /* called for DEVICE_DESTROTY to disable endpoint zero */ + struct usbd_endpoint_instance *endpoint); + + /* 9. called by xxx() if defined */ + void (*set_address) + (struct pcd_instance *, + unsigned char address); /* called for DEVICE_RESET and DEVICE_ADDRESS_ASSIGNED to */ + + + /* 10. called by xxx() if defined */ + void (*setup_ep) + (struct pcd_instance *, + unsigned int ep, /* called for DEVICE_CONFIGURED to setup specified endpoint for use */ + struct usbd_endpoint_instance *endpoint); + + /* 11. */ + void * (*alloc_buffer)(struct pcd_instance*, int); + void (*free_buffer)(struct pcd_instance*, void *); + + /* 12. called by xxx() if defined */ + void (*reset_ep) + (struct pcd_instance *, + unsigned int ep); /* called for DEVICE_RESET to reset endpoint zero */ + + + /* 13. called by usbd_send_urb() - REQUIRED */ + void (*start_endpoint_in) /* start an IN urb */ + (struct pcd_instance *, + struct usbd_endpoint_instance *); + + /* 14. called by usbd_start_recv() - REQUIRED */ + void (*start_endpoint_out) /* start an OUT urb */ + (struct pcd_instance *, + struct usbd_endpoint_instance *); + + /* 15. called by usbd_cancel_urb_irq() */ + void (*cancel_in_irq) + (struct pcd_instance *, + struct usbd_urb *urb); /* cancel active urb for IN endpoint */ + void (*cancel_out_irq) + (struct pcd_instance *, + struct usbd_urb *urb); /* cancel active urb for OUT endpoint */ + + + /* 16. called from ep0_recv_setup() for GET_STATUS request */ + int (*endpoint_halted) + (struct pcd_instance *, + struct usbd_endpoint_instance *); /* Return non-zero if requested endpoint is halted */ + int (*halt_endpoint) + (struct pcd_instance *, + struct usbd_endpoint_instance *, + int); /* Return non-zero if requested endpoint clear fails */ + + int (*device_feature) + (struct pcd_instance *, + int, + int); /* Return non-zero if requested endpoint clear fails */ + + int (*remote_wakeup) + (struct pcd_instance *); /* Return non-zero if remote wakeup fails */ + + /* 17 */ + void (*startup_events) (struct pcd_instance *); /* perform UDC specific USB events */ + + /* 18. root hub operations + */ + int (*hub_status) (struct pcd_instance *); + int (*port_status) (struct pcd_instance *, int); + int (*hub_feature) (struct pcd_instance *, int, int); + int (*port_feature) (struct pcd_instance *, int, int, int); + int (*get_ports_status) (struct pcd_instance *); + int (*wait_for_change) (struct pcd_instance *); + + /* 19. configuration changes + */ + int (*vbus_status) (struct pcd_instance *); + int (*softcon) (struct pcd_instance *, int); + u16 (*framenum) (struct pcd_instance *); + + otg_tick_t (*ticks)(struct pcd_instance *); + otg_tick_t (*elapsed)(otg_tick_t *, otg_tick_t *); + +}; + +extern struct usbd_pcd_ops usbd_pcd_ops; + +/*! bus_set_speed + */ +void bus_set_speed(struct usbd_bus_instance *bus, int hs); + + + +/*! pcd_rcv_complete_irq - complete a receive operation + * Called by UDC driver to show completion of outstanding I/O, this will + * update the urb length and if necessary will finish the urb passing it to the + * bottom half which will in turn call the function driver callback. + */ +struct usbd_urb * pcd_rcv_complete_irq (struct usbd_endpoint_instance *endpoint, int len, int urb_bad); +struct usbd_urb * pcd_rcv_finished_irq (struct usbd_endpoint_instance *endpoint, int len, int urb_bad); +void pcd_rcv_cancelled_irq (struct usbd_endpoint_instance *endpoint); +struct usbd_urb * pcd_tx_next_irq (struct usbd_endpoint_instance *endpoint); +struct usbd_urb * pcd_rcv_next_irq (struct usbd_endpoint_instance *endpoint); +void pcd_urb_finished_irq(struct usbd_urb *urb, int rc); + + +/*! + * pcd_configure_device is used by function drivers (usually the control endpoint) + * to change the device configuration. + * + * usbd_device_event is used by bus interface drivers to tell the higher layers that + * certain events have taken place. + */ +//void pcd_bus_event_handler (struct usbd_bus_instance *, usbd_device_event_t, int); +void pcd_bus_event_handler_irq (struct usbd_bus_instance *, usbd_device_event_t, int); + + + +/*! pcd_tx_complete_irq - complete a transmit operation + * Called by UDC driver to show completion of an outstanding I/O, this will update the urb sent + * information and if necessary will finish the urb passing it to the bottom half which will in + * turn call the function driver callback. + */ +struct usbd_urb * pcd_tx_complete_irq (struct usbd_endpoint_instance *endpoint, int restart); + + +/*! pcd_tx_cancelled_irq - finish pending tx_urb with USBD_URB_CANCELLED + * Called by UDC driver to cancel the current transmit urb. + */ +static __inline__ void pcd_tx_cancelled_irq (struct usbd_endpoint_instance *endpoint) +{ + struct usbd_urb *tx_urb; + struct usbd_bus_instance *bus; + struct pcd_instance *pcd; + RETURN_IF (! (tx_urb = endpoint->tx_urb)); + bus = tx_urb->bus; + pcd = (struct pcd_instance *)bus->privdata; + TRACE_MSG1(pcd->TAG, "BUS_TX CANCELLED: %p", (int) endpoint->tx_urb); + pcd_urb_finished_irq (tx_urb, USBD_URB_CANCELLED); + endpoint->sent = endpoint->last = 0; + endpoint->tx_urb = NULL; +} + + +/*! pcd_tx_sendzlp - test if we need to send a ZLP + * This has a side-effect of reseting the USBD_URB_SENDZLP flag. + */ +static __inline__ int pcd_tx_sendzlp (struct usbd_endpoint_instance *endpoint) +{ + struct usbd_urb *tx_urb = endpoint->tx_urb; + struct usbd_bus_instance *bus; + struct pcd_instance *pcd; + + RETURN_FALSE_UNLESS(tx_urb); // no urb + + bus = tx_urb->bus; + pcd = (struct pcd_instance *)bus->privdata; + TRACE_MSG3(pcd->TAG, "sent: %d actual: %d flags: %02x", endpoint->sent, tx_urb->actual_length, tx_urb->flags); + + RETURN_FALSE_IF(!endpoint->sent && tx_urb->actual_length); // nothing sent yet and there is data to send + RETURN_FALSE_IF(tx_urb->actual_length > endpoint->sent); // still data to send + RETURN_FALSE_UNLESS(tx_urb->flags & USBD_URB_SENDZLP); // flag not set + + TRACE_MSG0(pcd->TAG, "SENDZLP"); + tx_urb->flags &= ~USBD_URB_SENDZLP; + return TRUE; +} + + +/*! pcd_rcv_complete_irq - complete a receive + * Called from rcv interrupt to complete. + */ +static __inline__ void pcd_rcv_fast_complete_irq (struct usbd_endpoint_instance *endpoint, struct usbd_urb *rcv_urb) +{ + //TRACE_MSG1(PCD, "BUS_RCV FAST COMPLETE: %d", rcv_urb->actual_length); + pcd_urb_finished_irq (rcv_urb, USBD_URB_OK); +} + +/*! pcd_recv_setup - process a device request + * Note that we verify if a receive urb has been queued for H2D with non-zero wLength + * and return -EINVAL to stall if the upper layers have not properly tested for and + * setup a receive urb in this case. + */ +static __inline__ int pcd_recv_setup_irq (struct pcd_instance *pcd, struct usbd_device_request *request) +{ + struct usbd_bus_instance *bus = pcd->bus; + //struct usbd_endpoint_instance *endpoint = bus->endpoint_array + 0; + //TRACE_SETUP (pcd->TAG, request); + return usbd_device_request_irq(bus, request); // fail if already failed + #if 0 + RETURN_EINVAL_IF (usbd_device_request_irq(bus, request)); // fail if already failed + TRACE_MSG2(pcd->TAG, "BUS_RECV SETUP: RCV URB: %x TX_URB: %x", (int)endpoint->rcv_urb, (int)endpoint->tx_urb); + RETURN_ZERO_IF ( (request->bmRequestType & USB_REQ_DIRECTION_MASK) == USB_REQ_DEVICE2HOST); + RETURN_ZERO_IF (!le16_to_cpu (request->wLength)); + RETURN_EINVAL_IF (!endpoint->rcv_urb); + return 0; + #endif +} + +/*! pcd_recv_setup_emulate_irq - emulate a device request + * Called by the UDC driver to inject a SETUP request. This is typically used + * by drivers for UDC's that do not pass all SETUP requests to us but instead give us + * a configuration change interrupt. + */ +int pcd_recv_setup_emulate_irq(struct usbd_bus_instance *, u8, u8, u16, u16, u16); + +/*! pcd_ep0_reset_irq - reset ep0 endpoint + */ +static void __inline__ pcd_ep0_reset_endpoint_irq (struct usbd_endpoint_instance *endpoint) +{ + //TRACE_MSG0(PCD, "BUS_EP0 RESET"); + pcd_tx_cancelled_irq (endpoint); + pcd_rcv_cancelled_irq (endpoint); + endpoint->sent = endpoint->last = 0; +} + +/*! pcd_check_device_feature - verify that feature is set or clear + * Check current feature setting and emulate SETUP Request to set or clear + * if required. + */ +void pcd_check_device_feature(struct usbd_bus_instance *, int , int ); diff --git a/drivers/otg/otgcore/Makefile b/drivers/otg/otgcore/Makefile new file mode 100644 index 000000000000..31348c6b3b1d --- /dev/null +++ b/drivers/otg/otgcore/Makefile @@ -0,0 +1,50 @@ +# +# Belcarra OTG - On-The-Go +# @(#) sl@belcarra.com/whiskey.enposte.net|otg/otgcore/Makefile-l26|20061213004244|25170 +# +# Copyright (c) 2004 Belcarra Technologies Corp +# Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp +# Makefile for Linux 2.6.x systems + +OTG=$(TOPDIR)/drivers/otg +EXTRA_CFLAGS += -Wno-missing-prototypes -Wno-unused -Wno-format -I$(OTG) +EXTRA_CFLAGS_nostdinc += -Wno-missing-prototypes -Wno-unused -Wno-format -I$(OTG) + +otgcore-objs := core-init-lnx.o otg.o \ + otg-trace.o otg-trace-lnx.o \ + otg-mesg.o otg-mesg-lnx.o \ + otg-fw.o usbp-bops.o usbp-fops.o usbp-procfs.o + +ifeq ($(CONFIG_OTG_USB_PERIPHERAL),y) +otgcore-objs += otg-fw-mn.o +endif + +ifeq ($(CONFIG_OTG_USB_HOST),y) +otgcore-objs += otg-fw-mn.o +endif + +ifeq ($(CONFIG_OTG_USB_PERIPHERAL_OR_HOST),y) +otgcore-objs += otg-fw-mn.o +endif + +ifeq ($(CONFIG_OTG_BDEVICE_WITH_SRP),y) +otgcore-objs += otg-fw-df.o +endif + +ifeq ($(CONFIG_OTG_DEVICE),y) +otgcore-objs += otg-fw-df.o +endif + + +#ifeq ("$(CONFIG_OTG_TRACE)", "y") +#otgcore-objs += usbp-procfs.o +#endif + +#usbprocfs-objs := usbp-procfs.o + +obj-$(CONFIG_OTG) += otgcore.o + +#obj-$(CONFIG_OTG_PROCFSM) += usbprocfs.o + +#otgtrace-objs := otg-trace.o +#obj-$(CONFIG_OTG_TRACE) += otgtrace.o diff --git a/drivers/otg/otgcore/core-init-lnx.c b/drivers/otg/otgcore/core-init-lnx.c new file mode 100644 index 000000000000..d85860f0945f --- /dev/null +++ b/drivers/otg/otgcore/core-init-lnx.c @@ -0,0 +1,313 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/otg/core-init-l24.c - OTG Peripheral Controller Driver Module Initialization + * @(#) balden@belcarra.com/seth2.rillanon.org|otg/otgcore/core-init-lnx.c|20070808203850|03038 + * + * Copyright (c) 2004-2005 Belcarra Technologies Corp + * Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + * + * By: + * Stuart Lynne <sl@belcarra.com>, + * Bruce Balden <balden@belcarra.com> + * + * This is the linux 2.4 version. + * + */ +/*! + * @file otg/otgcore/core-init-lnx.c + * @brief OTG Core Linux initialization. + * + * This file is the starting point for defining the Linux OTG Core + * driver. It references and starts all of the other components that + * must be "linked" into the OTGCORE mdoule. + * + * @ingroup LINUXOS + */ + + +#include <otg/otg-compat.h> +#if defined(CONFIG_OTG_LNX) || defined(_OTG_DOXYGEN) + +#include <otg/otg-module.h> + +#include <otg/usbp-chap9.h> +#include <otg/usbp-func.h> +#include <otg/usbp-bus.h> +#include <otg/otg-trace.h> +#include <otg/otg-api.h> +#include <otg/otg-tcd.h> +#include <otg/otg-hcd.h> +#include <otg/otg-pcd.h> +#include <otg/otg-ocd.h> + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,15) +#include <linux/platform_device.h> +#endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,12) */ + +EMBED_LICENSE(); // When supported by the OS, embed license information in the binary + + +#ifdef OTG_MALLOC_TEST +int otg_mallocs; +#endif /* OTG_MALLOC_TEST */ + +otg_tag_t CORE; +//int usbd_procfs_init (void); +//void usbd_procfs_exit (void); + + +/* Tables for OTG firmware + */ +#if defined(CONFIG_OTG_USB_PERIPHERAL) || defined (CONFIG_OTG_USB_HOST) || defined(CONFIG_OTG_USB_PERIPHERAL_OR_HOST) +struct otg_firmware *otg_firmware_loaded = &otg_firmware_mn; +struct otg_firmware *otg_firmware_orig = &otg_firmware_mn; +#elif defined(CONFIG_OTG_BDEVICE_WITH_SRP) || defined(CONFIG_OTG_DEVICE) +struct otg_firmware *otg_firmware_loaded = &otg_firmware_df; +struct otg_firmware *otg_firmware_orig = &otg_firmware_df; +#else /* error */ +//#abort "Missing USB or OTG configuration" +#endif + + +struct otg_firmware *otg_firmware_loading; + + +//struct otg_instance otg_instance_private = { +//}; + + +/*! otg_get_state_name - get corresponding name of specific state value + * + * @param state - state value + * @return state name + */ +char * otg_get_state_name(int state) +{ + struct otg_state *otg_state; + if (!otg_firmware_loaded || (state >= otg_firmware_loaded->number_of_states)) + return "UNKNOWN_STATE"; + + otg_state = otg_firmware_loaded->otg_states + state; + return otg_state->name; +} + + +/* ************************************************************************************* */ + + + + +#if defined (CONFIG_OTG_USBD_PM) +int usbd_load(char * arg) { return 0; } +int usbd_unload(char *arg) { return 0; } +OTG_EXPORT_SYMBOL(usbd_load); +OTG_EXPORT_SYMBOL(usbd_unload); +#endif +/*! otg_create -create an otg instance + * + * @return created otg intance pointer + */ +struct otg_instance *otg_create(void) +{ + int message_init = 0; + int message_init_l24 = 0; + //int trace_init = 0; + //int trace_init_l24 = 0; + struct otg_instance *otg = NULL; + + + THROW_UNLESS((otg = CKMALLOC(sizeof(struct otg_instance))), error); + + //printk(KERN_INFO"%s:\n", __FUNCTION__); + //trace_init = otg_trace_init(); + //trace_init_l24 = otg_trace_init_l24(); + CORE = otg_trace_obtain_tag(NULL, "core-otg"); + otg->TAG = CORE; + + otg->function_name[0] = '\0'; + + TRACE_MSG0(CORE,"--"); + otg->current_outputs = otg_firmware_loaded->otg_states; + otg->previous_outputs = otg_firmware_loaded->otg_states; + + /* initialize otg-mesg and usbp + */ + THROW_IF((message_init = otg_message_init(otg)), error); + THROW_IF((message_init_l24 = otg_message_init_l24(otg)), error); + THROW_IF(usbd_device_init(), error); + + + otg_set_ocd_ops(otg, NULL); + otg_set_tcd_ops(otg, NULL); + otg_set_hcd_ops(otg, NULL); + otg_set_pcd_ops(otg, NULL); + + return otg; + + CATCH(error) { + printk(KERN_INFO"%s: ERROR\n", __FUNCTION__); + if (message_init_l24 )otg_message_exit_l24(otg); + if (message_init) otg_message_exit(); + CORE = otg_trace_invalidate_tag(CORE); + if (otg) LKFREE(otg); + return NULL; + } +} +/*! otg_destroy - free otg instance and associated resources + * @param otg - otg instance pointer + * @return none + */ +void otg_destroy(struct otg_instance *otg) +{ + //usbd_procfs_exit (); + usbd_device_exit(); + otg_message_exit_l24(otg); + otg_message_exit(); + + + if (otg_firmware_loading) { + lkfree(otg_firmware_loading->otg_states); + lkfree(otg_firmware_loading->otg_tests); + lkfree(otg_firmware_loading); + } + if (otg_firmware_loaded && (otg_firmware_loaded != otg_firmware_orig)) { + lkfree(otg_firmware_loaded->otg_states); + lkfree(otg_firmware_loaded->otg_tests); + lkfree(otg_firmware_loaded); + } + + CORE = otg_trace_invalidate_tag(CORE); + otg->TAG = NULL; + +//#if defined(LINUX24) +// otg_trace_exit_l24(); +//#endif + //otg_trace_exit(); + + LKFREE(otg); +} + +OTG_EXPORT_SYMBOL(otg_create); +OTG_EXPORT_SYMBOL(otg_destroy); + +/* ************************************************************************************* */ +int otg_trace_modinit_lnx(void); +void otg_trace_modexit_lnx(void); +int usbd_device_modinit(void); +int usbd_device_modexit(void); + + +/* ************************************************************************************* */ +//#if defined(LINUX26) +/*! otg_match - check if the device and driver match + * + * @param dev - pointer to device + * @param drv - pointer device driver + * @return int for match result + */ + +static int otg_match (struct device *dev, struct device_driver *drv) +{ + printk(KERN_INFO"%s:\n", __FUNCTION__); + return 0; +} + +void otg_unregister(void) +{ + printk(KERN_INFO"%s: \n", __FUNCTION__); + usbd_device_modexit(); + otg_trace_modexit_lnx(); + printk(KERN_INFO"%s: otg_mallocs: %d\n", __FUNCTION__, otg_mallocs); +} +int otg_register(void) +{ + int bus_registered = 0; + int driver_registered = 0; + + RETURN_EINVAL_IF(otg_trace_modinit_lnx()); + + THROW_IF(usbd_device_modinit(), error); + + + CATCH(error) { + return -EINVAL; + } + return 0; +} + + +/* ************************************************************************************* */ +extern u64 otg_events[64]; +extern otg_tag_t otg_tags[64]; +extern char * otg_msgs[64]; +extern u8 otg_head, otg_tail; + +//#endif + +/*! otg_modinit - linux module initialization + */ +static int otg_modinit (void) +{ + otg_led_init(LED1); + otg_led_init(LED2); + return otg_register(); +} +module_init (otg_modinit); + + +#if OTG_EPILOGUE /* Set nonzero in <otg-module.h> when -DMODULE is in force */ +/*! otg_modexit - This is *only* used for drivers compiled and used as a module. + */ +static void otg_modexit (void) +{ + otg_unregister(); +} +#endif + + + +#ifdef OTG_SKYE_LED +void otg_led(int led, int flag) +{ + if (led) + mxc_set_gpio_dataout(led, flag); +} + +void otg_led_init(int led) +{ + if (led) + mxc_set_gpio_direction(led,0); +} +#else /* OTG_SKYE_LED */ +void otg_led(int led, int flag) +{ +} + +void otg_led_init(int led) +{ +} +#endif /* OTG_SKYE_LED */ +OTG_EXPORT_SYMBOL(otg_led); +OTG_EXPORT_SYMBOL(otg_led_init); + + +MOD_EXIT(otg_modexit); +OTG_EXPORT_SYMBOL(otg_get_state_name); + + +#ifdef OTG_MALLOC_TEST +OTG_EXPORT_SYMBOL(otg_mallocs); +#endif /* OTG_MALLOC_TEST */ + +#endif /* defined(CONFIG_OTG_LNX) */ diff --git a/drivers/otg/otgcore/otg-fw-df.c b/drivers/otg/otgcore/otg-fw-df.c new file mode 100644 index 000000000000..a1393b44c001 --- /dev/null +++ b/drivers/otg/otgcore/otg-fw-df.c @@ -0,0 +1,137 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/* Generated by otg-tests-c.awk + * + * Do not Edit, see otg-state.awk + */ + +/* %Z %K */ + + +/*! +* @file otg/otgcore/otg-fw-df.c +* @brief OTG Firmware - Firmware for df +* +* This file defines the OTG State Machine tests. +* +* +* @ingroup OTGFW +*/ + +/*! +* @page OTGFW +* @section OTGFW_SECTION - otg-fw-df.c +* This contains the input, output and timout definitions for the OTG state machine firmware +*/ + + +#ifdef OTG_APPLICATION +#define NULL 0 +#include "otg-fw.h" +#include "otg-fw-df.h" +#else /* OTG_APPLICATION/ */ +#include <otg/otg-compat.h> +#include <otg/usbp-chap9.h> +#include <otg/usbp-func.h> +#include <otg/usbp-bus.h> +#include <otg/otg-trace.h> +//#include <otg/otg-api.h> +#include <otg/otg-fw.h> +#include <otg/otg-fw-df.h> +#endif /* OTG_APPLICATION */ + +char otg_fw_name_df[] = "otg_df"; + + +struct otg_test otg_tests_df[] = { + /* + * Copyright (c) 2005-2007 Belcarra Technologies 2005 Corp + * + */ + /*! + * This is the default Firmware. It is included in the + * compiled modules and supports the auto Traditional USB + * mode. No user inputs are implemented. + */ + { /* */ + 0, /* .test */ + invalid_state, /* .state */ + otg_disabled, /* .target */ + enable_otg, /* .test1 */ + }, + /* + * This is not an OTG State. It is used internally to mark the end of the + * list of states and inputs. + */ + { /* */ + 1, /* .test */ + terminator_state, /* .state */ + invalid_state, /* .target */ + 0, /* .test1 */ + }, + {2, invalid_state,}, + +}; + +#define OTG_TESTS_DF 2 + +int otg_test_max_df = 2; + + /* eof */ + +/* Generated by otg-info-c.awk + * + * Do not Edit, see otg-state.awk + */ + +/* %Z %K */ + +struct otg_state otg_states_df[OTG_STATES_DF + 1] = { + { /* 0 */ + invalid_state, /* .state */ + m_otg_init, /* .meta */ + "invalid_state", /* .name */ + 0, /* .tmout */ + 0, /* .reset */ + }, + { /* 1 */ + otg_disabled, /* .state */ + m_otg_init, /* .meta */ + "otg_disabled", /* .name */ + 0, /* .tmout */ + 0, /* .reset */ + }, + { /* 2 */ + terminator_state, /* .state */ + m_otg_init, /* .meta */ + "terminator_state", /* .name */ + 0, /* .tmout */ + 0, /* .reset */ + /* .outputs */ + 0, + }, + + {0, 0, "", 0, 0,}, + +}; + +struct otg_firmware otg_firmware_df = { + OTG_STATES_DF, /* number of states */ + OTG_TESTS_DF, /* number of tests */ + "otg-df", /* name of firmware */ + otg_states_df, /* struct otg_state * */ + otg_tests_df, /* struct otg_test * */ +}; + +/* eof */ diff --git a/drivers/otg/otgcore/otg-fw-mn.c b/drivers/otg/otgcore/otg-fw-mn.c new file mode 100644 index 000000000000..48abb03c2634 --- /dev/null +++ b/drivers/otg/otgcore/otg-fw-mn.c @@ -0,0 +1,928 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/* Generated by otg-tests-c.awk + * + * Do not Edit, see otg-state.awk + */ + +/* %Z %K */ + + +/*! +* @file otg/otgcore/otg-fw-mn.c +* @brief OTG Firmware - Firmware for mn +* +* This file defines the OTG State Machine tests. +* +* +* @ingroup OTGFW +*/ + +/*! +* @page OTGFW +* @section OTGFW_SECTION - otg-fw-mn.c +* This contains the input, output and timout definitions for the OTG state machine firmware +*/ + + +#ifdef OTG_APPLICATION +#define NULL 0 +#include "otg-fw.h" +#include "otg-fw-mn.h" +#else /* OTG_APPLICATION/ */ +#include <otg/otg-compat.h> +#include <otg/usbp-chap9.h> +#include <otg/usbp-func.h> +#include <otg/usbp-bus.h> +#include <otg/otg-trace.h> +//#include <otg/otg-api.h> +#include <otg/otg-fw.h> +#include <otg/otg-fw-mn.h> +#endif /* OTG_APPLICATION */ + +char otg_fw_name_mn[] = "otg_mn"; + + +struct otg_test otg_tests_mn[] = { + /* + * Copyright (c) 2005-2007 Belcarra Technologies 2005 Corp + */ + /*! + * This is the initialization set for pcd, hcd and tcd. + */ + /* + * This the initial state of the software when first loaded. + * It is not possible to return to this state. + */ + { /* Initialize by sending the otg_enable signal. */ + 0, /* .test */ + invalid_state, /* .state */ + otg_disabled, /* .target */ + enable_otg, /* .test1 */ + }, + /* + * The USBOTG State Machine has been initialized but is inactive. + * This state may have arrived at from either the invalid_state or + * from the otg_disable state. + */ + { /* Initialize by sending the otg_enable signal. */ + 1, /* .test */ + otg_disabled, /* .state */ + otg_enable_ocd, /* .target */ + enable_otg, /* .test1 */ + }, + /* + * The State Machine stops the device drivers and waits for them + * to signal that they have finished de-initializing. + */ + { /* Wait for ok from de-initializing drivers. */ + 2, /* .test */ + otg_disable_tcd, /* .state */ + otg_disable_hcd, /* .target */ + TCD_OK, /* .test1 */ + }, + /* + * The State Machine stops the device drivers and waits for them + * to signal that they have finished de-initializing. + */ + { /* Wait for ok from de-initializing drivers. */ + 3, /* .test */ + otg_disable_hcd, /* .state */ + otg_disable_pcd, /* .target */ + HCD_OK, /* .test1 */ + }, + /* + * The State Machine stops the device drivers and waits for them + * to signal that they have finished de-initializing. + */ + { /* Wait for ok from de-initializing drivers. */ + 4, /* .test */ + otg_disable_pcd, /* .state */ + otg_disable_ocd, /* .target */ + PCD_OK, /* .test1 */ + }, + /* + * The State Machine stops the device drivers and waits for them + * to signal that they have finished de-initializing. + */ + { /* Wait for ok from de-initializing drivers. */ + 5, /* .test */ + otg_disable_ocd, /* .state */ + otg_disabled, /* .target */ + OCD_OK, /* .test1 */ + }, + /* + * The State Machine starts the device drivers and waits for them + * to signal that they have finished initializing. + */ + { /* Wait for ok from initializing drivers. */ + 6, /* .test */ + otg_enable_ocd, /* .state */ + otg_enable_pcd, /* .target */ + OCD_OK, /* .test1 */ + }, + /* + * The State Machine starts the device drivers and waits for them + * to signal that they have finished initializing. + */ + { /* Wait for ok from initializing drivers. */ + 7, /* .test */ + otg_enable_pcd, /* .state */ + otg_enable_hcd, /* .target */ + PCD_OK, /* .test1 */ + }, + /* + * The State Machine starts the device drivers and waits for them + * to signal that they have finished initializing. + */ + { /* Wait for ok from initializing drivers. */ + 8, /* .test */ + otg_enable_hcd, /* .state */ + otg_enable_tcd, /* .target */ + HCD_OK, /* .test1 */ + }, + /* + * The State Machine starts the device drivers and waits for them + * to signal that they have finished initializing. + */ + { /* Wait for ok from initializing drivers. */ + 9, /* .test */ + otg_enable_tcd, /* .state */ + otg_enabled, /* .target */ + TCD_OK, /* .test1 */ + }, + /* + * Copyright (c) 2005-2007 Belcarra Technologies 2005 Corp + * + */ + /*! + * This is the minimal firmware. It can be included in the + * compiled modules and supports the auto Traditional USB + * mode. No user inputs are required for normal operation. + * + * The b_bus_drop input can be optionally used to disconnect and re-connect. + * + * The enable_otg input can be optionally used to disable and re-enable. + * Note that disable/enable will reset b_bus_drop. + * + * + * B-Device Input Signals + * + * ID_GND/ + * B_SESS_VLD + * + * + * + * A-Device Input Signals + * + * ID_GND + * DM_HIGH + * VBUS_VLD + * HUB_PORT_CONNECT + * BUS_RESET + * ADDRESSED + * CONFIGURED + * A_SESS_VLD/ + * + * + * + */ + /*! + * The State Machine has successfully started the device drivers and + * is waiting for an input event. Typically it will move from here to + * an idle state specific to the current conditions (peripheral_idle etc.) + * based on user request b_bus_drop. + * + */ + { /* Check for disable. */ + 10, /* .test */ + otg_enabled, /* .state */ + otg_disable_tcd, /* .target */ + enable_otg_, /* .test1 */ + Tst_one_second, /* .test2 */ + }, + { /* Move to idle. */ + 11, /* .test */ + otg_enabled, /* .state */ + peripheral_idle, /* .target */ + ID_GND_, /* .test1 */ + enable_otg, /* .test2 */ + }, + { /* */ + 12, /* .test */ + otg_enabled, /* .state */ + host_idle, /* .target */ + ID_GND, /* .test1 */ + enable_otg, /* .test2 */ + }, + /*! + * USB Peripheral is idle. + * Waiting for Vbus to indicate that it has been plugged into a USB Host. + * + */ + { /* Check for disable (must be done for check for bus_drop.) */ + 13, /* .test */ + peripheral_idle, /* .state */ + otg_enabled, /* .target */ + ID_GND | enable_otg_, /* .test1 */ + }, + { /* Check for b_bus_drop */ + 14, /* .test */ + peripheral_idle, /* .state */ + peripheral_dropped, /* .target */ + b_bus_drop, /* .test1 */ + }, + { /* Move to peripheral mode when SESSION valid. */ + 15, /* .test */ + peripheral_idle, /* .state */ + peripheral_wait, /* .target */ + B_SESS_VLD, /* .test1 */ + }, + /*! + * USB Peripheral, user has dropped the bus. + */ + { /* Wait for b_bus_drop/ or enable_otg/ */ + 16, /* .test */ + peripheral_dropped, /* .state */ + peripheral_idle, /* .target */ + ID_GND | b_bus_drop_ | enable_otg_, /* .test1 */ + }, + /*! + * USB Peripheral, Vbus sensed, enabling pullup. + * The D+ pullup is enabled and we are waiting for a BUS_RESET to + * indicate that the USB Host has recognized that a USB Device is attached. + */ + { /* Move to idle if we loose any of these inputs. */ + 17, /* .test */ + peripheral_wait, /* .state */ + peripheral_idle, /* .target */ + ID_GND | enable_otg_ | B_SESS_VLD_ | b_bus_drop, /* .test1 */ + }, + { /* Move to next state if bus reset is seen. */ + 18, /* .test */ + peripheral_wait, /* .state */ + peripheral_bus_reset, /* .target */ + BUS_RESET, /* .test1 */ + }, + /*! + * USB Peripheral, waiting to be addressed. + * It is waiting to be enumerated and configured by the USB Host. + */ + { /* Move to idle via discharge, if we loose any of these inputs. */ + 19, /* .test */ + peripheral_bus_reset, /* .state */ + peripheral_discharge_vbus, /* .target */ + ID_GND | enable_otg_ | B_SESS_VLD_ | b_bus_drop, /* .test1 */ + }, + { /* Progress if we are addressed. */ + 20, /* .test */ + peripheral_bus_reset, /* .state */ + peripheral_addressed, /* .target */ + ADDRESSED, /* .test1 */ + }, + /*! + * The State Machine in the configured state for a Traditional USB Device. + * This means that there is an active session, there is packet traffic + * with this device. + */ + { /* Move to idle via discharge, if we loose any of these inputs. */ + 21, /* .test */ + peripheral_addressed, /* .state */ + peripheral_discharge_vbus, /* .target */ + ID_GND | enable_otg_ | B_SESS_VLD_ | b_bus_drop, /* .test1 */ + }, + { /* Progress if we are configured. */ + 22, /* .test */ + peripheral_addressed, /* .state */ + peripheral_configured, /* .target */ + CONFIGURED, /* .test1 */ + }, + /*! + * The State Machine in the configured state for a Traditional USB Device. + * This means that there is an active session, there is packet traffic + * with this device. + */ + { /* */ + 23, /* .test */ + peripheral_configured, /* .state */ + peripheral_suspended, /* .target */ + BUS_SUSPENDED, /* .test1 */ + }, + { /* Move to idle via discharge, if we loose any of these inputs. */ + 24, /* .test */ + peripheral_configured, /* .state */ + peripheral_discharge_vbus, /* .target */ + ID_GND | enable_otg_ | B_SESS_VLD_ | b_bus_drop, /* .test1 */ + }, + /*! + * The State Machine in the discharge state for a Traditional USB Device. + * The device has been unplugged. The Vbus discharge resistor will be enabled + * for the TLDISC_DSCHRG time period. + */ + { /* Progress to idle on timeout. */ + 25, /* .test */ + peripheral_discharge_vbus, /* .state */ + peripheral_idle, /* .target */ + Tldisc_dschrg, /* .test1 */ + }, + /*! + * The State Machine in the suspend state for a Traditional USB Device. + */ + { /* Move to idle via discharge, if we loose any of these inputs. */ + 26, /* .test */ + peripheral_suspended, /* .state */ + peripheral_discharge_vbus, /* .target */ + ID_GND | enable_otg_ | B_SESS_VLD_ | b_bus_drop, /* .test1 */ + }, + { /* Check for a resumed bus. */ + 27, /* .test */ + peripheral_suspended, /* .state */ + peripheral_configured, /* .target */ + BUS_SUSPENDED_, /* .test1 */ + }, + { /* Is remote wakeup enabled? */ + 28, /* .test */ + peripheral_suspended, /* .state */ + peripheral_wakeup_enabled, /* .target */ + REMOTE_WAKEUP_ENABLED, /* .test1 */ + }, + /*! + * The State Machine in the suspend state for a Traditional USB Device, + * prior to suspended the USB Host enabled Remote Wakeup by sending a + * set REMOTE WAKUP request. + */ + { /* Move to idle via discharge, if we loose any of these inputs. */ + 29, /* .test */ + peripheral_wakeup_enabled, /* .state */ + peripheral_discharge_vbus, /* .target */ + enable_otg_ | B_SESS_VLD_, /* .test1 */ + }, + { /* Check for a resumed bus. */ + 30, /* .test */ + peripheral_wakeup_enabled, /* .state */ + peripheral_suspended, /* .target */ + BUS_SUSPENDED_, /* .test1 */ + }, + { /* Remote wakeup requested? */ + 31, /* .test */ + peripheral_wakeup_enabled, /* .state */ + peripheral_wakeup, /* .target */ + remote_wakeup_cmd, /* .test1 */ + }, + /*! + * The State Machine in the wakeup state for a Traditional USB Device, + * The REMOTE WAKEUP procedure will be performed. + */ + { /* Automatic return. */ + 32, /* .test */ + peripheral_wakeup, /* .state */ + peripheral_wakeup_enabled, /* .target */ + AUTO | AUTO_, /* .test1 */ + }, + /*! + * A-Device idle state. An A-Plug is inserted in the Mini A-B Receptacle. + * This is the Host Only idle state. Waiting for user to allow + * the bus to be used. + * + * N.B. Reset all progress indicator inputs here. + */ + { /* */ + 33, /* .test */ + host_idle, /* .state */ + host_idle_dropped, /* .target */ + a_bus_drop, /* .test1 */ + }, + { /* Check for ID change or disable (this must be before b_bus_drop test.) */ + 34, /* .test */ + host_idle, /* .state */ + otg_enabled, /* .target */ + ID_GND_ | enable_otg_, /* .test1 */ + }, + { /* is this correct? */ + 35, /* .test */ + host_idle, /* .state */ + host_wait_vrise, /* .target */ + AUTO | AUTO_, /* .test1 */ + }, + /*! + * A-Device idle state. An A-Plug is inserted in the Mini A-B Receptacle. + * This is the Host Only idle state. Waiting for user to allow + * the bus to be used. + * + * N.B. Reset all progress indicator inputs here. + */ + { /* */ + 36, /* .test */ + host_idle_dropped, /* .state */ + host_idle, /* .target */ + a_bus_drop_ | ID_GND_ | enable_otg_, /* .test1 */ + }, + /*! + * First check if external charge pump is required. + * + * XXX We force a wait of Ta_wait_vrise here, arriving in host wait + * port connect to quickly causes system hangs occasionally. + */ + { /* */ + 37, /* .test */ + host_wait_vrise, /* .state */ + host_idle, /* .target */ + ID_GND_ | enable_otg_ | a_bus_drop, /* .test1 */ + }, + { /* */ + 38, /* .test */ + host_wait_vrise, /* .state */ + host_wait_port_connect, /* .target */ + VBUS_VLD, /* .test1 */ + Ta_wait_vrise, /* .test2 */ + }, + { /* */ + 39, /* .test */ + host_wait_vrise, /* .state */ + host_wait_vrise_overcurrent, /* .target */ + Ta_wait_vrise, /* .test1 */ + }, + /*! + * A-Device Overcurrent condition (taking too long for Vbus to become valid.) + * + */ + { /* */ + 40, /* .test */ + host_wait_vrise_overcurrent, /* .state */ + host_wait_port_connect, /* .target */ + Tst_one_second, /* .test1 */ + }, + /*! + * Wait for a connection. + * + * A-Device wait for B-Device Connect. This is the normal route using the + * long debounce window. + * + * This state can optionally use the Ta_wait_bcon timeout (OTG mode) or + * wait forever (traditional USB Host mode.) + * + * XXX Arriving in this state to quickly from wait vrise can cause a system hang. + */ + { /* Vbus error? */ + 41, /* .test */ + host_wait_port_connect, /* .state */ + host_vbus_err, /* .target */ + VBUS_VLD_, /* .test1 */ + }, + { /* */ + 42, /* .test */ + host_wait_port_connect, /* .state */ + host_wait_dischrg, /* .target */ + ID_GND_ | enable_otg_ | a_bus_drop, /* .test1 */ + }, + { /* */ + 43, /* .test */ + host_wait_port_connect, /* .state */ + host_port_connected, /* .target */ + HUB_PORT_CONNECT, /* .test1 */ + }, + /*! + * A-Device host, the host controller driver has noticed a port status change, + * reset the bus and proceed. + */ + { /* */ + 44, /* .test */ + host_port_connected, /* .state */ + host_wait_dischrg, /* .target */ + ID_GND_ | enable_otg_ | a_bus_drop | HUB_PORT_CONNECT_ | Tst_two_second, /* .test1 */ + }, + { /* */ + 45, /* .test */ + host_port_connected, /* .state */ + host_bus_reset, /* .target */ + BUS_RESET, /* .test1 */ + }, + { /* */ + 46, /* .test */ + host_port_connected, /* .state */ + host_addressed, /* .target */ + ADDRESSED, /* .test1 */ + }, + /*! + * A-Device host, the bus has been reset, attempt to address the device. + */ + { /* */ + 47, /* .test */ + host_bus_reset, /* .state */ + host_wait_dischrg, /* .target */ + ID_GND_ | enable_otg_ | a_bus_drop, /* .test1 */ + }, + { /* */ + 48, /* .test */ + host_bus_reset, /* .state */ + host_addressed, /* .target */ + ADDRESSED, /* .test1 */ + }, + /*! + * A-Device host, the device has been addressed, attempt to enumerate, + * find the appropriate class driver and configure. + */ + { /* Lost B-Connect or user changed his mind? */ + 49, /* .test */ + host_addressed, /* .state */ + host_wait_dischrg, /* .target */ + ID_GND_ | enable_otg_ | a_bus_drop | HUB_PORT_CONNECT_, /* .test1 */ + }, + { /* Device configured, class driver loaded. */ + 50, /* .test */ + host_addressed, /* .state */ + host_configured, /* .target */ + CONFIGURED, /* .test1 */ + }, + /*! + * A-Device host, the enumerated device is supported, and has been configured. + * + * XXX N.B. Currently assuming HNP enable feature is automatically performed. + * XXX N.B. Currently forcing HNP_CAPABLE and HNP_ENABLED. + */ + { /* Vbus error? */ + 51, /* .test */ + host_configured, /* .state */ + host_vbus_err, /* .target */ + VBUS_VLD_, /* .test1 */ + }, + { /* */ + 52, /* .test */ + host_configured, /* .state */ + host_wait_dischrg, /* .target */ + HUB_PORT_CONNECT_, /* .test1 */ + }, + { /* */ + 53, /* .test */ + host_configured, /* .state */ + host_wait_dischrg, /* .target */ + ID_GND_ | enable_otg_ | a_bus_drop, /* .test1 */ + }, + /*! + * A-Device Vbus error. + * The user must be informed and allowed to clear the problem. + */ + { /* */ + 54, /* .test */ + host_vbus_err, /* .state */ + host_wait_dischrg, /* .target */ + ID_GND_ | clr_err_cmd | a_bus_drop, /* .test1 */ + }, + /*! + */ + { /* Normal finish, wait for a_sess_vld/ */ + 55, /* .test */ + host_wait_dischrg, /* .state */ + host_wait_vfall, /* .target */ + AUTO | AUTO_, /* .test1 */ + }, + /*! + * A-Device wait for Vbus to fall. + * + * XXX Currently reseting a_bus_req on entry, require explicit a_bus_req to proceed. + */ + { /* Normal finish, wait for a_sess_vld/ */ + 56, /* .test */ + host_wait_vfall, /* .state */ + host_idle, /* .target */ + A_SESS_VLD_ | Tst_one_second, /* .test1 */ + }, + /*! + * This is not an OTG State. It is used internally to mark the end of the + * list of states and inputs. + */ + { /* */ + 57, /* .test */ + terminator_state, /* .state */ + invalid_state, /* .target */ + 0, /* .test1 */ + }, + {58, invalid_state,}, + +}; + +#define OTG_TESTS_MN 58 + +int otg_test_max_mn = 58; + + /* eof */ + +/* Generated by otg-info-c.awk + * + * Do not Edit, see otg-state.awk + */ + +/* %Z %K */ + +struct otg_state otg_states_mn[OTG_STATES_MN + 1] = { + { /* 0 */ + invalid_state, /* .state */ + m_otg_init, /* .meta */ + "invalid_state", /* .name */ + 0, /* .tmout */ + 0, /* .reset */ + }, + { /* 1 */ + otg_disabled, /* .state */ + m_otg_init, /* .meta */ + "otg_disabled", /* .name */ + 0, /* .tmout */ + enable_otg_ | PCD_OK_ | TCD_OK_ | HCD_OK_, /* .reset */ + /* .outputs */ + chrg_vbus_out_ | drv_vbus_out_ | charge_pump_out_ | dm_pullup_out_ | dp_pullup_out_ | loc_sof_out_ | + hcd_rh_out_, + }, + { /* 2 */ + otg_disable_tcd, /* .state */ + m_otg_init, /* .meta */ + "otg_disable_tcd", /* .name */ + 0, /* .tmout */ + TCD_OK_, /* .reset */ + /* .outputs */ + tcd_init_out_, + }, + { /* 3 */ + otg_disable_hcd, /* .state */ + m_otg_init, /* .meta */ + "otg_disable_hcd", /* .name */ + 0, /* .tmout */ + HCD_OK_, /* .reset */ + /* .outputs */ + hcd_init_out_, + }, + { /* 4 */ + otg_disable_pcd, /* .state */ + m_otg_init, /* .meta */ + "otg_disable_pcd", /* .name */ + 0, /* .tmout */ + PCD_OK_, /* .reset */ + /* .outputs */ + pcd_init_out_, + }, + { /* 5 */ + otg_disable_ocd, /* .state */ + m_otg_init, /* .meta */ + "otg_disable_ocd", /* .name */ + 0, /* .tmout */ + OCD_OK_, /* .reset */ + /* .outputs */ + ocd_init_out_, + }, + { /* 6 */ + otg_enable_ocd, /* .state */ + m_otg_init, /* .meta */ + "otg_enable_ocd", /* .name */ + 0, /* .tmout */ + OCD_OK_, /* .reset */ + /* .outputs */ + ocd_init_out, + }, + { /* 7 */ + otg_enable_pcd, /* .state */ + m_otg_init, /* .meta */ + "otg_enable_pcd", /* .name */ + 0, /* .tmout */ + PCD_OK_, /* .reset */ + /* .outputs */ + pcd_init_out, + }, + { /* 8 */ + otg_enable_hcd, /* .state */ + m_otg_init, /* .meta */ + "otg_enable_hcd", /* .name */ + 0, /* .tmout */ + HCD_OK_, /* .reset */ + /* .outputs */ + hcd_init_out, + }, + { /* 9 */ + otg_enable_tcd, /* .state */ + m_otg_init, /* .meta */ + "otg_enable_tcd", /* .name */ + 0, /* .tmout */ + TCD_OK_, /* .reset */ + /* .outputs */ + tcd_init_out, + }, + { /* 10 */ + otg_enabled, /* .state */ + m_otg_init, /* .meta */ + "otg_enabled", /* .name */ + TST_ONE_SECOND, /* .tmout */ + b_bus_drop_, /* .reset */ + /* .outputs */ + dm_det_out_ | dp_det_out_, + }, + { /* 11 */ + peripheral_idle, /* .state */ + m_b_idle, /* .meta */ + "peripheral_idle", /* .name */ + 0, /* .tmout */ + 0, /* .reset */ + /* .outputs */ + dp_pullup_out_ | hcd_rh_out | pcd_en_out_ | tcd_en_out_power | dischrg_vbus_out_ | dp_pulldown_out | + dm_pulldown_out | hcd_en_out_, + }, + { /* 12 */ + peripheral_dropped, /* .state */ + m_b_idle, /* .meta */ + "peripheral_dropped", /* .name */ + 0, /* .tmout */ + 0, /* .reset */ + }, + { /* 13 */ + peripheral_wait, /* .state */ + m_b_peripheral, /* .meta */ + "peripheral_wait", /* .name */ + 0, /* .tmout */ + BUS_RESET_, /* .reset */ + /* .outputs */ + dp_pullup_out | pcd_en_out | dp_pulldown_out_ | dm_pulldown_out_ | hcd_en_out_, + }, + { /* 14 */ + peripheral_bus_reset, /* .state */ + m_b_peripheral, /* .meta */ + "peripheral_bus_reset", /* .name */ + 0, /* .tmout */ + BUS_RESET_ | ADDRESSED_, /* .reset */ + }, + { /* 15 */ + peripheral_addressed, /* .state */ + m_b_peripheral, /* .meta */ + "peripheral_addressed", /* .name */ + 0, /* .tmout */ + ADDRESSED_ | CONFIGURED_ | REMOTE_WAKEUP_ENABLED_, /* .reset */ + }, + { /* 16 */ + peripheral_configured, /* .state */ + m_b_peripheral, /* .meta */ + "peripheral_configured", /* .name */ + 0, /* .tmout */ + CONFIGURED_ | BUS_SUSPENDED_, /* .reset */ + }, + { /* 17 */ + peripheral_discharge_vbus, /* .state */ + m_b_peripheral, /* .meta */ + "peripheral_discharge_vbus", /* .name */ + TLDISC_DSCHRG, /* .tmout */ + 0, /* .reset */ + /* .outputs */ + dp_pullup_out_ | dischrg_vbus_out | pcd_en_out_ | hcd_en_out_, + }, + { /* 18 */ + peripheral_suspended, /* .state */ + m_b_suspended, /* .meta */ + "peripheral_suspended", /* .name */ + 0, /* .tmout */ + 0, /* .reset */ + }, + { /* 19 */ + peripheral_wakeup_enabled, /* .state */ + m_b_suspended, /* .meta */ + "peripheral_wakeup_enabled", /* .name */ + 0, /* .tmout */ + remote_wakeup_cmd_, /* .reset */ + }, + { /* 20 */ + peripheral_wakeup, /* .state */ + m_b_suspended, /* .meta */ + "peripheral_wakeup", /* .name */ + 0, /* .tmout */ + 0, /* .reset */ + /* .outputs */ + remote_wakeup_out_power, + }, + { /* 21 */ + host_idle, /* .state */ + m_a_idle, /* .meta */ + "host_idle", /* .name */ + 0, /* .tmout */ + 0, /* .reset */ + /* .outputs */ + chrg_vbus_out_ | hcd_en_out | hcd_rh_out_ | dp_pulldown_out | dm_pulldown_out | HUB_PORT_CONNECT_ | + pcd_en_out_, + }, + { /* 22 */ + host_idle_dropped, /* .state */ + m_a_idle, /* .meta */ + "host_idle_dropped", /* .name */ + 0, /* .tmout */ + 0, /* .reset */ + }, + { /* 23 */ + host_wait_vrise, /* .state */ + m_a_wait_vrise, /* .meta */ + "host_wait_vrise", /* .name */ + TA_WAIT_VRISE, /* .tmout */ + 0, /* .reset */ + /* .outputs */ + drv_vbus_out, + }, + { /* 24 */ + host_wait_vrise_overcurrent, /* .state */ + m_a_wait_vrise, /* .meta */ + "host_wait_vrise_overcurrent", /* .name */ + TST_ONE_SECOND, /* .tmout */ + 0, /* .reset */ + }, + { /* 25 */ + host_wait_port_connect, /* .state */ + m_a_wait_bcon, /* .meta */ + "host_wait_port_connect", /* .name */ + 0, /* .tmout */ + 0, /* .reset */ + /* .outputs */ + loc_sof_out_ | loc_suspend_out_ | dp_pullup_out_ | hcd_rh_out, + }, + { /* 26 */ + host_port_connected, /* .state */ + m_a_host, /* .meta */ + "host_port_connected", /* .name */ + TST_TWO_SECOND * 5, /* .tmout */ + BUS_RESET_, /* .reset */ + /* .outputs */ + loc_sof_out, + }, + { /* 27 */ + host_bus_reset, /* .state */ + m_a_host, /* .meta */ + "host_bus_reset", /* .name */ + 0, /* .tmout */ + a_suspend_req_, /* .reset */ + }, + { /* 28 */ + host_addressed, /* .state */ + m_a_host, /* .meta */ + "host_addressed", /* .name */ + 0, /* .tmout */ + 0, /* .reset */ + }, + { /* 29 */ + host_configured, /* .state */ + m_a_host, /* .meta */ + "host_configured", /* .name */ + TST_TWO_SECOND, /* .tmout */ + CONFIGURED_ | HNP_CAPABLE | HNP_ENABLED, /* .reset */ + }, + { /* 30 */ + host_vbus_err, /* .state */ + m_a_vbus_err, /* .meta */ + "host_vbus_err", /* .name */ + 0, /* .tmout */ + clr_err_cmd_ | a_suspend_req_, /* .reset */ + /* .outputs */ + hcd_en_out | pcd_en_out_ | drv_vbus_out_ | charge_pump_out_ | dp_pullup_out_ | loc_sof_out_ | loc_suspend_out_ + | dp_pulldown_out | dm_pulldown_out | hcd_rh_out_, + }, + { /* 31 */ + host_wait_dischrg, /* .state */ + m_a_wait_vfall, /* .meta */ + "host_wait_dischrg", /* .name */ + 0, /* .tmout */ + 0, /* .reset */ + /* .outputs */ + drv_vbus_out_ | charge_pump_out_ | loc_sof_out_ | tcd_en_out_power | hcd_rh_out_ | dischrg_vbus_out, + }, + { /* 32 */ + host_wait_vfall, /* .state */ + m_a_wait_vfall, /* .meta */ + "host_wait_vfall", /* .name */ + TST_ONE_SECOND, /* .tmout */ + 0, /* .reset */ + /* .outputs */ + hcd_en_out_ | loc_suspend_out_ | pcd_en_out_ | chrg_vbus_out_, + }, + { /* 33 */ + terminator_state, /* .state */ + m_otg_init, /* .meta */ + "terminator_state", /* .name */ + 0, /* .tmout */ + 0, /* .reset */ + /* .outputs */ + 0, + }, + + {0, 0, "", 0, 0,}, + +}; + +struct otg_firmware otg_firmware_mn = { + OTG_STATES_MN, /* number of states */ + OTG_TESTS_MN, /* number of tests */ + "otg-mn", /* name of firmware */ + otg_states_mn, /* struct otg_state * */ + otg_tests_mn, /* struct otg_test * */ +}; + +/* eof */ diff --git a/drivers/otg/otgcore/otg-fw.c b/drivers/otg/otgcore/otg-fw.c new file mode 100644 index 000000000000..cc4ded30c478 --- /dev/null +++ b/drivers/otg/otgcore/otg-fw.c @@ -0,0 +1,274 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/* Generated by otg-fw-c.awk + * + * Do not Edit, see otg-state.awk + */ + +/* %Z %K */ + +/*! +* @file otg/otgcore/otg-fw.c +* @brief OTG Firmware - Input, Output and Timeout definitions +* +* This file defines the OTG State Machine input, output and timeout constants. +* +* +* @ingroup OTGFW +*/ + +/*! +* @page OTGFW +* @section OTGFW_SECTION - otg-fw.c +* This contains the input, output and timout definitions for the OTG state machine firmware +*/ + + +#ifdef OTG_APPLICATION +#include "otg-fw.h" +#else /* OTG_APPLICATION/ */ +#include <otg/otg-compat.h> +#include <otg/usbp-chap9.h> +#include <otg/usbp-func.h> +#include <otg/usbp-bus.h> +#include <otg/otg-trace.h> +#include <otg/otg-api.h> +#endif /* OTG_APPLICATION */ + +#define OTG_VERSION_FWC 200704251606L + + +#if OTG_VERSION_FWC != OTG_VERSION_FW +#error OTG_VERSION_FWC != OTG_VERSION_FW +#endif /* OTG_VERSION_FWC != OTG_VERSION_FW */ + +/* Generated by otg-input-name-c.awk + * + * Do not Edit, see otg-state.awk + */ + +/* %Z %K */ + + +/* #include "otg-fw.h" */ + +#define OTG_VERSION_INPUT_TABLE 200704251606L + + +#if OTG_VERSION_INPUT_TABLE != OTG_VERSION_FW +#error OTG_VERSION_INPUT_TABLE != OTG_VERSION_FW +#endif /* OTG_VERSION_INPUT_TABLE != OTG_VERSION_FW */ + + +struct otg_input_name otg_input_names[] = { + {ID_GND, "ID_GND",}, + {ID_FLOAT, "ID_FLOAT",}, + {DP_HIGH, "DP_HIGH",}, + {DM_HIGH, "DM_HIGH",}, + {B_SESS_END, "B_SESS_END",}, + {A_SESS_VLD, "A_SESS_VLD",}, + {B_SESS_VLD, "B_SESS_VLD",}, + {VBUS_VLD, "VBUS_VLD",}, + {SRP_DET, "SRP_DET",}, + {SE0_DET, "SE0_DET",}, + {SE1_DET, "SE1_DET",}, + {BDIS_ACON, "BDIS_ACON",}, + {CR_INT_DET, "CR_INT_DET",}, + {HUB_PORT_CONNECT, "HUB_PORT_CONNECT",}, + {BUS_RESET, "BUS_RESET",}, + {ADDRESSED, "ADDRESSED",}, + {DEVICE_REQUEST, "DEVICE_REQUEST",}, + {CONFIGURED, "CONFIGURED",}, + {NOT_SUPPORTED, "NOT_SUPPORTED",}, + {BUS_SUSPENDED, "BUS_SUSPENDED",}, + {a_bcon_no_tmout_req, "a_bcon_no_tmout_req",}, + {a_hpwr_req, "a_hpwr_req",}, + {bus_drop, "bus_drop",}, + {a_bus_drop, "a_bus_drop",}, + {b_bus_drop, "b_bus_drop",}, + {bus_req, "bus_req",}, + {a_bus_req, "a_bus_req",}, + {b_bus_req, "b_bus_req",}, + {b_sess_req, "b_sess_req",}, + {suspend_req, "suspend_req",}, + {a_suspend_req, "a_suspend_req",}, + {b_suspend_req, "b_suspend_req",}, + {set_remote_wakeup_cmd, "set_remote_wakeup_cmd",}, + {remote_wakeup_cmd, "remote_wakeup_cmd",}, + {reset_remote_wakeup_cmd, "reset_remote_wakeup_cmd",}, + {clr_err_cmd, "clr_err_cmd",}, + {b_hnp_cmd, "b_hnp_cmd",}, + {ph_int_cmd, "ph_int_cmd",}, + {ph_audio_cmd, "ph_audio_cmd",}, + {cr_int_cmd, "cr_int_cmd",}, + {led_on_cmd, "led_on_cmd",}, + {led_off_cmd, "led_off_cmd",}, + {HNP_ENABLED, "HNP_ENABLED",}, + {HNP_CAPABLE, "HNP_CAPABLE",}, + {HNP_SUPPORTED, "HNP_SUPPORTED",}, + {REMOTE_WAKEUP_ENABLED, "REMOTE_WAKEUP_ENABLED",}, + {REMOTE_CAPABLE, "REMOTE_CAPABLE",}, + {PCD_OK, "PCD_OK",}, + {TCD_OK, "TCD_OK",}, + {HCD_OK, "HCD_OK",}, + {OCD_OK, "OCD_OK",}, + {TMOUT, "TMOUT",}, + {enable_otg, "enable_otg",}, + {AUTO, "AUTO",}, + {0, "0",}, + +}; + + /* eof */ + +/* Generated by otg-ioctls-names-c.awk + * + * Do not Edit, see otg-state.awk + */ + +/* %Z %K */ +#if defined(OTG_LINUX) || defined(OTG_WINCE) + + +/* #include "otg-fw.h" */ + +#define OTG_VERSION_IOCTL_TABLE 200704251606L + + +#if OTG_VERSION_IOCTL_TABLE != OTG_VERSION_FW +#error OTG_VERSION_IOCTL_TABLE != OTG_VERSION_FW +#endif /* OTG_VERSION_IOCTL_TABLE != OTG_VERSION_FW */ + +struct otg_ioctl_name otg_ioctl_names[] = { + {OTGADMIN_A_BCON_NO_TMOUT_REQ, a_bcon_no_tmout_req, "OTGADMIN_A_BCON_NO_TMOUT_REQ",}, + {OTGADMIN_A_HPWR_REQ, a_hpwr_req, "OTGADMIN_A_HPWR_REQ",}, + {OTGADMIN_BUS_DROP, bus_drop, "OTGADMIN_BUS_DROP",}, + {OTGADMIN_A_BUS_DROP, a_bus_drop, "OTGADMIN_A_BUS_DROP",}, + {OTGADMIN_B_BUS_DROP, b_bus_drop, "OTGADMIN_B_BUS_DROP",}, + {OTGADMIN_BUS_REQ, bus_req, "OTGADMIN_BUS_REQ",}, + {OTGADMIN_A_BUS_REQ, a_bus_req, "OTGADMIN_A_BUS_REQ",}, + {OTGADMIN_B_BUS_REQ, b_bus_req, "OTGADMIN_B_BUS_REQ",}, + {OTGADMIN_B_SESS_REQ, b_sess_req, "OTGADMIN_B_SESS_REQ",}, + {OTGADMIN_SUSPEND_REQ, suspend_req, "OTGADMIN_SUSPEND_REQ",}, + {OTGADMIN_A_SUSPEND_REQ, a_suspend_req, "OTGADMIN_A_SUSPEND_REQ",}, + {OTGADMIN_B_SUSPEND_REQ, b_suspend_req, "OTGADMIN_B_SUSPEND_REQ",}, + {OTGADMIN_SET_REMOTE_WAKEUP_CMD, set_remote_wakeup_cmd, "OTGADMIN_SET_REMOTE_WAKEUP_CMD",}, + {OTGADMIN_REMOTE_WAKEUP_CMD, remote_wakeup_cmd, "OTGADMIN_REMOTE_WAKEUP_CMD",}, + {OTGADMIN_RESET_REMOTE_WAKEUP_CMD, reset_remote_wakeup_cmd, "OTGADMIN_RESET_REMOTE_WAKEUP_CMD",}, + {OTGADMIN_CLR_ERR_CMD, clr_err_cmd, "OTGADMIN_CLR_ERR_CMD",}, + {OTGADMIN_B_HNP_CMD, b_hnp_cmd, "OTGADMIN_B_HNP_CMD",}, + {OTGADMIN_PH_INT_CMD, ph_int_cmd, "OTGADMIN_PH_INT_CMD",}, + {OTGADMIN_PH_AUDIO_CMD, ph_audio_cmd, "OTGADMIN_PH_AUDIO_CMD",}, + {OTGADMIN_CR_INT_CMD, cr_int_cmd, "OTGADMIN_CR_INT_CMD",}, + {OTGADMIN_LED_ON_CMD, led_on_cmd, "OTGADMIN_LED_ON_CMD",}, + {OTGADMIN_LED_OFF_CMD, led_off_cmd, "OTGADMIN_LED_OFF_CMD",}, + {OTGADMIN_ENABLE_OTG, enable_otg, "OTGADMIN_ENABLE_OTG",}, + {0, 0, "",}, + +}; + + /* eof */ +#endif /* defined(OTG_LINUX) || defined(OTG_WINCE) */ + +/* Generated by otg-output-names-c.awk + * + * Do not Edit, see otg-state.awk + */ + +/* %Z %K */ + +char *otg_output_names[] = { + "tcd_init_out", /* 0 Initiate Transceiver Controller Driver Initialization (or De-initialization.) */ + "pcd_init_out", /* 1 Initiate Peripheral Controller Driver Initialization (or De-initialization.) */ + "hcd_init_out", /* 2 Initiate Host Controller Driver Initialization (or De-initialization). */ + "ocd_init_out", /* 3 Initiate OTG Controller Driver Initialization (or De-initialization). */ + "tcd_en_out", /* 4 Enable Transceiver Controller Driver */ + "pcd_en_out", /* 5 Enable Peripheral Controller Driver */ + "hcd_en_out", /* 6 Enable Host Controller Driver */ + "drv_vbus_out", /* 7 A-Device will Drive Vbus to 5V through charge pump. */ + "chrg_vbus_out", /* 8 B-Device will charge Vbus to 3.3V through resistor (SRP.) */ + "dischrg_vbus_out", /* 9 B-Device will discharge Vbus (enable dischage resistor.) */ + "dm_pullup_out", /* 10 DM pullup control - aka loc_carkit */ + "dm_pulldown_out", /* 11 DM pulldown control */ + "dp_pullup_out", /* 12 DP pullup control - aka loc_conn */ + "dp_pulldown_out", /* 13 DP pulldown control */ + "clr_overcurrent_out", /* 14 Clear overcurrent indication */ + "dm_det_out", /* 15 Enable B-Device D- High detect */ + "dp_det_out", /* 16 Enable B-Device D+ High detect */ + "cr_det_out", /* 17 Enable D+ CR detect */ + "charge_pump_out", /* 18 Enable external charge pump. */ + "bdis_acon_out", /* 19 Enable auto A-connect after B-disconnect. */ + "id_pulldown_out", /* 20 Enable the ID to ground pulldown ( (CEA-936 - 5 wire carkit.) */ + "uart_out", /* 21 Enable Transparent UART mode (CEA-936.) */ + "audio_out", /* 22 Enable Audio mode (CEA-936 CarKit interrupt detector.) */ + "mono_out", /* 23 Enable Mono-Audio mode (CEA-936.) */ + "remote_wakeup_out", /* 24 Peripheral will perform remote wakeup. */ + "hcd_rh_out", /* 25 Host will enable root hub */ + "loc_sof_out", /* 26 Host will enable packet traffic. */ + "loc_suspend_out", /* 27 Host will suspend bus. */ + "remote_wakeup_en_out", /* 28 Host will send remote wakeup enable or disable request. */ + "hnp_en_out", /* 29 Host will send HNP enable request. */ + "hpwr_out", /* 30 Host will enable high power (external charge pump.) */ + NULL, + +}; + + /* eof */ + +/* Generated by otg-meta-names-c.awk + * + * Do not Edit, see otg-state.awk + */ + +/* %Z %K */ + + + +char *otg_meta_names[] = { + "a_idle", /* 0 - */ + "a_wait_vrise", /* 1 - */ + "a_wait_bcon", /* 2 - */ + "a_host", /* 3 - */ + "a_suspend", /* 4 - */ + "a_peripheral", /* 5 - */ + "a_wait_vfall", /* 6 - */ + "a_vbus_err", /* 7 - */ + "b_idle", /* 8 - */ + "b_srp_init", /* 9 - */ + "b_peripheral", /* 10 - */ + "b_suspend", /* 11 - */ + "b_wait_acon", /* 12 - */ + "b_host", /* 13 - */ + "b_suspended", /* 14 - */ + "ph_disc", /* 15 - Equivalent to b_peripheral */ + "ph_init", /* 16 - */ + "ph_uart", /* 17 - */ + "ph_aud", /* 18 - */ + "ph_wait", /* 19 - */ + "ph_exit", /* 20 - */ + "cr_init", /* 21 - */ + "cr_uart", /* 22 - */ + "cr_aud", /* 23 - */ + "cr_ack", /* 24 - */ + "cr_wait", /* 25 - */ + "cr_disc", /* 26 - */ + "otg_init", /* 27 - */ + "usb_accessory", /* 28 - */ + "usb_factory", /* 29 - */ + "unknown", /* 30 - */ + + "", +}; + +/* eof */ diff --git a/drivers/otg/otgcore/otg-linux.c b/drivers/otg/otgcore/otg-linux.c new file mode 100644 index 000000000000..2f55601b12fe --- /dev/null +++ b/drivers/otg/otgcore/otg-linux.c @@ -0,0 +1,900 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#if 0 +/* + * otg/otgcore/otg-linux.c + * @(#) sl@belcarra.com/whiskey.enposte.net|otg/otgcore/otg-linux.c|20070920072547|06330 + * + * Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + * + */ +xxx +xxx + +/*! + * @file otg/otgcore/otg-linux.c + * @brief Linux OS Compatibility defines + * + * @ingroup OTGINIT + */ + +#include <otg/otg-compat.h> +#if defined(CONFIG_OTG_LNX) + +#if !defined(_OTG_MODULE_H) +#include <otg/otg-module.h> +#endif + + +#include <linux/kernel.h> +#include <linux/version.h> +#include <linux/init.h> +#include <linux/ctype.h> +#include <linux/slab.h> +#include <asm/types.h> +#include <asm/uaccess.h> +#include <asm/semaphore.h> +#include <asm/atomic.h> +#include <linux/proc_fs.h> +#include <linux/interrupt.h> +#include <linux/random.h> +#include <linux/sched.h> +#include <linux/workqueue.h> + +#if !defined(LINUX26) && !defined(LINUX24) +#error "One of LINUX24 and LINUX26 needs to be defined here" +#endif + +#if defined(LINUX26) +#include <linux/device.h> +#endif + + +/* ********************************************************************************************** */ + +/*! @name Compilation Related + */ + +/*@{*/ +#undef PRAGMAPACK +/** Macros to support packed structures **/ +#define PACKED1 __attribute__((packed)) +#define PACKED2 +#define PACKED0 + +/** Macros to support packed enum's **/ +#define PACKED_ENUM enum +#define PACKED_ENUM_EXTRA + +#define INLINE __inline__ + +/*@}*/ + +/* ********************************************************************************************** */ +/* otg-trace - included so that TRACE_MSG can be used if desired to debug + * the otg-xxx implementation + */ + +#include <otg/otg-trace.h> + + +/* ********************************************************************************************** */ + + +/* ********************************************************************************************** */ +/*! + * @name Task Allocation + * + * Long lived, low priority, high latency tasks. + * + * Tasks may use semaphores, mutexes, atomic operations and memory allocations. + * @{ + * + * TASKS are long lived, MAY HAVE low priority and high latency, the typical + * implementation has the work item wait in a semaphore waiting for work. :w + * + * The TASK work functions are structured to run until terminated, using + * a semaphore to wait for work. + * + * TASK creation: + * struct otg_task *task = otg_task_init("taskname", task_work, TAG); + * if (task) + * otg_task_start(task); + * + * TASK termination: + * otg_task_exit(task); + * + * TASK work notification + * otg_up_work(task); + * + * TASK work process + * + * void task_work(void *data) + * { + * struct otg_task *task = (struct otg_task *)data; + * void *mydata = task->data; + * otg_up_admin(task); + * do { + * // wait for work + * otg_down_work(task); + * // do work + * } while (!task->terminating); + * task->terminated = TRUE; + * otg_up_admin(task); + * } + * + * static struct otg_task *otg_task_init(char *name, void (*work) (void *), void *data, otg_tag_t tag) + * static void otg_up_work(struct otg_task *task) + * static void otg_up_admin(struct otg_task *task) + * static void otg_down_work(struct otg_task *task) + * static void otg_down_admin(struct otg_task *task) + * static void otg_task_start(struct otg_task *task) + * static void otg_task_exit(struct otg_task *task) + * + */ + +/* XXX OTG_TASK_WORK + * + * Linux Implementation Configuration + * + * #define OTG_TASK_WORK + * Use work items only for TASK implemenation + */ + +#define OTG_TASK_WORK +//#undef OTG_TASK_WORK + +#if defined(LINUX26) + #define SCHEDULE_WORK(item) schedule_work(&item) +#else /* LINUX26 */ + #define SCHEDULE_WORK(item) schedule_task(&item) +#endif /* LINUX26 */ + +/*! struct otg_task + */ +typedef void *otg_task_arg_t; +typedef void (* otg_task_proc_t) (otg_task_arg_t data); +struct otg_task { + #if !defined(OTG_TASK_WORK) + struct workqueue_struct *work_queue; + otg_sem_t admin_sem; + otg_sem_t work_sem; + #endif /* defined(OTG_TASK_WORK) */ + struct work_struct work; + otg_task_proc_t proc; + BOOL terminate; + BOOL terminated; + otg_task_arg_t *data; + BOOL debug; + otg_tag_t tag; + char *name; +}; + +static void inline otg_sleep(int n) +{ + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout( n * HZ ); +} + +/*! otg_up_admin + * @brief Signal start task to re-start. + * @param task - otg_task instance pointer + */ +void otg_up_admin(struct otg_task *task) +{ + #if defined(OTG_TASK_WORK) + /* NOTHING */ + #else /* defined(OTG_TASK_WORK) */ + //TRACE_STRING(task->tag, "UP ADMIN: %s", task->name); + if (task->debug) + printk(KERN_INFO"%s: %s\n", __FUNCTION__, task->name); + up(&task->admin_sem); + #endif /* defined(OTG_TASK_WORK) */ +} + +/*! otg_down_work + *@brief Used by work task to wait. + *@param task - otg_task instance pointer + */ +void otg_down_work(struct otg_task *task) +{ + #if defined(OTG_TASK_WORK) + /* NOTHING */ + #else /* defined(OTG_TASK_WORK) */ + //TRACE_STRING(task->tag, "DOWN WORK: %s", task->name); + if (task->debug) + printk(KERN_INFO"%s: %s\n", __FUNCTION__, task->name); + + while (down_interruptible(&task->work_sem)); + #endif /* defined(OTG_TASK_WORK) */ +} + +/*! otg_task_proc - otg task handler + * @param data - otg_task instance pointer + */ +void otg_task_proc(otg_task_arg_t data) +{ + struct otg_task *task = (struct otg_task *)data; + otg_up_admin(task); + do { + // wait for work + if (task->debug) + printk(KERN_INFO"%s: DOWN %s\n", __FUNCTION__, task->name); + + otg_down_work(task); + + if (task->debug) + printk(KERN_INFO"%s: WORKING %s\n", __FUNCTION__, task->name); + + task->proc(task->data); + + } while (!task->terminate); + if (task->debug) + printk(KERN_INFO"%s: TERMINATING %s\n", __FUNCTION__, task->name); + task->terminated = TRUE; + otg_up_admin(task); +} + +/*! otg_task_init + *@brief Create otg task structure, create workqueue, initialize it. + *@param name - name of task or workqueue + *@param proc - handler + *@param data - parameter pointer for handler + *@param tag- + *@return initialized otg_task instance pointer + */ +struct otg_task *otg_task_init2(char *name, otg_task_proc_t proc, otg_task_arg_t data, otg_tag_t tag) +{ + struct otg_task *task; + + //TRACE_STRING(tag, "INIT: %s", name); + + RETURN_NULL_UNLESS((task = CKMALLOC(sizeof (struct otg_task)))); + + task->tag = tag; + task->data = data; + task->name = name; + task->proc = proc; + + #if defined(OTG_TASK_WORK) + task->terminated = task->terminate = TRUE; + #else /* defined(OTG_TASK_WORK) */ + task->terminated = task->terminate = FALSE; + #if defined(LINUX26) + THROW_UNLESS((task->work_queue = create_singlethread_workqueue(name)), error); + #else /* LINUX26 */ + THROW_UNLESS((task->work_queue = create_workqueue(name)), error); + #endif /* LINUX26 */ + init_MUTEX_LOCKED(&task->admin_sem); + init_MUTEX_LOCKED(&task->work_sem); + #endif /* defined(OTG_TASK_WORK) */ + + INIT_WORK(&task->work, otg_task_proc, task); + + return task; + + CATCH(error) { + printk(KERN_INFO"%s: ERROR\n", __FUNCTION__); + if (task) LKFREE(task); + return NULL; + } +} + +/*! otg_up_work + * Signal work param taskto re-start. + * @param task - otg_task instance pointer + */ +void otg_up_work(struct otg_task *task) +{ + //TRACE_STRING(task->tag, "UP WORK: %s", task->name); + + if (task->debug) + printk(KERN_INFO"%s: %s\n", __FUNCTION__, task->name); + + #if defined(OTG_TASK_WORK) + task->terminated = FALSE; + SCHEDULE_WORK(task->work); + #else /* defined(OTG_TASK_WORK) */ + up(&task->work_sem); + #endif /* defined(OTG_TASK_WORK) */ +} + +/*! otg_down_admin + * Used by admin task to wait. + * @param task - otg_task instance pointer + */ +void otg_down_admin(struct otg_task *task) +{ + #if defined(OTG_TASK_WORK) + /* NOTHING */ + #else /* defined(OTG_TASK_WORK) */ + //TRACE_STRING(task->tag, "DOWN ADMIN: %s", task->name); + if (task->debug) + printk(KERN_INFO"%s: %s\n", __FUNCTION__, task->name); + while (down_interruptible(&task->admin_sem)); + //DOWN(&task->admin_sem); + #endif /* defined(OTG_TASK_WORK) */ +} + +/*! otg_task_start + * Start and wait for otg task. + * @param task - otg_task instance pointer + */ +void otg_task_start(struct otg_task *task) +{ + #if defined(OTG_TASK_WORK) + /* NOTHING */ + #else /* defined(OTG_TASK_WORK) */ + //TRACE_STRING(task->tag, "START: %s", task->name); + if (task->debug) + printk(KERN_INFO"%s: %s\n", __FUNCTION__, task->name); + + /* schedule work item and wait until it starts */ + queue_work(task->work_queue, &task->work); + otg_down_admin(task); + #endif /* defined(OTG_TASK_WORK) */ +} + +/*! otg_task_exit + * Terminate and wait for otg task. + * @param task - otg_task instance pointer + */ +void otg_task_exit(struct otg_task *task) +{ + TRACE_STRING(task->tag, "EXIT: %s", task->name); + if (task->debug) + printk(KERN_INFO"%s: %s\n", __FUNCTION__, task->name); + + #if defined(OTG_TASK_WORK) + while (!task->terminated) { + otg_sleep( 1 ); + } + #else /* defined(OTG_TASK_WORK) */ + /* signal termination */ + task->terminate = TRUE; + otg_up_work(task); + otg_down_admin(task); + + /* destroy workqueue */ + flush_workqueue(task->work_queue); + destroy_workqueue(task->work_queue); + #endif /* defined(OTG_TASK_WORK) */ + + LKFREE(task); +} + +/*! otg_sleep -schedule current task to start before n seconds + * @param n - timeout for schedule + */ + + +/* ********************************************************************************************** */ +/*! + * @name WorkItem Allocation + * + * Short lived, low priority, high latency task. + * + * @{ + * + * Work items are similar to tasks except that they are not expected + * to run for long periods of time or wait. + * + * A Work item function is run once and exits when the work + * is finished. + * + * static struct otg_workitem *otg_workitem_init(char *name, void (*work) (unsigned long), unsigned long data, otg_tag_t tag) + * static void otg_workitem_start(struct otg_workitem *tasklet) + * static void otg_workitem_exit(struct otg_workitem *tasklet) + * + * WORK creation: + * struct otg_workitem *tasklet = otg_workitem_init("taskletname", tasklet_work, TAG); + * + * WORK start: + * otg_workitem_start(tasklet); + * + * WORK termination: + * otg_workitem_exit(tasklet); + * + * WORK work process + * + * void task_work(void *data) + * { + * struct otg_workitem *tasklet = (struct otg_workitem *)data; + * void *mydata = task->data; + * + * // do work + * + * } + * + */ + +/*! struct otg_workitem + */ +typedef void *otg_workitem_arg_t; +typedef void (* otg_workitem_proc_t) (otg_task_arg_t data); + +struct otg_workitem { + struct work_struct work; + otg_workitem_proc_t proc; + BOOL terminate; + BOOL terminated; + otg_task_arg_t *data; + BOOL debug; + otg_tag_t tag; + char *name; +}; + +/*! otg_workitem_run - run workitem process + * @param data - workite poiner + */ +static inline void otg_workitem_run(void *data) +{ + struct otg_workitem *workitem = (struct otg_workitem *)data; + + if (workitem->debug) + printk(KERN_INFO"%s: %s\n", __FUNCTION__, workitem->name); + + workitem->proc(workitem->data); // XXX convert to workitem->data + workitem->terminated = TRUE; + + if (workitem->debug) + printk(KERN_INFO"%s: %s finished\n", __FUNCTION__, workitem->name); + +} + +/*! otg_workitem_init + * Create otg work structure, create workqueue, initialize it. + * @param name - workitem name + * @param proc - workitem handler + * @param data - workitem data + * @param tag - otg tag + * @return otg_workitem instance pointer + */ +static inline struct otg_workitem *otg_workitem_init(char *name, otg_workitem_proc_t proc, otg_workitem_arg_t data, otg_tag_t tag) +{ + struct otg_workitem *workitem; + + //TRACE_STRING(tag, "INIT: %s", name); + + //printk(KERN_INFO"%s: %s\n", __FUNCTION__, name); + + RETURN_NULL_UNLESS((workitem = CKMALLOC(sizeof (struct otg_workitem)))); + + workitem->data = data; + workitem->tag = tag; + workitem->name = name; + workitem->proc = proc; + //workitem->debug = TRUE; + + workitem->terminated = workitem->terminate = TRUE; + + INIT_WORK(&workitem->work, otg_workitem_run, workitem); + + return workitem; + + CATCH(error) { + printk(KERN_INFO"%s: ERROR\n", __FUNCTION__); + if (workitem) LKFREE(workitem); + return NULL; + } +} + +/*! otg_workitem_start + * Signal work work to run. + * @param workitem - workitem pointer + */ +static void inline otg_workitem_start(struct otg_workitem *workitem) +{ + //TRACE_STRING(workitem->tag, "START: %s", workitem->name); + + if (workitem->debug) + printk(KERN_INFO"%s: %s\n", __FUNCTION__, workitem->name); + + workitem->proc(workitem->data); + workitem->terminated = FALSE; + SCHEDULE_WORK(workitem->work); +} + +/*! otg_workitem_exit + * Terminate and wait for otg work. + * @param workitem - workitem pointer + */ +static void inline otg_workitem_exit(struct otg_workitem *workitem) +{ + //TRACE_STRING(workitem->tag, "EXIT: %s", workitem->name); + if (workitem->debug) + printk(KERN_INFO"%s: %s terminating\n", __FUNCTION__, workitem->name); + + while (!workitem->terminated) { + otg_sleep( 1 ); + } + + if (workitem->debug) + printk(KERN_INFO"%s: %s terminated\n", __FUNCTION__, workitem->name); + + LKFREE(workitem); +} + + +/*! @} */ + +/* ********************************************************************************************** */ +/*! + * @name Tasklet Allocation + * + * Short lived, high priority, low latency tasklets. + * + * Tasklets MAY NOT use memory allocation or wait on semaphores. + * + * Taskslets may use signal semaphores, mutexes and atomic operations. + * @{ + * + * TASKLETS are short lived, MUST run with low latency and SHOULD have high + * priority. + * + * The TASKLET work function is structure to run once and exit when the work + * is finished. + * + * static struct otg_tasklet *otg_tasklet_init(char *name, void (*work) (unsigned long), unsigned long data, otg_tag_t tag) + * static void otg_tasklet_start(struct otg_tasklet *tasklet) + * static void otg_tasklet_exit(struct otg_tasklet *tasklet) + * + * TASKLET creation: + * struct otg_tasklet *tasklet = otg_tasklet_init("taskletname", tasklet_work, TAG); + * + * TASKLET start: + * otg_tasklet_start(tasklet); + * + * TASKLET termination: + * otg_tasklet_exit(tasklet); + * + * TASKLET work process + * + * void task_work(otg_tasklet_arg_t data) + * { + * void *mydata = data; + * + * // do work + * + * } + * + */ +/* XXX OTG_TASKLET_WORK + * + * Linux Implementation Configuration + * + * #define OTG_TASKLET_WORK + * Use work items only for TASK implemenation + * + * N.B. the Linux work item implementation for tasklets is not suitable + * for full OTG Dual-Role implemenation. + * + */ + +#define OTG_TASKLET_WORK +#undef OTG_TASKLET_WORK + +/*! struct otg_tasklet + */ +#ifdef OTG_TASKLET_WORK +typedef void *otg_tasklet_arg_t; +#else /* OTG_TASKLET_WORK */ +typedef unsigned long otg_tasklet_arg_t; +#endif /* OTG_TASKLET_WORK */ +typedef void (* otg_tasklet_proc_t) (otg_tasklet_arg_t data); +struct otg_tasklet { + #ifdef OTG_TASKLET_WORK + struct work_struct work; + #else /* OTG_TASKLET_WORK */ + struct tasklet_struct tasklet; + #endif /* OTG_TASKLET_WORK */ + BOOL terminated; + otg_tasklet_proc_t proc; + otg_tasklet_arg_t data; + BOOL debug; + otg_tag_t tag; + char *name; +}; + +/*! otg_tasklet_run - statr otg_tasklet process + * @param data - otg_tasklet pointer + */ +static inline void otg_tasklet_run(otg_tasklet_arg_t data) +{ + struct otg_tasklet *tasklet = (struct otg_tasklet *)data; + + if (tasklet->debug) + printk(KERN_INFO"%s: %s\n", __FUNCTION__, tasklet->name); + + tasklet->proc(tasklet->data); + tasklet->terminated = TRUE; + + if (tasklet->debug) + printk(KERN_INFO"%s: %s finished\n", __FUNCTION__, tasklet->name); +} + +/*! otg_tasklet_init + * Create otg task structure, create workqueue, initialize it. + * @param name - otg_tasklet name + * @param proc - otg_tasklet process + * @param data - otg_taskle data, argument for proc + * @param tag - otg_tasklet tag + * @return initialized otg_tasklet instance pointer + */ +static inline struct otg_tasklet *otg_tasklet_init(char *name, otg_tasklet_proc_t proc, otg_tasklet_arg_t data, otg_tag_t tag) +{ + struct otg_tasklet *tasklet; + + //TRACE_STRING(tag, "INIT: %s", name); + //printk(KERN_INFO"%s: %s\n", __FUNCTION__, name); + + RETURN_NULL_UNLESS((tasklet = CKMALLOC(sizeof (struct otg_tasklet)))); + + tasklet->tag = tag; + tasklet->name = name; + tasklet->terminated = TRUE; + tasklet->proc = proc; + tasklet->data = data; + + #ifdef OTG_TASKLET_WORK + INIT_WORK(&tasklet->work, otg_tasklet_run, tasklet); + #else /* OTG_TASKLET_WORK */ + tasklet_init(&tasklet->tasklet, otg_tasklet_run, (otg_tasklet_arg_t) tasklet); + #endif /* OTG_TASKLET_WORK */ + + return tasklet; + + CATCH(error) { + if (tasklet) LKFREE(tasklet); + return NULL; + } +} + +/*! otg_tasklet_start + * Signal work task to re-start. + * @param tasklet - otg_tasklet instance pointer + */ +static void inline otg_tasklet_start(struct otg_tasklet *tasklet) +{ + //TRACE_STRING(tag, "START: %s", name); + if (tasklet->debug) + printk(KERN_INFO"%s: %s\n", __FUNCTION__, tasklet->name); + + tasklet->terminated = FALSE; + #ifdef OTG_TASKLET_WORK + SCHEDULE_WORK(tasklet->work); + #else /* OTG_TASKLET_WORK */ + tasklet_schedule(&tasklet->tasklet); + #endif /* OTG_TASKLET_WORK */ +} + +/*! otg_tasklet_exit + * Terminate and wait for otg task. + * @param tasklet - otg_tasklet instance pointer + */ +static void inline otg_tasklet_exit(struct otg_tasklet *tasklet) +{ + //TRACE_STRING(tag, "EXIT: %s", name); + if (tasklet->debug) + printk(KERN_INFO"%s: %s\n", __FUNCTION__, tasklet->name); + + #ifdef OTG_TASKLET_WORK + while (!tasklet->terminated) { + if (tasklet->debug) + printk(KERN_INFO"%s: SLEEPING\n", __FUNCTION__); + otg_sleep( 1 ); + if (tasklet->debug) + printk(KERN_INFO"%s: RUNNING\n", __FUNCTION__); + } + #else /* OTG_TASKLET_WORK */ + tasklet_kill(&tasklet->tasklet); + #endif /* OTG_TASKLET_WORK */ + + LKFREE(tasklet); +} + + +/*! @} */ + +/* ********************************************************************************************** */ +/*! + * @name DMA Cache + * + * @{ + * + * + * #define CACHE_SYNC_RCV(buf, len) pci_map_single (NULL, (void *) buf, len, PCI_DMA_FROMDEVICE) + * #define CACHE_SYNC_TX(buf, len) consistent_sync (NULL, buf, len, PCI_DMA_TODEVICE) + */ +/*! @} */ +#define CACHE_SYNC_RCV(buf, len) pci_map_single (NULL, (void *) buf, len, PCI_DMA_FROMDEVICE) +#if defined(CONFIG_OTG_NEW_TX_CACHE) +#define CACHE_SYNC_TX(buf, len) consistent_sync (NULL, buf, len, PCI_DMA_TODEVICE) +#else +#define CACHE_SYNC_TX(buf, len) consistent_sync (buf, len, PCI_DMA_TODEVICE) +#endif + +/*@}*/ + + + +/* ********************************************************************************************** */ +/*! @name likely and unlikely + * + * Under linux these can be used to provide a hint to GCC that + * the expression being evaluated is probably going to evaluate + * to true (likely) or false (unlikely). The intention is that + * GCC can then provide optimal code for that. + * @{ + */ +#ifndef likely +#define likely(x) x +#endif +#define otg_likely(x) likely(x) + +#ifndef unlikely +#define unlikely(x) x +#endif +#define otg_unlikely(x) unlikely(x) +/* @} */ + + + + +/* ********************************************************************************************** */ +/* + * XXX Deprecated and/or move to platform or architecture specific + */ + + + +// Common to all supported versions of Linux ?? + +#if 1 +#include <linux/list.h> +#define LIST_NODE struct list_head +#define LIST_NODE_INIT(name) struct list_head name = {&name, &name} +#define INIT_LIST_NODE(ptr) INIT_LIST_HEAD(ptr) +#define LIST_ENTRY(pointer, type, member) list_entry(pointer, type, member) +#define LIST_FOR_EACH(cursor, head) list_for_each(cursor, head) +#define LIST_ADD_TAIL(n,h) list_add_tail(n,h) +#define LIST_DEL(h) list_del(&(h)) +#else +#include <otg/otg-list.h> +#endif + +/*! @} */ + + +/*!@name Scheduling Primitives + * + * WORK_STRUCT + * WORK_ITEM + * + * SET_WORK_ARG()\n + * SCHEDULE_IMMEDIATE_WORK()\n + * NO_WORK_DATA()\n + * MOD_DEC_USE_COUNT\n + * MOD_INC_USE_COUNT\n + */ + +/*! @{ */ + + +/* Separate Linux 2.4 and 2.6 versions of scheduling primitives */ +#if defined(LINUX26) + #include <linux/workqueue.h> + + #define OLD_WORK_STRUCT work_struct + #define WORK_ITEM work_struct + #define OLD_WORK_ITEM work_struct + typedef struct OLD_WORK_ITEM OLD_WORK_ITEM; + #if 0 + #define PREPARE_WORK_ITEM(__item,__routine,__data) INIT_WORK((__item),(__routine),(__data)) + #else + #include <linux/interrupt.h> + #define PREPARE_WORK_ITEM(__item,__routine,__data) __prepare_work(&(__item),(__routine),(__data)) + static inline void __prepare_work(struct work_struct *_work, + void (*_routine), + void * _data){ + INIT_LIST_HEAD(&_work->entry); + _work->pending = 0; + _work->func = _routine; + _work->data = _data; + init_timer(&_work->timer); + } + #endif + //#undef PREPARE_WORK + typedef void (* WORK_PROC)(void *); + + #define SET_WORK_ARG(__item, __data) (__item).data = __data + + #define SCHEDULE_DELAYED_WORK(item) schedule_delayed_work(&item, 0) + #define SCHEDULE_IMMEDIATE_WORK(item) SCHEDULE_WORK((item)) + #define PENDING_WORK_ITEM(item) (item.pending != 0) + #define NO_WORK_DATA(item) (!item.data) + //#define _MOD_DEC_USE_COUNT //Not used in 2.6 + //#define _MOD_INC_USE_COUNT //Not used in 2.6 + + //#define MODPARM(a) _##a + + //#define MOD_PARM_BOOL(a, d, v) static int _##a = v; module_param_named(a, _##a, bool, 0); MODULE_PARM_DESC(a, d) + //#define MOD_PARM_INT(a, d, v) static int _##a = v; module_param_named(a, _##a, uint, 0); MODULE_PARM_DESC(a, d) + //#define MOD_PARM_STR(a, d, v) static char * _##a = v; module_param_named(a, _##a, charp, 0); MODULE_PARM_DESC(a, d) + #define OTG_INTERRUPT 0 /* SA_INTERRUPT in some environments */ + + + + +#else /* LINUX26 */ + + #define OLD_WORK_STRUCT tq_struct + #define OLD_WORK_ITEM tq_struct + typedef struct OLD_WORK_ITEM OLD_WORK_ITEM; + #define PREPARE_WORK_ITEM(item,work_routine,work_data) { item.routine = work_routine; item.data = work_data; } + #define SET_WORK_ARG(__item, __data) (__item).data = __data + #define NO_WORK_DATA(item) (!(item).data) + #define PENDING_WORK_ITEM(item) (item.sync != 0) + //#define _MOD_DEC_USE_COUNT MOD_DEC_USE_COUNT + //#define _MOD_INC_USE_COUNT MOD_INC_USE_COUNT + + #define MODPARM(a) a + + //#define MOD_PARM_BOOL(a, d, v) static int a = v; MOD_PARM(a, "i"); MOD_PARM_DESC(a, d); + //#define MOD_PARM_INT(a, d, v) static int a = v; MOD_PARM(a, "i"); MOD_PARM_DESC(a, d); + //#define MOD_PARM_STR(a, d, v) static char * a = v; MOD_PARM(a, "s"); MOD_PARM_DESC(a, d); + + typedef void (* WORK_PROC)(void *); + + #define OTG_INTERRUPT 0 /* SA_INTERRUPT in some environments */ +#endif /* LINUX26 */ + +/*! @} */ + + +/* ********************************************************************************************* */ +/* Linux specific - not part of otg-os.h implementation + */ + + +#if defined(LINUX26) +/*! @name OTG Bus Type + */ +/*@{*/ +extern struct bus_type otg_bus_type; +/*@}*/ +#endif /* defined(LINUX26) */ + + + +#if defined(LINUX26) + #include <linux/gfp.h> +#define GET_KERNEL_PAGE() __get_free_page(GFP_KERNEL) + +#else /* LINUX26 */ + + #include <linux/mm.h> + #define GET_KERNEL_PAGE() get_free_page(GFP_KERNEL) + #if !defined(IRQ_HANDLED) + // Irq's + typedef void irqreturn_t; + #define IRQ_NONE + #define IRQ_HANDLED + #define IRQ_RETVAL(x) + #endif +#endif /* LINUX26 */ + + + +#endif /* defined(CONFIG_OTG_LNX) */ +#endif diff --git a/drivers/otg/otgcore/otg-mesg-lnx.c b/drivers/otg/otgcore/otg-mesg-lnx.c new file mode 100644 index 000000000000..254319396856 --- /dev/null +++ b/drivers/otg/otgcore/otg-mesg-lnx.c @@ -0,0 +1,519 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/otgcore/otg-mesg-l24.c - OTG state machine Administration + * @(#) tt@belcarra.com|otg/otgcore/otg-mesg-lnx.c|20070316085010|26362 + * + * Copyright (c) 2004-2005 Belcarra Technologies Corp + * Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + * + * By: + * Stuart Lynne <sl@belcarra.com>, + * Bruce Balden <balden@belcarra.com> + * + */ +/*! + * @file otg/otgcore/otg-mesg-lnx.c + * @brief OTG Core Linux Message Interface. + * + * Implement linux char device via /proc/otg-mesg. + * + * @ingroup OTGMESG + * @ingroup LINUXOS + */ + +#include <otg/otg-compat.h> +#if defined(CONFIG_OTG_LNX) || defined(_OTG_DOXYGEN) + +#include <otg/otg-module.h> + +#include <otg/usbp-chap9.h> +#include <otg/usbp-func.h> +#include <otg/usbp-bus.h> +#include <otg/otg-trace.h> +#include <otg/otg-api.h> +//#include <otg/otg-task.h> +#include <otg/otg-tcd.h> +#include <otg/otg-hcd.h> +#include <otg/otg-pcd.h> +#include <otg/otg-ocd.h> + +#include <linux/poll.h> +#include <linux/sched.h> + +#if defined(LINUX24) +#include <asm/div64.h> +#endif + + +wait_queue_head_t otg_message_queue; + + +/*! + * otg_message_task() - Bottom half handler to process sent or received urbs. + * + * @param data pointer to parameters struct + */ +#if 0 +void otg_message_task(otg_task_arg_t data) +{ + struct otg_task * task = (struct otg_task *) data; + + struct otg_instance *otg = (struct otg_instance *)task->data; + + /* signal we have started */ + otg_up_admin(task); + + do { + /* wait for work */ + TRACE_MSG0(CORE, "SLEEPING"); + otg_down_work(task); + TRACE_MSG0(CORE, "WORKING"); + + wake_up_interruptible(&otg_message_queue); + + } while (!task->terminate); + + + /* signal exiting */ + task->terminated = TRUE; + otg_up_admin(task); +} +#endif +void *otg_message_task(otg_task_arg_t data) +{ + struct otg_instance *otg = (struct otg_instance *)data; + wake_up_interruptible(&otg_message_queue); + return NULL; +} + + +#if 0 +/*! + * otg_message() - queue message data + * @param buf message to send. + */ +void otg_message (struct otg_instance *otg, char *buf) +{ + char time_buf[64]; + static otg_tick_t save_ticks; + otg_tick_t new_ticks; + otg_tick_t ticks; + + new_ticks = otg_tmr_ticks(); + + otg_write_message(otg, buf, strlen(buf)); + + ticks = otg_tmr_elapsed(&new_ticks, &save_ticks); + save_ticks = new_ticks; + + if (ticks < 10000) + sprintf(time_buf, " %d uS\n", ticks); + + else if (ticks < 10000000) { +// do_div(ticks, 1000); + otg_do_div(ticks, 1000); + sprintf(time_buf, " %d mS\n", ticks); + } + else { +// do_div(ticks, 1000000); + otg_do_div(ticks, 1000000); + sprintf(time_buf, " %d S\n", ticks); + } + + otg_write_message(otg, time_buf, strlen(time_buf)); + + if (otg->message_task) + otg_up_work(otg->message_task); +} +#endif + +/*! + * otg_message_irq() - queue message data + * @param otg - otg instance pointer + * @param buf message to send. + */ +void otg_message_irq(struct otg_instance *otg, char *buf) +{ + otg_message(otg, buf); +} + +/*! + * otg_message_block() - implement block + */ +unsigned int otg_message_block(void) +{ + int count = otg_data_queued(); + if (count) return count; + interruptible_sleep_on(&otg_message_queue); + return otg_data_queued(); +} + +/*! + * otg_message_poll() - implement poll + * @param filp file pointer + * @param wait poll table structure + */ +unsigned int otg_message_poll(struct file *filp, struct poll_table_struct *wait) +{ + //TRACE_MSG0(CORE, "polling "); + poll_wait(filp, &otg_message_queue, wait); + return otg_data_queued() ? POLLIN | POLLRDNORM : 0; +} + +/*! + * otg_set_usbd_ops() - connect usbd operations to otg instance + * + * @param data - usbd_ops + * @return 0 on finish + */ +struct usbd_ops *otg_usbd_ops; +int otg_set_usbd_ops(void *data) +{ + struct usbd_ops *usbd_ops = (struct usbd_ops *)data; + otg_usbd_ops = usbd_ops; + return 0; +} + + + +/*! + * otg_message_ioctl_internal() - ioctl call + * @param cmd ioctl command. + * @param arg ioctl arguement. + * @return non-zero for error. + */ +int otg_message_ioctl_internal(unsigned int cmd, unsigned long arg) +{ + int i; + int len; + int flag; + struct otg_admin_command admin_command; + struct otg_status_update status_update; + struct otg_firmware_info firmware_info; + struct otg_state otg_state; + struct otg_test otg_test; + struct otg_ioctl_name *otg_ioctl_name; + static char func_buf[32]; + char *sp, *dp; + char *name; + + //TRACE_MSG2(CORE, "cmd: %08x %08x", cmd, _IOC_NR(cmd)); + + switch (_IOC_DIR(cmd)) { + case _IOC_NONE: + switch (_IOC_NR(cmd)) { + } + break; + + case _IOC_WRITE: + switch (_IOC_NR(cmd)) { + case _IOC_NR(OTGADMIN_SET_FUNCTION): + TRACE_MSG0(CORE, "OTGADMIN_SET_FUNCTION"); + RETURN_EINVAL_UNLESS(otg_firmware_loaded); + memset(&admin_command, 0x41, sizeof(struct otg_admin_command)); + RETURN_EINVAL_IF(copy_from_user(&admin_command, (void *)arg, _IOC_SIZE(cmd))); + len = sizeof(mesg_otg_instance->function_name); + admin_command.string[len] = '\0'; + TRACE_MSG1(CORE, "Setting function: \"%s\"", mesg_otg_instance->function_name); + strncpy(mesg_otg_instance->function_name, admin_command.string, len); + mesg_otg_instance->function_name[len-1] = '\0'; +#if defined(LINUX26) + { + char *cp = mesg_otg_instance->function_name; + TRACE_MSG8(CORE, "function_name: %c%c%c%c%c%c%c%c", + cp[0], cp[1], cp[2], cp[3], cp[4], cp[5], cp[6], cp[7]); + + } +#endif + return 0; + + case _IOC_NR(OTGADMIN_SET_SERIAL): + TRACE_MSG0(CORE, "OTGADMIN_SET_SERIAL"); + RETURN_EINVAL_UNLESS(otg_firmware_loaded); + RETURN_EINVAL_IF(copy_from_user(&admin_command, (void *)arg, _IOC_SIZE(cmd))); + admin_command.string[sizeof(admin_command.string) - 1] = '\0'; + for (sp = admin_command.string, dp = mesg_otg_instance->serial_number, i = 0; + *sp && (i < (sizeof(admin_command.string) - 1)); i++, sp++) + if (isxdigit(*sp)) *dp++ = toupper(*sp); + + *sp = '\0'; + //TRACE_MSG1(CORE, "serial_number: %s", mesg_otg_instance->serial_number); + return 0; + + case _IOC_NR(OTGADMIN_SET_INFO): + TRACE_MSG0(CORE, "OTGADMIN_SET_INFO"); + memset(&firmware_info, 0x41, sizeof(firmware_info)); + RETURN_EINVAL_IF(copy_from_user(&firmware_info, (void *)arg, _IOC_SIZE(cmd))); + + + return otg_mesg_set_firmware_info(&firmware_info); + return 0; + + case _IOC_NR(OTGADMIN_SET_STATE): + //TRACE_MSG0(CORE, "OTGADMIN_XXX_STATE"); + RETURN_EINVAL_IF(copy_from_user(&otg_state, (void *)arg, _IOC_SIZE(cmd))); + + RETURN_EINVAL_UNLESS(otg_firmware_loading); + //TRACE_MSG0(CORE, "OTGADMIN_SET_STATE"); + RETURN_EINVAL_UNLESS (otg_state.state < otg_firmware_loading->number_of_states); + memcpy(otg_firmware_loading->otg_states + otg_state.state, &otg_state, sizeof(otg_state)); + return 0; + + case _IOC_NR(OTGADMIN_SET_TEST): + //TRACE_MSG0(CORE, "OTGADMIN_GET/SET"); + RETURN_EINVAL_IF(copy_from_user(&otg_test, (void *)arg, _IOC_SIZE(cmd))); + + RETURN_EINVAL_UNLESS(otg_firmware_loading); + //TRACE_MSG1(CORE, "OTGADMIN_SET_TEST : %d", otg_test.test); + RETURN_EINVAL_UNLESS (otg_test.test < otg_firmware_loading->number_of_tests); + memcpy(otg_firmware_loading->otg_tests + otg_test.test, &otg_test, sizeof(otg_test)); + return 0; + } + break; + + case _IOC_READ: + switch (_IOC_NR(cmd)) { + case _IOC_NR(OTGADMIN_GET_FUNCTION): + TRACE_MSG0(CORE, "OTGADMIN_GET_FUNCTION"); + RETURN_EINVAL_UNLESS(otg_firmware_loaded); + RETURN_EINVAL_IF(copy_from_user(&admin_command, (void *)arg, _IOC_SIZE(cmd))); + name = otg_usbd_ops->function_name(admin_command.n); + admin_command.string[0] = '\0'; + if (name) + strncat(admin_command.string, name, sizeof(admin_command.string)); + + RETURN_EINVAL_IF(copy_to_user((void *)arg, &admin_command, _IOC_SIZE(cmd))); + return 0; + + case _IOC_NR(OTGADMIN_GET_SERIAL): + TRACE_MSG0(CORE, "OTGADMIN_GET_SERIAL"); + RETURN_EINVAL_UNLESS(otg_firmware_loaded); + strncpy(admin_command.string, mesg_otg_instance->serial_number, sizeof(admin_command.string)); + admin_command.string[sizeof(admin_command.string) - 1] = '\0'; + RETURN_EINVAL_IF(copy_to_user((void *)arg, &admin_command, _IOC_SIZE(cmd))); + return 0; + + case _IOC_NR(OTGADMIN_STATUS): + //TRACE_MSG0(CORE, "OTGADMIN_STATUS"); + RETURN_EINVAL_UNLESS(otg_firmware_loaded); + memset(&status_update, 0, sizeof(struct otg_status_update)); + + otg_mesg_get_status_update(&status_update); + + RETURN_EINVAL_IF(copy_to_user((void *)arg, &status_update, _IOC_SIZE(cmd))); + return 0; + + case _IOC_NR(OTGADMIN_GET_INFO): + TRACE_MSG0(CORE, "OTGADMIN_GET_INFO"); + RETURN_EINVAL_UNLESS(otg_firmware_loaded); + + otg_mesg_get_firmware_info(&firmware_info); + + RETURN_EINVAL_IF(copy_to_user((void *)arg, &firmware_info, _IOC_SIZE(cmd))); + //TRACE_MSG0(CORE, "OTGADMIN_GET_INFO: finished"); + return 0; + + case _IOC_NR(OTGADMIN_GET_STATE): + //TRACE_MSG0(CORE, "OTGADMIN_XXX_STATE"); + RETURN_EINVAL_IF(copy_from_user(&otg_state, (void *)arg, _IOC_SIZE(cmd))); + + RETURN_EINVAL_UNLESS(otg_firmware_loaded); + //TRACE_MSG0(CORE, "OTGADMIN_GET_STATE"); + RETURN_EINVAL_UNLESS (otg_state.state < otg_firmware_loaded->number_of_states); + memcpy(&otg_state, otg_firmware_loaded->otg_states + otg_state.state, sizeof(otg_state)); + RETURN_EINVAL_IF(copy_to_user((void *)arg, &otg_state, _IOC_SIZE(cmd))); + return 0; + + case _IOC_NR(OTGADMIN_GET_TEST): + //TRACE_MSG0(CORE, "OTGADMIN_GET/SET"); + RETURN_EINVAL_IF(copy_from_user(&otg_test, (void *)arg, _IOC_SIZE(cmd))); + + RETURN_EINVAL_UNLESS(otg_firmware_loaded); + //TRACE_MSG1(CORE, "OTGADMIN_GET_TEST: %d", otg_test.test); + RETURN_EINVAL_UNLESS (otg_test.test < otg_firmware_loaded->number_of_tests); + memcpy(&otg_test, otg_firmware_loaded->otg_tests + otg_test.test, sizeof(otg_test)); + RETURN_EINVAL_IF(copy_to_user((void *)arg, &otg_test, _IOC_SIZE(cmd))); + return 0; + } + break; + } + + TRACE_MSG0(CORE, "OTGADMIN_"); + RETURN_EINVAL_UNLESS(otg_firmware_loaded); + for (otg_ioctl_name = otg_ioctl_names; otg_ioctl_name && otg_ioctl_name->cmd; otg_ioctl_name++) { + //TRACE_MSG4(CORE, "lookup: %04x %04x %08x %08x", + // _IOC_NR(cmd), _IOC_NR(otg_ioctl_name->cmd), cmd, otg_ioctl_name->cmd); + BREAK_IF(_IOC_NR(otg_ioctl_name->cmd) == _IOC_NR(cmd)); + } + TRACE_MSG3(CORE, "checking %d %08x %08x", _IOC_NR(cmd), otg_ioctl_name->cmd, cmd); + RETURN_EINVAL_UNLESS(otg_ioctl_name->cmd); + __get_user(flag, (int *)arg); + TRACE_MSG3(CORE, "%s %08x flag: %d", otg_ioctl_name->name, otg_ioctl_name->set, flag); + otg_event (mesg_otg_instance, flag ? otg_ioctl_name->set : _NOT(otg_ioctl_name->set), CORE, otg_ioctl_name->name); + return 0; +} + + + +/*! + * otg_message_ioctl() - ioctl + * @param inode inode structure. + * @param filp file. + * @param cmd ioctl command. + * @param arg ioctl argument. + * @return non-zero for error. + */ +int otg_message_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg) +{ + int i; + int len; + int flag; + struct otg_admin_command admin_command; + struct otg_status_update status_update; + struct otg_firmware_info firmware_info; + struct otg_state otg_state; + struct otg_test otg_test; + //struct otg_ioctl_map *map = ioctl_map; + struct otg_ioctl_name *otg_ioctl_name; + + static char func_buf[32]; + + //TRACE_MSG6(CORE, "cmd: %08x arg: %08x type: %02d nr: %02d dir: %02d size: %02d", + // cmd, arg, _IOC_TYPE(cmd), _IOC_NR(cmd), _IOC_DIR(cmd), _IOC_SIZE(cmd)); + + RETURN_EINVAL_UNLESS (_IOC_TYPE(cmd) == OTGADMIN_MAGIC); + RETURN_EINVAL_UNLESS (_IOC_NR(cmd) <= OTGADMIN_MAXNR); + + RETURN_EFAULT_IF((_IOC_DIR(cmd) == _IOC_READ) && !access_ok(VERIFY_WRITE, (void *)arg, _IOC_SIZE(cmd))); + RETURN_EFAULT_IF((_IOC_DIR(cmd) == _IOC_WRITE) && !access_ok(VERIFY_READ, (void *)arg, _IOC_SIZE(cmd))); + + return otg_message_ioctl_internal(cmd, arg); +} + + +/*! + * otg_message_proc_read() - implement proc file system read. + * Standard proc file system read function. + * @param file file. + * @param buf buffer to fill + * @param count size of buffer + * @param pos file position + * @return number of bytes or negative number for error + */ +static ssize_t otg_message_proc_read (struct file *file, char *buf, size_t count, loff_t * pos) +{ + unsigned long page; + int len = 0, avail; + char *bp,*ep; + struct list_head *lhd; + int state; + + // get a page, max 4095 bytes of data... + RETURN_ENOMEM_UNLESS ((page = GET_KERNEL_PAGE())); + + bp = (char *) page; + + len = bp - (char *) page; + + if (( avail = otg_message_block())) { + + len += otg_read_message(bp, 4095); + + if (len > count) + len = -EINVAL; + else if ((len > 0) && copy_to_user (buf, (char *) page, len)) + len = -EFAULT; + + } + + free_page (page); + return len; +} + +/*! otg_message_proc_ioctl() - + * @param inode inode structure. + * @param filp file. + * @param cmd ioctl command. + * @param arg ioctl argument. + * @return non-zero for error. + */ +int otg_message_proc_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg) +{ + return otg_message_ioctl(inode, filp, cmd, arg); +} + +/*! + * @param filp file. + * @param wait buffer to fill + * @return number of bytes or negative number for error + */ +unsigned int otg_message_proc_poll(struct file *filp, struct poll_table_struct *wait) +{ + return otg_message_poll(filp, wait); +} + +/*! + * @var otg_message_proc_switch_functions + */ +static struct file_operations otg_message_proc_switch_functions = { + ioctl:otg_message_proc_ioctl, + poll:otg_message_proc_poll, + read:otg_message_proc_read, +}; + + +/*! + * otg_message_init_l24() - initialize + * @param otg - otg instance pointer + */ +int otg_message_init_l24(struct otg_instance *otg) +{ + struct proc_dir_entry *message = NULL; + init_waitqueue_head(&otg_message_queue); + RETURN_EINVAL_UNLESS((otg->message_task = otg_task_init2("otgmessage", otg_message_task, (void *) otg, CORE))); + + //otg->message_task->debug = TRUE; + + otg_task_start(otg->message_task); + + THROW_IF (!(message = create_proc_entry ("otg_message", 0666, 0)), error); + message->proc_fops = &otg_message_proc_switch_functions; + CATCH(error) { + if (message) + remove_proc_entry("otg_message", NULL); + if (otg->message_task) { + otg_task_exit(otg->message_task); + otg->message_task = NULL; + } + + return -EINVAL; + + } + return 0; +} + +/*! + * otg_message_exit_l24() - exit + * @param otg - otg instance pointer + */ +void otg_message_exit_l24(struct otg_instance *otg) +{ + remove_proc_entry("otg_message", NULL); + otg_task_exit(otg->message_task); + otg->message_task = NULL; +} + +OTG_EXPORT_SYMBOL(otg_message); + +#endif /* defined(CONFIG_OTG_LNX) */ diff --git a/drivers/otg/otgcore/otg-mesg.c b/drivers/otg/otgcore/otg-mesg.c new file mode 100644 index 000000000000..d8ceeaceb9c4 --- /dev/null +++ b/drivers/otg/otgcore/otg-mesg.c @@ -0,0 +1,416 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/otgcore/otg-mesg.c - OTG state machine Administration + * @(#) sl@belcarra.com/whiskey.enposte.net|otg/otgcore/otg-mesg.c|20061220065259|48878 + * + * Copyright (c) 2004-2005 Belcarra Technologies Corp + * Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + * + * By: + * Stuart Lynne <sl@lbelcarra.com>, + * Bruce Balden <balden@belcarra.com> + * + */ +/*! + * @file otg/otgcore/otg-mesg.c + * @brief OTG Core Message Facility. + * + * The OTG Core Message Facility has two functions: + * + * 1. implement a message queue for messages from the otg state machine to the otg + * management application + * + * 2. implement an ioctl control mechanism allowing the otg managment application + * to pass data and commands to the otg state machine + * + * @ingroup OTGMESG + */ + +#include <otg/otg-compat.h> +#include <otg/otg-module.h> + +#include <otg/usbp-chap9.h> +#include <otg/usbp-func.h> +#include <otg/usbp-bus.h> +#include <otg/otg-trace.h> +#include <otg/otg-api.h> +#include <otg/otg-tcd.h> +#include <otg/otg-hcd.h> +#include <otg/otg-pcd.h> +#include <otg/otg-ocd.h> + +//#if defined(OTG_LINUX) +//#include <linux/poll.h> +//#include <linux/sched.h> +//#endif /* defined(OTG_LINUX) */ + +//#define OTG_MESSAGE_BUF_SIZE 512 +#define OTG_MESSAGE_BUF_SIZE 2048 + +struct otg_instance * mesg_otg_instance; + +char *otg_message_buf; // buffer +char *otg_message_head; // next available empty space +char *otg_message_tail; // next available data if not equal to head + +/*! + * otg_data_avail() - amount of data in buffer + * space data + * tp hp 20 - (10 - 5) = 15 (10 - 5) + * 0......5.data.10......20 + * + * hp tp (10 - 5) = 5 20 - (10 - 5) + * 0.data.5......10.data.20 + * + * @return Bytes available in message ring buffer. + */ +int otg_data_avail(void) +{ + int avail = (otg_message_head >= otg_message_tail) ? + (otg_message_head - otg_message_tail) : + OTG_MESSAGE_BUF_SIZE - (otg_message_tail - otg_message_head) ; + + //TRACE_MSG4(CORE, "buf: %p head: %p tail: %p data avail: %d", otg_message_buf, otg_message_head, otg_message_tail, avail); + return avail; +} + +/*! + * otg_space_avail() - amount of space in buffer + * + * @return Space available in message ring buffer. + */ +int otg_space_avail(void) +{ + int avail = (otg_message_head >= otg_message_tail) ? + OTG_MESSAGE_BUF_SIZE - (otg_message_head - otg_message_tail) : + (otg_message_tail - otg_message_head) ; + + //TRACE_MSG4(CORE, "buf: %p head: %p tail: %p space avail: %d", otg_message_buf, otg_message_head, otg_message_tail, avail); + return avail; +} + +/*! + * otg_get_byte() - return next byte + * + * @return Next byte in message ring buffer + */ +char otg_get_byte(void) +{ + char c = *otg_message_tail; + + //TRACE_MSG3(CORE, "buf: %p head: %p tail: %p", otg_message_buf, otg_message_head, otg_message_tail); + if ((otg_message_tail - otg_message_buf) == OTG_MESSAGE_BUF_SIZE) + otg_message_tail = otg_message_buf; + else + otg_message_tail++; + + return c; +} + +/*! + * otg_put_byte() - put byte into buffer + * + * Put byte into message ring buffer + * + * @param byte put into message ring. + */ +void otg_put_byte(char byte) +{ + *otg_message_head = byte; + if ((otg_message_head - otg_message_buf) == OTG_MESSAGE_BUF_SIZE) + otg_message_head = otg_message_buf; + else + otg_message_head++; + +} + +/*! + * otg_write_message_irq() - queue message data (interrupt version) + * + * Attempt to write message data into ring buffer, return amount + * of data actually written. + * @param otg - otg instance pointer + * @param buf - pointer to data + * @param size - amount of available data + * @return number of bytes that where not written. + */ +static int otg_write_message_irq (struct otg_instance *otg, char *buf, int size) +{ + struct pcd_instance *pcd = NULL; + struct usbd_bus_instance *bus = NULL; + + pcd = (struct pcd_instance *)mesg_otg_instance->pcd; + if (pcd) + bus = pcd->bus; + + if (size >= (OTG_MESSAGE_BUF_SIZE - 2)) + return size; + + while (otg_space_avail() < (size + 1)) + otg_get_byte(); + + + /* copy, note that we don't have to test for space as we have + * already ensured that there is enough room for all of the data + */ + while (size--) + otg_put_byte(*buf++); + + //otg_put_byte('\n'); + + return size; +} + +/*! + * otg_write_message() - queue message data + * + * Attempt to write message data into ring buffer, return amount + * of data actually written. + * @param otg - otg instance pointer + * @param buf - pointer to data + * @param size - amount of available data + * @return number of bytes that where not written. + */ +int otg_write_message (struct otg_instance *otg, char *buf, int size) +{ + int rc; + otg_pthread_mutex_lock(&mesg_otg_instance->mutex); /* lock mutex */ + rc = otg_write_message_irq(otg, buf, size); + otg_pthread_mutex_unlock(&mesg_otg_instance->mutex); /* unlock mutex */ + return size; +} + +/*! + * otg_message() - queue message data + * @param otg pointer to otg instance + * @param buf message to send. + */ +void otg_message (struct otg_instance *otg, char *buf) +{ + char time_buf[64]; + static otg_tick_t save_ticks; + otg_tick_t new_ticks; + otg_tick_t ticks; + + new_ticks = otg_tmr_ticks(); + + otg_write_message(otg, buf, strlen(buf)); + + ticks = otg_tmr_elapsed(&new_ticks, &save_ticks); + save_ticks = new_ticks; + + if (ticks < 10000) + sprintf(time_buf, " %ld uS\n", ticks); + + else if (ticks < 10000000) { + //ticks = otg_do_div(ticks, (otg_tick_t)1000); + sprintf(time_buf, " %ld mS\n", ticks / 1000); + } + else { + //ticks = otg_do_div(ticks, (otg_tick_t)1000000); + sprintf(time_buf, " %ld S\n", ticks / 1000000); + } + + //TRACE_MSG3(CORE, "ticks: new_ticks: %d - save_ticks: %d = ticks: %d ", new_ticks, save_ticks, ticks); + //TRACE_STRING(CORE, "time_buf: %s", time_buf); + + otg_write_message(otg, time_buf, strlen(time_buf)); + + if (otg->message_task) + otg_up_work(otg->message_task); +} + + +/*! + * otg_read_message() - get queued message data + * + * @param buf - pointer to data + * @param size - amount of available data + * @return the number of bytes read. + */ +int otg_read_message(char *buf, int size) +{ + int count = 0; + otg_pthread_mutex_lock(&mesg_otg_instance->mutex); /* lock mutex */ + + /* Copy bytes to the buffer while there is data available and space + * to put it in the buffer + */ + for ( ; otg_data_avail() && size--; count++) + BREAK_IF((*buf++ = otg_get_byte()) == '\n'); + + otg_pthread_mutex_unlock(&mesg_otg_instance->mutex); /* unlock mutex */ + return count; +} + +/*! + * otg_data_queued() - return amount of data currently queued + * + * @return Amount of data currently queued in mesage ring buffer. + */ +int otg_data_queued(void) +{ + int count; + otg_pthread_mutex_lock(&mesg_otg_instance->mutex); /* lock mutex */ + count = otg_data_avail(); + otg_pthread_mutex_unlock(&mesg_otg_instance->mutex); /* unlock mutex */ + return count; +} + + +/*! + * otg_message_init() - initialize + * @param otg OTG instance + */ +int otg_message_init(struct otg_instance *otg) +{ + //struct proc_dir_entry *message = NULL; + otg_message_buf = otg_message_head = otg_message_tail = NULL; + THROW_UNLESS((otg_message_buf = CKMALLOC(OTG_MESSAGE_BUF_SIZE + 16)), error); + otg_message_head = otg_message_tail = otg_message_buf ; + + mesg_otg_instance = otg; + CATCH(error) { + #if defined(OTG_LINUX) + #endif /* defined(OTG_LINUX) */ + #if defined(OTG_WINCE) + DEBUGMSG(ZONE_INIT, (_T("otg_message_init: creating //proc//otg_message failed\n")) ); + #endif /* defined(OTG_LINUX) */ + return -EINVAL; + } + return 0; +} + +/*! + * otg_message_exit() - exit + */ +void otg_message_exit(void) +{ + mesg_otg_instance = NULL; + if (otg_message_buf) + LKFREE(otg_message_buf); + otg_message_buf = otg_message_head = otg_message_tail = NULL; +} + + +/* ********************************************************************************************* */ +/* ********************************************************************************************* */ + +/*! + * otg_mesg_get_status_update() - get status update + * @param status_update + */ +void otg_mesg_get_status_update(struct otg_status_update *status_update) +{ + + //TRACE_MSG0(CORE, "--"); + status_update->state = mesg_otg_instance->state; + status_update->meta = mesg_otg_instance->current_outputs->meta; + status_update->inputs = mesg_otg_instance->current_inputs; + status_update->outputs = mesg_otg_instance->outputs; + status_update->capabilities = 0; + + strncpy(status_update->fw_name, otg_firmware_loaded->fw_name, OTGADMIN_MAXSTR); + strncpy(status_update->state_name, otg_get_state_name(mesg_otg_instance->state), OTGADMIN_MAXSTR); + + if (mesg_otg_instance->function_name) + strncpy(status_update->function_name, mesg_otg_instance->function_name, OTGADMIN_MAXSTR); + + TRACE_MSG5(CORE, "state: %02x meta: %02x inputs: %08x outputs: %04x", + status_update->state, status_update->meta, status_update->inputs, + status_update->outputs, status_update->capabilities); +} + + +/*! + * otg_mesg_get_firmware_info() - get firmware info + * @param firmware_info + */ +void otg_mesg_get_firmware_info(struct otg_firmware_info *firmware_info) +{ + memset(firmware_info, 0, sizeof(firmware_info)); + // XXX irq lock + + firmware_info->number_of_states = otg_firmware_loaded->number_of_states; + firmware_info->number_of_tests = otg_firmware_loaded->number_of_tests; + memcpy(&firmware_info->fw_name, otg_firmware_loaded->fw_name, sizeof(firmware_info->fw_name)); + +} + +/*! + * otg_mesg_set_firmware_info() - set firmware info + * @param firmware_info + */ +int otg_mesg_set_firmware_info(struct otg_firmware_info *firmware_info) +{ + + if (otg_firmware_loading) { + + if ( (otg_firmware_loading->number_of_states == firmware_info->number_of_states) && + (otg_firmware_loading->number_of_tests == firmware_info->number_of_tests) + ) { + TRACE_MSG0(CORE, "OTGADMIN_SET_INFO: loaded"); + otg_firmware_loaded = otg_firmware_loading; + otg_firmware_loading = NULL; + return 0; + } + else { + TRACE_MSG0(CORE, "OTGADMIN_SET_INFO: abandoned"); + LKFREE(otg_firmware_loading->otg_states); + LKFREE(otg_firmware_loading->otg_tests); + LKFREE(otg_firmware_loading); + return -EINVAL; + } + } + + TRACE_MSG2(CORE, "OTGADMIN_SET_INFO: otg_firmware_loaded: %x otg_firmware_orig: %x", + otg_firmware_loaded, otg_firmware_orig); + if (otg_firmware_loaded && (otg_firmware_loaded != otg_firmware_orig)) { + LKFREE(otg_firmware_loaded->otg_states); + LKFREE(otg_firmware_loaded->otg_tests); + LKFREE(otg_firmware_loaded); + otg_firmware_loaded = otg_firmware_orig; + TRACE_MSG0(CORE, "OTGADMIN_SET_INFO: unloaded"); + } + if (firmware_info->number_of_states && firmware_info->number_of_tests) { + int size; + otg_firmware_loaded = NULL; + TRACE_MSG0(CORE, "OTGADMIN_SET_INFO: setup"); + THROW_UNLESS((otg_firmware_loading = CKMALLOC(sizeof(struct otg_firmware))), error); + size = sizeof(struct otg_state) * firmware_info->number_of_states; + THROW_UNLESS((otg_firmware_loading->otg_states = CKMALLOC(size)), error); + size = sizeof(struct otg_test) * firmware_info->number_of_tests; + THROW_UNLESS((otg_firmware_loading->otg_tests = CKMALLOC(size)), error); + + otg_firmware_loading->number_of_states = firmware_info->number_of_states; + otg_firmware_loading->number_of_tests = firmware_info->number_of_tests; + memcpy(otg_firmware_loading->fw_name, &firmware_info->fw_name, sizeof(firmware_info->fw_name)); + + CATCH(error) { + TRACE_MSG0(CORE, "OTGADMIN_SET_INFO: setup error"); + if (otg_firmware_loading) { + if (otg_firmware_loading->otg_states) LKFREE(otg_firmware_loading->otg_states); + if (otg_firmware_loading->otg_tests) LKFREE(otg_firmware_loading->otg_tests); + LKFREE(otg_firmware_loading); + return -EINVAL; + } + } + + } + return 0; +} + + +/* ********************************************************************************************* */ +/* ********************************************************************************************* */ diff --git a/drivers/otg/otgcore/otg-trace-lnx.c b/drivers/otg/otgcore/otg-trace-lnx.c new file mode 100644 index 000000000000..ff2ac93e2177 --- /dev/null +++ b/drivers/otg/otgcore/otg-trace-lnx.c @@ -0,0 +1,318 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/otgcore/otg-trace-l24.c + * @(#) sl@belcarra.com/whiskey.enposte.net|otg/otgcore/otg-trace-lnx.c|20061218212925|12732 + * + * Copyright (c) 2004-2005 Belcarra + * Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + * + * By: + * Stuart Lynne <sl@belcarra.com>, + * + */ +/*! + * @file otg/otgcore/otg-trace-lnx.c + * @brief OTG Core Linux Debug Trace Facility + * + * This allows access to the OTG Core Trace via /proc/trace_otg. Each time + * this file is opened and read it will contain a snapshot of the most + * recent trace messages. + * + * Reading the trace resets it, the next read will open a new snapshot. + * + * N.B. There is no protection from multiple reads. + * + * N.B. This is for debugging and is not normally enabled for production software. + * + * @ingroup OTGTRACE + * @ingroup LINUXOS + */ + +#include <otg/otg-compat.h> +#if defined(CONFIG_OTG_LNX) || defined(_OTG_DOXYGEN) + +#include <otg/otg-module.h> +#include <linux/vmalloc.h> +#include <stdarg.h> + +#include <otg/usbp-chap9.h> +#include <otg/usbp-func.h> +#include <otg/usbp-bus.h> + +#include <otg/otg-trace.h> +#include <otg/otg-api.h> + +#if defined(CONFIG_OTG_TRACE) || defined(CONFIG_OTG_TRACE_MODULE) || defined(_OTG_DOXYGEN) + + +extern otg_trace_snapshot otg_snapshot1; +extern otg_trace_snapshot otg_snapshot2; + +extern int otg_trace_exiting; +extern otg_trace_snapshot *otg_trace_current_traces; +extern otg_trace_snapshot *otg_trace_other_traces; +extern otg_pthread_mutex_t otg_trace_mutex; + + + + +/* * + * otg_trace_proc_read_l24 - implement proc file system read. + * @file + * @buf + * @count + * @pos + * + * Standard proc file system read function. + */ +static ssize_t otg_trace_proc_read_l24 (struct file *file, char *buf, size_t count, loff_t * pos) +{ + unsigned long page; + int len = 0; + int index; + int oindex; + int rc; + + otg_tick_t ticks = 0; + + unsigned char *cp; + int skip = 0; + + otg_trace_t px; + otg_trace_t ox; + otg_trace_t vx; + otg_trace_t *o, *p; + + RETURN_ENOMEM_IF (!(page = GET_KERNEL_PAGE())); + + //_MOD_INC_USE_COUNT; + + // Lock out tag changes while reading + //DOWN(&otg_trace_sem_l24); + //while(otg_sem_wait(&otg_trace_sem_l24)); + len = otg_trace_proc_read((char *)page, count, (int *)pos); + + if (len > count) { + //printk(KERN_INFO"%s: EINVAL\n", __FUNCTION__); + len = -EINVAL; + } + + else if (len > 0 && copy_to_user (buf, (char *) page, len)) { + + printk(KERN_INFO"%s: EFAULT\n", __FUNCTION__); + len = -EFAULT; + } + + //free_page (page); + //UP(&otg_trace_sem_l24); + //otg_sem_post(&otg_trace_sem_l24); + free_page (page); + //_MOD_DEC_USE_COUNT; + return len; +} + +#define MAX_TRACE_CMD_LEN 64 +#if 1 +/*! otg_trace_proc_write - implement proc file system write. + * @param buf - command message buffer + * @param count- message length + * @param pos - trace slot to write to + * @return count + * + */ +int otg_trace_proc_write (const char *buf, int count, int * pos) +{ +#define MAX_TRACE_CMD_LEN 64 + char command[MAX_TRACE_CMD_LEN+1]; + size_t n = count; + size_t l; + char c; + + if (n > 0) { + l = MIN(n,MAX_TRACE_CMD_LEN); + if (copy_from_user (command, buf, l)) + count = -EFAULT; + else { + n -= l; + while (n > 0) { + if (copy_from_user (&c, buf + (count - n), 1)) { + count = -EFAULT; + break; + } + n -= 1; + } + // Terminate command[] + if (l > 0 && command[l-1] == '\n') { + l -= 1; + } + command[l] = 0; + } + } + + if (0 >= count) { + return count; + } + + + return count; +} +#endif +/* * + * otg_trace_proc_write_l24 - implement proc file system write. + * @file + * @buf + * @count + * @pos + * + * Proc file system write function. + */ +static ssize_t otg_trace_proc_write_l24 (struct file *file, const char *buf, size_t count, loff_t * pos) +{ + char command[MAX_TRACE_CMD_LEN+1]; + size_t l = MIN(count, MAX_TRACE_CMD_LEN); + + RETURN_ZERO_UNLESS(count); + RETURN_EINVAL_IF (copy_from_user (command, buf, l)); + + + RETURN_EINVAL_IF(copy_from_user (command, buf, l)); + + return otg_trace_proc_write(command, l, (int *)pos); +#if 0 + else { + n -= l; + while (n > 0) { + if (copy_from_user (&c, buf + (count - n), 1)) { + count = -EFAULT; + break; + } + n -= 1; + } + // Terminate command[] + if (l > 0 && command[l-1] == '\n') { + l -= 1; + } + command[l] = 0; + } +#endif +} + +/* Module init ************************************************************** */ + + + +static struct file_operations otg_trace_proc_operations_functions = { + read: otg_trace_proc_read_l24, + write: otg_trace_proc_write_l24, +}; + + +int otg_trace_modinit(void); +void otg_trace_modexit(void); +void otg_trace_init_snapshot(otg_trace_snapshot *tss); + +otg_trace_snapshot *test; + +/*! rel_snapshot - release trace snapshot + * + * @param tss - pointer to otg_trace_snapshot + * @return NULL + */ + +otg_trace_snapshot *rel_snapshot_lnx(otg_trace_snapshot *tss) +{ + RETURN_NULL_UNLESS(tss); + if (tss->traces) { + vfree(tss->traces); + tss->traces = NULL; + } + return NULL; +} + +/*! otg_trace_snapshot - allocate otg trace snapshot memory resource + * + * @param tss - pointer to otg_trace_snapshot + * @return otg_trace_snapshot point on success + */ + +otg_trace_snapshot *get_snapshot_lnx(otg_trace_snapshot *tss) +{ + UNLESS ((tss->traces = vmalloc(sizeof(otg_trace_t) * TRACE_MAX))) { +// printk(KERN_ERR "Cannot allocate memory for OTG trace log.\n"); + return rel_snapshot_lnx(tss); + } + otg_trace_init_snapshot(tss); + return tss; +} + + +/*! otg_trace_modinit_lnx + * + * Return non-zero if not successful. + */ +int otg_trace_modinit_lnx (void) +{ + struct proc_dir_entry *p; + + otg_trace_current_traces = otg_trace_other_traces = NULL; + + UNLESS ((otg_trace_current_traces = get_snapshot_lnx(&otg_snapshot1)) && + (otg_trace_other_traces = get_snapshot_lnx(&otg_snapshot2))) + { + printk(KERN_INFO"%s: ERROR\n", __FUNCTION__); + otg_trace_current_traces = rel_snapshot_lnx(otg_trace_current_traces); + otg_trace_other_traces = rel_snapshot_lnx(otg_trace_other_traces); + return -ENOMEM; + } + + RETURN_EINVAL_IF(otg_trace_modinit()); + + //RETURN_EINVAL_IF(otg_sem_init_unlocked((&otg_trace_sem_l24))); + + // create proc filesystem entries + if ((p = create_proc_entry ("trace_otg", 0, 0)) == NULL) + printk(KERN_INFO"%s PROC FS failed\n", "trace_otg"); + else + p->proc_fops = &otg_trace_proc_operations_functions; + + return 0; +} + +/*! otg_trace_modexit_lnx - remove procfs entry, free trace data space. + */ +void otg_trace_modexit_lnx (void) +{ + otg_trace_t *p; + unsigned long flags; + otg_trace_snapshot *c,*o; + + remove_proc_entry ("trace_otg", NULL); + + c = otg_trace_current_traces; + o = otg_trace_other_traces; + + otg_trace_modexit(); + + rel_snapshot_lnx(c); + rel_snapshot_lnx(o); +} + +#else /* defined(CONFIG_OTG_TRACE) || defined(CONFIG_OTG_TRACE_MODULE) */ +//int otg_trace_init_l24 (void) {return 0;} +//void otg_trace_exit_l24 (void) {} +void otg_trace_modexit_lnx (void) {} +int otg_trace_modinit_lnx (void) {return 0;} +#endif /* defined(CONFIG_OTG_TRACE) || defined(CONFIG_OTG_TRACE_MODULE) */ + +#endif /* defined(CONFIG_OTG_LNX) */ diff --git a/drivers/otg/otgcore/otg-trace.c b/drivers/otg/otgcore/otg-trace.c new file mode 100644 index 000000000000..fd71e7f4863b --- /dev/null +++ b/drivers/otg/otgcore/otg-trace.c @@ -0,0 +1,881 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/otgcore/otg-trace.c + * @(#) balden@belcarra.com/seth2.rillanon.org|otg/otgcore/otg-trace.c|20070529052439|20500 + * + * Copyright (c) 2004-2005 Belcarra Technologies Corp + * Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + * + * By: + * Stuart Lynne <sl@belcarra.com>, + * + */ +/*! + * @file otg/otgcore/otg-trace.c + * @brief OTG Core Debug Trace Facility + * + * This implements the OTG Core Trace Facility. + * + * @ingroup OTGTRACE + */ + +#include <otg/otg-compat.h> +#include <otg/otg-module.h> + +//#if defined(OTG_LINUX) +//#include <linux/vmalloc.h> +//#include <stdarg.h> +//#endif /* defined(OTG_LINUX) */ + +#include <otg/usbp-chap9.h> +#include <otg/usbp-func.h> +#include <otg/usbp-bus.h> + +#include <otg/otg-trace.h> +#include <otg/otg-api.h> + +//#if defined(LINUX24) +//#include <asm/div64.h> +//#endif + +#define STATIC static + +#if defined(CONFIG_OTG_TRACE) || defined(CONFIG_OTG_TRACE_MODULE) || defined(_OTG_DOXYGEN) + +otg_sem_t (otg_trace_sem); +extern framenum_t otg_hcd_ops_framenum; +extern framenum_t otg_pcd_ops_framenum; +extern otg_tick_t (* otg_ocd_ops_ticks) (void); +extern otg_tick_t (* otg_ocd_ops_elapsed) (otg_tick_t *, otg_tick_t *); +extern u32 *otg_interrupts; + + + +#define OTG_TRACE_NAME "trace_otg" + +#if 0 +/*! + * @typedef struct otg_trace_snapshot + */ +typedef struct otg_trace_snapshot { + int first; + int next; + int total; + otg_trace_t *traces; +} otg_trace_snapshot; +#endif + +otg_trace_snapshot otg_snapshot1; +otg_trace_snapshot otg_snapshot2; + +int otg_trace_exiting; +otg_trace_snapshot *otg_trace_current_traces; +otg_trace_snapshot *otg_trace_other_traces; +otg_pthread_mutex_t otg_trace_mutex; + +/*! otg_trace_init_snapshot - initialize otg_trace_snapshot struct + * + * @param tss - pointer to otg_trace_snapshot + * @return none + */ + +void otg_trace_init_snapshot(otg_trace_snapshot *tss) +{ + tss->first = tss->next = tss->total = 0; + memset(tss->traces,0,sizeof(otg_trace_t) * TRACE_MAX); +} + +#if 0 +/*! rel_snapshot - release trace snapshot + * + * @param tss - pointer to otg_trace_snapshot + * @return NULL + */ + +STATIC otg_trace_snapshot *rel_snapshot(otg_trace_snapshot *tss) +{ + RETURN_NULL_UNLESS(tss); + if (tss->traces) { + vfree(tss->traces); + tss->traces = NULL; + } + return NULL; +} + +/*! otg_trace_snapshot - allocate otg trace snapshot memory resource + * + * @param tss - pointer to otg_trace_snapshot + * @return otg_trace_snapshot point on success + */ + +STATIC otg_trace_snapshot *get_snapshot(otg_trace_snapshot *tss) +{ + unsigned long flags; + UNLESS ((tss->traces = vmalloc(sizeof(otg_trace_t) * TRACE_MAX))) { +// printk(KERN_ERR "Cannot allocate memory for OTG trace log.\n"); + return rel_snapshot(tss); + } + otg_trace_init_snapshot(tss); + return tss; +} +#endif + +#if 0 +/*! + * otg_get_trace_info() - get otg trace information + * @param p - pointer to trace slot + * @return none + */ +void otg_get_trace_info(struct otg_instance *otg, otg_trace_t *p) +{ + #if defined(CONFIG_OTG_TRACE) || defined(CONFIG_OTG_TRACE_MODULE) + RETURN_UNLESS( (p) ); + + //p->hcd_pcd = 0; + + // XXX p->id_gnd = otg_instance_info->current_inputs & ID_GND ? 1 : 0; + // XXX if (hcd_instance_private.active) p->hcd_pcd |= 0x1; + // XXX if (pcd_instance_private.active) p->hcd_pcd |= 0x2; + + //printk(KERN_INFO"%s:\n", __FUNCTION__); + p->ticks = (otg_ocd_ops_ticks) ? otg_ocd_ops_ticks () : 0; + p->interrupts = otg_interrupts ? *otg_interrupts : 0; + p->h_framenum = ((otg && otg_hcd_ops_framenum) ? otg_hcd_ops_framenum(otg) : 0); + p->p_framenum = ((otg && otg_pcd_ops_framenum) ? otg_pcd_ops_framenum(otg) : 0); + if (in_interrupt()) p->flags |= OTG_TRACE_IN_INTERRUPT; + //p->in_interrupt = in_interrupt(); + #endif /* defined(CONFIG_OTG_TRACE) || defined(CONFIG_OTG_TRACE_MODULE) */ +} +#endif + + +/*! otg_trace_get_next - get snapshot info and move to next trace slot + * + * @param tag - otg_tag type + * @param fn - function pointer + * @param otg_trace_type otg trace type + * @return next trace slot + */ + +STATIC otg_trace_t *otg_trace_get_next(otg_tag_t tag, const char *fn, otg_trace_types_t otg_trace_type) +{ + otg_trace_snapshot *tss; + otg_trace_t *p; + int first, next, last; + int gap; + + UNLESS(tag) { + printk(KERN_INFO"%s: TAG NULL\n", __FUNCTION__); + return NULL; + } + UNLESS ((tss = otg_trace_current_traces)) { + printk(KERN_INFO"%s: TSS NULL\n", __FUNCTION__); + return NULL; + } + UNLESS(tss->traces) { + printk(KERN_INFO"%s: TSS->traces NULL\n", __FUNCTION__); + return NULL; + } + UNLESS((p = tss->traces + tss->next)) { + printk(KERN_INFO"%s: p NULL\n", __FUNCTION__); + return NULL; + } + + /* advance */ + next = tss->next; + first = tss->first; + + gap = (first <= next) ? (TRACE_MAX - next) + first : first - next; + + if (1 > gap) first = first + 0x200; + + last = next + 1; + first &= TRACE_MASK; + last &= TRACE_MASK; + + tss->total += 1; + tss->next = last; + tss->first = first; + + p->otg_trace_type = otg_trace_type; + p->tag = tag->tag; + p->function = fn; + + otg_get_trace_info(tag->otg, p); + + return p; +} + + +/*! otg_trace_dump - setup otg trace with setup information + * + * @param tag - otg tag + * @param fn - function pointe + * @param trace_type + * @param len + * @param dump - pointer to setup information + */ +void otg_trace_dump(otg_tag_t tag, const char *fn, otg_trace_types_t trace_type, int len, void *dump) +{ + while (len > 0) { + otg_trace_t *p; + int bytes = MIN(len, sizeof(p->trace.dump)); + + otg_pthread_mutex_lock(&otg_trace_mutex); /* lock mutex */ + if ((p = otg_trace_get_next(tag, fn, trace_type))) { + p->va_num_args = bytes; + memcpy(&p->trace.dump, dump, bytes); + } + otg_pthread_mutex_unlock(&otg_trace_mutex); /* lock mutex */ + len -= bytes; + dump += bytes; + } +} + +/*! otg_trace_setup - setup otg trace with setup information + * + * @param tag - otg tag + * @param fn - function pointer + * @param setup - pointer to setup information + */ +void otg_trace_setup(otg_tag_t tag, const char *fn, void *setup) +{ + otg_trace_t *p; + otg_pthread_mutex_lock(&otg_trace_mutex); /* lock mutex */ + if ((p = otg_trace_get_next(tag, fn, otg_trace_setup_n))) { + p->va_num_args = 0; + memcpy(&p->trace.setup, setup, sizeof(p->trace.setup) /*sizeof(struct usbd_device_request)*/); + } + otg_pthread_mutex_unlock(&otg_trace_mutex); /* lock mutex */ +} + +/*! otg_trace_string - put trace message in snapshot + * + * @param tag - otg tag + * @param fn - function pointer + * @param fmt - used for formatting trace message + * @param str - trace message pointer + * @return none + */ +void otg_trace_string(otg_tag_t tag, const char *fn, char *fmt, void *str) +{ + otg_trace_t *p; + int len = MIN(strlen(str), sizeof(p->trace.string)); + otg_pthread_mutex_lock(&otg_trace_mutex); /* lock mutex */ + if ((p = otg_trace_get_next(tag, fn, otg_trace_string_n))) { + p->va_num_args = len; + p->fmt = fmt; + memcpy(&p->trace.string, str, len); + } + otg_pthread_mutex_unlock(&otg_trace_mutex); /* lock mutex */ +} + +/*! otg_trace_elapsed - put trace message in snapshot + * + * @param tag - otg tag + * @param fn - function pointer + * @param fmt - used for formatting trace message + * @param id - + * @param ticks + * @return none + */ +void otg_trace_elapsed(otg_tag_t tag, const char *fn, char *fmt, u32 id, otg_tick_t ticks) +{ + otg_trace_t *p; + otg_pthread_mutex_lock(&otg_trace_mutex); /* lock mutex */ + if ((p = otg_trace_get_next(tag, fn, otg_trace_elapsed_n))) { + p->va_num_args = 0; + p->fmt = fmt; + p->trace.ticks = ticks; + p->trace.id = id; + } + otg_pthread_mutex_unlock(&otg_trace_mutex); /* lock mutex */ +} + +extern void otg_trace_copy(u32 *p, u32 val); + +/*! otg_trace_msg - write trace message to trace slot + * + * @param tag - otg tag + * @param fn - function pointer + * @param nargs - argument number + * @param fmt - pointer to format string + * @param a1, a2, a3, a4, a5, a6, a7, a8 + * @return none + */ + +void otg_trace_msg(otg_tag_t tag, const char *fn, u8 nargs, char *fmt, u32 a1, u32 a2, u32 a3, u32 a4, + u32 a5, u32 a6, u32 a7, u32 a8) +{ + //int n; + //otg_trace_t trace; + //otg_trace_t *p1 = &trace; + otg_trace_t *p2 = NULL; + + /* Figure out how many trace slots we're going to need. */ + + UNLESS(tag) { + //printk(KERN_INFO"%s: TAG NULL\n", __FUNCTION__); + return; + } + UNLESS(nargs <= (OTG_TRACE_MAX_IN_VA)) { + printk(KERN_INFO"%s: NARGS to large (%d > %d)\n", __FUNCTION__, nargs, OTG_TRACE_MAX_IN_VA); + return; + } + + #if 0 + memset(p1, 0, sizeof(trace)); + + // otg_get_trace_info(tag->otg, p1); + p->otg_trace_type = otg_trace_type; + p->tag = tag->tag; + p->function = fn; + + p1->otg_trace_type = otg_trace_msg_va_start_n; + p1->tag = tag->tag; + p1->function = fn; + + p1->fmt = fmt; + p1->va_num_args = nargs; + + p1->trace.val[0] = a1; + p1->trace.val[1] = a2; + p1->trace.val[2] = a3; + p1->trace.val[3] = a4; + p1->trace.val[4] = a5; + p1->trace.val[5] = a6; + p1->trace.val[6] = a7; + p1->trace.val[7] = a8; + #else + #endif + + otg_pthread_mutex_lock(&otg_trace_mutex); /* lock mutex */ + + if((p2 = otg_trace_get_next(tag, fn, otg_trace_msg_va_start_n))) { + + #if 0 + memcpy(p2, p1, sizeof(otg_trace_t)); + #else + p2->otg_trace_type = otg_trace_msg_va_start_n; + p2->tag = tag->tag; + p2->function = fn; + + p2->fmt = fmt; + p2->va_num_args = nargs; + + p2->trace.val[0] = a1; + p2->trace.val[1] = a2; + p2->trace.val[2] = a3; + p2->trace.val[3] = a4; + p2->trace.val[4] = a5; + p2->trace.val[5] = a6; + p2->trace.val[6] = a7; + p2->trace.val[7] = a8; + #endif + } + + otg_pthread_mutex_unlock(&otg_trace_mutex); /* lock mutex */ +} + +/* Proc Filesystem *************************************************************************** */ + +/*! int slot_in_data - validate slot region + * + * @param tss - otg trace snapshot pointer + * @param slot - slot number to validate + * @return 1 for valid, otherwise 0 + */ +STATIC int slot_in_data(otg_trace_snapshot *tss, int slot) +{ + // Return 1 if slot is in the valid data region, 0 otherwise. + return (((tss->first < tss->next) && (slot >= tss->first) && (slot < tss->next)) || + ((tss->first > tss->next) && ((slot < tss->next) || (slot >= tss->first)))); + +} + +/*! otg_trace_read_slot - Place a slot pointer in *ps (and possibly *pv), with the older entry (if any) in *po. + * + * @param tss - pointer to otg trace snapshot + * @param index + * @param ps - pointer to slot pointer + * @param po - pointer to slot pointer + * Return: -1 at end of data + * 0 if not a valid enty + * 1 if entry valid but no older valid entry + * 2 if both entry and older are valid. + */ +STATIC int otg_trace_read_slot(otg_trace_snapshot *tss, int index, otg_trace_t **ps, otg_trace_t **po) +{ + int res; + int previous; + otg_trace_t *s,*o; + + *ps = *po = NULL; + index = (tss->first + index) & TRACE_MASK; + // Are we at the end of the data? + if (!slot_in_data(tss,index)) { + // End of data. + return -1; + } + /* Nope, there's data to show. */ + s = tss->traces + index; + if (!s->tag || + s->otg_trace_type == otg_trace_msg_invalid_n + /* || s->otg_trace_type == otg_trace_msg_va_list_n */ + ) { + /* Ignore this slot, it's not valid, or is part + of a previously processed varargs. */ + return 0; + } + *ps = s; + res = 1; + + // Is there a previous event (for "ticks" calculation)? + previous = (index - 1) & TRACE_MASK; + if (previous != tss->next && tss->total > 1) { + // There is a valid previous event. + res = 2; + o = tss->traces + previous; + #if 0 + /* If the previous event was a varargs event, we want + a copy of the first slot, not the last. */ + while (o->otg_trace_type == otg_trace_msg_va_list_n) { + previous = (previous - 1) & TRACE_MASK; + if (previous == tss->next) { + res = 1; + o = NULL; + break; + } + o = tss->traces + previous; + } + #endif + *po = o; + } + + return res; +} + +#if defined(CONFIG_OTG_TRACE) + +/*! otg_trace_proc_read - implement proc file system read. + * + * @brief Standard proc file system read function. + * @param page -pointer to char to stroe read message + * @param count + * @param entry position pointer to read + * @return read message length + */ +int otg_trace_proc_read(char *page, int count, int * pos) +{ + int len = 0; + int index; + int oindex; + int rc; + + otg_tick_t ticks = 0; + + unsigned char *cp; + int skip = 0; + char hcd_pcd; + char str[256]; + int i; + struct usbd_device_request *request; + + otg_trace_t *o; + otg_trace_t *s; + otg_trace_snapshot *tss; + + while (otg_sem_wait(&otg_trace_sem)); + + /* Grab the current snapshot, and replace it with the other. This + * needs to be atomic WRT otg_trace_msg() calls. + */ + UNLESS (*pos) { + // unsigned long flags; + tss = otg_trace_other_traces; + otg_trace_init_snapshot(tss); // clear previous + otg_pthread_mutex_lock(&otg_trace_mutex); /* lock mutex */ + otg_trace_other_traces = otg_trace_current_traces; // swap snapshots + otg_trace_current_traces = tss; + otg_pthread_mutex_unlock(&otg_trace_mutex); /* lock mutex */ + } + + tss = otg_trace_other_traces; + + /* Get the index of the entry to read + */ + s = o = NULL; + oindex = index = (*pos)++; + do { + rc = otg_trace_read_slot(tss,index,&s,&o); + switch (rc) { + case -1: // End of data. + otg_sem_post(&otg_trace_sem); + return 0; + + case 0: // Invalid slot, skip it and try the next. + index = (*pos)++; + break; + + case 1: // s valid, o NULL + case 2: // s and o valid + break; + } + } while (rc == 0); + + len = 0; + + UNLESS (oindex) + len += sprintf((char *) page + len, "Tag Ints Framenum Ticks\n"); + + + /* If there is a previous trace event, we want to calculate how many + * ticks have elapsed siince it happened. Unfortunately, determining + * if there _is_ a previous event isn't obvious, since we have to watch + * out for startup, varargs and wraparound. + */ + if (o) { + + ticks = otg_tmr_elapsed(&s->ticks, &o->ticks); + + if ((o->interrupts != s->interrupts) || (o->tag != s->tag) || + ((o->flags & OTG_TRACE_IN_INTERRUPT) != (s->flags & OTG_TRACE_IN_INTERRUPT)) + ) + skip++; + } + + switch (s->flags & OTG_TRACE_HCD_PCD) { + default: + case 0: hcd_pcd = ' '; break; + case 1: hcd_pcd = 'H'; break; + case 2: hcd_pcd = 'P'; break; + case 3: hcd_pcd = 'B'; break; + } + + len += sprintf((char *) page + len, "%s%02x %c%c %6x ", skip?"\n":"", + s->tag, (s->flags & OTG_TRACE_ID_GND) ? 'A' : 'B', hcd_pcd, s->interrupts); + + + //len += sprintf((char *) page + len, "%05x %05x ", s->h_framenum & 0x7fff, s->p_framenum & 0x7fff); + len += sprintf((char *) page + len, "%06d ", s->p_framenum & 0x7fff); + + + if (ticks < 10000) + len += sprintf((char *) page + len, "%6duS", (int)ticks); + else if (ticks < 10000000) { + //ticks = otg_do_div(ticks, (otg_tick_t)1000); + len += sprintf((char *) page + len, "%6dmS", (int)ticks / 1000); + } + else { + //ticks = otg_do_div(ticks, (otg_tick_t)1000000); + len += sprintf((char *) page + len, "%6dS ", (int)ticks / 1000000); + } + + ticks = 0LL; + + + ticks = otg_tmr_elapsed(&ticks, &s->ticks) / 1000; + //ticks = otg_do_div(ticks, (otg_tick_t)1000); + + len += sprintf((char *) page + len, "%6dmS", (int)(ticks & 0xffff)); + + + len += sprintf((char *) page + len, " %s ", s->flags & OTG_TRACE_IN_INTERRUPT ? "--" : "=="); + len += sprintf((char *) page + len, "%-24s: ",s->function); + + + switch (s->otg_trace_type) { + case otg_trace_msg_invalid_n: + len += sprintf((char *) page + len, " -- N/A"); + break; + + case otg_trace_msg_va_start_n: + + len += sprintf((char *) page + len, s->fmt, + s->trace.val[0], s->trace.val[1], s->trace.val[2], + s->trace.val[3], s->trace.val[4], s->trace.val[5], + s->trace.val[6], s->trace.val[7], s->trace.val[8] + ); + + break; + + case otg_trace_string_n: + i = s->va_num_args; + memcpy(str, &s->trace.string, i); + str[i] = '\0'; + len += sprintf((char *) page + len, s->fmt, str); + break; + + case otg_trace_elapsed_n: + ticks = otg_tmr_elapsed(&s->ticks, &s->trace.ticks); + + i = s->va_num_args; + len += sprintf((char *) page + len, "ELAPSED %s ", s->fmt); + if (ticks < 10000) + len += sprintf((char *) page + len, "%duS", ticks); + else { + //ticks = otg_do_div(ticks, (otg_tick_t)1000); + len += sprintf((char *) page + len, "%dmS", ticks / 1000); + } + len += sprintf((char *) page + len, " (%x)", s->trace.id); + break; + + case otg_trace_recv_n: + case otg_trace_send_n: + cp = (unsigned char *)&s->trace.dump; + + len += sprintf((char *) page + len, "%s [ ", (s->otg_trace_type == otg_trace_recv_n) ? "RECV" : "SEND" ); + for (i = 0; i < s->va_num_args; i++) { + len += sprintf((char *) page + len, "%02x ", cp[i]); + if ((i % 8) == 7) + len += sprintf((char *) page + len, " "); + } + len += sprintf((char *) page + len, "]"); + break; + + case otg_trace_nrecv_n: + case otg_trace_nsend_n: + cp = (unsigned char *)&s->trace.dump; + + len += sprintf((char *) page + len, "%s [ ", (s->otg_trace_type == otg_trace_nrecv_n) ? "NRCV" : "NSND" ); + for (i = 0; i < s->va_num_args; i++) { + len += sprintf((char *) page + len, "%02x ", cp[i]); + + switch(i) { + case 5: + case 11: + case 13: + case 15: + len += sprintf((char *) page + len, "]["); + break; + default: + break; + } + } + len += sprintf((char *) page + len, "]"); + break; + + case otg_trace_setup_n: + cp = (unsigned char *)&s->trace.setup; + len += sprintf((char *) page + len, + "REQUEST [%02x %02x %02x %02x %02x %02x %02x %02x]", + cp[0], cp[1], cp[2], cp[3], cp[4], cp[5], cp[6], cp[7]); + request = (struct usbd_device_request*) &s->trace.setup; + len += sprintf((char *) page + len, + " bmRequestType:%02x bRequest:%02x wValue:%04x wIndex:%04x wLength:%04x", + request->bmRequestType, + request->bRequest, + le16_to_cpu(request->wValue), + le16_to_cpu(request->wIndex), + le16_to_cpu(request->wLength) + ); + + break; + } + len += sprintf((char *) page + len, "\n"); + + otg_sem_post(&otg_trace_sem); + return len; +} + +#endif + +static u32 otg_tags_mask = 0x0; +extern void otg_trace_exit (void); + +/*! otg_trace_obtain_tag(void) - get next unused tag value + * + * @param data - otg instance pointer + * @param msg - message to initialize newly got tag + * @return tag valude , 0 for unavailable + */ +otg_tag_t otg_trace_obtain_tag(void *data, char *msg) +{ + struct otg_instance *otg = (struct otg_instance *) data; + otg_tag_t tag = NULL; + + + RETURN_NULL_UNLESS((tag = CKMALLOC(sizeof (struct otg_tag)))); + + tag->otg = otg; + tag->tag = 0; + tag->msg = msg; + //strncpy(tag->msg, msg, sizeof(tag->msg)-1); + //tag->msg[sizeof(tag->msg)-1] = '\0'; + + /* Return the next unused tag value [1..32] if one is available, + * return 0 if none is available. + */ + while(otg_sem_wait(&otg_trace_sem)); + if (otg_tags_mask == 0xffffffff || otg_trace_exiting) { + // They're all in use. + otg_sem_post(&otg_trace_sem); + return tag; + } + if (otg_tags_mask != 0x00000000) { + // 2nd or later tag, search for an unused one + while (otg_tags_mask & (0x1 << tag->tag)) { + tag->tag += 1; + } + } + + // Successful 1st or later tag. + otg_tags_mask |= 0x1 << tag->tag; + otg_sem_post(&otg_trace_sem); + tag->tag += 1; + //printk(KERN_INFO"%s: %s %d\n", __FUNCTION__, msg, tag->tag); + return tag; +} + +/*! otg_trace_invalidate_tag(otg_tag_t tag) - invalidate otg trace tag + * + * @param tag - tag to invalidate + * @return 0 on finish + */ +otg_tag_t otg_trace_invalidate_tag(otg_tag_t tag) +{ + otg_trace_t *p,*e; + otg_trace_snapshot *css,*oss; + + //printk(KERN_INFO"%s: %x %s %d\n", __FUNCTION__, tag, tag ? tag->msg : "NULL", tag ? tag->tag : 0); + + RETURN_NULL_UNLESS(tag); + + + // Nothing to do + if ((0 >= tag->tag) || (tag->tag > 32)) { + LKFREE(tag); + return NULL; + } + + // Grab a local copy of the snapshot pointers. + otg_pthread_mutex_lock(&otg_trace_mutex); /* lock mutex */ + css = otg_trace_current_traces; + oss = otg_trace_other_traces; + otg_pthread_mutex_unlock(&otg_trace_mutex); /* lock mutex */ + + + // Run through all the trace messages, and invalidate any with the given tag. + + while(otg_sem_wait(&otg_trace_sem)); + if (css) + for (e = TRACE_MAX + (p = css->traces); p < e; p++) { + if (p->tag == tag->tag) { + p->otg_trace_type = otg_trace_msg_invalid_n; + p->tag = 0; + } + } + if (oss) + for (e = TRACE_MAX + (p = oss->traces); p < e; p++) { + if (p->tag == tag->tag) { + p->otg_trace_type = otg_trace_msg_invalid_n; + p->tag = 0; + } + } + + // Turn off the tag + otg_tags_mask &= ~(0x1 << (tag->tag-1)); + if (otg_tags_mask == 0x00000000) { /* otg_trace_exit (); */ } + otg_sem_post(&otg_trace_sem); + LKFREE(tag); + return NULL; +} + + + +/* Module init ************************************************************** */ + +#define OT otg_trace_init_test +otg_tag_t OT; + +/*! otg_trace_modinit - initialization for otg trace + * + * @return non-zero if not successful. + */ +int otg_trace_modinit (void) +{ + RETURN_EINVAL_IF(otg_sem_init_unlocked("otg_trace", &otg_trace_sem)); + otg_trace_exiting = 0; + + OT = otg_trace_obtain_tag(NULL, "OTG Trace Init"); + RETURN_ENOMEM_IF(!OT); + TRACE_MSG0(OT,"--"); + return 0; +} + +/*! otg_trace_exit - remove procfs entry, free trace data space. + */ +void otg_trace_modexit (void) +{ + struct otg_tag tag; + + // unsigned long flags; + otg_trace_snapshot *c,*o; + + otg_trace_invalidate_tag(OT); + otg_trace_exiting = 1; + + otg_pthread_mutex_lock(&otg_trace_mutex); /* lock mutex */ + + /* Look for any outstanding tags. */ + tag.otg = NULL; + for (tag.tag = 32; otg_tags_mask && tag.tag > 0; tag.tag--) { + + + if (otg_tags_mask & (0x1 << (tag.tag-1))) { + printk(KERN_INFO"%s: ERROR otg_tags_mask: %x %x tag: %d %s\n", __FUNCTION__, + otg_tags_mask, 0x1 << (tag.tag-1), tag.tag, "" /*tag.msg*/); + + //otg_trace_invalidate_tag(&tag); + } + } + + /* traces are cleared here, the os specific function must have saved them */ + c = otg_trace_current_traces; + o = otg_trace_other_traces; + otg_trace_current_traces = otg_trace_other_traces = NULL; + otg_pthread_mutex_unlock(&otg_trace_mutex); /* lock mutex */ + + /* traces must be released in os specific modexit function */ +} + + +OTG_EXPORT_SYMBOL(otg_trace_dump); +OTG_EXPORT_SYMBOL(otg_trace_setup); +OTG_EXPORT_SYMBOL(otg_trace_string); +OTG_EXPORT_SYMBOL(otg_trace_elapsed); + + +#else /* defined(CONFIG_OTG_TRACE) || defined(CONFIG_OTG_TRACE_MODULE) */ +int otg_trace_init (void) {return 0;} +void otg_trace_exit (void) {} +otg_tag_t otg_trace_obtain_tag(void *p,char *msg1) { return NULL; } + +void otg_trace_setup(otg_tag_t tag, const char *fn, void *setup) { } +void otg_trace_msg(otg_tag_t tag, const char *fn, u8 nargs, char *fmt, ...) { } +otg_tag_t otg_trace_invalidate_tag(otg_tag_t tag) { return NULL; } +void otg_trace_modexit (void) { } +int otg_trace_modinit (void) { return 0; } + + +#endif /* defined(CONFIG_OTG_TRACE) || defined(CONFIG_OTG_TRACE_MODULE) */ + +OTG_EXPORT_SYMBOL(otg_trace_msg); +OTG_EXPORT_SYMBOL(otg_trace_obtain_tag); +OTG_EXPORT_SYMBOL(otg_trace_invalidate_tag); + + + +// XXX OTG_EXPORT_SYMBOL(otg_tmr_ticks); +// XXX OTG_EXPORT_SYMBOL(otg_tmr_elapsed); diff --git a/drivers/otg/otgcore/otg.c b/drivers/otg/otgcore/otg.c new file mode 100644 index 000000000000..35ba56a1053f --- /dev/null +++ b/drivers/otg/otgcore/otg.c @@ -0,0 +1,1182 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/otgcore/otg.c - OTG state machine + * @(#) sl@belcarra.com/whiskey.enposte.net|otg/otgcore/otg.c|20070919232149|63517 + * + * Copyright (c) 2004-2005 Belcarra + * Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + * + * By: + * Stuart Lynne <sl@lbelcarra.com>, + * Bruce Balden <balden@belcarra.com> + * + */ +/*! + * @file otg/otgcore/otg.c + * @brief OTG Core State Machine Event Processing + * + * The OTG State Machine receives "input" information passed to it + * from the other drivers (pcd, tcd, hcd and ocd) that describe what + * is happening. + * + * The State Machine uses the inputs to move from state to state. Each + * state defines four things: + * + * 1. Reset - what inputs to set or reset on entry to the state. + * + * 2. Outputs - what output functions to call to set or reset. + * + * 3. Timeout - an optional timeout value to set + * + * 4. Tests - a series of tests that allow the state machine to move to + * new states based on current or new inputs. + * + * @ingroup OTGCORE + */ + +//#define OTG_TRACE_DISABLE +#ifndef OTG_REGRESS + +//#include <sys/time.h> +#include <otg/otg-compat.h> +#include <otg/otg-module.h> + +#include <otg/usbp-chap9.h> +#include <otg/usbp-func.h> +#include <otg/usbp-bus.h> + +#include <otg/otg-trace.h> +#include <otg/otg-api.h> +//#include <otg/otg-task.h> +#include <otg/otg-tcd.h> +#include <otg/otg-hcd.h> +#include <otg/otg-pcd.h> +#include <otg/otg-ocd.h> +//#include <otg/otg-linux.h> + +/* XXX these need to be replaced so that we can + * support multiple devices + */ + +otg_tick_t (* otg_ocd_ops_ticks) (void); +otg_tick_t (* otg_ocd_ops_elapsed) (otg_tick_t *, otg_tick_t *); + +otg_tick_t (* otg_pcd_ops_ticks) (void); +otg_tick_t (* otg_pcd_ops_elapsed) (otg_tick_t *, otg_tick_t *); + +u32 *otg_interrupts; + +framenum_t otg_hcd_ops_framenum; +framenum_t otg_pcd_ops_framenum; + +static char otg_message_buf[256]; + +#if defined (OTG_WINCE) +#define TRACE_SM_CURRENT(t, m, o) \ + +#else +#define TRACE_SM_CURRENT(t, m, o) \ + TRACE_STRING(t, "SM_CURRENT: %s",\ + m \ + ); \ + TRACE_MSG4(t, "SM_CURRENT: RESET: %08x SET: %08x %s (%s)",\ + (u32)(~o->current_inputs), \ + (u32)(o->current_inputs), \ + o->current_outputs->name, \ + o->previous_outputs->name \ + ) +#endif //OTG_WINCE +#define TRACE_SM_CHANGE(t, m, o) \ + TRACE_STRING(t, "SM_CHANGE: %s",\ + m \ + ); \ + TRACE_MSG4(t, "SM_CHANGE: RESET: %08x SET: %08x %s (%s)",\ + (u32)(~o->current_inputs), \ + (u32)(o->current_inputs), \ + o->current_outputs->name, \ + o->previous_outputs->name \ + ) + +#define TRACE_SM_NEW(t, m, o) \ + TRACE_STRING(t, "SM_NEW: %s",\ + m \ + ); \ + TRACE_MSG4(t, "SM_NEW: RESET: %08x SET: %08x %s (%s)",\ + (u32)(~o->current_inputs), \ + (u32)(o->current_inputs), \ + o->current_outputs->name, \ + o->previous_outputs->name \ + ) + + +#define TRACE_SM_INPUTS(t, m, u, c) \ + TRACE_STRING(t, "SM_INPUTS: %s",\ + m \ + ); \ + TRACE_MSG3(t, "SM_INPUTS: RESET: %08x SET: %08x CUR: %08x",\ + (u32)(u >> 32), \ + (u32)(u & 0xffffffff), \ + c \ + ) + +#endif + +//DECLARE_MUTEX (otg_sem); + +/*! + * Output change lookup table - this maps the current output and the desired + * output to what should be done. This is just so that we don't redo changes, + * if the output does not change we do not want to re-call the output function + */ +u8 otg_output_lookup[4][4] = { + /* NC SET RESET PULSE, <--- current/ new */ + { NC, NC, NC, PULSE, }, /* NC */ + { SET, NC, SET, PULSE, }, /* SET */ + { RESET, RESET, NC, PULSE, }, /* RESET */ + { PULSE, PULSE, PULSE, PULSE, }, /* UNKNOWN */ +}; + + +/*! + * otg_new() - check OTG new inputs and select new state to be in + * + * This is used by the OTG event handler to see if the state should + * change based on the current input values. + * + * @param otg pointer to the otg instance. + * @return either the next OTG state or invalid_state if no change. + */ +static int otg_new(struct otg_instance *otg) +{ + int state = otg->state; + struct otg_test *otg_test = otg_firmware_loaded ? otg_firmware_loaded->otg_tests : NULL; + /* get a copy of current inputs */ + otg_current_t input_mask = ((otg_current_t)otg->current_inputs) | ((otg_current_t)(~otg->current_inputs) << 32); + + UNLESS(otg_test) return invalid_state; + + TRACE_MSG3(CORE, "OTG_NEW: %s inputs: %08x %08x", otg_get_state_name(state), + (u32)(input_mask>>32&0xffffffff), (u32)(input_mask&0xffffffff)); + + /* iterate across the otg inputs table, for each entry that matches the current + * state, test the input_mask to see if a state transition is required. + */ + for (; otg_test->target != invalid_state; otg_test++) { + + otg_current_t test1 = otg_test->test1; + otg_current_t test2 = otg_test->test2; + otg_current_t test3 = otg_test->test3; + + CONTINUE_UNLESS(otg_test->state == state); // skip states that don't match + + + TRACE_MSG7(CORE, "OTG_NEW: %s (%s, %d) test1: %08x %08x test2 %08x %08x", + otg_get_state_name(otg_test->state), + otg_get_state_name(otg_test->target), + otg_test->test, + (u32)(test1>>32&0xffffffff), (u32)(test1&0xffffffff), + (u32)(test2>>32&0xffffffff), (u32)(test2&0xffffffff) + ); + + /* the otg inputs table has multiple masks that define between multiple tests. Each + * test is a simple OR to check if specific inputs are present. + */ + CONTINUE_UNLESS ( + (!otg_test->test1 || ((test1 = otg_test->test1 & input_mask))) && + (!otg_test->test2 || ((test2 = otg_test->test2 & input_mask))) && + (!otg_test->test3 || ((test3 = otg_test->test3 & input_mask))) /*&& + (!otg_test->test4 || (otg_test->test4 & input_mask)) */ ); + + TRACE_MSG7(CORE, "OTG_NEW: GOTO %s test1: %08x %08x test2 %08x %08x test3 %08x %08x", + otg_get_state_name(otg_test->target), + (u32)(test1>>32&0xffffffff), (u32)(test1&0xffffffff), + (u32)(test2>>32&0xffffffff), (u32)(test2&0xffffffff), + (u32)(test3>>32&0xffff), (u32)(test3&0xffff) + ); + + return otg_test->target; + } + TRACE_MSG0(CORE, "OTG_NEW: finis"); + + return invalid_state; +} + + +/*! + * otg_write_state_message_irq() - + * + * Send a message to the otg management application. + * @param otg -otg instance pointer + * @param msg + * @param reset + * @param inputs + */ +void otg_write_state_message_irq(struct otg_instance *otg, char *msg, otg_current_t reset, u32 inputs) +{ + sprintf(otg_message_buf, + "State: %s reset: %08x set: %08x inputs: %08x ", msg, (u32)(reset>>32), (u32)(reset&0xffffffff), inputs); + otg_message_buf[95] = '\0'; + otg_message(otg, otg_message_buf); +} + +/*! + * otg_write_output_message_irq() - + * + * Send a message to the otg management application. + * @param otg - otg instance pointer + * @param msg + * @param val + */ +void otg_write_output_message_irq(struct otg_instance *otg, char *msg, int val) +{ + strcpy(otg_message_buf, "Output: "); + strncat(otg_message_buf, msg, 64 - strlen(otg_message_buf)); + if (val == 2) strncat(otg_message_buf, "/", 64 - strlen(otg_message_buf)); + otg_message_buf[sizeof(otg_message_buf)] = '\0'; + otg_message(otg, otg_message_buf); +} + +/*! + * otg_write_timer_message_irq() - + * + * Send a message to the otg management application. + * @param otg - otg instance pointer + * @param msg + * @param val + */ +void otg_write_timer_message_irq(struct otg_instance *otg, char *msg, int val) +{ + if (val < 10000) + sprintf(otg_message_buf, "Timer: %s %d uS %u", msg, val, (u32)otg_tmr_ticks()); + else if (val < 10000000) + //sprintf(otg_message_buf, " %d mS\n", ticks / 1000); + //sprintf(otg_message_buf, "Timer: %s %d mS %u", msg, val >> 10, otg_tmr_ticks()); + sprintf(otg_message_buf, "Timer: %s %d mS %u", msg, val / 1000, (u32)otg_tmr_ticks()); + else + //sprintf(otg_message_buf, " %d S\n", ticks / 1000000); + //sprintf(otg_message_buf, "Timer: %s %d S %u", msg, val >> 20, otg_tmr_ticks()); + sprintf(otg_message_buf, "Timer: %s %d S %u", msg, val / 1000000, (u32)otg_tmr_ticks()); + + otg_message_buf[sizeof(otg_message_buf)] = '\0'; + otg_message(otg, otg_message_buf); +} + +//#if defined(LINUX24) +/*! + * otg_write_info_message() - + * + * Send a message to the otg management application. + * @param privdata - pcd_instance type pointer + * @param msg - + */ +void otg_write_info_message(void *privdata, char *msg) +{ + struct pcd_instance *pcd_instance = (struct pcd_instance *) privdata; + + struct otg_instance *otg = pcd_instance ? pcd_instance->otg : NULL; + + RETURN_UNLESS(otg); + sprintf(otg_message_buf, "Info: %s", msg); + otg_message_buf[sizeof(otg_message_buf)] = '\0'; + otg_message(otg, otg_message_buf); // XXX +} + +#if 0 +/*! + * otg_write_reset_message_irq() - + * + * Send a message to the otg management application. + * @param otg - otg instance pointer + * @param msg + * @param val + */ +void otg_write_reset_message(struct otg_instance *otg, char *msg, int val) +{ + sprintf(otg_message_buf, "State: %s reset: %x", msg, val); + otg_message_buf[sizeof(otg_message_buf)] = '\0'; + otg_message(otg, otg_message_buf); +} +#endif + +//#if defined(LINUX24) +OTG_EXPORT_SYMBOL(otg_write_info_message); +//#endif + +char otg_change_names[4] = { + '_', ' ', '/', '#', +}; + +/*! + * otg_process_event() - process OTG events and determine OTG outputs to reflect new state + * + * This function is passed a mask with changed input values. This + * is passed to the otg_new() function to see if the state should + * change. If it changes then the output functions required for + * the new state are called. + * + * @param otg pointer to the otg instance. + * @param inputs input mask + * @param tag trace tag to use for tracing + * @param msg message for tracing + */ +static void otg_process_event(struct otg_instance *otg, otg_current_t inputs, otg_tag_t tag, char *msg) +{ + u32 current_inputs = otg->current_inputs; + int current_state = otg->state; + u32 inputs_set = (u32)(inputs & 0xffffffff); // get changed inputs that SET something + u32 inputs_reset = (u32) (inputs >> 32); // get changed inputs that RESET something + int target; + + RETURN_UNLESS(otg && inputs); + + TRACE_SM_INPUTS(tag, msg, inputs, current_inputs); + TRACE_MSG2(CORE, "otg->active: %d %s", otg->active, msg); + + /* Special Overrides - These are special tests that satisfy specific injunctions from the + * OTG Specification. + */ + if (inputs_set & inputs_reset) { // verify that there is no overlap between SET and RESET masks + TRACE_MSG1(CORE, "OTG_EVENT: ERROR attempting to set and reset the same input: %08x", inputs_set & inputs_reset); + return; + } + /* don't allow bus_req and bus_drop to be set at the same time + */ + if ((inputs_set & (bus_req | bus_drop)) == (bus_req | bus_drop) ) { + TRACE_MSG2(CORE, "OTG_EVENT: ERROR attempting to set both bus_req and bus_drop: %08x %08x", + inputs_set, (bus_req | bus_drop)); + return; + } + /* set bus_drop_ if bus_req, set bus_req_ if bus_drop + */ + if (inputs_set & bus_req) { + inputs |= bus_drop_; + TRACE_MSG1(CORE, "OTG_EVENT: forcing bus_drop/: %08x", inputs_set); + } + if (inputs_set & bus_drop) { + inputs |= bus_req_; + TRACE_MSG1(CORE, "OTG_EVENT: forcing bus_req/: %08x", inputs_set); + } + + otg->current_inputs &= ~inputs_reset; + otg->current_inputs |= inputs_set; + //TRACE_MSG3(CORE, "OTG_EVENT: reset: %08x set: %08x inputs: %08x", inputs_reset, inputs_set, otg->current_inputs); + + TRACE_SM_CHANGE(tag, msg, otg); + + RETURN_IF(otg->active); + + otg->active++; + + /* Search the state input change table to see if we need to change to a new state. + * This may take several iterations. + */ + while (invalid_state != (target = otg_new(otg))) { + + struct otg_state *otg_state; + //int original_state = otg->state; + otg_current_t current_outputs; + otg_current_t output_results; + otg_current_t new_outputs; + int i; + + /* if previous output started timer, then cancel timer before proceeding + */ + if ((otg_state = otg->current_outputs) && otg_state->tmout && otg->start_timer) { + TRACE_MSG0(CORE, "reseting timer"); + otg->start_timer(otg, 0); + } + + BREAK_UNLESS(otg_firmware_loaded); + + BREAK_UNLESS(target < otg_firmware_loaded->number_of_states); + + otg_state = otg_firmware_loaded->otg_states + target; + + BREAK_UNLESS(otg_state->name); + + otg->previous = otg->state; + otg->state = target; + otg->tickcount = otg_tmr_ticks(); + + + otg->previous_outputs= otg->current_outputs; + //otg->current_outputs = NULL; + + + /* A matching input table rule has been found, we are transitioning to a new + * state. We need to find the new state entry in the output table. + * XXX this could be a table lookup instead of linear search. + */ + + current_outputs = otg->outputs; + new_outputs = otg_state->outputs; + output_results = 0; + + TRACE_SM_NEW(tag, msg, otg); + + /* reset any inputs that the new state want's reset + */ + otg_process_event(otg, otg_state->reset | TMOUT_, tag, otg_state->name); + + otg_write_state_message_irq(otg, otg_state->name, otg_state->reset, otg->current_inputs); + +#if 1 + switch (target) { /* C.f. 6.6.1.12 b_conn reset */ + case otg_disabled: + otg->current_inputs = 0; + otg->outputs = 0; + break; + default: + //otg_event(otg, not(b_conn), "a_host/ & a_suspend/: set b_conn/"); + break; + } +#endif + #if 0 + TRACE_MSG5(CORE, "OTG_EVENT: OUTPUT MAX:%d OUTPUTS CURRENT: %08x %08x NEW: %08x %08x", + MAX_OUTPUTS, + (u32)(current_outputs >> 32), + (u32)(current_outputs & 0xffffffff), + (u32)(new_outputs >> 32), + (u32)(new_outputs & 0xffffffff) + ); + #endif + + /* Iterate across the outputs bitmask, calling the appropriate functions + * to make required changes. The current outputs are saved and we DO NOT + * make calls to change output values until they change again. + */ + for (i = 0; i < MAX_OUTPUTS; i++) { + + u8 current_output = (u8)(current_outputs & 0x3); + u8 new_output = (u8)(new_outputs & 0x3); + u8 changed = otg_output_lookup[new_output][current_output]; + + output_results >>= 2; + + switch (changed) { + case NC: + output_results |= ((current_outputs & 0x3) << (MAX_OUTPUTS * 2)); + break; + case SET: + case RESET: + output_results |= (((otg_current_t)changed) << (MAX_OUTPUTS * 2)); + case PULSE: + + + otg_write_output_message_irq(otg, otg_output_names[i], changed); + + //TRACE_MSG3(CORE, "OTG_EVENT: CHECKING OUTPUT %s %d %s", + // otg_state->name, i, otg_output_names[i]); + + BREAK_UNLESS(otg->otg_output_ops[i]); // Check if we have an output routine + + TRACE_MSG7(CORE, "OTG_EVENT: CALLING OUTPUT %s %d %s%c cur: %02x new: %02x chng: %02x", + otg_state->name, i, otg_output_names[i], + otg_change_names[changed&0x3], + current_output, new_output, + changed); + + otg->otg_output_ops[i](otg, changed); // Output changed, call function to do it + + //TRACE_MSG2(CORE, "OTG_EVENT: OUTPUT FINISED %s %s", otg_state->name, otg_output_names[i]); + break; + } + + current_outputs >>= 2; + new_outputs >>= 2; + } + + output_results >>= 2; + otg->outputs = output_results; + otg->current_outputs = otg_state; + + //TRACE_MSG2(CORE, "OTG_EVENT: STATE: otg->current_outputs: %08x %08x", + // (u32)(otg->outputs & 0xffffffff), (u32)(otg->outputs >> 32)); + + if (((otg->outputs & pcd_en_out_set) == pcd_en_out_set) && + ((otg->outputs & hcd_en_out_set) == hcd_en_out_set)) + { + TRACE_MSG0(CORE, "WARNING PCD_EN and HCD_EN both set"); + } + + /* start timer? + */ + CONTINUE_UNLESS(otg_state->tmout); + //TRACE_MSG1(CORE, "setting timer: %d", otg_state->tmout); + if (otg->start_timer) { + otg_write_timer_message_irq(otg, otg_state->name, otg_state->tmout); + otg->start_timer(otg, otg_state->tmout); + } + else { + //TRACE_MSG0(CORE, "NO TIMER"); + otg_process_event(otg, TMOUT_, CORE, otg_state->name); + } + + } + TRACE_MSG0(CORE, "finishing"); + otg->active = 0; + + if (current_state == otg->state) + TRACE_MSG0(CORE, "OTG_EVENT: NOT CHANGED"); + +} + +#if 0 +#if defined(LINUX24) +/*! + * otg_write_input_message_irq() - + + * @param inputs input mask + * @param tag trace tag to use for tracing + * @param msg message for tracing + */ +void otg_write_input_message_irq(struct otg_instance *otg, otg_current_t inputs, otg_tag_t tag, char *msg) +{ + u32 reset = (u32)(inputs >> 32); + u32 set = (u32) (inputs & 0xffffffff); + + char buf[64]; + //snprintf(buf, sizeof(buf), "Input: %08x %08x %s", *inputs >> 32, *inputs & 0xffffffff, msg ? msg : "NULL"); + //TRACE_MSG3(tag, "Inputs: %08x %08x %s", set, reset, msg ? msg : "NULL"); + #ifdef OTG_WINCE + sprintf(buf, "Inputs: %08x %08x %s", reset, set, msg ? msg : "NULL"); + #else /* OTG_WINCE */ + snprintf(buf, sizeof(buf), "Inputs: %08x %08x %s", reset, set, msg ? msg : "NULL"); + #endif /* OTG_WINCE */ + buf[63] = '\0'; + otg_message(otg, buf); +} +#endif +#endif + +/* + */ +static otg_current_t otg_events[64]; +static otg_tag_t otg_tags[64]; +static char * otg_msgs[64]; +static u8 otg_head, otg_tail; + + +/*! + * otg_do_work() - Bottom half handler to process sent or received urbs. + * + * @param otg - otg instance pointer + * @param inputs - input events mask + * @param tag - otg tag + * @param msg - message + * @param queued - BOOL flag for queuing event handler + */ +void otg_do_work(struct otg_instance *otg, otg_current_t inputs, otg_tag_t tag, char *msg, BOOL queued ) +{ + u32 reset = (u32)(inputs >> 32); + u32 set = (u32) (inputs & 0xffffffff); + + TRACE_SM_INPUTS(tag, msg ? msg : "NULL", inputs, otg->current_inputs); + otg_message_buf[0] = '\0'; + snprintf(otg_message_buf, sizeof(otg_message_buf), + "Inputs: %08x %08x %s [%s]", reset, set, msg ? msg : "NULL", queued ? "Q":"D"); + otg_message_buf[sizeof(otg_message_buf)] = '\0'; + + otg_message(otg, otg_message_buf); + otg_process_event(otg, inputs, tag, msg); +} + +/*! + * otg_dequeue_tasklet() - tasklet to dequeue events + * + * @param data pointer to parameters struct + */ +void *otg_dequeue_tasklet(otg_tasklet_arg_t data) +{ + struct otg_instance *otg = (struct otg_instance *) data; + TRACE_MSG0(CORE, "DEQUEUE - START"); + /* lock interrupts */ + while (otg_head != otg_tail) { + + + /* get input information + */ + otg_current_t inputs = otg_events[otg_head]; + otg_tag_t tag = otg_tags[otg_head]; + char * msg = otg_msgs[otg_head]; + + TRACE_MSG0(CORE, "DEQUEUE"); + otg_head = (otg_head + 1) & 0x3f; + otg_do_work(otg, inputs, tag, msg, TRUE); + } + TRACE_MSG0(CORE, "DEQUEUE - FINISH"); + return NULL; +} + +/*! + * otg_queue_event() - otg input change event handler + * @param otg pointer to the otg instance. + * @param inputs input mask + * @param tag trace tag to use for tracing + * @param msg message for tracing + * @return non-zero if state changed. + */ +void otg_queue_event(struct otg_instance *otg, otg_current_t inputs, otg_tag_t tag, char *msg) +{ + //unsigned long flags; + u8 otg_tail_save; + + u32 reset = (u32)(inputs >> 32); + u32 set = (u32) (inputs & 0xffffffff); + TRACE_MSG3(CORE, "QUEUE: RESET: %08x SET: %08x %s", reset, set, msg); + + /* atomic insertion */ + otg_pthread_mutex_lock(&otg->mutex); /* lock mutex */ + otg_tail_save = otg_tail; + otg_events[otg_tail] = inputs; + otg_tags[otg_tail] = tag; + otg_msgs[otg_tail] = msg; + + otg_tail = (otg_tail + 1) & 0x3f; + + /* check for overrun */ + if (otg_tail == otg_head) + otg_tail = otg_tail_save; + + otg_pthread_mutex_unlock(&otg->mutex); /* unlock mutex */ + + if(otg->tasklet) otg_tasklet_start(otg->tasklet); + //TRACE_MSG0(CORE, "FINISHED"); +} + +/*! + * otg_event() - otg input change event handler + * @param otg pointer to the otg instance. + * @param inputs input mask + * @param tag trace tag to use for tracing + * @param msg message for tracing + * @return non-zero if state changed. + */ +void otg_event(struct otg_instance *otg, otg_current_t inputs, otg_tag_t tag, char *msg) +{ + #if 1 + otg_queue_event(otg, inputs, tag, msg); + #else + #ifdef LINUX26 + if (in_atomic()) { + printk(KERN_INFO"%s: IN_ATOMIC %s\n", __FUNCTION__, msg); + return; + } + if (in_interrupt()) { + printk(KERN_INFO"%s: IN_INTERRUPT %s\n", __FUNCTION__, msg); + return; + } + #endif /* LINUX26 */ + + UNLESS (/*in_atomic() ||*/ down_trylock(&otg->event)) { + printk(KERN_INFO"%s: DIRECT %s\n", __FUNCTION__, msg); + TRACE_MSG0(CORE, "DIRECT"); + otg_do_work(otg, inputs, tag, msg, FALSE); + //UP(&otg->event); + while (otg_sem_wait(&otg->event)); + //TRACE_MSG0(CORE, "FINISHED"); + } + else + otg_queue_event(otg, inputs, tag, msg); + #endif + +} + + +/*! + * otg_event_set_irq() -set changed otg event for handling + * @param otg pointer to the otg instance. + * @param changed + * @param flag + * @param input input mask + * @param tag trace tag to use for tracing + * @param msg message for tracing + * @return non-zero if state changed. + */ +void otg_event_set_irq(struct otg_instance *otg, int changed, int flag, u32 input, otg_tag_t tag, char *msg) +{ + RETURN_UNLESS(changed); + TRACE_MSG4(tag, "%s: %08x changed: %d flag: %d", msg, input, changed, flag); + otg_queue_event(otg, flag ? input : _NOT(input), tag, msg); +} + + +/*! + * otg_serial_number() - set the device serial number + * + * @param otg pointer to the otg instance. + * @param serial_number_str + */ +void otg_serial_number (struct otg_instance *otg, char *serial_number_str) +{ + int i; + char *cp = serial_number_str; + + //DOWN(&otg_sem); + //while (otg_sem_wait(&otg_sem)); + + for (i = 0; cp && *cp && (i < OTGADMIN_MAXSTR); cp++) { + CONTINUE_UNLESS (isxdigit(*cp)); + otg->serial_number[i++] = toupper(*cp); + } + otg->serial_number[i] = '\0'; + + //TRACE_MSG2(CORE, "serial_number_str: %s serial_number: %s", serial_number_str, otg->serial_number); + + //UP(&otg_sem); + //while(otg_sem_wait(&otg_sem)); +} + + +/*! + * otg_init() - create and initialize otg instance + * + * Called to initialize the OTG state machine. This will cause the state + * to change from the invalid_state to otg_disabled. + * + * If CONFIG_OTG_TR_AUTO is defined thten + * an initial enable_otg event is generated. This will cause + * the state to move from otg_disabled to otg_enabled. The + * requird drivers will be initialized by calling the appropriate + * output functions: + * + * - pcd_init_func + * - hcd_init_func + * - tcd_init_func + * + * @param otg pointer to the otg instance. + */ +void otg_init (struct otg_instance *otg) +{ + + //TRACE_MSG0(CORE, "START"); + + //init_MUTEX(&otg->command); + //otg_sem_init("otg_command", &otg->command, 0, 0); + //DOWN(&otg_sem); + //otg_sem_post(&otg_sem); + otg->outputs = tcd_init_out_ | pcd_init_out_ | hcd_init_out_; + + RETURN_UNLESS((otg->tasklet = otg_tasklet_init("otg_event", otg_dequeue_tasklet, (otg_tasklet_arg_t) otg, CORE))); + //otg->tasklet->debug = TRUE; + + /* This will move the state machine into the otg_disabled state + */ + otg_event(otg, enable_otg, CORE, "enable_otg"); // XXX + + #if defined(CONFIG_OTG_TR_AUTO) + otg_event(otg, AUTO, CORE, "AUTO"); // XXX + #endif /* defined(CONFIG_OTG_TR_AUTO) */ +} + + +/*! + * otg_exit() + * + * This is called by the driver that started the state machine to + * cause it to exit. The state will move to otg_disabled. + * + * The appropriate output functions will be called to disable the + * peripheral drivers. + * + * @param otg pointer to the otg instance. + */ +void otg_exit (struct otg_instance *otg) +{ + TRACE_MSG0(CORE, "OTG_EXIT"); + + //DOWN(&otg_sem); + //while(otg_sem_wait(&otg_sem)); + + //TRACE_MSG0(CORE, "OTG_EXIT"); + //otg_event(otg, exit_all | not(a_bus_req) | a_bus_drop | not(b_bus_req), "tcd_otg_exit"); + + //printk(KERN_INFO"%s: otg->state: %d START\n", __FUNCTION__, otg->state); + otg_event(otg, enable_otg_, CORE, "enable_otg_"); + + while (otg->state != otg_disabled) { + //printk(KERN_INFO"%s: otg->state: %d SLEEP\n", __FUNCTION__, otg->state); + //SCHEDULE_TIMEOUT(10 * HZ); + otg_sleep(1); + } + //printk(KERN_INFO"%s: otg->state: %d FINISH\n", __FUNCTION__, otg->state); + + otg_tasklet_exit(otg->tasklet); + otg->tasklet = NULL; + + //TRACE_MSG0(CORE, "UP OTG_SEM"); + //UP(&otg_sem); + //otg_sem_post(&otg_sem); + + // XXX MODULE UNLOCK HERE +} + + +OTG_EXPORT_SYMBOL(otg_queue_event); +OTG_EXPORT_SYMBOL(otg_event); +OTG_EXPORT_SYMBOL(otg_serial_number); +OTG_EXPORT_SYMBOL(otg_init); +OTG_EXPORT_SYMBOL(otg_exit); +OTG_EXPORT_SYMBOL(otg_event_set_irq); + +/* ********************************************************************************************* */ + +/*! + * otg_gen_init_func() - + * + * This is a default tcd_init_func. It will be used + * if no other is provided. + * + * @param otg pointer to the otg instance. + * @param flag set or reset + */ +void otg_gen_init_func (struct otg_instance *otg, u8 flag) +{ + TRACE_MSG1(CORE, "GENERIC INIT %s", flag ? "SET" : "RESET"); + otg_event(otg, OCD_OK, CORE, "GENERIC OK"); +} + + +/*! otg_gen_start_timer - + * Fake - Set or reset timer to interrupt in number of uS (micro-seconds). + * This is only suitable for MN or TR firmware. + * + * @param otg otg instance pointer + * @param usec + * @return 0 + */ +int otg_gen_start_timer(struct otg_instance *otg, int usec) +{ + otg_event(otg, TMOUT, CORE, "FAKE TMOUT"); + return 0; +} + + +/*! + * otg_hcd_set_ops() - + * @param hcd_ops - host operations table to use + * @param otg pointer to otg instance + * @return pointer to host controller driver instance with initialized operations + */ +struct hcd_instance * otg_set_hcd_ops(struct otg_instance *otg, struct hcd_ops *hcd_ops) +{ + struct hcd_instance *hcd = NULL; + otg->hcd_ops = hcd_ops; + if (hcd_ops) { + RETURN_NULL_UNLESS((hcd = CKMALLOC(sizeof(struct hcd_instance)))); + hcd->otg = otg; + hcd->TAG = otg_trace_obtain_tag(otg, "set_hcd_ops"); + otg->hcd = hcd; + otg->otg_output_ops[HCD_INIT_OUT] = hcd_ops->hcd_init_func; + otg->otg_output_ops[HCD_EN_OUT] = hcd_ops->hcd_en_func; + otg->otg_output_ops[HCD_RH_OUT] = hcd_ops->hcd_rh_func; + otg->otg_output_ops[LOC_SOF_OUT] = hcd_ops->loc_sof_func; + otg->otg_output_ops[LOC_SUSPEND_OUT] = hcd_ops->loc_suspend_func; + otg->otg_output_ops[REMOTE_WAKEUP_EN_OUT] = hcd_ops->remote_wakeup_en_func; + otg->otg_output_ops[HNP_EN_OUT] = hcd_ops->hnp_en_func; + otg_hcd_ops_framenum = hcd_ops->framenum; + } + else { + if (otg->hcd) { + otg->hcd->TAG = otg_trace_invalidate_tag(otg->hcd->TAG); + LKFREE(otg->hcd); + otg->hcd = NULL; + } + otg->otg_output_ops[HCD_INIT_OUT] = + otg->otg_output_ops[HCD_EN_OUT] = + otg->otg_output_ops[HCD_RH_OUT] = + otg->otg_output_ops[LOC_SOF_OUT] = + otg->otg_output_ops[LOC_SUSPEND_OUT] = + otg->otg_output_ops[REMOTE_WAKEUP_EN_OUT] = + otg->otg_output_ops[HNP_EN_OUT] = NULL; + otg_hcd_ops_framenum = NULL; + } + + UNLESS (otg->otg_output_ops[HCD_INIT_OUT]) { + TRACE_MSG0(CORE, "USING OTG_GEN_INIT_FUNC"); + otg->otg_output_ops[HCD_INIT_OUT] = otg_gen_init_func; + } + + return hcd; +} + +/*! + * otg_ocd_set_ops() -Connect ocd operations to otg instance + * + * @param otg - otg instance pointer + * @param ocd_ops - ocd operations table to use + * @return pointer to ocd instance + */ +struct ocd_instance * otg_set_ocd_ops(struct otg_instance *otg, struct ocd_ops *ocd_ops) +{ + struct ocd_instance *ocd = NULL; + otg->ocd_ops = ocd_ops; + if (ocd_ops) { + RETURN_NULL_UNLESS((ocd = CKMALLOC(sizeof(struct ocd_instance)))); + ocd->otg = otg; + ocd->TAG = otg_trace_obtain_tag(otg, "set_ocd_ops"); + otg->ocd = ocd; + otg->otg_output_ops[OCD_INIT_OUT] = ocd_ops->ocd_init_func; + otg->start_timer = ocd_ops->start_timer; + otg_ocd_ops_ticks = ocd_ops->ticks; + otg_ocd_ops_elapsed = ocd_ops->elapsed; + otg_interrupts = &otg->interrupts; + } + else { + if (otg->ocd) { + otg->ocd->TAG = otg_trace_invalidate_tag(otg->ocd->TAG); + LKFREE(otg->ocd); + otg->ocd = NULL; + } + otg_ocd_ops_ticks = NULL; + otg_ocd_ops_elapsed = NULL; + otg->otg_output_ops[OCD_INIT_OUT] = NULL; + otg->start_timer = NULL; + otg_interrupts = NULL; + } + + UNLESS (otg->otg_output_ops[OCD_INIT_OUT]) { + TRACE_MSG0(CORE, "USING OTG_GEN_INIT_FUNC"); + otg->otg_output_ops[OCD_INIT_OUT] = otg_gen_init_func; + } + + UNLESS (otg->start_timer) + otg->start_timer = otg_gen_start_timer; + + return ocd; +} + +/*! + * otg_pcd_set_ops() - connect pcd operations to otg instance operations + * @param otg - otg instance pointer + * @param pcd_ops - pcd operations table to use + * @return pointer to initialized pcd instance + */ +struct pcd_instance * otg_set_pcd_ops(struct otg_instance *otg, struct pcd_ops *pcd_ops) +{ + struct pcd_instance *pcd = NULL; + otg->pcd_ops = pcd_ops; + if (pcd_ops) { + RETURN_NULL_UNLESS((pcd = CKMALLOC(sizeof(struct pcd_instance)))); + pcd->otg = otg; + pcd->TAG = otg_trace_obtain_tag(otg, "set_pcd_ops"); + otg->pcd = pcd; + otg->otg_output_ops[PCD_INIT_OUT] = pcd_ops->pcd_init_func; + otg->otg_output_ops[PCD_EN_OUT] = pcd_ops->pcd_en_func; + otg->otg_output_ops[REMOTE_WAKEUP_OUT] = pcd_ops->remote_wakeup_func; + otg_pcd_ops_framenum = pcd_ops->framenum; + + if (pcd_ops->tcd_en_func) + otg->otg_output_ops[TCD_EN_OUT] = pcd_ops->tcd_en_func; + if (pcd_ops->dp_pullup_func) + otg->otg_output_ops[DP_PULLUP_OUT] = pcd_ops->dp_pullup_func; + otg_pcd_ops_ticks = pcd_ops->ticks; + otg_pcd_ops_elapsed = pcd_ops->elapsed; + } + else { + if (otg->pcd) { + otg->pcd->TAG = otg_trace_invalidate_tag(otg->pcd->TAG); + LKFREE(otg->pcd); + otg->pcd = NULL; + } + otg_pcd_ops_ticks = NULL; + otg_pcd_ops_elapsed = NULL; + otg->otg_output_ops[PCD_INIT_OUT] = + otg->otg_output_ops[PCD_EN_OUT] = + otg->otg_output_ops[REMOTE_WAKEUP_OUT] = NULL; + otg_pcd_ops_framenum = NULL; + } + + UNLESS (otg->otg_output_ops[PCD_INIT_OUT]) { + TRACE_MSG0(CORE, "USING OTG_GEN_INIT_FUNC"); + otg->otg_output_ops[PCD_INIT_OUT] = otg_gen_init_func; + } + + return pcd; +} + +/*! + * otg_tcd_set_ops() - connect tcd opereations to otg instance operations + * @param otg - otg instance pointer + * @param tcd_ops - tcd operations table to use + * @return pointer to tcd intance + */ +struct tcd_instance * otg_set_tcd_ops(struct otg_instance *otg, struct tcd_ops *tcd_ops) +{ + struct tcd_instance *tcd = NULL; + otg->tcd_ops = tcd_ops; + if (tcd_ops) { + RETURN_NULL_UNLESS((tcd = CKMALLOC(sizeof(struct tcd_instance)))); + tcd->otg = otg; + tcd->TAG = otg_trace_obtain_tag(otg, "set_tcd_ops"); + otg->tcd = tcd; + otg->otg_output_ops[TCD_INIT_OUT] = tcd_ops->tcd_init_func; + otg->otg_output_ops[TCD_EN_OUT] = tcd_ops->tcd_en_func; + otg->otg_output_ops[CHRG_VBUS_OUT] = tcd_ops->chrg_vbus_func; + otg->otg_output_ops[DRV_VBUS_OUT] = tcd_ops->drv_vbus_func; + otg->otg_output_ops[DISCHRG_VBUS_OUT] = tcd_ops->dischrg_vbus_func; + otg->otg_output_ops[DP_PULLUP_OUT] = tcd_ops->dp_pullup_func; + otg->otg_output_ops[DM_PULLUP_OUT] = tcd_ops->dm_pullup_func; + otg->otg_output_ops[DP_PULLDOWN_OUT] = tcd_ops->dp_pulldown_func; + otg->otg_output_ops[DM_PULLDOWN_OUT] = tcd_ops->dm_pulldown_func; + //otg->otg_output_ops[PERIPHERAL_HOST] = tcd_ops->peripheral_host_func; + otg->otg_output_ops[CLR_OVERCURRENT_OUT] = tcd_ops->overcurrent_func; + otg->otg_output_ops[DM_DET_OUT] = tcd_ops->dm_det_func; + otg->otg_output_ops[DP_DET_OUT] = tcd_ops->dp_det_func; + otg->otg_output_ops[CR_DET_OUT] = tcd_ops->cr_det_func; + otg->otg_output_ops[AUDIO_OUT] = tcd_ops->audio_func; + otg->otg_output_ops[CHARGE_PUMP_OUT] = tcd_ops->charge_pump_func; + otg->otg_output_ops[BDIS_ACON_OUT] = tcd_ops->bdis_acon_func; + //otg->otg_output_ops[MX21_VBUS_DRAIN] = tcd_ops->mx21_vbus_drain_func; + otg->otg_output_ops[ID_PULLDOWN_OUT] = tcd_ops->id_pulldown_func; + otg->otg_output_ops[UART_OUT] = tcd_ops->uart_func; + otg->otg_output_ops[MONO_OUT] = tcd_ops->mono_func; + } + else { + if (otg->tcd){ + otg->tcd->TAG = otg_trace_invalidate_tag(otg->tcd->TAG); + LKFREE(otg->tcd); + otg->tcd = NULL; + } + otg->otg_output_ops[TCD_INIT_OUT] = + otg->otg_output_ops[TCD_EN_OUT] = + otg->otg_output_ops[CHRG_VBUS_OUT] = + otg->otg_output_ops[DRV_VBUS_OUT] = + otg->otg_output_ops[DISCHRG_VBUS_OUT] = + otg->otg_output_ops[DP_PULLUP_OUT] = + otg->otg_output_ops[DM_PULLUP_OUT] = + otg->otg_output_ops[DP_PULLDOWN_OUT] = + otg->otg_output_ops[DM_PULLDOWN_OUT] = + //otg->otg_output_ops[PERIPHERAL_HOST] = + otg->otg_output_ops[CLR_OVERCURRENT_OUT] = + otg->otg_output_ops[DM_DET_OUT] = + otg->otg_output_ops[DP_DET_OUT] = + otg->otg_output_ops[CR_DET_OUT] = + otg->otg_output_ops[AUDIO_OUT] = + otg->otg_output_ops[CHARGE_PUMP_OUT] = + otg->otg_output_ops[BDIS_ACON_OUT] = + //otg->otg_output_ops[MX21_VBUS_DRAIN] = + otg->otg_output_ops[ID_PULLDOWN_OUT] = + otg->otg_output_ops[UART_OUT] = + otg->otg_output_ops[MONO_OUT] = NULL; + } + + UNLESS (otg->otg_output_ops[TCD_INIT_OUT]) { + TRACE_MSG0(CORE, "USING OTG_GEN_INIT_FUNC"); + otg->otg_output_ops[TCD_INIT_OUT] = otg_gen_init_func; + } + + return tcd; +} + +/*! + * otg_get_ocd_info + */ +void otg_get_ocd_info(struct otg_instance *otg, otg_tick_t *ticks, u16 *p_framenum) +{ + *ticks = (otg_ocd_ops_ticks) ? otg_ocd_ops_ticks () : ((otg_pcd_ops_ticks) ? otg_pcd_ops_ticks () : 0); + if (p_framenum) + *p_framenum = ((otg && otg_pcd_ops_framenum) ? otg_pcd_ops_framenum(otg) : 0); +} + + +#if defined(CONFIG_OTG_TRACE) +/*! + * otg_get_trace_info() - get otg trace inforamtion + * @param p - pointer to tarce slot + * @return none + */ +void otg_get_trace_info(struct otg_instance *otg, otg_trace_t *p) +{ + #if !defined(OTG_TRACE_DISABLE) && ( defined(CONFIG_OTG_TRACE) || defined(CONFIG_OTG_TRACE_MODULE)) + RETURN_UNLESS( (p) ); + + //p->hcd_pcd = 0; + + // XXX p->id_gnd = otg_instance_info->current_inputs & ID_GND ? 1 : 0; + // XXX if (hcd_instance_private.active) p->hcd_pcd |= 0x1; + // XXX if (pcd_instance_private.active) p->hcd_pcd |= 0x2; + + #if 1 + p->ticks = (otg && otg_ocd_ops_ticks) ? otg_ocd_ops_ticks () : ((otg && otg_pcd_ops_ticks) ? otg_pcd_ops_ticks () : 0); + p->interrupts = (otg && otg_interrupts) ? *otg_interrupts : 0; + p->h_framenum = ((otg && otg_hcd_ops_framenum) ? otg_hcd_ops_framenum(otg) : 0); + p->p_framenum = ((otg && otg_pcd_ops_framenum) ? otg_pcd_ops_framenum(otg) : 0); + #else + otg_get_ocd_info(otg, &p->ticks, &p->interrupts, &p->h_framenum, &p->p_framenum); + #endif + + #ifdef LINUX26 + if (in_interrupt()) p->flags |= OTG_TRACE_IN_INTERRUPT; + #endif /* LINUX26 */ + //p->in_interrupt = in_interrupt(); + #endif /* !defined(OTG_TRACE_DISABLE) && ( defined(CONFIG_OTG_TRACE) || defined(CONFIG_OTG_TRACE_MODULE)) */ +} + +void otg_trace_copy(u32 *p, u32 val) +{ + //*p = val; +} + +#endif + + +/*! + * otg_tmr_ticks() - get ticks + * + * @return number of ticks. + */ +otg_tick_t otg_tmr_ticks(void) +{ + struct timeval tv; + + if (otg_ocd_ops_ticks) + return otg_ocd_ops_ticks (); + + if (otg_pcd_ops_ticks) + return otg_pcd_ops_ticks (); + + otg_gettimeofday(&tv); + return tv.tv_sec * 1000000 + tv.tv_usec; +} + +/*! + * otg_tmr_elapsed() - + * @param t1 + * @param t2 + * @return number of uSecs between t1 and t2 ticks. + */ +otg_tick_t otg_tmr_elapsed(otg_tick_t *t1, otg_tick_t *t2) +{ + return otg_ocd_ops_elapsed ? otg_ocd_ops_elapsed (t1, t2) : + (otg_pcd_ops_elapsed ? otg_pcd_ops_elapsed (t1, t2) : + (((*t1 > *t2) ? (*t1 - *t2) : (*t2 - *t1)))); +} + +OTG_EXPORT_SYMBOL(otg_set_hcd_ops); +OTG_EXPORT_SYMBOL(otg_set_ocd_ops); +OTG_EXPORT_SYMBOL(otg_set_pcd_ops); +OTG_EXPORT_SYMBOL(otg_set_tcd_ops); +OTG_EXPORT_SYMBOL(otg_set_usbd_ops); +OTG_EXPORT_SYMBOL(otg_tmr_ticks); +OTG_EXPORT_SYMBOL(otg_tmr_elapsed); +OTG_EXPORT_SYMBOL(otg_get_ocd_info); +#if defined(LINUX24) +//OTG_EXPORT_SYMBOL(otg_write_input_message_irq); +#endif + + +#if defined(OTG_WINCE) +#else /* defined(OTG_WINCE) */ +#endif /* defined(OTG_WINCE) */ diff --git a/drivers/otg/otgcore/usbp-bops.c b/drivers/otg/otgcore/usbp-bops.c new file mode 100644 index 000000000000..eb230827c289 --- /dev/null +++ b/drivers/otg/otgcore/usbp-bops.c @@ -0,0 +1,2042 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/otgcore/usbp-bops.c - USB Device Prototype + * @(#) sl@belcarra.com/whiskey.enposte.net|otg/otgcore/usbp-bops.c|20070816060612|46629 + * + * Copyright (c) 2004-2005 Belcarra Technologies Corp + * Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + * + * By: + * Stuart Lynne <sl@belcarra.com>, + * Bruce Balden <balden@belcarra.com> + * + */ +/*! + * @file otg/otgcore/usbp-bops.c + * @brief Bus Interface related functions. + * + * This implements the functions used to implement Peripheral Controller Drivers (PCD.) + * + * @ingroup USBDCORE + */ + +#include <otg/otg-compat.h> +#include <otg/otg-module.h> + +#include <otg/usbp-chap9.h> +#include <otg/otg-trace.h> +#include <otg/otg-api.h> +//#include <otg/otg-task.h> +#include <otg/otg-trace.h> +#include <otg/usbp-func.h> +#include <otg/usbp-bus.h> +#include <otg/otg-pcd.h> + +int usbd_procfs_init (struct usbd_bus_instance *); +void usbd_procfs_exit (struct usbd_bus_instance *); + + +struct usbd_simple_driver *usbp_find_simple_driver(char *); +struct usbd_simple_instance *usbp_alloc_simple_instance(struct usbd_simple_driver *, struct usbd_bus_instance *); +void usbp_dealloc_simple_instance(struct usbd_simple_instance *simple_instance); + +void usbp_alloc_simple_configuration_descriptor(struct usbd_simple_instance *simple_instance, int cfg_size, int hs); + +struct usbd_composite_driver *usbp_find_composite_driver(char *); +struct usbd_composite_instance *usbp_alloc_composite_instance(struct usbd_composite_driver *, struct usbd_bus_instance *); + +void usbp_dealloc_composite_instance(struct usbd_composite_instance *composite_instance); + +void usbp_alloc_composite_configuration_descriptor(struct usbd_composite_instance *composite_instance, int cfg_size, int hs); + +void *usbd_device_reset(void *data); +void *usbd_device_suspended(void *data); +void *usbd_device_resumed(void *data); + +/* ********************************************************************************************* */ +/* ********************************************************************************************* */ +/*! + * Note that all of the list functions overlapped operation using the usbd_bus_sem; + * + * usbd_device_bh + * + * usbd_register_bus + * usbd_deregister_bus + * usbd_enable_function + * usbd_disable_function + */ +//DECLARE_MUTEX(usbd_bus_sem); +otg_sem_t usbd_bus_sem; + +/* ********************************************************************************************* */ +/* ********************************************************************************************* */ +/*! + * Bus Interface Management: + * usbd_register_bus() + * usbd_deregister_bus() + */ + +/*! urb_link_init() - initialize urb linke + * + * Initialize an urb_link to be a single element list. + * If the urb_link is being used as a distinguished list head + * the list is empty when the head is the only link in the list. + * + * @param ul link to urb + */ +static INLINE void urb_link_init (urb_link * ul) +{ + ul->prev = ul->next = ul; +} + +struct usbd_endpoint_map ep0_endpoint_map_array[2]; + +#if defined(OTG_C99) +struct usbd_interface_instance ep0_instance = { + .function = { + .name = "EP0", + .function_type = function_ep0, }, + .endpoint_map_array = ep0_endpoint_map_array, +}; +#else /* defined(OTG_C99) */ +struct usbd_interface_instance ep0_instance; +#endif /* defined(OTG_C99) */ + + +/*! + * usbd_register_bus() - called by a USB BUS INTERFACE driver to register a bus driver + * + * Used by a USB Bus interface driver to register itself with the usb device layer. + * + * @param driver + * @param ep0_wMaxPacketSize + * @return non-zero if error + */ +struct usbd_bus_instance *usbd_register_bus (struct usbd_bus_driver *driver, int ep0_wMaxPacketSize) +{ + int i; + struct usbd_bus_instance *bus = NULL; + + RETURN_NULL_IF(otg_sem_wait(&usbd_bus_sem)); + + THROW_UNLESS((bus = CKMALLOC (sizeof (struct usbd_bus_instance))), error); + THROW_UNLESS((bus->reset_bh = otg_workitem_init("busreset", usbd_device_reset, bus, USBD)), error); + THROW_UNLESS((bus->resume_bh = otg_workitem_init("busact", usbd_device_resumed, bus, USBD)), error); + THROW_UNLESS((bus->suspend_bh = otg_workitem_init("busidle", usbd_device_suspended, bus, USBD)), error); + + + //bus->reset_bh->debug = TRUE; + //bus->resume_bh->debug = TRUE; + //bus->suspend_bh->debug = TRUE; + + bus->driver = driver; + bus->endpoints = bus->driver->max_endpoints; + bus->bmAttributes = bus->driver->bmAttributes; + bus->bMaxPower = bus->driver->bMaxPower; + TRACE_MSG2(USBD, "bmAttributes: %02x bMaxPower: %02x", bus->bmAttributes, bus->bMaxPower); + + + THROW_IF(!(bus->endpoint_array = CKMALLOC(sizeof (struct usbd_endpoint_instance) * bus->endpoints)), error); + + for (i = 0; i < bus->endpoints; i++) { + struct usbd_endpoint_instance *endpoint = bus->endpoint_array + i; + //endpoint->physical_endpoint = i; + urb_link_init (&endpoint->rdy); + endpoint->bus = bus; + //init_MUTEX(&endpoint->sem); + } + + bus->ep0 = &ep0_instance; + bus->ep0->function.bus = bus; + + #if !defined(OTG_C99) + bus->ep0->function.name = "EP0"; + bus->ep0->function.function_type = function_ep0; + bus->ep0->endpoint_map_array = ep0_endpoint_map_array; + #endif /* defined(OTG_C99) */ + + for (i = 0; i < 2; i++) { + bus->ep0->endpoint_map_array[i].wMaxPacketSize[0] = + bus->ep0->endpoint_map_array[i].wMaxPacketSize[1] = ep0_wMaxPacketSize; + bus->ep0->endpoint_map_array[i].endpoint = bus->endpoint_array; + } + + bus->device_state = STATE_CREATED; + bus->status = USBD_OPENING; + TRACE_MSG1(USBD, "bus->status: %d", bus->status); + + //new_usbd_bus_instance = bus; + + #if defined(CONFIG_OTG_TRACE) + THROW_IF(usbd_procfs_init (bus), error); + #endif /* defined(CONFIG_OTG_TRACE) */ + + CATCH(error) { + if (bus) { + if (bus->reset_bh) otg_workitem_exit(bus->reset_bh); + if (bus->resume_bh) otg_workitem_exit(bus->resume_bh); + if (bus->suspend_bh) otg_workitem_exit(bus->suspend_bh); + LKFREE(bus); + } + bus->endpoints = 0; + bus = NULL; + } + otg_sem_post(&usbd_bus_sem); + return bus; +} + +/*! + * usbd_deregister_bus() - called by a USB BUS INTERFACE driver to deregister a bus driver + * + * Used by a USB Bus interface driver to de-register itself with the usb device + * layer. + * + * @param bus + */ +void usbd_deregister_bus (struct usbd_bus_instance *bus) +{ + while (otg_sem_wait(&usbd_bus_sem)); + + #if defined(CONFIG_OTG_TRACE) + usbd_procfs_exit (bus); + #endif /* defined(CONFIG_OTG_TRACE) */ + //new_usbd_bus_instance = NULL; + + if (bus->reset_bh) otg_workitem_exit(bus->reset_bh); + if (bus->resume_bh) otg_workitem_exit(bus->resume_bh); + if (bus->suspend_bh) otg_workitem_exit(bus->suspend_bh); + LKFREE (bus->arg); + LKFREE (bus->endpoint_array); + LKFREE (bus); + //bus->endpoints = 0; + otg_sem_post(&usbd_bus_sem); +} + +OTG_EXPORT_SYMBOL(usbd_register_bus); +OTG_EXPORT_SYMBOL(usbd_deregister_bus); + +/* ********************************************************************************************* */ +/* ********************************************************************************************* */ +/*! + * bus_function_enable() - enable a usbd function driver for use + * + * This will copy descriptors from the function driver and get them ready + * for use. + * @param bus + * @param function + * @param serial_number + * @return non-zero if error + */ +int +bus_function_enable (struct usbd_bus_instance *bus, struct usbd_function_instance *function, char *serial_number) +{ + struct usbd_simple_instance *simple_instance = NULL; + struct usbd_simple_driver *simple_driver = NULL; + struct usbd_composite_instance *composite_instance = NULL; + struct usbd_composite_driver *composite_driver = NULL; + + struct usbd_device_description *device_description = NULL; + //struct usbd_device_descriptor *device_descriptor = NULL; +#ifdef CONFIG_OTG_HIGH_SPEED + struct usbd_device_qualifier_descriptor *device_qualifier_descriptor; +#endif /* CONFIG_OTG_HIGH_SPEED */ + //struct usbd_otg_descriptor *otg_descriptor; + int i; + int rc = 0; + + TRACE_MSG1(USBD, "function: %x", function); + + /* point functions at the bus + */ + function->bus = bus; + + if (function_simple == function->function_type) { + simple_instance = (struct usbd_simple_instance *)function; + simple_driver = (struct usbd_simple_driver *)simple_instance->function.function_driver; + simple_driver->driver.flags |= FUNCTION_ENABLED; + /* call enable function + */ + if (simple_driver->driver.fops->function_enable) + simple_driver->driver.fops->function_enable (function); + + /* update device descriptor + */ + RETURN_ZERO_IF(!(device_description = simple_driver->device_description)); + } + if (function_composite == function->function_type) { + + composite_instance = (struct usbd_composite_instance *)function; + composite_driver = (struct usbd_composite_driver *)composite_instance->function.function_driver; + + TRACE_MSG0(USBD, "enable composite"); + + /* call enable functions + */ + if (composite_driver->driver.fops->function_enable) + composite_driver->driver.fops->function_enable (function); + composite_driver->driver.flags |= FUNCTION_ENABLED; + + TRACE_MSG0(USBD, "enable class"); + if (composite_instance->class_instance) { + struct usbd_class_instance *class_instance = composite_instance->class_instance; + struct usbd_class_driver *class_driver = + (struct usbd_class_driver *)class_instance->function.function_driver; + + composite_instance->class_instance->function.bus = bus; + if (class_driver->driver.fops->function_enable) + class_driver->driver.fops->function_enable + ((struct usbd_function_instance *)class_instance); + class_driver->driver.flags |= FUNCTION_ENABLED; + } + for (i = 0; i < composite_instance->interface_functions; i++) { + struct usbd_interface_instance *interfaces_array = composite_instance->interfaces_array; + struct usbd_interface_instance *interface_instance = interfaces_array + i; + struct usbd_interface_driver *interface_driver; + CONTINUE_UNLESS(interface_instance); + TRACE_MSG1(USBD, "enable interface[%d]", i); + interface_driver = (struct usbd_interface_driver *)interface_instance->function.function_driver; + interface_instance->function.bus = bus; + if (interface_driver->driver.fops->function_enable) + interface_driver->driver.fops->function_enable + ((struct usbd_function_instance *)interface_instance); + interface_driver->driver.flags |= FUNCTION_ENABLED; + } + + /* update device descriptor + */ + RETURN_ZERO_IF(!(device_description = composite_driver->device_description)); + } + + + //RETURN_ZERO_IF(!(device_descriptor = device_description->device_descriptor)); + + device_description->bMaxPacketSize0 = bus->driver->maxpacketsize; + #if 0 + if (strlen (serial_number)) + device_descriptor->iSerialNumber = usbd_alloc_string (serial_number); + else + device_descriptor->iSerialNumber = usbd_alloc_string (device_description->iSerialNumber); + #endif + + #if 0 + // XXX not used + if (function_simple == function->function_type) { + + if ((otg_descriptor = device_description->otg_descriptor)) { + otg_descriptor->bmAttributes = usbd_otg_bmattributes(function); + simple_driver->otg_descriptor = otg_descriptor; + } + + //simple_instance->device_descriptor = device_descriptor; + + } + #endif + #if 0 + // XXX not used + if (function_composite == function->function_type) { + if ((otg_descriptor = device_description->otg_descriptor)) { + otg_descriptor->bmAttributes = usbd_otg_bmattributes(function); + composite_driver->otg_descriptor = otg_descriptor; + } + + } + #endif + return rc; +} + +/*! + * bus_function_disable() - disable a usbd function driver for use + * + * This will copy descriptors from the function driver and get them ready + * for use. + * @param bus + * @param function + * @return non-zero if error + */ +int +bus_function_disable (struct usbd_bus_instance *bus, struct usbd_function_instance *function) +{ + struct usbd_simple_instance *simple_instance = NULL; + struct usbd_simple_driver *simple_driver = NULL; + struct usbd_composite_instance *composite_instance = NULL; + struct usbd_composite_driver *composite_driver = NULL; + + struct usbd_device_description *device_description = NULL; + //struct usbd_device_descriptor *device_descriptor = NULL; +#ifdef CONFIG_OTG_HIGH_SPEED + struct usbd_device_qualifier_descriptor *device_qualifier_descriptor; +#endif /* CONFIG_OTG_HIGH_SPEED */ + //struct usbd_otg_descriptor *otg_descriptor; + int i; + int rc = 0; + + TRACE_MSG1(USBD, "function: %x", function); + + /* point functions at the bus + */ + function->bus = bus; + + if (function_simple == function->function_type) { + simple_instance = (struct usbd_simple_instance *)function; + simple_driver = (struct usbd_simple_driver *)simple_instance->function.function_driver; + simple_driver->driver.flags &= ~FUNCTION_ENABLED; + /* call disable function + */ + if (simple_driver->driver.fops->function_disable) + simple_driver->driver.fops->function_disable (function); + + /* update device descriptor + */ + RETURN_ZERO_IF(!(device_description = simple_driver->device_description)); + } + if (function_composite == function->function_type) { + + composite_instance = (struct usbd_composite_instance *)function; + composite_driver = (struct usbd_composite_driver *)composite_instance->function.function_driver; + + TRACE_MSG0(USBD, "disable composite"); + + /* call disable functions + */ + if (composite_driver->driver.fops->function_disable) + composite_driver->driver.fops->function_disable (function); + composite_driver->driver.flags &= ~FUNCTION_ENABLED; + + TRACE_MSG0(USBD, "disable class"); + if (composite_instance->class_instance) { + struct usbd_class_instance *class_instance = composite_instance->class_instance; + struct usbd_class_driver *class_driver = + (struct usbd_class_driver *)class_instance->function.function_driver; + + composite_instance->class_instance->function.bus = bus; + if (class_driver->driver.fops->function_disable) + class_driver->driver.fops->function_disable + ((struct usbd_function_instance *)class_instance); + class_driver->driver.flags |= FUNCTION_ENABLED; + } + for (i = 0; i < composite_instance->interface_functions; i++) { + struct usbd_interface_instance *interfaces_array = composite_instance->interfaces_array; + struct usbd_interface_instance *interface_instance = interfaces_array + i; + struct usbd_interface_driver *interface_driver; + CONTINUE_UNLESS(interface_instance); + TRACE_MSG1(USBD, "disable interface[%d]", i); + interface_driver = (struct usbd_interface_driver *)interface_instance->function.function_driver; + interface_instance->function.bus = bus; + if (interface_driver->driver.fops->function_disable) + interface_driver->driver.fops->function_disable + ((struct usbd_function_instance *)interface_instance); + interface_driver->driver.flags |= FUNCTION_ENABLED; + } + } + return rc; +} + +/*! + * Function Management: + * usbd_enable_function() + * usbd_disable_function() + * + */ + +/*! + * usbd_enable_function() - called to enable the desired function + * + * Used by a USB Bus interface driver to create a virtual device. + * + * @param bus + * @param arg + * @param serial_number + * @return non-zero if error + */ +int usbd_enable_function(struct usbd_bus_instance *bus, char *arg, char *serial_number) +{ + struct usbd_composite_driver *composite_driver = NULL; + struct usbd_composite_instance *composite_instance = NULL; + + struct usbd_simple_driver *simple_driver = NULL; + struct usbd_simple_instance *simple_instance = NULL; + + //struct usbd_configuration_descriptor *configuration_descriptor = NULL; + struct usbd_endpoint_map *endpoint_map_array = NULL; + struct usbd_endpoint_request *requestedEndpoints = NULL; + + //struct otg_list_node *lhd = NULL; + int len = 0; + int i; + int rc = -EINVAL; + int epn; + int endpointsRequested = 0; + //int endpoints; + + TRACE_STRING(USBD, "serial_number: %s", serial_number); + + RETURN_EINVAL_IF(otg_sem_wait(&usbd_bus_sem)); + + if (arg && bus->arg) + LKFREE(bus->arg); + + if (arg) { + bus->arg = LSTRDUP(arg); + len = strlen(arg); + } + + for (i = 1; i < bus->endpoints; i++) { + struct usbd_endpoint_instance *endpoint = bus->endpoint_array + i; + endpoint->active_urb = NULL; + } + + TRACE_STRING(USBD, "arg: %s", arg); + + //PREPARE_WORK_ITEM(bus->reset_bh, usbd_device_reset, bus); + //PREPARE_WORK_ITEM(bus->suspend_bh, usbd_device_suspended, bus); + //PREPARE_WORK_ITEM(bus->resume_bh, usbd_device_resumed, bus); + bus->status = USBD_OK; + TRACE_MSG1(USBD, "bus->status: %d", bus->status); + + /* find requested function + */ + if ((simple_driver = usbp_find_simple_driver(arg))) { + + TRACE_MSG0(USBD, "simple"); + + // allocate simple_instance + // + THROW_UNLESS((simple_instance = usbp_alloc_simple_instance (simple_driver, bus)), error); + bus->function_instance = (struct usbd_function_instance *)simple_instance; + + #if 0 + simple_driver->iManufacturer = + usbd_realloc_string (&simple_instance->function, strindex_product, + simple_driver->device_description->iManufacturer); + #else + simple_driver->iManufacturer = usbd_alloc_string (&simple_instance->function, + simple_driver->device_description->iManufacturer); + #endif + simple_driver->iProduct = usbd_alloc_string (&simple_instance->function, + simple_driver->device_description->iProduct); + + if (serial_number && strlen (serial_number)) + simple_driver->iSerialNumber = usbd_alloc_string (&simple_instance->function, serial_number); + else + simple_driver->iSerialNumber = usbd_alloc_string (&simple_instance->function, + simple_driver->device_description->iSerialNumber); + + bus_function_enable (bus, &simple_instance->function, serial_number); + + requestedEndpoints = simple_driver->requestedEndpoints; + endpointsRequested = simple_driver->endpointsRequested; + endpoint_map_array = simple_instance->endpoint_map_array; + } + + else if ((composite_driver = usbp_find_composite_driver(arg))) { + + TRACE_MSG0(USBD, "composite"); + THROW_UNLESS((composite_instance = usbp_alloc_composite_instance (composite_driver, bus)), error); + + TRACE_MSG1(USBD, "composite: composite_instance: %x", composite_instance); + + bus->function_instance = (struct usbd_function_instance *)composite_instance; + + /* CLASS MISC devices must have SubClass set to 0x2 and Protocol set to 0x1 + */ + if ((USB_CLASS_MISC == composite_driver->device_description->bDeviceClass) ) { + composite_driver->device_description->bDeviceSubClass = 0x2; + composite_driver->device_description->bDeviceProtocol = 0x1; + } + + #if 0 + composite_driver->iManufacturer = + usbd_realloc_string (&composite_instance->function, strindex_product, + composite_driver->device_description->iManufacturer); + #else + composite_driver->iManufacturer = usbd_alloc_string (&composite_instance->function, + composite_driver->device_description->iManufacturer); + #endif + composite_driver->iProduct = usbd_alloc_string (&composite_instance->function, + composite_driver->device_description->iProduct); + if (serial_number && strlen (serial_number)) + composite_driver->iSerialNumber = usbd_alloc_string (&composite_instance->function, serial_number); + else + composite_driver->iSerialNumber = usbd_alloc_string (&composite_instance->function, + composite_driver->device_description->iSerialNumber); + + //TRACE_MSG3(USBD, "iManufacturer: %d iProduct: %d iSerialNumber: %d", composite_driver->iManufacturer, + // composite_driver->iProduct, composite_driver->iSerialNumber); + + TRACE_MSG0(USBD, "composite - enable"); + bus_function_enable (bus, &composite_instance->function, serial_number); + + requestedEndpoints = composite_instance->requestedEndpoints; + endpointsRequested = composite_instance->endpointsRequested; + endpoint_map_array = composite_instance->endpoint_map_array; + } + else + THROW(error); + + + /* Request endpoints from bus interface driver + */ + THROW_IF(bus->driver->bops->request_endpoints( bus, endpoint_map_array, endpointsRequested, requestedEndpoints), error); + + /* create configuration descriptors + */ + #ifdef CONFIG_OTG_HIGH_SPEED + for (i = 0; i < 2; i++) + #else /* CONFIG_OTG_HIGH_SPEED */ + for (i = 0; i < 1; i++) + #endif /* CONFIG_OTG_HIGH_SPEED */ + { + if (simple_instance) + usbp_alloc_simple_configuration_descriptor (simple_instance, + simple_instance->configuration_size, i); + else if (composite_instance) + usbp_alloc_composite_configuration_descriptor (composite_instance, + composite_instance->configuration_size, i); + } + + /* set the endpoints in the peripheral driver + */ + THROW_IF(bus->driver->bops->set_endpoints( bus, endpointsRequested, endpoint_map_array), error); + + /* iterate across the logical endpoint map to copy appropriate information + * into the physical endpoint instance array + */ + for (epn = 0; epn < endpointsRequested; epn++) { + + struct usbd_endpoint_map *endpoint_map = endpoint_map_array + epn; + int physicalEndpoint = endpoint_map->physicalEndpoint[0]; + struct usbd_endpoint_instance *endpoint = bus->endpoint_array + physicalEndpoint; + int hs; + + endpoint_map->endpoint = endpoint; + + for (hs = 0; hs < 2; hs++) { + + endpoint->physicalEndpoint[hs] = endpoint_map->physicalEndpoint[0]; + endpoint->bEndpointAddress[hs] = endpoint_map->bEndpointAddress[hs]; + endpoint->bmAttributes[hs] = endpoint_map->bmAttributes[hs]; + endpoint->transferSize[hs] = endpoint_map->transferSize[hs]; + endpoint->wMaxPacketSize[hs] = endpoint_map->wMaxPacketSize[hs]; + + switch(endpoint->bEndpointAddress[hs] & USB_ENDPOINT_DIR_MASK) { + case USB_DIR_IN: + endpoint->tx_transferSize = endpoint_map->transferSize[hs]; + endpoint->last = 0; + endpoint->tx_urb = NULL; + break; + + case USB_DIR_OUT: + endpoint->rcv_transferSize = endpoint_map->transferSize[hs]; + endpoint->rcv_urb = NULL; + break; + } + + TRACE_MSG4(USBD, "endpoint[%d]: %x wMaxPacketSize: %02x %02x", + hs, endpoint, + endpoint->wMaxPacketSize[hs], + endpoint_map->wMaxPacketSize[hs]); + } + + } + bus->ep0->endpoint_map_array->endpoint = bus->endpoint_array; + + /* endpoint zero has no wMaxPacketSize set yet... so alloc buffer separately */ + THROW_UNLESS(bus->ep0_urb = usbd_alloc_urb((struct usbd_function_instance *) bus->ep0, 0, 0, NULL), error); + THROW_UNLESS((bus->ep0_urb->buffer = (u8 *)KMALLOC (512)), error); + + rc = 0; + CATCH(error) { + if (simple_instance) + usbp_dealloc_simple_instance(simple_instance); + if (composite_instance) + usbp_dealloc_composite_instance(composite_instance); + + } + otg_sem_post(&usbd_bus_sem); + return rc; +} + +#if 0 +/*! + * usbd_enable_function_irq () - + * + * @param bus + * @param arg + * @param serial_number + */ +int usbd_enable_function_irq (struct usbd_bus_instance *bus, char *arg, char *serial_number) +{ + return usbd_enable_function(bus, arg, serial_number); +} +#endif + +/*! + * usbd_function_disable() - disable a usbd function driver + * @param function + */ +void usbd_function_disable (struct usbd_function_instance *function) +{ + //int configuration; + struct usbd_function_driver *function_driver = function->function_driver; + /* + * XXX composite or class? + */ + TRACE_MSG3(USBD, "DISABLE: function_instance: %x function_driver: %x function_disable: %x", + function, function_driver, + function_driver ? function_driver->fops->function_disable : NULL + ); + /* + if (function_driver && function->function_driver->fops->function_disable) + function->function_driver->fops->function_disable (function); + */ + + + function->bus = NULL; + function_driver->flags &= ~FUNCTION_ENABLED; +} + + +/*! + * usbd_disable_function() - called to disable the current function + * + * Used by a USB Bus interface driver to destroy a virtual device. + * + * @param bus + */ +void usbd_disable_function (struct usbd_bus_instance *bus) +{ + TRACE_MSG0(USBD, "DISABLE-"); + + // prevent any more bottom half scheduling + while (otg_sem_wait(&usbd_bus_sem)); + bus->status = USBD_CLOSING; + TRACE_MSG1(USBD, "bus->status: %d", bus->status); + otg_sem_post(&usbd_bus_sem); + + + if (bus->function_instance) { + + TRACE_MSG0(USBD, "disable function"); + usbd_function_disable (bus->function_instance); + bus_function_disable (bus, bus->function_instance); + usbp_dealloc_composite_instance((struct usbd_composite_instance *)bus->function_instance); + + if (bus->ep0_urb->buffer) LKFREE(bus->ep0_urb->buffer); + LKFREE (bus->ep0_urb); + bus->ep0_urb = NULL; + + while (otg_sem_wait(&usbd_bus_sem)); + bus->function_instance = NULL; + otg_sem_post(&usbd_bus_sem); + } + bus->status = USBD_CLOSED; + TRACE_MSG1(USBD, "bus->status: %d", bus->status); + + /* XXX wait for reset, resume and suspend bh's */ + // XXX +} + +OTG_EXPORT_SYMBOL(usbd_enable_function); +OTG_EXPORT_SYMBOL(usbd_disable_function); + +/* ************************************************************************** */ +/* ************************************************************************** */ +/*! + * @brief usbd_device_reset() - tell function driver it has been reset + * + * Used by a USB Bus interface driver to pass received device request to + * the appropriate USB Function driver. + * + * @param data - bus instance pointer + * @return non-zero if errror + */ +void *usbd_device_reset(void *data) +{ + struct usbd_bus_instance *bus = (struct usbd_bus_instance *) data; + struct usbd_function_instance *function = bus->function_instance; + BOOL rc; + + TRACE_MSG0(USBD, "DEVICE_RESET AAAA"); + + RETURN_NULL_UNLESS(bus->device_state == STATE_CONFIGURED); + RETURN_NULL_UNLESS(bus->status == USBD_OK); + + /* Call all function drivers reset() operation and if available any + * interface drivers reset() operations. + */ + rc = function->function_driver->fops->reset && function->function_driver->fops->reset (function); + + TRACE_MSG0(USBD, "DEVICE_RESET BBBB"); + TRACE_MSG1(USBD, "type: %d", function->function_type); + + + if (function_composite == function->function_type) { + struct usbd_composite_instance *composite_instance = (struct usbd_composite_instance *)function; + struct usbd_class_instance *class_instance = composite_instance->class_instance; + int i; + + TRACE_MSG1(USBD, "interfaces: %d", composite_instance->interface_functions); + TRACE_MSG0(USBD, "DEVICE_RESET CCCC"); + rc = class_instance && + class_instance->function.function_driver->fops->reset && + class_instance->function.function_driver->fops->reset ((struct usbd_function_instance *)class_instance); + + TRACE_MSG0(USBD, "DEVICE_RESET DDDD"); + for (i = 0; i < composite_instance->interface_functions; i++) { + struct usbd_interface_instance *interface_instance = composite_instance->interfaces_array + i; + rc = interface_instance && + interface_instance->function.function_driver->fops->reset && + interface_instance->function.function_driver->fops->reset + ((struct usbd_function_instance *)interface_instance); + } + TRACE_MSG0(USBD, "DEVICE_RESET EEEE"); + } + TRACE_MSG0(USBD, "DEVICE_RESET FFFF"); + return NULL; +} + +/*! + * @brief device_suspended() - tell function driver it has been suspended + * + * Used by a USB Bus interface driver to pass received device request to + * the appropriate USB Function driver. + * + * @param data - bus instance pointer + * @return non-zero if errror + */ +void *usbd_device_suspended(void *data) +{ + struct usbd_bus_instance *bus = (struct usbd_bus_instance *) data; + struct usbd_function_instance *function = bus->function_instance; + BOOL rc; + + TRACE_MSG2(USBD, "device_state: %d status: %d",bus->device_state, bus->status); + + RETURN_NULL_UNLESS(bus->device_state == STATE_SUSPENDED); + RETURN_NULL_UNLESS(bus->status == USBD_OK); + + /* Call all function drivers suspended() operation and if available any + * interface drivers suspended() operations. + */ + rc = function->function_driver->fops->suspended && function->function_driver->fops->suspended (function); + + if (function_composite == function->function_type) { + struct usbd_composite_instance *composite_instance = (struct usbd_composite_instance *)function; + struct usbd_class_instance *class_instance = composite_instance->class_instance; + int i; + rc = class_instance && + class_instance->function.function_driver->fops->suspended && + class_instance->function.function_driver->fops->suspended + ((struct usbd_function_instance *)class_instance); + for (i = 0; i < composite_instance->interface_functions; i++) { + struct usbd_interface_instance *interface_instance = composite_instance->interfaces_array + i; + rc = interface_instance && + interface_instance->function.function_driver->fops->suspended && + interface_instance->function.function_driver->fops->suspended + ((struct usbd_function_instance *)interface_instance); + } + } + return NULL; +} + +/*! + * @brief device_resumed() - tell function driver it has been resumed + * + * Used by a USB Bus interface driver to pass received device request to + * the appropriate USB Function driver. + * + * @param data - bus instance pointer + * @return non-zero if errror + */ +void *usbd_device_resumed(void *data) +{ + struct usbd_bus_instance *bus = (struct usbd_bus_instance *) data; + struct usbd_function_instance *function = bus->function_instance; + BOOL rc; + + TRACE_MSG1(USBD, "type: %d", function->function_type); + + RETURN_NULL_UNLESS(bus->device_state == STATE_CONFIGURED); + RETURN_NULL_UNLESS(bus->status == USBD_OK); + + /* Call all function drivers resumed() operation and if available any + * interface drivers resumed() operations. + */ + rc = function->function_driver->fops->resumed && function->function_driver->fops->resumed (function); + + if (function_composite == function->function_type) { + struct usbd_composite_instance *composite_instance = (struct usbd_composite_instance *)function; + struct usbd_class_instance *class_instance = composite_instance->class_instance; + int i; + rc = class_instance && + class_instance->function.function_driver->fops->resumed && + class_instance->function.function_driver->fops->resumed + ((struct usbd_function_instance *)class_instance); + for (i = 0; i < composite_instance->interface_functions; i++) { + struct usbd_interface_instance *interface_instance = composite_instance->interfaces_array + i; + rc = interface_instance && + interface_instance->function.function_driver->fops->resumed && + interface_instance->function.function_driver->fops->resumed + ((struct usbd_function_instance *)interface_instance); + } + } + return NULL; +} + +/* ********************************************************************************************* */ +/* ********************************************************************************************* */ +/*! + * Device Request Processing: + * usbd_device_request() + * + */ + +/*! + * @name C.f. 9.4 Standard Requests and table 9.3 + * + * Encode valid requests into a bitmap for each recipient type for + * both directions. This is needed because there are some requests + * that appear to be a standard request but are not described in + * Chapter nine. For example the mouse driver hid request. + * + * So we only process the actual set of standard requests that are + * defined in chapter nine and even if it appears to be standard but + * is not in chapter nine then we pass it to the function driver. + * + */ + +#define STD(x) (1<<(x+1)) + +/*! @{ */ +/*! h2d_standard_requests and d2h_standard_requests + * + * These tables list all of the valid Chapter Nine requests. Any request + * not listed in these tables will NOT be processed by the EP0 function and + * will instead be passed to the appropriate function driver. + */ +u32 h2d_standard_requests[4] = { + // 0 - Device + STD(USB_REQ_CLEAR_FEATURE) | + STD(USB_REQ_SET_FEATURE) | + STD(USB_REQ_SET_ADDRESS) | + STD(USB_REQ_SET_DESCRIPTOR) | + STD(USB_REQ_SET_CONFIGURATION) , + // 1 - Interface + STD(USB_REQ_CLEAR_FEATURE) | + STD(USB_REQ_SET_FEATURE) | + STD(USB_REQ_SET_INTERFACE) , + // 2 - Endpoint + STD(USB_REQ_CLEAR_FEATURE) | + STD(USB_REQ_SET_FEATURE) , + // 3 - Other + 0, +}; + +u32 d2h_standard_requests[4] = { + // 0 - Device + STD(USB_REQ_GET_STATUS) | + STD(USB_REQ_GET_DESCRIPTOR) | + STD(USB_REQ_GET_CONFIGURATION), + // 1 - Interface + STD(USB_REQ_GET_STATUS) | + STD(USB_REQ_GET_INTERFACE) , + // 2 - Endpoint + STD(USB_REQ_GET_STATUS) | + STD(USB_REQ_SYNCH_FRAME) , + // 3 - Other + 0, +}; + +/* @} */ + + +/*! + * @brief devreq_set_configuration() - process a received urb + * + * Used by a USB Bus interface driver to pass received device request to + * the appropriate USB Function driver. + * + * @param function + * @param wValue + * @return non-zero if errror + */ +static int devreq_set_configuration(struct usbd_function_instance *function, int wValue) +{ + struct usbd_bus_instance *bus = function->bus; + BOOL rc; + + // c.f. 9.4.7 - the top half of wValue is reserved + // c.f. 9.4.7 - zero is the default or addressed state, in our case this + // is the same is configuration zero, but will be fixed in usbd.c when used. + + u8 ConfigurationValue = wValue & 0x7f; + + //RETURN_EINVAL_IF(ConfigurationValue > bNumConfigurations); + ///* A ConfigurationValue of zero is for the default configuration (i.e.) the + // * first one. A non-zero value needs to be decremented to allow it to be used + // * as an index. + // */ + //ConfigurationValue = !ConfigurationValue ? 0 : ConfigurationValue -1; + + + /* XXX check configuration value is correct, look in pre-built descriptors + * RETURN_EINVAL_UNLESS(configuration == ...) + */ + + + /* propagate event */ + //XXX pcd_bus_event_handler_irq (function->bus, ConfigurationValue ? DEVICE_CONFIGURED : DEVICE_DE_CONFIGURED, 0); + + if (bus->driver->bops->set_configuration) bus->driver->bops->set_configuration (bus, ConfigurationValue); + + TRACE_MSG1(USBD, "type: %d", function->function_type); + + /* Call all function drivers set_configuration() operation and if available any + * interface drivers set_configuration() operations. + */ + if (function_simple == function->function_type) { + + struct usbd_simple_instance *simple_instance = (struct usbd_simple_instance *)function; + + /* save ConfigurationValue for GET_CONFIGURATION request + */ + simple_instance->ConfigurationValue = wValue & 0x7f;//ConfigurationValue; + + } + + rc = function->function_driver->fops->set_configuration && + function->function_driver->fops->set_configuration (function, wValue); + + if (function_composite == function->function_type) { + struct usbd_composite_instance *composite_instance = (struct usbd_composite_instance *)function; + struct usbd_class_instance *class_instance = composite_instance->class_instance; + int i; + + /* save ConfigurationValue for GET_CONFIGURATION request, call + * the reset function for the class and all interface functins. + */ + composite_instance->ConfigurationValue = wValue & 0x7f;//ConfigurationValue; + + rc = class_instance && + class_instance->function.function_driver->fops->set_configuration && + class_instance->function.function_driver->fops->set_configuration + ((struct usbd_function_instance *)class_instance, wValue); + + for (i = 0; i < composite_instance->interface_functions; i++) { + struct usbd_interface_instance *interface_instance = composite_instance->interfaces_array + i; + rc = interface_instance && + interface_instance->function.function_driver->fops->set_configuration && + interface_instance->function.function_driver->fops->set_configuration + ((struct usbd_function_instance *)interface_instance, wValue); + } + } + return 0; +} + +/*! + * @brief find_interface_instance() + * + * Used by a USB Bus interface driver to pass received device request to + * the appropriate USB Function driver. + * + * @param composite_instance + * @param wIndex + * @return interface_instance + */ +static struct usbd_interface_instance* +find_interface_instance(struct usbd_composite_instance *composite_instance, int wIndex) +{ + int i; + TRACE_MSG1(USBD, "wIndex: %d", wIndex); + for (i = 0; i < composite_instance->interface_functions; i++) { + struct usbd_interface_instance *interface_instance = composite_instance->interfaces_array + i; + struct usbd_interface_driver *interface_driver = + (struct usbd_interface_driver *) interface_instance->function.function_driver; + + TRACE_MSG3(USBD, "wIndex: %d interface->wIndex: %d, interface->interfaces: %d", + wIndex, interface_instance->wIndex, interface_driver->interfaces); + + CONTINUE_UNLESS((wIndex >= interface_instance->wIndex) && + (wIndex < interface_instance->wIndex + interface_driver->interfaces) ); + + TRACE_MSG2(USBD, "found interface: %d altSetting: %d", + interface_instance->wIndex, interface_instance->altsetting); + return interface_instance; + } + TRACE_MSG0(USBD, "ERROR"); + return NULL; +} + +/*! + * @brief devreq_set_interface_altsetting() - + * + * Used by a USB Bus interface driver to pass received device request to + * the appropriate USB Function driver. + * + * @param function + * @param wIndex + * @param wValue + * @return non-zero if errror + */ +static int devreq_set_interface_altsetting(struct usbd_function_instance *function, int wIndex, int wValue) +{ + //struct usbd_bus_instance *bus = function->bus; + BOOL rc; + + TRACE_MSG2(USBD, "Interface/wIndex: %02x altsetting/wValue: %02x", wIndex, wValue); + + // XXX pcd_bus_event_handler_irq (bus, DEVICE_SET_INTERFACE, 0); + + /* Call function driver set_interface() operation and if available any + * interface drivers set_interface() operations. + */ + if (function_simple == function->function_type) { + struct usbd_simple_instance *simple_instance = (struct usbd_simple_instance *)function; + RETURN_EINVAL_IF(wIndex > simple_instance->interfaces); + simple_instance->altsettings[wIndex] = (u8) wValue; + rc = simple_instance->function.function_driver->fops->set_interface && + simple_instance->function.function_driver->fops->set_interface (function, wIndex, wValue); + } + + /* Note that composite and class drivers does not receive set_interface information + */ + if (function_composite == function->function_type) { + struct usbd_composite_instance *composite_instance = (struct usbd_composite_instance *)function; + struct usbd_interface_instance *interface_instance; + RETURN_EINVAL_UNLESS((interface_instance = find_interface_instance(composite_instance, wIndex))); + + rc = interface_instance->function.function_driver->fops->set_interface && + interface_instance->function.function_driver->fops->set_interface + ((struct usbd_function_instance *)interface_instance, wIndex, wValue); + } + return 0; +} + + + +/*! + * @brief do_endpoint_cleared - clear endpoint + * + * Set or clear endpoint. + * + * Call bus interface halt_endpoint to set or clear. + * + * Call interface function endpoint clear to indicate clear. + * + * Return non-zero to indicate failure. + * @param function + * @param bEndpointAddress + * @param setclear_flag + * @return int + */ +static int do_endpoint_cleared (struct usbd_function_instance *function, int bEndpointAddress, int setclear_flag) +{ + u16 endpoint_index; + //int endpoints; + //int i; + struct usbd_endpoint_instance *endpoint; + + TRACE_MSG2(USBD, "bEndpointAddress: %04x setclear: %x", bEndpointAddress, setclear_flag); + + /* Find and verify the endpoint map index as well. Then clear the + * endpoint using the bus interface driver halt_endpoint() + * operation, if that is ok, then inform the function driver using + * the endpoint_cleared() operation. + */ + RETURN_EINVAL_UNLESS((endpoint_index = usbd_find_endpoint_index(function->bus, bEndpointAddress)) + < function->bus->endpoints); + TRACE_MSG1(USBD, "endpoint_index: %x", endpoint_index); + + RETURN_EINVAL_UNLESS((endpoint = function->bus->endpoint_array + endpoint_index)); + + RETURN_EINVAL_IF(function->bus->driver->bops->halt_endpoint(function->bus, bEndpointAddress, setclear_flag)); + TRACE_MSG0(USBD, "halt_endpoint OK"); + + RETURN_ZERO_IF(setclear_flag); + + if (function_simple == function->function_type) { + struct usbd_simple_instance *simple_instance = (struct usbd_simple_instance *)function; + TRACE_MSG2(USBD, "SIMPLE bEndpointAddress: %0dx interfaces: %02d", bEndpointAddress, simple_instance->interfaces); + //RETURN_EINVAL_IF(wIndex > simple_instance->interfaces); + if (simple_instance->function.function_driver->fops->endpoint_cleared) + simple_instance->function.function_driver->fops->endpoint_cleared (function, endpoint_index); + } + + if (function_composite == function->function_type) { + struct usbd_composite_instance *composite_instance = (struct usbd_composite_instance *)function; + struct usbd_interface_instance *interface_instance; + TRACE_MSG2(USBD, "COMPOSITE endpoint->interface_wIndex: %0d find: %x", + endpoint->interface_wIndex, + find_interface_instance(composite_instance, endpoint->interface_wIndex)); + + RETURN_EINVAL_UNLESS((interface_instance = + find_interface_instance(composite_instance, endpoint->interface_wIndex))); + TRACE_MSG1(USBD, "interface_instance: %x", interface_instance); + + if (interface_instance->function.function_driver->fops->endpoint_cleared) + interface_instance->function.function_driver->fops->endpoint_cleared + ((struct usbd_function_instance *)interface_instance, bEndpointAddress); + } + return 0; +} + + +/*! + * @brief do_non_standard_device_request - process a device request + * + * Process a received device request. If not a Chapter nine request pass it + * to the other loaded function driver device_request() function. + * + * @param function + * @param request + @return int Return non-zero to indicate failure. + */ +static int do_non_standard_device_request (struct usbd_function_instance *function, struct usbd_device_request *request) +{ + struct usbd_class_instance *class_instance; + struct usbd_interface_instance *interface_instance; + u8 bmRequestType = request->bmRequestType; + //u16 wValue = le16_to_cpu(request->wValue); + u16 wIndex = le16_to_cpu(request->wIndex); + //u16 wLength = le16_to_cpu(request->wLength); + + u16 endpoint_index; + //int endpoints; + //int i; + + if (function_simple == function->function_type) { + struct usbd_simple_instance *simple_instance = (struct usbd_simple_instance *)function; + return simple_instance->function.function_driver->fops->device_request ? + simple_instance->function.function_driver->fops->device_request(function, request) : 0; + } + + if (function_composite == function->function_type) { + struct usbd_composite_instance *composite_instance = (struct usbd_composite_instance *)function; + int i; + + /* direct request to simple, interface or composite driver as appropriate + */ + switch(bmRequestType & USB_REQ_RECIPIENT_MASK) { + case USB_REQ_RECIPIENT_ENDPOINT: + + /* Need to find interface function responsible for endpoint and and give + * it the device request. + */ + RETURN_EINVAL_UNLESS((endpoint_index = usbd_find_endpoint_index(function->bus, wIndex)) + < function->bus->endpoints); + + wIndex = function->bus->endpoint_array[endpoint_index].interface_wIndex; + + /* FALL THROUGH */ + + case USB_REQ_RECIPIENT_INTERFACE: + + /* Determine interface function responsible for interface and give it + * the device request. + */ + RETURN_EINVAL_UNLESS((interface_instance = find_interface_instance(composite_instance, wIndex))); + + return interface_instance->function.function_driver->fops->device_request ? + (interface_instance->function.function_driver->fops->device_request + ((struct usbd_function_instance *)interface_instance, request)) : + -EINVAL; + /* + * XXX It may be necessary to allow all failed requests above + * to fall through to here.... + */ + case USB_REQ_RECIPIENT_DEVICE: + case USB_REQ_RECIPIENT_OTHER: + default: + /* Attempt to find someone who will handle this request ... + * As long as each interface properly fails requests it does + * not understand there is a reasonably good chance that it + * will get to the correct handler. + */ + RETURN_ZERO_UNLESS((class_instance = composite_instance->class_instance) && + class_instance->function.function_driver->fops->device_request ? + class_instance->function.function_driver->fops->device_request + ((struct usbd_function_instance *)class_instance, request) : -EINVAL); + + for (i = 0; i < composite_instance->interface_functions; i++) { + struct usbd_interface_instance *interface_instance = composite_instance->interfaces_array + i; + + TRACE_MSG2(USBD, "interface_instance: %x %s", + interface_instance, + interface_instance ? interface_instance->function.name : ""); + + RETURN_ZERO_UNLESS(interface_instance && + interface_instance->function.function_driver->fops->device_request ? + (interface_instance->function.function_driver->fops->device_request + ((struct usbd_function_instance *)interface_instance, request)) : + -EINVAL); + } + return -EINVAL; + } + } + return -EINVAL; /* never used */ +} + +/*! + * @brief devreq_get_device_feature_settings() - process a received urb + * + * Used by a USB Bus interface driver to pass received device request to + * the appropriate USB Function driver. + * + * @param function + * @return non-zero if errror + */ +int devreq_get_device_feature_settings(struct usbd_function_instance *function) +{ + struct usbd_bus_instance *bus = function->bus; + return bus->device_feature_settings; +} + + +/*! + * @brief devreq_device_feature - handle set/clear feature requests for device + * Used by the USB Device Core to set/clear endpoint halt status. + * + * We assume that if the udc driver does not implement anything then + * we should just return zero for ok. + + * @param bus + * @param features + * @param flag + * @return int + */ +int devreq_device_feature (struct usbd_bus_instance *bus, int features, int flag) +{ + struct pcd_instance *pcd = (struct pcd_instance *)bus->privdata; + //u32 features = bus->device_feature_settings; + + TRACE_MSG0(USBD, "BUS_DEVICE FEATURE"); + + /* if USB Host has enabled remote wakeup + */ + if (features & FEATURE(USB_DEVICE_REMOTE_WAKEUP) ) + otg_queue_event(pcd->otg, + flag ? REMOTE_WAKEUP_ENABLED : REMOTE_WAKEUP_ENABLED_, USBD, + flag ? "DEVICE FEATURE - REMOTE_WAKEUP_ENABLED" : + "DEVICE FEATURE - REMOTE_WAKEUP_ENABLED/"); + + /* not legal to clear HNP */ + if (flag) { + + /* if A-Device has enabled hnp + */ + if (features & FEATURE(USB_OTG_B_HNP_ENABLE) ) + otg_queue_event(pcd->otg, HNP_ENABLED, USBD, "DEVICE FEATURE - B_HNP_ENABLE"); + + /* if A-Device does not support HNP on this port + */ + else if ( features & FEATURE(USB_OTG_A_ALT_HNP_ENABLE) ) + ; //otg_event(pcd->otg, not(a_hnp_support)); + + /* if A-Device does support HNP + */ + else if (features & FEATURE(USB_OTG_A_HNP_SUPPORT)) + ; //otg_event(pcd->otg, a_hnp_support); + + } + return 0; +} + +/*! + * @brief copy_config() - copy data into buffer + + * @param cp + * @param data + * @param actual_length + * @param max_buf + * @return length + */ +static int copy_config (u8 *cp, void *data, int actual_length, int max_buf) +{ + int available = max_buf - actual_length; + int length = MIN(*(u8 *)data, available); + RETURN_ZERO_UNLESS (data); + RETURN_ZERO_UNLESS (length); + memcpy (cp, data, length); + return length; +} + +#if 0 +/*! + * @brief copy_endpoint() - copy data into buffer + * @param function + * @param cp + * @param endpoint + * @param endpoint_index + * @param actual_length + * @param max_buf + * @param hs + * @return length + */ +static int copy_endpoint (struct usbd_function_instance *function, u8 *cp, + struct usbd_endpoint_descriptor *endpoint, int endpoint_index, int actual_length, int max_buf, int hs) +{ + int available = max_buf - actual_length; + int length = MIN(endpoint->bLength, available); + struct usbd_endpoint_descriptor endpoint_copy; + + RETURN_ZERO_IF (!length); + memcpy (&endpoint_copy, endpoint, endpoint->bLength); + usbd_endpoint_update(function, endpoint_index, &endpoint_copy, hs); + memcpy (cp, &endpoint_copy, length); + return length; +} +#endif + +/*! + * @brief usbd_get_descriptor() - copy a descriptor into buffer + * + * @param function + * @param buffer + * @param wLength + * @param descriptor_type + * @param index + * @return non-zero for error. + */ +int usbd_get_descriptor (struct usbd_function_instance *function, u8 *buffer, int wLength, int descriptor_type, int index) +{ + struct usbd_bus_instance *bus = function->bus; + struct usbd_function_driver *function_driver = bus->function_instance->function_driver; + int actual_length = 0; + + //printk(KERN_INFO"%s:\n", __FUNCTION__); + //TRACE_MSG3(USBD, "descriptor_type: %d index: %d wLength: %d", descriptor_type, index, wLength); + switch (descriptor_type) { + case USB_DT_DEVICE: { + struct usbd_device_descriptor device_descriptor; + struct usbd_device_description *device_description; + u8 iManufacturer; + u8 iProduct; + u8 iSerialNumber = 0; + + //printk(KERN_INFO"%s: DT_DEVICE\n", __FUNCTION__); + //TRACE_MSG1(USBD, "DT_DEVICE function_type: %d", function->function_type); + + if (function_simple == function->function_type) { + struct usbd_simple_driver * simple_driver = + (struct usbd_simple_driver *)function_driver; + + device_description = simple_driver->device_description; + iManufacturer = simple_driver->iManufacturer; + iProduct = simple_driver->iProduct; + iSerialNumber = simple_driver->iSerialNumber; + } + else if (function_composite == function->function_type) { + + struct usbd_composite_driver * composite_driver = + (struct usbd_composite_driver *)function_driver; + + device_description = composite_driver->device_description; + + //TRACE_MSG6(USBD, "iManufacturer: %d iProduct: %d iSerialNumber: %d " + // "idVendor: %04x idProduct: %04x bcdDevice: %04x", + // composite_driver->iManufacturer, composite_driver->iProduct, + // composite_driver->iSerialNumber, device_description->idVendor, + // device_description->idProduct, device_description->bcdDevice + // ); + + iManufacturer = composite_driver->iManufacturer; + iProduct = composite_driver->iProduct; + iSerialNumber = composite_driver->iSerialNumber; + } + else + return 0; + + //TRACE_MSG3(USBD, "function_driver: %x device_descriptor: %x %d", + // function_driver, device_descriptor, device_descriptor->bLength); + + RETURN_EINVAL_UNLESS(device_description); + + // correct the correct control endpoint 0 wLength packet size into the descriptor + //device_descriptor = (struct usbd_device_descriptor *) buffer; + + device_descriptor.bLength = sizeof(struct usbd_device_descriptor);; + device_descriptor.bDescriptorType = USB_DT_DEVICE; + device_descriptor.bcdUSB = __constant_cpu_to_le16(USB_BCD_VERSION); + device_descriptor.bDeviceClass = device_description->bDeviceClass; + device_descriptor.bDeviceSubClass = device_description->bDeviceSubClass; + device_descriptor.bDeviceProtocol = device_description->bDeviceProtocol; + device_descriptor.bMaxPacketSize0 = device_description->bMaxPacketSize0; + + device_descriptor.idVendor = cpu_to_le16(device_description->idVendor); + device_descriptor.idProduct = cpu_to_le16(device_description->idProduct); + device_descriptor.bcdDevice = cpu_to_le16(device_description->bcdDevice); + device_descriptor.iManufacturer = iManufacturer; + device_descriptor.iProduct = iProduct; + device_descriptor.iSerialNumber = iSerialNumber; + device_descriptor.bNumConfigurations = 1; + + //actual_length += wLength; + //memcpy(buffer, device_descriptor, wLength); + + /* copy device descriptor for this device */ + actual_length += copy_config (buffer + actual_length, &device_descriptor, actual_length, wLength); + break; + } + + #ifdef CONFIG_OTG_HIGH_SPEED + case USB_DT_DEVICE_QUALIFIER: { + // c.f. 9.6.2 Device Qualifier + struct usbd_device_qualifier_descriptor device_qualifier_descriptor; + + struct usbd_device_description *device_description; + + //printk(KERN_INFO"%s: DT_DEVICE_QUALIFIER\n", __FUNCTION__); + + if (function_simple == function->function_type) { + struct usbd_simple_driver * simple_driver = + (struct usbd_simple_driver *)function_driver; + + device_description = simple_driver->device_description; + } + else if (function_composite == function->function_type) { + + struct usbd_composite_driver * composite_driver = + (struct usbd_composite_driver *)function_driver; + + device_description = composite_driver->device_description; + } + else + return 0; + + device_qualifier_descriptor.bLength = sizeof(struct usbd_device_qualifier_descriptor);; + device_qualifier_descriptor.bDescriptorType = USB_DT_DEVICE_QUALIFIER; + device_qualifier_descriptor.bcdUSB = __constant_cpu_to_le16(USB_BCD_VERSION); + device_qualifier_descriptor.bDeviceClass = device_description->bDeviceClass; + device_qualifier_descriptor.bDeviceSubClass = device_description->bDeviceSubClass; + device_qualifier_descriptor.bDeviceProtocol = device_description->bDeviceProtocol; + device_qualifier_descriptor.bMaxPacketSize0 = device_description->bMaxPacketSize0; + device_qualifier_descriptor.bNumConfigurations = 1; // XXX + device_qualifier_descriptor.bReserved = 0; + + // copy descriptor for this device + actual_length += copy_config (buffer + actual_length, &device_qualifier_descriptor, actual_length, wLength); + + break; + } + + case USB_DT_OTHER_SPEED_CONFIGURATION: + #endif /* CONFIG_OTG_HIGH_SPEED */ + + case USB_DT_CONFIGURATION: { + //printk(KERN_INFO"%s: DT_CONFIGURATION\n", __FUNCTION__); + #ifdef CONFIG_OTG_HIGH_SPEED + int hs = bus->high_speed ? descriptor_type == USB_DT_CONFIGURATION: + descriptor_type == USB_DT_OTHER_SPEED_CONFIGURATION; + #else /* CONFIG_OTG_HIGH_SPEED */ + int hs = 0; + #endif /* CONFIG_OTG_HIGH_SPEED */ + struct usbd_configuration_descriptor *configuration_descriptor; + + if (function_simple == function->function_type) + configuration_descriptor = ((struct usbd_simple_instance *)function)->configuration_descriptor[hs]; + else if (function_composite == function->function_type) + configuration_descriptor = ((struct usbd_composite_instance *)function)->configuration_descriptor[hs]; + else + return 0; + + actual_length = MIN(le16_to_cpu(configuration_descriptor->wTotalLength), wLength); + memcpy (buffer, configuration_descriptor, actual_length); + + /* set descriptor type to requested type */ + configuration_descriptor = (struct usbd_configuration_descriptor *)buffer; + configuration_descriptor->bDescriptorType = descriptor_type; + + #if 0 + { + u8 * cp = buffer; + + int i; + int size = configuration_descriptor->wTotalLength; + + TRACE_MSG2(USBD, "cfg: %x size: %d", cp, size); + for (i = 0; i < size; i+= 8) { + TRACE_MSG8(USBD, "%02x %02x %02x %02x %02x %02x %02x %02x", + cp[i + 0], cp[i + 1], cp[i + 2], cp[i + 3], + cp[i + 4], cp[i + 5], cp[i + 6], cp[i + 7] + ); + } + + } + #endif + break; + } + + case USB_DT_STRING: { + struct usbd_string_descriptor *string_descriptor = NULL; + //printk(KERN_INFO"%s: DT_STRING\n", __FUNCTION__); + RETURN_EINVAL_IF (!(string_descriptor = usbd_get_string_descriptor (function, (u8)index))); + actual_length += copy_config (buffer + actual_length, string_descriptor, actual_length, wLength); + break; + } + + case USB_DT_OTG: { + struct usbd_otg_descriptor *otg_descriptor = (struct usbd_otg_descriptor *) buffer; + actual_length += sizeof(struct usbd_otg_descriptor); + otg_descriptor->bLength = sizeof(struct usbd_otg_descriptor); + otg_descriptor->bDescriptorType = USB_DT_OTG; + otg_descriptor->bLength = 0; // XXX + break; + } + default: + return -EINVAL; + } + return actual_length; +} + + + + +/*! + * @brief setclear() - set or reset a bit in a mask + * @param features + * @param flag + * @param value + */ +static void setclear(u32 *features, u32 flag, u32 value) +{ + if (value) *features |= flag; + else *features &= ~flag; +} + +/*! + * @brief do_standard_device_request - process a device request + * + * Process a received device request. If not a Chapter nine request pass it + * to the other loaded function driver device_request() function. + * + * @param function + * @param request + * @return non-zero to indicate failure. + */ +static int do_standard_device_request (struct usbd_function_instance *function, struct usbd_device_request *request) +{ + struct usbd_simple_instance *simple_instance = (struct usbd_simple_instance *)function; + struct usbd_composite_instance *composite_instance = (struct usbd_composite_instance *)function; + + struct usbd_bus_instance *bus = function->bus; + u8 bRequest = request->bRequest; + u8 bmRequestType = request->bmRequestType; + u16 wValue = le16_to_cpu(request->wValue); + u16 wIndex = le16_to_cpu(request->wIndex); + u16 wLength = le16_to_cpu(request->wLength); + //u16 endpoint_index; + struct pcd_instance *pcd = (struct pcd_instance *)bus->privdata; + usbd_device_state_t device_state = usbd_get_device_state(function); + + + switch (device_state) { + case STATE_CREATED: + case STATE_ATTACHED: + case STATE_POWERED: + printk(KERN_INFO"%s: bad device state: %d\n", __FUNCTION__, device_state); + TRACE_MSG1(USBD, "bad device_state: %d", device_state); + return -EINVAL; + + case STATE_INIT: + case STATE_DEFAULT: + switch (bRequest) { + case USB_REQ_CLEAR_FEATURE: + case USB_REQ_GET_INTERFACE: + case USB_REQ_GET_STATUS: + case USB_REQ_SET_DESCRIPTOR: + case USB_REQ_SET_INTERFACE: + case USB_REQ_SYNCH_FRAME: + printk(KERN_INFO"%s: bad request %d for this device state: %d\n", __FUNCTION__, + bRequest, device_state); + TRACE_MSG2(USBD, "bad request: %d for this device_state: %d", bRequest, device_state); + return -EINVAL; + + case USB_REQ_GET_CONFIGURATION: + case USB_REQ_SET_ADDRESS: + otg_queue_event(pcd->otg, DEVICE_REQUEST, USBD, "DEVICE REQUEST"); + case USB_REQ_SET_CONFIGURATION: + case USB_REQ_GET_DESCRIPTOR: + case USB_REQ_SET_FEATURE: + break; + } + case STATE_ADDRESSED: + case STATE_CONFIGURED: + case STATE_SUSPENDED: + break; + case STATE_UNKNOWN: + TRACE_MSG1(USBD, "unknown device_state: %d", device_state); + return -EINVAL; + } + + /* Handle all requests that return data (direction bit set on bm RequestType). + * N.B. no Chapter 9 requests send additional data. + */ + if ((bmRequestType & USB_REQ_DIRECTION_MASK)) { + struct usbd_urb *urb; + int rc = 1; + switch (bRequest) { + case USB_REQ_CLEAR_FEATURE: + case USB_REQ_SET_ADDRESS: + case USB_REQ_SET_CONFIGURATION: + case USB_REQ_SET_DESCRIPTOR: + case USB_REQ_SET_FEATURE: + case USB_REQ_SET_INTERFACE: + case USB_REQ_SYNCH_FRAME: + printk(KERN_INFO"%s: bad direction\n", __FUNCTION__); + TRACE_MSG0(USBD, "bad direction"); + return -EINVAL; + } + + RETURN_EINVAL_IF(!wLength); + + /* allocate urb, no notify callback, urb will be automatically de-allocated + */ + RETURN_EINVAL_UNLESS((urb = bus->ep0_urb)); + urb->status = USBD_URB_OK; + urb->irq_flags = 0; + + switch (bRequest) { + + case USB_REQ_GET_STATUS: + urb->actual_length = 2; + urb->buffer[0] = urb->buffer[1] = 0; + + switch (bmRequestType & USB_REQ_RECIPIENT_MASK) { + case USB_REQ_RECIPIENT_DEVICE: + + UNLESS (usbd_get_bMaxPower(function)) urb->buffer[0] |= USB_STATUS_SELFPOWERED; + urb->buffer[0] |= + (devreq_get_device_feature_settings(function) & FEATURE(USB_DEVICE_REMOTE_WAKEUP)) ? + USB_STATUS_REMOTEWAKEUP : 0; + TRACE_MSG2(USBD, "GET STATUS DEVICE status: %02x %02x", + urb->buffer[0], urb->buffer[1]); + rc = 0; + break; + case USB_REQ_RECIPIENT_ENDPOINT: + urb->buffer[0] = function->bus->driver->bops->endpoint_halted (bus,wIndex); + rc = 0; + TRACE_MSG2(USBD, "GET STATUS ENDPOINT status: %02x %02x", urb->buffer[0], urb->buffer[1]); + break; + case USB_REQ_RECIPIENT_INTERFACE: + TRACE_MSG2(USBD, "GET STATUS INTERFACE status: %02x %02x", urb->buffer[0], urb->buffer[1]); + rc = 0; + break; + case USB_REQ_RECIPIENT_OTHER: + TRACE_MSG0(USBD, "GET STATUS OTHER"); + default: + TRACE_MSG0(USBD, "GET STATUS BAD RECIPIENT"); + rc = -EINVAL; + } + break; + + case USB_REQ_GET_DESCRIPTOR: + //printk(KERN_INFO"%s: GET DESCRIPTOR\n", __FUNCTION__); + + TRACE_MSG2(USBD, "GET_DESCRIPTOR type : %04x, wLength: %d", wValue, wLength); + + rc = usbd_get_descriptor (function, urb->buffer, wLength, wValue >> 8, wValue & 0xff); + + + //TRACE_MSG1(USBD, "get descriptor rc : %d", rc); + if (rc != -EINVAL) { + urb->actual_length = rc; + //printk(KERN_INFO"%s: length: %d\n", __FUNCTION__, rc); + rc = 0; + } + else { + //printk(KERN_INFO"%s: bad description\n", __FUNCTION__); + TRACE_MSG0(USBD, "bad description"); + } + break; + + case USB_REQ_GET_CONFIGURATION: + urb->actual_length = 1; + if (function_simple == function->function_type) + urb->buffer[0] = simple_instance->ConfigurationValue; + + else if (function_composite == function->function_type) + urb->buffer[0] = composite_instance->ConfigurationValue; + + TRACE_MSG1(USBD, "GET CONFIGURATION: %d", urb->buffer[0]); + rc=0; // XXX Always success??? + break; + + case USB_REQ_GET_INTERFACE: + + if (function_simple == function->function_type) { + rc = 1; + BREAK_IF(wIndex > simple_instance->interfaces); + urb->buffer[0] = simple_instance->altsettings[wIndex]; + urb->actual_length = 1; + rc = 0; + TRACE_MSG1(USBD, "GET INTERFACE: %d simple", urb->buffer[0]); + } + else if (function_composite == function->function_type) { + struct usbd_interface_instance *interface_instance; + if ((interface_instance = find_interface_instance(composite_instance, wIndex))) { + urb->buffer[0] = interface_instance->altsetting; + urb->actual_length = 1; + rc = 0; + TRACE_MSG1(USBD, "GET INTERFACE: %d composite", urb->buffer[0]); + } + else { + TRACE_MSG1(USBD, "GET INTERFACE: %d composite not found", urb->buffer[0]); + + } + } + break; + default: + //printk(KERN_INFO"%s: bad descriptor\n", __FUNCTION__); + TRACE_MSG0(USBD, "bad descriptor type"); + rc = 1; + } + + if (!(urb->actual_length % usbd_endpoint_zero_wMaxPacketSize(function, usbd_high_speed(function))) && + (urb->actual_length < wLength)) + urb->flags |= USBD_URB_SENDZLP; + + RETURN_ZERO_UNLESS(rc || usbd_start_in_urb(urb)); + /* only get here if error */ + usbd_free_urb(urb); + //TRACE_MSG0(USBD, "get failed"); + TRACE_MSG1(USBD, "get failed rc:=%x",rc); + return -EINVAL; + } + /* Handle the requests that do not return data. + */ + else { + int setclear_flag = USB_REQ_SET_FEATURE == bRequest; + switch (bRequest) { + case USB_REQ_CLEAR_FEATURE: + case USB_REQ_SET_FEATURE: + TRACE_MSG4(USBD, "FEATURE: recipient: %d wIndex: %04x wValue: %04x setclear: %02x", + bmRequestType & USB_REQ_RECIPIENT_MASK, wIndex, wValue, setclear_flag); + switch (bmRequestType & USB_REQ_RECIPIENT_MASK) { + case USB_REQ_RECIPIENT_DEVICE: + //switch (wValue) { + // otg_event(pcd->otg, HNP_ENABLED, PCD, "DEVICE FEATURE - B_HNP_ENABLE"); + // break; + //} + + switch (wValue) { + case USB_OTG_A_ALT_HNP_ENABLE: // C.f. OTG 6.5.3 + case USB_OTG_A_HNP_SUPPORT: // C.f. OTG 6.5.2 + case USB_OTG_B_HNP_ENABLE: // C.f. OTG 6.5.1 + TRACE_MSG0(USBD, "HNP"); + RETURN_EINVAL_UNLESS(setclear_flag); // cleared by reset + break; + case USB_DEVICE_REMOTE_WAKEUP: + TRACE_MSG0(USBD, "REMOTE WAKEUP"); + break; + case USB_TEST_MODE: + TRACE_MSG2(USBD, "TEST MODE selector: %04x %02x", wIndex, wIndex >> 8); + switch (wIndex >> 8) { + case USB_TEST_J: + case USB_TEST_K: + case USB_TEST_SE0_NAK: + case USB_TEST_PACKET: // XXX it should be possible to do this generically here + case USB_TEST_FORCE_ENABLE: + return function->bus->driver->bops->device_feature( + function->bus, wIndex >> 8, setclear_flag); + break; + default: + break; + } + break; + default: + return -EINVAL; + } + setclear(&bus->device_feature_settings, FEATURE(wValue), setclear_flag); + return devreq_device_feature(bus, FEATURE(wValue), setclear_flag); + + case USB_REQ_RECIPIENT_ENDPOINT: + RETURN_EINVAL_UNLESS(USB_ENDPOINT_HALT == wValue); + + setclear(&(bus->endpoint_array[wIndex].feature_setting), + FEATURE(wValue), bRequest == USB_REQ_SET_FEATURE); + + /* wIndex contains bEndpointAddress, also find and verify the endpoint map index as well. + * Then clear the endpoint using the bus interface driver halt_endpoint() operation, + * if that is ok, then inform the function driver using the endpoint_cleared() operation. + */ + return do_endpoint_cleared(function, wIndex, setclear_flag); + + + case USB_REQ_RECIPIENT_INTERFACE: + case USB_REQ_RECIPIENT_OTHER: + default: + TRACE_MSG0(USBD, "bad recipient"); + return -EINVAL; + } + + case USB_REQ_SET_ADDRESS: + if (bus->driver->bops->set_address) bus->driver->bops->set_address (bus, wValue); + // XXX pcd_bus_event_handler_irq (bus, DEVICE_ADDRESS_ASSIGNED, wValue); + return 0; + + case USB_REQ_SET_DESCRIPTOR: + TRACE_MSG0(USBD, "set descriptor not supported"); + return -EINVAL; + + case USB_REQ_SET_CONFIGURATION: + return devreq_set_configuration(function, wValue); + + case USB_REQ_SET_INTERFACE: + return devreq_set_interface_altsetting(function, wIndex, wValue); + + case USB_REQ_GET_CONFIGURATION: + case USB_REQ_GET_DESCRIPTOR: + case USB_REQ_GET_INTERFACE: + case USB_REQ_GET_STATUS: + case USB_REQ_SYNCH_FRAME: + TRACE_MSG0(USBD, "unkown"); + return -EINVAL; + } + } + TRACE_MSG0(USBD, "not possible"); + return -EINVAL; +} + + +/*! + * @brief usbd_device_request_irq() - process a device request + * + * Used by a USB Bus interface driver to pass received device request to + * the appropriate USB Function driver. + * + * @param bus + * @param request + * @return non-zero if errror + */ +int usbd_device_request_irq(struct usbd_bus_instance *bus, struct usbd_device_request *request) +{ + struct usbd_function_instance *function = bus->function_instance; + u8 bRequest = request->bRequest; + u8 bmRequestType = request->bmRequestType; + + /* Handle only USB Standard Requests (c.f. USB Spec table 9-2, D6..5 must be 0), + * otherwise call the currently enabled function specific receive setup. + */ + TRACE_SETUP(USBD, request);; + if (!(bmRequestType & USB_REQ_TYPE_MASK) && + ((bmRequestType & USB_DIR_IN ? d2h_standard_requests : h2d_standard_requests) + [bmRequestType & 0x3] & STD(bRequest))) + return do_standard_device_request(function, request); + else + return do_non_standard_device_request(function, request); + +} +OTG_EXPORT_SYMBOL(usbd_device_request_irq); +OTG_EXPORT_SYMBOL(usbd_get_descriptor); + +/* ************************************************************************** */ +/*! + * Device I/O: + * usbd_urb_finished() + * usbd_first_urb_detached() + * usbd_find_endpoint_address() + */ + +/*! + * @brief usbd_do_urb_callback() - tell function that an urb has been transmitted. + * + * Must be called from an interrupt or with interrupts disabled. + * + * Used by a USB Bus driver to pass a sent urb back to the function + * driver via the endpoints done queue. + * + * If there is no callback or if the URB call back returns non-zero then the + * urb must be de-allocated here. + * + * @param urb + * @param rc + */ +void usbd_do_urb_callback (struct usbd_urb *urb, int rc) +{ + TRACE_MSG1(USBD,"urb: %p", urb); + RETURN_UNLESS (urb); + if (!urb->notify || urb->notify(urb,rc)) + usbd_free_urb(urb); +} + +/*! + * @brief usbd_find_endpoint_index() + + * Find the endpoint map index for the specified bEndpointAddress. + + * @param bus + * @param bEndpointAddress + */ +int usbd_find_endpoint_index(struct usbd_bus_instance *bus, int bEndpointAddress) +{ + int i,rc1; + rc1=0; + for (i = 0; i < bus->endpoints; i++){ + rc1=i; + BREAK_IF (bus->endpoint_array[i].bEndpointAddress[bus->high_speed] == bEndpointAddress); + } + return rc1; +} + +OTG_EXPORT_SYMBOL(usbd_find_endpoint_index); +OTG_EXPORT_SYMBOL(usbd_do_urb_callback); + + +/* ************************************************************************** */ +/* Device init ************************************************************** */ + +extern char * usbd_function_name(int n); +struct usbd_ops usbd_ops; +otg_tag_t USBD; + +/*! + * @brief usbd_device_init() - initialize + */ +int usbd_device_init (void) +{ + //USBD = otg_trace_obtain_tag(NULL, "usbd"); + TRACE_MSG0(USBD,"--"); + //otg_sem_init_unlocked("usbd_device", &usbd_bus_sem); + usbd_ops.function_name = usbd_function_name; + otg_set_usbd_ops(&usbd_ops); + return 0; +} + +/*! + * @brief usbd_device_exit() - de-initialize + */ +void usbd_device_exit (void) +{ + otg_set_usbd_ops(NULL); + //otg_trace_invalidate_tag(USBD); +} + +/* Module init ************************************************************** */ +/*! + * @brief usbd_device_init() - initialize + */ +int usbd_device_modinit (void) +{ + USBD = otg_trace_obtain_tag(NULL, "usbd"); + otg_sem_init_unlocked("usbd_bus", &usbd_bus_sem); + return 0; +} + +/*! + * @brief usbd_device_exit() - de-initialize + */ +void usbd_device_modexit (void) +{ + otg_trace_invalidate_tag(USBD); +} diff --git a/drivers/otg/otgcore/usbp-fops.c b/drivers/otg/otgcore/usbp-fops.c new file mode 100644 index 000000000000..49ac62624882 --- /dev/null +++ b/drivers/otg/otgcore/usbp-fops.c @@ -0,0 +1,2385 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/otgcore/usbp-fops.c - USB Function support + * @(#) sl@belcarra.com/whiskey.enposte.net|otg/otgcore/usbp-fops.c|20070816060612|51897 + * + * Copyright (c) 2004-2005 Belcarra Technologies Corp + * Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + * + * By: + * Stuart Lynne <sl@belcarra.com>, + * Bruce Balden <balden@belcarra.com> + * + */ + +/*! + * @file otg/otgcore/usbp-fops.c + * @brief Function driver related functions. + * + * This implements the functions used to implement USB Function drivers. + * + * @ingroup USBDCORE + */ + +#include <otg/otg-compat.h> +#include <otg/otg-module.h> + +#include <otg/usbp-chap9.h> +#include <otg/usbp-cdc.h> +#include <otg/otg-trace.h> +#include <otg/usbp-func.h> +#include <otg/usbp-bus.h> + +#define TRACE_VERBOSE 1 + +void urb_append(urb_link *hd, struct usbd_urb *urb); +struct usbd_function_driver * +usbd_find_function_internal(struct otg_list_node *usbd_function_drivers, const char *name); + +//extern struct semaphore usbd_bus_sem; +extern otg_sem_t usbd_bus_sem; + +int usbd_strings_init(struct usbd_bus_instance *bus, struct usbd_function_instance *function_instance); +void usbd_strings_exit(struct usbd_bus_instance *bus); +void usbd_free_string_descriptor (struct usbd_bus_instance *bus, u8 index); +extern void otg_write_info_message(void *, char *); + +/* ********************************************************************************************* */ +/* ********************************************************************************************* */ +/*! + * Simple Function Registration: + * usbd_register_simple_function() + * usbd_deregister_simple_function() + * + */ + +LIST_NODE_INIT(usbd_simple_drivers); // list of all registered configuration function modules + +/*! + * @brief usbd_register_simple_function() - register a usbd function driver + * + * USBD Simple Function drivers call this to register with the USBD Core layer. + * Return NULL on failure. + * + * @param simple_driver - driver to register + * @param name - driver name + * @param privdata - driver private data + * @return 0 on success, otherwise NULL + * + */ +int +usbd_register_simple_function (struct usbd_simple_driver *simple_driver, const char * name, void *privdata) +{ + //TRACE_MSG1(USBD, "SIMPLE function: %s", name); + RETURN_EINVAL_IF(otg_sem_wait(&usbd_bus_sem)); + simple_driver->driver.name = name; + simple_driver->driver.privdata = privdata; + simple_driver->driver.function_type = function_simple; + LIST_ADD_TAIL (&simple_driver->driver.drivers, &usbd_simple_drivers); + simple_driver->driver.flags |= FUNCTION_REGISTERED; + otg_sem_post(&usbd_bus_sem); + return 0; +} + +/*! + * @brief usbd_deregister_simple_function() - de-register a usbd function driver instance + * + * USBD Function drivers call this to de-register with the USBD Core layer. + * @param simple_driver - driver to de-register + */ +void usbd_deregister_simple_function (struct usbd_simple_driver *simple_driver) +{ + RETURN_UNLESS (simple_driver); + TRACE_MSG1(USBD, "SIMPLE function: %s", simple_driver->driver.name); + while (otg_sem_wait(&usbd_bus_sem)); + if (simple_driver->driver.flags & FUNCTION_ENABLED) { + // XXX ERROR need to disable + } + if (simple_driver->driver.flags & FUNCTION_REGISTERED) { + simple_driver->driver.flags &= ~FUNCTION_REGISTERED; + LIST_DEL (simple_driver->driver.drivers); + } + otg_sem_post(&usbd_bus_sem); +} + +/*! + * @brief usbp_find_simple_driver() - register a usbd function driver + * + * USBD Function drivers call this to register with the USBD Core layer. + * Return NULL on failure. + * + * @param name - by which to look for driver + * @return function instance + * + */ +struct usbd_simple_driver * +usbp_find_simple_driver (char *name) +{ + return (struct usbd_simple_driver *) usbd_find_function_internal(&usbd_simple_drivers, name); +} + +OTG_EXPORT_SYMBOL(usbd_register_simple_function); +OTG_EXPORT_SYMBOL(usbd_deregister_simple_function); + +/* ********************************************************************************************* */ +/* ********************************************************************************************* */ +/*! + * Function Registration: + * usbd_register_class_function_function() + * usbd_register_interface_function() + * usbd_register_composite_function() + * usbd_deregister_class_function() + * usbd_deregister_interface_function() + * usbd_deregister_composite_function() + */ + +LIST_NODE_INIT(usbd_interface_drivers); // list of all registered interface function modules +LIST_NODE_INIT(usbd_class_drivers); // list of all registered composite function modules +LIST_NODE_INIT(usbd_composite_drivers); // list of all registered composite function modules + +/*! + * @brief usbd_register_interface_function() - register a usbd function driver + * + * USBD Interface Function drivers call this to register with the USBD Core layer. + * Return NULL on failure. + * + * @param interface_driver - driver to register + * @param name - driver name + * @param privdata - driver's private data + * @return 0 on finish, or NULL + * + */ +int +usbd_register_interface_function(struct usbd_interface_driver *interface_driver, const char * name, void *privdata) +{ + //TRACE_MSG1(USBD, "INTERFACE function: %s", name); + RETURN_EINVAL_IF(otg_sem_wait(&usbd_bus_sem)); + interface_driver->driver.name = name; + interface_driver->driver.privdata = privdata; + interface_driver->driver.function_type = function_interface; + LIST_ADD_TAIL (&interface_driver->driver.drivers, &usbd_interface_drivers); + interface_driver->driver.flags |= FUNCTION_REGISTERED; + otg_sem_post(&usbd_bus_sem); + return 0; +} + +/*! + * @brief usbd_register_class_function() - register a usbd function driver + * + * USBD composite Function drivers call this to register with the USBD Core layer. + * Return NULL on failure. + * + * @param class_driver- class driver to register + * @param name - class driver name + * @param privdata -class driver private data + * @return 0 on finish, NULL on failure + * + */ +int +usbd_register_class_function(struct usbd_class_driver *class_driver, const char * name, void *privdata) +{ + //TRACE_MSG1(USBD, "CLASS function: %s", name); + RETURN_EINVAL_IF(otg_sem_wait(&usbd_bus_sem)); + class_driver->driver.name = name; + class_driver->driver.privdata = privdata; + class_driver->driver.function_type = function_class; + LIST_ADD_TAIL (&class_driver->driver.drivers, &usbd_class_drivers); + class_driver->driver.flags |= FUNCTION_REGISTERED; + otg_sem_post(&usbd_bus_sem); + return 0; +} + +/*! + * usbd_register_composite_function() - register a usbd function driver + * + * USBD composite Function drivers call this to register with the USBD Core layer. + * Return NULL on failure. + * + * @param composite_driver - function driver to register + * @param name - function driver's register name + * @param class_name - function driver's class name + * @param interface_function_names + * @param privdata - function driver's private data + * @return 0 on success, or NULL + * + */ +int +usbd_register_composite_function (struct usbd_composite_driver *composite_driver, const char* name, + const char* class_name, const char **interface_function_names, void *privdata) +{ + TRACE_MSG1(USBD, "COMPOSITE function: %s", name); + RETURN_EINVAL_IF(otg_sem_wait(&usbd_bus_sem)); + composite_driver->driver.name = name; + composite_driver->driver.privdata = privdata; + composite_driver->driver.function_type = function_composite; + composite_driver->class_name = class_name; + composite_driver->interface_function_names = interface_function_names; + LIST_ADD_TAIL (&composite_driver->driver.drivers, &usbd_composite_drivers); + composite_driver->driver.flags |= FUNCTION_REGISTERED; + otg_sem_post(&usbd_bus_sem); + return 0; +} + +/*! + * usbd_deregister_interface_function() - de-register a usbd function driver instance + * + * USBD Function drivers call this to de-register with the USBD Core layer. + * @param interface_driver - driver to de-register + */ +void usbd_deregister_interface_function (struct usbd_interface_driver *interface_driver) +{ + RETURN_UNLESS (interface_driver); + //TRACE_MSG1(USBD, "INTERFACE function: %s", interface_driver->driver.name); + while (otg_sem_wait(&usbd_bus_sem)); + if (interface_driver->driver.flags & FUNCTION_ENABLED) { + // XXX ERROR need to disable + } + if (interface_driver->driver.flags & FUNCTION_REGISTERED) { + interface_driver->driver.flags &= ~FUNCTION_REGISTERED; + LIST_DEL (interface_driver->driver.drivers); + } + otg_sem_post(&usbd_bus_sem); +} + +/*! + * @brief usbd_deregister_class_function() - de-register a usbd function driver instance + * + * USBD Function drivers call this to de-register with the USBD Core layer. + * @param class_driver - pointer to class driver to de-register + */ +void usbd_deregister_class_function (struct usbd_class_driver *class_driver) +{ + RETURN_UNLESS (class_driver); + //TRACE_MSG1(USBD, "CLASS function: %s", class_driver->driver.name); + while (otg_sem_wait(&usbd_bus_sem)); + if (class_driver->driver.flags & FUNCTION_ENABLED) { + // XXX ERROR need to disable + } + if (class_driver->driver.flags & FUNCTION_REGISTERED) { + class_driver->driver.flags &= ~FUNCTION_REGISTERED; + LIST_DEL (class_driver->driver.drivers); + } + otg_sem_post(&usbd_bus_sem); +} + +/*! + * @brief usbd_deregister_composite_function() - de-register a usbd function driver instance + * + * USBD Function drivers call this to de-register with the USBD Core layer. + * @param composite_driver + */ +void usbd_deregister_composite_function (struct usbd_composite_driver *composite_driver) +{ + RETURN_UNLESS (composite_driver); + //TRACE_MSG1(USBD, "COMPOSITE function: %s", composite_driver->driver.name); + while (otg_sem_wait(&usbd_bus_sem)); + if (composite_driver->driver.flags & FUNCTION_ENABLED) { + // XXX ERROR need to disable + } + if (composite_driver->driver.flags & FUNCTION_REGISTERED) { + composite_driver->driver.flags &= ~FUNCTION_REGISTERED; + LIST_DEL (composite_driver->driver.drivers); + } + otg_sem_post(&usbd_bus_sem); +} + +/*! + * @brief usbp_find_interface_driver() - register a usbd function driver + * + * USBD Function drivers call this to register with the USBD Core layer. + * Return NULL on failure. + * + * @param name - by which to search + * @return found interface driver + * + */ +struct usbd_interface_driver *usbp_find_interface_driver(const char *name) +{ + #if defined(CONFIG_OTG_TRACE) + TRACE_STRING(USBD, "name: %s", (char *)name); + #endif + + return (struct usbd_interface_driver *) usbd_find_function_internal(&usbd_interface_drivers, name); +} + + +/*! + * @brief usbp_find_class_driver() - register a usbd function driver + * + * USBD Function drivers call this to register with the USBD Core layer. + * Return NULL on failure. + * + * @param name - driver's name by which to search + * @return function instance + * + */ +struct usbd_class_driver *usbp_find_class_driver(const char *name) +{ + TRACE_MSG1(USBD, "%s", name); + return (struct usbd_class_driver *) usbd_find_function_internal(&usbd_class_drivers, name); +} + +/*! + * @brief usbp_find_composite_driver() - register a usbd function driver + * + * USBD Function drivers call this to register with the USBD Core layer. + * Return NULL on failure. + * + * This will typically only be called within usbd_bus_sem protected area. + * + * @param name - by which to search + * @return usbd_composite_driver instance + * + */ +struct usbd_composite_driver *usbp_find_composite_driver(char *name) +{ + TRACE_MSG1(USBD, "%s", name); + return (struct usbd_composite_driver *) usbd_find_function_internal(&usbd_composite_drivers, name); +} + + +OTG_EXPORT_SYMBOL(usbd_register_interface_function); +OTG_EXPORT_SYMBOL(usbd_register_composite_function); +OTG_EXPORT_SYMBOL(usbd_register_class_function); +OTG_EXPORT_SYMBOL(usbd_deregister_interface_function); +OTG_EXPORT_SYMBOL(usbd_deregister_class_function); +OTG_EXPORT_SYMBOL(usbd_deregister_composite_function); + + +/* ********************************************************************************************* */ +/* ********************************************************************************************* */ + +/*! + * @brief copy_descriptor() - copy data into buffer + * + * @param cp - buffer to receive + * @param data - source to copy + * @return bLength - copied data length + */ +static int copy_descriptor (u8 *cp, void *data) +{ + RETURN_ZERO_UNLESS (data); + memcpy (cp, data, ((struct usbd_generic_descriptor *)data)->bLength); + return ((struct usbd_generic_descriptor *)data)->bLength; +} + + +/*! + * @brief usbp_alloc_simple_configuration_descriptor() - + * + * @param simple_instance + * @param cfg_size + * @param hs + * @return pointer to configuration descriptor + */ +void usbp_alloc_simple_configuration_descriptor(struct usbd_simple_instance *simple_instance, int cfg_size, int hs) +{ + struct usbd_simple_driver *simple_driver = (struct usbd_simple_driver *) + simple_instance->function.function_driver; + struct usbd_bus_instance *bus = simple_instance->function.bus; + + + struct usbd_configuration_description *configuration_description; + struct usbd_configuration_descriptor *configuration_descriptor = NULL; + + + //int cfg_size = sizeof(struct usbd_configuration_descriptor) + sizeof(struct usbd_otg_descriptor); + u8 *cp = NULL; + + //int i, j, k, l; + int i, k, l; + + //RETURN_NULL_UNLESS((configuration_descriptor = CKMALLOC(cfg_size))); + + configuration_description = simple_driver->configuration_description; + + configuration_descriptor = simple_instance->configuration_descriptor[hs]; + + cfg_size = sizeof(struct usbd_configuration_descriptor) + sizeof(struct usbd_otg_descriptor); + + /* Compose the configuration descriptor + * First copy the function driver configuration configuration descriptor. + */ + cp = (u8 *)configuration_descriptor; + //cfg_size = copy_descriptor(cp + 0, configuration_description->configuration_descriptor); + cfg_size = sizeof(struct usbd_configuration_descriptor); + configuration_descriptor->bLength = sizeof(struct usbd_configuration_descriptor); + configuration_descriptor->bDescriptorType = USB_DT_CONFIGURATION; + + /* copy peripheral controller / platform dependant values into configuration */ + configuration_descriptor->bmAttributes = USB_BMATTRIBUTE_RESERVED | + (bus->bmAttributes & (USB_BMATTRIBUTE_SELF_POWERED | USB_BMATTRIBUTE_REMOTE_WAKEUP)); + + /* Self Powered devices must set bMaxPower to 1 unit */ + configuration_descriptor->bMaxPower = + ((bus->bmAttributes & USB_BMATTRIBUTE_SELF_POWERED) || !bus->bMaxPower) ? 1: bus->bMaxPower; + + configuration_descriptor->iConfiguration = usbd_alloc_string (&simple_instance->function, + configuration_description->iConfiguration); + + + for (i = 0; i < simple_driver->interfaces; i++) { + + struct usbd_interface_description *interface_description = simple_driver->interface_list + i; + + TRACE_MSG3(USBD, "INTERFACE[%02d] interface_description: %x alternates: %d", i, + interface_description, interface_description->alternates); + + for (k = 0; k < interface_description->alternates; k++) { + + struct usbd_alternate_description *alternate_description = interface_description->alternate_list + k; + + struct usbd_interface_descriptor *interface_descriptor; + + TRACE_MSG2(USBD, "ALTERNATE[%02d:%02d]", i, k); + + /* copy alternate interface descriptor, update descriptor fields + */ + interface_descriptor = (struct usbd_interface_descriptor *)(cp + cfg_size); + + //cfg_size += copy_descriptor(cp + cfg_size, alternate_description->interface_descriptor); + cfg_size += sizeof(struct usbd_interface_descriptor); + + interface_descriptor->bLength = sizeof(struct usbd_interface_descriptor); + interface_descriptor->bDescriptorType = USB_DT_INTERFACE; + interface_descriptor->bInterfaceNumber = i; + interface_descriptor->bAlternateSetting = k; + interface_descriptor->bNumEndpoints = alternate_description->endpoints; + + interface_descriptor->bInterfaceClass = alternate_description->bInterfaceClass; + interface_descriptor->bInterfaceSubClass = alternate_description->bInterfaceSubClass; + interface_descriptor->bInterfaceProtocol = alternate_description->bInterfaceProtocol; + // XXX + interface_descriptor->iInterface = usbd_alloc_string (&simple_instance->function, + alternate_description->iInterface); + + TRACE_MSG6(USBD, "COPY ALT[%02d:%02d] classes: %d endpoints: %d %s size: %d", + i, k, alternate_description->classes, alternate_description->endpoints, + alternate_description->iInterface, cfg_size); + + /* copy class descriptors + */ + for (l = 0; l < alternate_description->classes; l++) + cfg_size += copy_descriptor(cp + cfg_size, *(alternate_description->class_list + l)); + + /* copy endpoint descriptors, update descriptor fields + */ + for (l = 0; l < alternate_description->endpoints; l++) { + int index = alternate_description->endpoint_index[l]; + struct usbd_endpoint_map *endpoint_map = simple_instance->endpoint_map_array + index; + struct usbd_endpoint_descriptor *endpoint_descriptor = + (struct usbd_endpoint_descriptor *)(cp + cfg_size); + + cfg_size += sizeof(struct usbd_endpoint_descriptor); + endpoint_descriptor->bLength = sizeof(struct usbd_endpoint_descriptor);; + endpoint_descriptor->bDescriptorType = USB_DT_ENDPOINT; + endpoint_descriptor->bEndpointAddress = endpoint_map->bEndpointAddress[hs]; + endpoint_descriptor->bmAttributes = endpoint_map->bmAttributes[hs] & 0x3; + endpoint_descriptor->wMaxPacketSize = cpu_to_le16(endpoint_map->wMaxPacketSize[hs]); + endpoint_descriptor->bInterval = endpoint_map->bInterval[hs]; + } + } + } + TRACE_MSG2(USBD, "cfg: %x size: %d", cp, cfg_size); + + configuration_descriptor->wTotalLength = cpu_to_le16(cfg_size); + configuration_descriptor->bNumInterfaces = simple_driver->interfaces; + configuration_descriptor->bConfigurationValue = 1; + + #if 0 + for (i = 0; i < cfg_size; i+= 8) { + TRACE_MSG8(USBD, "%02x %02x %02x %02x %02x %02x %02x %02x", + cp[i + 0], cp[i + 1], cp[i + 2], cp[i + 3], + cp[i + 4], cp[i + 5], cp[i + 6], cp[i + 7] + ); + } + #endif +} + +/*! + * @brief usbp_dealloc_simple_instance() - + * + * @param simple_instance + */ +void usbp_dealloc_simple_instance(struct usbd_simple_instance *simple_instance) +{ + int i; + + usbd_strings_exit(simple_instance->function.bus); + + for (i = 0; i < 2; i++) + if (simple_instance->configuration_descriptor[i]) + LKFREE(simple_instance->configuration_descriptor[i]); + + if (simple_instance->endpoint_map_array) LKFREE(simple_instance->endpoint_map_array); + + //if (simple_instance->requestedEndpoints) LKFREE(simple_instance->requestedEndpoints); + //if (simple_instance->interface_list) LKFREE(simple_instance->interface_list); + + LKFREE(simple_instance); +} + +/*! + * @brief usbp_alloc_simple_instance() - create a usbp_simple_instance + * + * @param simple_driver - usbd_simple_driver instance pointer + * @param bus - usbd_bus_instance pointer + * @return usbd_simple_instance + */ +struct usbd_simple_instance * +usbp_alloc_simple_instance(struct usbd_simple_driver *simple_driver, + struct usbd_bus_instance *bus) +{ + struct usbd_simple_instance *simple_instance = NULL; + int i, k, l; + int cfg_size = sizeof(struct usbd_configuration_descriptor) + sizeof(struct usbd_otg_descriptor); + struct usbd_configuration_descriptor *configuration_descriptor[2] = { NULL, NULL, }; + struct usbd_endpoint_map *endpoint_map_array = NULL; + u8 *altsettings = NULL; + + THROW_UNLESS((simple_instance = CKMALLOC(sizeof(struct usbd_simple_instance))), error); + simple_instance->function.bus = bus; + THROW_IF(usbd_strings_init(bus, &simple_instance->function), error); + + /* copy fields + */ + simple_instance->function.function_driver = (struct usbd_function_driver *)simple_driver; + simple_instance->function.function_type = function_simple; + simple_instance->function.name = simple_driver->driver.name; + simple_instance->function.bus = bus; + + /* + * Find and total descriptor sizes. + */ + for (i = 0; i < simple_driver->interfaces; i++) { + + struct usbd_interface_description *interface_description = simple_driver->interface_list + i; + + for (k = 0; k < interface_description->alternates; k++) { + + struct usbd_alternate_description *alternate_description = + interface_description->alternate_list + k; + + /* size interface descriptor + */ + cfg_size += sizeof(struct usbd_interface_descriptor);; + + TRACE_MSG6(USBD, "SIZE ALT[%02d:%02d] classes: %d endpoints: %d %s size: %d", + i, k, alternate_description->classes, + alternate_description->endpoints, alternate_description->iInterface, cfg_size); + + /* size class descriptors + */ + for (l = 0; l < alternate_description->classes; l++) { + struct usbd_generic_class_descriptor *class_descriptor = + *(alternate_description->class_list + l); + cfg_size += class_descriptor->bLength; + } + + cfg_size += alternate_description->endpoints * sizeof(struct usbd_endpoint_descriptor); + + #if 0 + /* size endpoint descriptors (in theory could be endpoints * sizeof()) + */ + for (l = 0; l < alternate_description->endpoints; l++) { + struct usbd_endpoint_descriptor *endpoint_descriptor = + *(alternate_description->endpoint_list + l); + cfg_size += endpoint_descriptor->bLength; + } + #endif + + } + } + TRACE_MSG1(USBD, "cfg_size: %d", cfg_size); + + /* allocate configuration descriptor + */ + #ifdef CONFIG_OTG_HIGH_SPEED + for (i = 0; i < 2; i++) + #else /* CONFIG_OTG_HIGH_SPEED */ + for (i = 0; i < 1; i++) + #endif /* CONFIG_OTG_HIGH_SPEED */ + { + THROW_UNLESS((configuration_descriptor[i] = (struct usbd_configuration_descriptor *) + CKMALLOC (cfg_size + 4)), error); + simple_instance->configuration_descriptor[i] = configuration_descriptor[i]; + } + simple_instance->configuration_size = cfg_size; + + /* allocate endpoint map array and altsetting + */ + THROW_UNLESS((endpoint_map_array = (struct usbd_endpoint_map *) CKMALLOC (sizeof(struct usbd_endpoint_map) * + simple_driver->endpointsRequested)), error); + simple_instance->endpoint_map_array = endpoint_map_array; + + THROW_UNLESS((altsettings = (u8 *)CKMALLOC(sizeof (u8) * simple_instance->interfaces)), error); + simple_instance->altsettings = altsettings; + + TRACE_MSG1(USBD, "request endpoints: %x", simple_driver->endpointsRequested); + + /* request endpoints, allocate configuration descriptor, updating endpoint descriptors with correct + * information from endpoint map allocated above and set the endpoints + */ + + THROW_IF(bus->driver->bops->set_endpoints( bus, simple_driver->endpointsRequested, + simple_instance->endpoint_map_array), error); + + return simple_instance; + + CATCH(error) { + TRACE_MSG0(USBD, "FAILED"); + for (i = 0; i < 2; i++) if (configuration_descriptor[i]) LKFREE(configuration_descriptor[i]); + if (altsettings) LKFREE(altsettings); + if (endpoint_map_array) LKFREE(endpoint_map_array); + if (simple_instance) { + usbd_strings_exit(bus); + usbp_dealloc_simple_instance(simple_instance); + } + return NULL; + } +} +/* ********************************************************************************************* */ +/* ********************************************************************************************* */ + +/*! + * @brief usbp_dealloc_composite_instance() - free composite instance and associated resources + * + * @param composite_instance + */ +void usbp_dealloc_composite_instance(struct usbd_composite_instance *composite_instance) +{ + int i; + for (i = 0; i < 2; i++) + if (composite_instance->configuration_descriptor[i]) + LKFREE(composite_instance->configuration_descriptor[i]); + + usbd_strings_exit(composite_instance->function.bus); + if (composite_instance->interfaces_array) LKFREE(composite_instance->interfaces_array); + if (composite_instance->endpoint_map_array) LKFREE(composite_instance->endpoint_map_array); + if (composite_instance->requestedEndpoints) LKFREE(composite_instance->requestedEndpoints); + if (composite_instance->interface_list) LKFREE(composite_instance->interface_list); + + LKFREE(composite_instance); +} + + +/*! + * @brief usbp_alloc_composite_configuration_descriptor() - + * + * @param composite_instance + * @param cfg_size + * @param hs + * @return pointer to configuration descriptor + */ +void usbp_alloc_composite_configuration_descriptor(struct usbd_composite_instance *composite_instance, int cfg_size, int hs) +{ + struct usbd_composite_driver *composite_driver = (struct usbd_composite_driver*) + composite_instance->function.function_driver; + struct usbd_bus_instance *bus = composite_instance->function.bus; + struct usbd_configuration_description *configuration_description = composite_driver->configuration_description; + + struct usbd_configuration_descriptor *configuration_descriptor = composite_instance->configuration_descriptor[hs]; + u8 *cp = NULL; + + int i, j, k, l, m; + + /* Compose the configuration descriptor + * First copy the function driver configuration configuration descriptor. + */ + cp = (u8 *)configuration_descriptor; + + TRACE_MSG4(USBD, "bDeviceClass: %02x bus->bmAttributes: %02x descriptor[%d] %x", + composite_driver->device_description->bDeviceClass, + bus->bmAttributes, + hs, composite_instance->configuration_descriptor[hs]); + + //cfg_size = copy_descriptor(cp + 0, configuration_description->configuration_descriptor); + cfg_size = sizeof(struct usbd_configuration_descriptor); + configuration_descriptor->bLength = sizeof(struct usbd_configuration_descriptor); + + /* copy peripheral controller / platform dependant values into configuration */ + configuration_descriptor->bDescriptorType = USB_DT_CONFIGURATION; + + /* Self Powered devices must set bMaxPower to 1 unit */ + configuration_descriptor->bmAttributes = USB_BMATTRIBUTE_RESERVED | + (bus->bmAttributes & (USB_BMATTRIBUTE_SELF_POWERED | USB_BMATTRIBUTE_REMOTE_WAKEUP)); + + /* Self Powered devices must set bMaxPower to 1 unit */ + configuration_descriptor->bMaxPower = + ((bus->bmAttributes & USB_BMATTRIBUTE_SELF_POWERED) || !bus->bMaxPower) ? 1: bus->bMaxPower; + + configuration_descriptor->iConfiguration = usbd_alloc_string (&composite_instance->function, + configuration_description->iConfiguration); + + for (i = 0; i < composite_instance->interface_functions; i++) { + + struct usbd_interface_instance *interface_instance = composite_instance->interfaces_array +i; + struct usbd_interface_driver *interface_driver = (struct usbd_interface_driver *) + interface_instance->function.function_driver; + + //TRACE_MSG6(USBD, "INTERFACE[%02d] interfaces: %d endpoints: %d bFunctionClass: %d hs: %d %s", i, + // interface_driver->interfaces, interface_driver->endpointsRequested, + // interface_driver->bFunctionClass, hs, + // interface_driver->driver.name); + + TRACE_MSG8(USBD, "INTERFACE[%02d:%02d] %x endpoints: %d endpoint_map_array: %x " + "bFunctionClass: %d hs: %d %s", + i, interface_driver->interfaces, + interface_instance, + interface_driver->endpointsRequested, + interface_instance->endpoint_map_array, + interface_driver->bFunctionClass, + hs, + interface_driver->driver.name); + + /* insert Interface Association Descriptor if required + */ + if ((USB_CLASS_MISC == composite_driver->device_description->bDeviceClass)) { + + struct usbd_interface_association_descriptor *iad = + (struct usbd_interface_association_descriptor *) (cp + cfg_size); + + TRACE_MSG4(USBD, " IAD[%02d] Class: %d SubClass: %d Protocol: %d", i, + interface_driver->bFunctionClass, interface_driver->bFunctionSubClass, + interface_driver->bFunctionProtocol); + + cfg_size += sizeof(struct usbd_interface_association_descriptor); + iad->bLength = sizeof(struct usbd_interface_association_descriptor); + iad->bDescriptorType = USB_DT_INTERFACE_ASSOCIATION; + iad->bFirstInterface = interface_instance->wIndex; + iad->bInterfaceCount = interface_driver->interfaces; + iad->bFunctionClass = interface_driver->bFunctionClass; + iad->bFunctionSubClass = interface_driver->bFunctionSubClass; + iad->bFunctionProtocol = interface_driver->bFunctionProtocol; + iad->iFunction = usbd_alloc_string (&composite_instance->function, + interface_driver->iFunction); + } + + /* iterate across interfaces and alternates for this for this function + * XXX this will need to be modified if interface functions are allowed + * to define multiple interfaces. + */ + + for (j = 0; j < interface_driver->interfaces; j++) { + + struct usbd_interface_description *interface_description = interface_driver->interface_list + j; + + TRACE_MSG5(USBD, "INTERFACE[%02d:%02d] interface_description: %x alternates: %d wIndex: %d", i, j, + interface_description, interface_description->alternates, + interface_instance->wIndex); + + for (k = 0; k < interface_description->alternates; k++) { + + struct usbd_alternate_description *alternate_description = + interface_description->alternate_list + k; + + struct usbd_interface_descriptor *interface_descriptor; + + TRACE_MSG3(USBD, "ALTERNATE[%02d:%02d:%02d]", i, j, k); + + /* copy alternate interface descriptor, update descriptor fields + */ + interface_descriptor = (struct usbd_interface_descriptor *)(cp + cfg_size); + + //cfg_size += copy_descriptor(cp + cfg_size, alternate_description->interface_descriptor); + cfg_size += sizeof(struct usbd_interface_descriptor); + + //interface_descriptor->bInterfaceNumber = i; // XXX see note above + interface_descriptor->bLength = sizeof(struct usbd_interface_descriptor); + interface_descriptor->bDescriptorType = USB_DT_INTERFACE; + interface_descriptor->bInterfaceNumber = interface_instance->wIndex + j; + interface_descriptor->bAlternateSetting = k; + interface_descriptor->bNumEndpoints = alternate_description->endpoints; + + interface_descriptor->bInterfaceClass = alternate_description->bInterfaceClass; + interface_descriptor->bInterfaceSubClass = alternate_description->bInterfaceSubClass; + interface_descriptor->bInterfaceProtocol = alternate_description->bInterfaceProtocol; + + interface_descriptor->iInterface = usbd_alloc_string (&composite_instance->function, + alternate_description->iInterface); + + TRACE_MSG7(USBD, "COPY ALT[%02d:%02d:%02d] classes: %d endpoints: %d %s size: %d", i, j, k, + alternate_description->classes, alternate_description->endpoints, + alternate_description->iInterface, cfg_size); + + /* copy class descriptors + */ + for (l = 0; l < alternate_description->classes; l++) { + struct usbd_generic_class_descriptor *generic_descriptor = + (struct usbd_generic_class_descriptor *)(cp + cfg_size); + + cfg_size += copy_descriptor(cp + cfg_size, *(alternate_description->class_list + l)); + + TRACE_MSG7(USBD, "COPY CLS[%02d:%02d:%02d:%02d] type: %02x:%02x:%02x", + i, j, k, l, + generic_descriptor->bLength, + generic_descriptor->bDescriptorType, + generic_descriptor->bDescriptorSubtype + ); + + /* update + * call management functional descriptor + * union functional descriptor + * and update interface numbers + */ + CONTINUE_UNLESS (USB_DT_CLASS_SPECIFIC == generic_descriptor->bDescriptorType); + switch (generic_descriptor->bDescriptorSubtype) { + case USB_ST_CMF: { + struct usbd_call_management_functional_descriptor *cmfd = + (struct usbd_call_management_functional_descriptor *) + generic_descriptor; + cmfd->bDataInterface += interface_instance->wIndex + j; + TRACE_MSG5(USBD, "COPY CLS[%02d:%02d:%02d:%02d] CMF bDataInterface %02x", + i, j, k, l, cmfd->bDataInterface); + break; + } + case USB_ST_UF: { + struct usbd_union_functional_descriptor *ufd = + (struct usbd_union_functional_descriptor *) + generic_descriptor; + ufd->bMasterInterface = interface_instance->wIndex + j; + TRACE_MSG6(USBD, "COPY CLS[%02d:%02d:%02d:%02d] UFD " + "bMasterInterface %02x " + "bSlaveInterface %02x", + i, j, k, l, + ufd->bMasterInterface, + ufd->bSlaveInterface[0] + ); + + for (m = 0; m < interface_driver->interfaces - 1; m++) { + + + TRACE_MSG7(USBD, "COPY CLS[%02d:%02d:%02d:%02d] UFD " + "bSlaveInterface[%02x] %02x ->%02x", + i, j, k, l, m, + ufd->bSlaveInterface[m], + ufd->bSlaveInterface[m] + ufd->bMasterInterface + ); + ufd->bSlaveInterface[m] += ufd->bMasterInterface; + } + break; + } + default: break; + } + } + + /* copy endpoint descriptors, update descriptor fields + */ + for (l = 0; l < alternate_description->endpoints; l++) { + int index = alternate_description->endpoint_index[l]; + struct usbd_endpoint_map *endpoint_map = interface_instance->endpoint_map_array + index; + struct usbd_endpoint_descriptor *endpoint_descriptor = + (struct usbd_endpoint_descriptor *)(cp + cfg_size); + + TRACE_MSG4(USBD, "REQUEST[%02d:%02d] index: %d endpoint_map: %x", + l, alternate_description->endpoints, index, endpoint_map); + + cfg_size += sizeof(struct usbd_endpoint_descriptor); + endpoint_descriptor->bLength = sizeof(struct usbd_endpoint_descriptor);; + endpoint_descriptor->bDescriptorType = USB_DT_ENDPOINT; + endpoint_descriptor->bEndpointAddress = endpoint_map->bEndpointAddress[hs]; + endpoint_descriptor->bmAttributes = endpoint_map->bmAttributes[hs] & 0x3; + endpoint_descriptor->wMaxPacketSize = cpu_to_le16(endpoint_map->wMaxPacketSize[hs]); + endpoint_descriptor->bInterval = endpoint_map->bInterval[hs]; + } + } + } + } + + #if defined(CONFIG_OTG_BDEVICE_WITH_SRP) || defined(CONFIG_OTG_DEVICE) + { + struct usbd_otg_descriptor *otg_descriptor = (struct usbd_otg_descriptor *)(cp + cfg_size); + otg_descriptor->bLength = sizeof(struct usbd_otg_descriptor); + otg_descriptor->bDescriptorType = USB_DT_OTG; + otg_descriptor->bmAttributes = bus->driver->bmAttributes & (USB_OTG_HNP_SUPPORTED | USB_OTG_SRP_SUPPORTED); + cfg_size += sizeof(struct usbd_otg_descriptor); + } + #endif /* defined(CONFIG_OTG_BDEVICE_WITH_SRP) || defined(CONFIG_OTG_DEVICE) */ + + configuration_descriptor->wTotalLength = cpu_to_le16(cfg_size); + configuration_descriptor->bNumInterfaces = composite_instance->interfaces; + configuration_descriptor->bConfigurationValue = 1; + + TRACE_MSG2(USBD, "cfg: %x size: %d", cp, cfg_size); + #if 0 + for (i = 0; i < cfg_size; i+= 8) { + TRACE_MSG8(USBD, "%02x %02x %02x %02x %02x %02x %02x %02x", + cp[i + 0], cp[i + 1], cp[i + 2], cp[i + 3], + cp[i + 4], cp[i + 5], cp[i + 6], cp[i + 7] + ); + } + #endif +} + +/*! + * alloc_function_interface_function() - + * + * @param interface_driver + * @return pointer to interface instance + */ +struct usbd_interface_instance * +alloc_function_interface_function(struct usbd_interface_driver *interface_driver) +{ + struct usbd_interface_instance *function_instance = NULL; + RETURN_NULL_UNLESS((function_instance = CKMALLOC(sizeof(struct usbd_interface_instance)))); + function_instance->function.function_type = function_interface; + function_instance->function.name = interface_driver->driver.name; + return function_instance; +} + + +/*! + * alloc_function_class_function() - + * + * @param class_driver + * @return pointer to class instance + */ +struct usbd_class_instance * +alloc_function_class_function(struct usbd_class_driver *class_driver) +{ + struct usbd_class_instance *function_instance = NULL; + RETURN_NULL_UNLESS((function_instance = CKMALLOC(sizeof(struct usbd_class_instance)))); + function_instance->function.function_type = function_class; + function_instance->function.name = class_driver->driver.name; + return function_instance; +} + +/*! + * alloc_usb_string + */ +void alloc_usb_strings(struct usbd_function_instance *function, u8 *index, const char **str) +{ + TRACE_MSG4(USBD, "function: %x index: %x str: %x %d", function, index, str, str ? *str : NULL); + + for ( ; str && *str; str++, index++) { + + TRACE_MSG2(USBD, "str: %s index: %d", *str, *index); + + if (*index) + usbd_realloc_string (function, *index, *str); + else + usbd_alloc_string (function, *str); + } +} + +/*! + * @brief usbp_alloc_composite_instance() - register a usbd function driver + * + * USBD Composite Function drivers call this to register with the USBD Core layer. + * + * This takes a list of interface functions (by name) to use. Each of these + * must be found and the interface descriptor and endpoint request information + * must be copied. + * + * @param composite_driver + * @param bus + * + * @return function instance or NULL on failure. + * + */ +struct usbd_composite_instance * +usbp_alloc_composite_instance (struct usbd_composite_driver *composite_driver, struct usbd_bus_instance *bus) +{ + struct usbd_composite_instance *composite_instance = NULL; + //struct usbd_class_instance *class_instnace = NULL; + //struct usbd_class_driver *class_driver = NULL; + struct usbd_interface_instance *interfaces_array = NULL; + struct usbd_endpoint_request *requestedEndpoints = NULL; + struct usbd_endpoint_map *endpoint_map_array = NULL; + struct usbd_interface_description *interface_list = NULL; + + u32 interfaces_used; + + const char ** interface_function_names = composite_driver->interface_function_names; + + int i = 0, j = 0, k, l; + int endpoint_number, interface_number, function_number = 0; + //u8 *cp; + + int cfg_size = sizeof(struct usbd_configuration_descriptor) + sizeof(struct usbd_otg_descriptor); + + THROW_UNLESS((composite_instance = CKMALLOC(sizeof(struct usbd_composite_instance))), error); + composite_instance->function.bus = bus; + THROW_IF(usbd_strings_init(bus, &composite_instance->function), error); + + /* copy fields + */ + composite_instance->function.function_driver = (struct usbd_function_driver *)composite_driver; + composite_instance->function.function_type = function_composite; + composite_instance->function.name = composite_driver->driver.name; + composite_instance->function.bus = bus; + alloc_usb_strings((struct usbd_function_instance*)composite_instance, composite_driver->driver.usb_string_indexes, + composite_driver->driver.usb_strings); + + /* count and list interfaces, then allocate interfaces_array. + */ + for (endpoint_number = interface_number = function_number = 0; + *(interface_function_names + function_number); + function_number++) + { + const char *interface_name = *(interface_function_names + function_number); + char *cp; + struct usbd_interface_driver *interface_driver; + int interfaces; + + if ((cp = strchr(interface_name, '='))) + interface_name = cp + 1; + + + TRACE_MSG1(USBD, "interface_name: \"%s\"", interface_name ? interface_name : ""); + + THROW_UNLESS(interface_driver = usbp_find_interface_driver(interface_name), error); + + TRACE_MSG5(USBD, "INTERFACE[%02d] interfaces: %d endpoints: %d bFunctionClass: %d %s", + endpoint_number, interface_driver->interfaces, + interface_driver->endpointsRequested, interface_driver->bFunctionClass, + interface_driver->driver.name); + + interfaces = interface_driver->interfaces; + + TRACE_MSG6(USBD, "FUNCTION[%02d] interfaces: %d:%d endpoints: %d:%d %20s", + function_number, interface_driver->interfaces, interface_number, + interface_driver->endpointsRequested, endpoint_number, interface_name); + + interface_number += interface_driver->interfaces; + endpoint_number += interface_driver->endpointsRequested; + + if ((USB_CLASS_MISC == composite_driver->device_description->bDeviceClass) ) { + cfg_size += sizeof(struct usbd_interface_association_descriptor); + TRACE_MSG2(USBD, "FUNCTION[%02d] IAD size: %d", function_number, cfg_size); + } + + /* iterate across interfaces for this function and their alternates to get descriptors sizes + */ + for (j = 0; j < interface_driver->interfaces; j++) { + + struct usbd_interface_description *interface_description = interface_driver->interface_list + j; + + for (k = 0; k < interface_description->alternates; k++) { + + struct usbd_alternate_description *alternate_description = + interface_description->alternate_list + k; + + /* size interface descriptor + */ + cfg_size += sizeof(struct usbd_interface_descriptor);; + + TRACE_MSG7(USBD, "SIZE ALT[%02d:%02d:%02d] classes: %d endpoints: %d %s size: %d", + function_number, j, k, alternate_description->classes, + alternate_description->endpoints, alternate_description->iInterface, cfg_size); + + /* size class descriptors + */ + for (l = 0; l < alternate_description->classes; l++) { + struct usbd_generic_class_descriptor *class_descriptor = + *(alternate_description->class_list + l); + cfg_size += class_descriptor->bLength; + } + + cfg_size += alternate_description->endpoints * sizeof(struct usbd_endpoint_descriptor); + + #if 0 + /* size endpoint descriptors (in theory could be endpoints * sizeof()) + */ + for (l = 0; l < alternate_description->endpoints; l++) { + struct usbd_endpoint_descriptor *endpoint_descriptor = + *(alternate_description->endpoint_list + l); + cfg_size += endpoint_descriptor->bLength; + } + #endif + } + } + } + + TRACE_MSG2(USBD, "interfaces: %d endpoints: %d", interface_number, endpoint_number); + + /* Allocate and save information. + * + * Save endpoints, bNumInterfaces and configuration size, Allocate + * configuration descriptor, allocate enough memory for all + * interface instances plus one extra for class instance if + * required, allocate endpoint map array and endpoint requests. + */ + /* + THROW_UNLESS((configuration_descriptor = + (struct usbd_configuration_descriptor *) CKMALLOC (cfg_size + 4)), error); + composite_instance->configuration_descriptor = configuration_descriptor; + */ + + THROW_UNLESS((interfaces_array = (struct usbd_interface_instance *) + CKMALLOC (sizeof(struct usbd_interface_instance) * + (function_number + (composite_driver->class_name ? 1 : 0)))), error); + + THROW_UNLESS((endpoint_map_array = (struct usbd_endpoint_map *) + CKMALLOC (sizeof(struct usbd_endpoint_map) * endpoint_number)), error); + + THROW_UNLESS((requestedEndpoints = (struct usbd_endpoint_request *) + CKMALLOC (sizeof(struct usbd_endpoint_request) * endpoint_number)), error); + + THROW_UNLESS((interface_list = (struct usbd_interface_description *) + CKMALLOC (sizeof(struct usbd_interface_description) * interface_number)), error); + + + #ifdef CONFIG_OTG_HIGH_SPEED + for (i = 0; i < 2; i++) + #else /* CONFIG_OTG_HIGH_SPEED */ + for (i = 0; i < 1; i++) + #endif /* CONFIG_OTG_HIGH_SPEED */ + { + THROW_UNLESS((composite_instance->configuration_descriptor[i] = + (struct usbd_configuration_descriptor *) CKMALLOC (cfg_size + 4)), + error); + } + + composite_instance->interfaces_array = interfaces_array; + composite_instance->endpoint_map_array = endpoint_map_array; + composite_instance->requestedEndpoints = requestedEndpoints; + composite_instance->interface_list = interface_list; + + composite_instance->configuration_size = cfg_size; // total configuration size + composite_instance->interface_functions = function_number; // number of interface function drivers + composite_instance->interfaces = interface_number; // number of interfaces + composite_instance->endpointsRequested = endpoint_number; // number of endpoints + composite_instance->endpoints = endpoint_number; + + /* Setup interface_instance array. + * + * Find all interfaces and compose the interface list, count the + * endpoints and allocte the endpoint request array. Also find and + * total descriptor sizes. Compose the endpoint request array and + * configuration descriptor, cannot request endpoints yet as the bus + * interface driver is not loaded, so we cannot compose + * configuration descriptor here. + */ + for (interfaces_used = endpoint_number = interface_number = function_number = 0; + function_number < composite_instance->interface_functions; + function_number++) + { + struct usbd_interface_instance *interface_instance = interfaces_array + function_number; + const char *interface_name = *(interface_function_names + function_number); + struct usbd_interface_driver *interface_driver; + const char *cp; + int wIndex = 0; + + if ((cp = strchr(interface_name, '='))) { + const char *sp = interface_name; + interface_name = cp + 1; + for (; sp < cp; sp++) { + BREAK_UNLESS(isdigit(*sp)); + wIndex = wIndex * 10 + *sp - '0'; + } + } + else { + for (wIndex = 0; wIndex < 32; wIndex++) { + BREAK_UNLESS(interfaces_used & (1 << wIndex)); + } + THROW_IF(wIndex == 32, error); + } + + THROW_UNLESS(interface_driver = usbp_find_interface_driver(interface_name), error); + + interface_instance->endpoint_map_array = composite_instance->endpoint_map_array + endpoint_number; + interface_instance->function.function_driver = (struct usbd_function_driver *)interface_driver; + interface_instance->function.function_type = function_interface; + interface_instance->function.name = interface_driver->driver.name; + interface_instance->function.bus = bus; + interface_instance->wIndex = wIndex; + interface_instance->lIndex = j; + + alloc_usb_strings((struct usbd_function_instance*)interface_instance, + interface_driver->driver.usb_string_indexes, + interface_driver->driver.usb_strings); + + for (i = 0; i < interface_driver->interfaces; i++) { + THROW_IF(interfaces_used & (1 << (wIndex + 1)), error); + interfaces_used |= (1 << (wIndex + i)); + } + + TRACE_MSG6(USBD, "INTERFACE FUNCTION[%02d] %x interfaces: %d endpointsRequested: %d " + "wIndex: %d endpoint_map_array: %x", + function_number, interface_instance, interface_driver->interfaces, + interface_driver->endpointsRequested, wIndex, + interface_instance->endpoint_map_array); + + /* copy requested endpoint information + */ + for (k = 0; k < interface_driver->endpointsRequested; k++, endpoint_number++) { + + TRACE_MSG5(USBD, "REQUEST[%2d %d:%d %d:%d]", + function_number, j, interface_number, k, endpoint_number); + + TRACE_MSG3(USBD, "%x %x %d", requestedEndpoints + endpoint_number, + interface_driver->requestedEndpoints + j, + sizeof(struct usbd_endpoint_request)); + + TRACE_MSG5(USBD, "REQUEST[%2d:%2d:%2d] bm: %02x addr: %02x", function_number, + k, endpoint_number, + interface_driver->requestedEndpoints[k].bmAttributes, + interface_driver->requestedEndpoints[k].bEndpointAddress + ); + + memcpy(requestedEndpoints + endpoint_number, interface_driver->requestedEndpoints + k, + sizeof(struct usbd_endpoint_request)); + + requestedEndpoints[endpoint_number].interface_num += wIndex; + } + + interface_number += interface_driver->interfaces; + } + + if (composite_driver->class_name) { + + struct usbd_class_instance *class_instance = (struct usbd_class_instance *)(interfaces_array + function_number); + struct usbd_class_driver *class_driver; + + THROW_UNLESS(class_driver = usbp_find_class_driver(composite_driver->class_name), error); + + class_instance->function.function_driver = (struct usbd_function_driver *)composite_driver; + class_instance->function.function_type = function_composite; + class_instance->function.name = composite_driver->driver.name; + class_instance->function.bus = bus; + alloc_usb_strings((struct usbd_function_instance *)class_instance, class_driver->driver.usb_string_indexes, + class_driver->driver.usb_strings); + } + + return composite_instance; + + CATCH(error) { + TRACE_MSG0(USBD, "FAILED"); + //if (configuration_descriptor) LKFREE(configuration_descriptor); + if (composite_instance) { + usbd_strings_exit(bus); + for (i = 0; i < 2; i++) if (composite_instance->configuration_descriptor[i]) + LKFREE(composite_instance->configuration_descriptor[i]); + } + + if (requestedEndpoints) LKFREE(requestedEndpoints); + if (interfaces_array) LKFREE(interfaces_array); + if (interface_list) LKFREE(interface_list); + return NULL; + } +} + + +/* ********************************************************************************************* */ +/* ********************************************************************************************* */ +/*! + * Function Registration: + * usbd_register_function_function() + * usbd_register_interface_function() + * usbd_register_composite_function() + * usbd_deregister_function() + * usbd_interface_deregister_function() + * usbd_class_deregister_function() + * usbd_composite_deregister_function() + * usbd_find_function() + * usbd_find_interface_function() + * + */ + +/*! + * @brief usbd_find_function_internal() - search a usbd_function_driver instance by name + * + * USBD Function drivers call this to register with the USBD Core layer. + * Return NULL on failure. + * + * @param usbd_function_drivers - registerd usbd_function_driver table + * @param name - name to search + * @return function instance + * + */ +struct usbd_function_driver * +usbd_find_function_internal(struct otg_list_node *usbd_function_drivers, const char *name) +{ + int len = name ? strlen(name) : 0; + struct otg_list_node *lhd = NULL; + + #if defined(CONFIG_OTG_TRACE) + TRACE_STRING(USBD, "name:: %s", (char *)name); + #endif + + LIST_FOR_EACH (lhd, usbd_function_drivers) { + + struct usbd_function_driver *function_driver = LIST_ENTRY (lhd, struct usbd_function_driver, drivers); + + CONTINUE_IF(len && strncmp(function_driver->name, name, len)); + + #if defined(CONFIG_OTG_TRACE) + TRACE_MSG1(USBD, "FOUND: %s", function_driver->name); + #endif + + return function_driver; + } + return NULL; +} + + +/*! + * usbd_function_name() - return name of nth function driver + * @param n index to function name + * @return pointer to name + */ +char * usbd_function_name(int n) +{ + struct usbd_function_driver *function_driver = NULL; + struct otg_list_node *lhd = NULL; + + TRACE_MSG1(USBD, "n: %d", n); + LIST_FOR_EACH (lhd, &usbd_simple_drivers) { + function_driver = LIST_ENTRY (lhd, struct usbd_function_driver, drivers); + TRACE_MSG2(USBD, "simple name: [%d] %s", n, function_driver->name); + CONTINUE_IF(n--); + TRACE_MSG1(USBD, "simple: %s", function_driver->name); + return (char *)function_driver->name; + }; + + LIST_FOR_EACH (lhd, &usbd_composite_drivers) { + function_driver = LIST_ENTRY (lhd, struct usbd_function_driver, drivers); + TRACE_MSG2(USBD, "composite name: [%d] %s", n, function_driver->name); + CONTINUE_IF(n--); + TRACE_MSG1(USBD, "composite: %s", function_driver->name); + return (char *)function_driver->name; + }; + + return NULL; +} + +/* ********************************************************************************************* */ +/* ********************************************************************************************* */ +/*! + * String Descriptors: + * usbd_alloc_string_descriptor() + * usbd_free_string_descriptor() + * usbd_get_string_descriptor() + */ + +#define LANGID_ENGLISH "\011" +#define LANGID_US_ENGLISH "\004" +#define LANGIDs LANGID_US_ENGLISH LANGID_ENGLISH + +/*! + * usbd_alloc_string_zero() - allocate a string descriptor and return index number + * + * Find an empty slot in index string array, create a corresponding descriptor + * and return the slot number. + * + * @param str string to allocate + * @param bus + * @return the slot number + */ +static u8 usbd_alloc_string_zero (struct usbd_bus_instance *bus, char *str) +{ + u8 bLength; + //u16 *wData; + struct usbd_langid_descriptor *langid = NULL; + unsigned char *cp; + + RETURN_ZERO_IF(bus->usb_strings[0] != NULL); + + TRACE_MSG2(USBD, "LANGID: %02x %02x", str[0], str[1]); + + bLength = 4; + + RETURN_ZERO_IF(!(langid = (struct usbd_langid_descriptor *) CKMALLOC (bLength))); + + + langid->bLength = bLength; // set descriptor length + langid->bDescriptorType = USB_DT_STRING; // set descriptor type + langid->bData[0] = str[1]; + langid->bData[1] = str[0]; + + bus->usb_strings[0] = (struct usbd_string_descriptor *)langid; // save in string index array + + TRACE_MSG4(USBD, "LANGID String: %02x %02x %02x %02x", + langid->bLength, langid->bDescriptorType, langid->bData[0], langid->bData[1]); + cp = (char *) langid; + TRACE_MSG4(USBD, "LANGID String: %02x %02x %02x %02x", cp[0], cp[1], cp[2], cp[3]); + + return 0; +} + +/*! + * usbd_strings_init() - initialize usb strings pool + */ +int usbd_strings_init(struct usbd_bus_instance *bus, struct usbd_function_instance *function_instance) +{ + bus->usbd_maxstrings = 50; // XXX Need configuration parameter... + + RETURN_EINVAL_IF (!(bus->usb_strings = CKMALLOC ( + sizeof (struct usbd_string_descriptor *) * bus->usbd_maxstrings))); + + #if defined(CONFIG_OTG_LANGID) + { + char langid_str[4]; + u16 langid; + TRACE_MSG1(USBD, "LANGID: %04x", CONFIG_OTG_LANGID); + langid = cpu_to_le16(CONFIG_OTG_LANGID); + memset(langid_str, 0, sizeof(langid_str)); + langid_str[0] = langid & 0xff; + langid_str[1] = (langid >> 8) & 0xff; + if (usbd_alloc_string_zero(bus, langid_str)) { + LKFREE (function_instance->bus->usb_strings); + return -EINVAL; + } + } + #else /* defined(CONFIG_OTG_LANGID) */ + + if (usbd_alloc_string_zero(ubs, LANGIDs) != 0) { + LKFREE (function_instance->bus->usb_strings); + return -EINVAL; + } + #endif /* defined(CONFIG_OTG_LANGID) */ + return 0; +} + +/*! + * usbd_strings_exit() - de-initialize usb strings pool + */ +void usbd_strings_exit(struct usbd_bus_instance *bus) +{ + int i; + RETURN_IF (!bus->usb_strings); + for (i = 0; i < bus->usbd_maxstrings; i++) + usbd_free_string_descriptor(bus, i); + LKFREE (bus->usb_strings); + bus->usb_strings = NULL; +} + + +/*! + * usbd_get_string_descriptor() - find and return a string descriptor + * + * Find an indexed string and return a pointer to a it. + * @param function_instance + * @param index index of string + * @return pointer to string descriptor or NULL + */ +struct usbd_string_descriptor *usbd_get_string_descriptor (struct usbd_function_instance *function_instance, u8 index) +{ + RETURN_NULL_IF (index >= function_instance->bus->usbd_maxstrings); + return function_instance->bus->usb_strings[index]; +} + +/*! + * usbd_realloc_string() - allocate a string descriptor and return index number + * + * Find an empty slot in index string array, create a corresponding descriptor + * and return the slot number. + * @param function_instance + * @param index + * @param str + * @return new index + */ +u8 usbd_realloc_string (struct usbd_function_instance *function_instance, u8 index, const char *str) +{ + const char *save = str; + struct usbd_string_descriptor *string; + u8 bLength; + u16 *wData; + + RETURN_EINVAL_IF(!str || !strlen (str)); + + // XXX should have semaphores... + + if ((index < function_instance->bus->usbd_maxstrings) && (string = function_instance->bus->usb_strings[index])) { + function_instance->bus->usb_strings[index] = NULL; + LKFREE (string); + } + + bLength = USBD_STRING_SIZEOF (struct usbd_string_descriptor) + 2 * strlen (str); + + RETURN_ENOMEM_IF(!(string = (struct usbd_string_descriptor *)CKMALLOC (bLength))); + + string->bLength = bLength; + string->bDescriptorType = USB_DT_STRING; + + for (wData = string->wData; *str;) + *wData++ = cpu_to_le16 ((u16) (*str++)); + + // store in string index array + function_instance->bus->usb_strings[index] = string; + + TRACE_MSG2(USBD, "STRING[%d] %s", index, save); + return index; +} + +/*! + * @brief strtest() - check if a string exists in string descriptor table + * + * @param string - string descriptor table + * @param str - string to test + * @return non-zero if match + */ +int strtest(struct usbd_string_descriptor *string, const char *str) +{ + int i; + for (i = 0; i < string->bLength; i++ ) { + CONTINUE_IF ( (*str == (string->wData[i] & 0xff))); + return 1; + } + return 0; +} + + +/*! + * usbd_alloc_string() - allocate a string descriptor and return index number + * + * Find an empty slot in index string array, create a corresponding descriptor + * and return the slot number. + * @param function_instance + * @param str + * @return index + * + * XXX rename to usbd_alloc_string_descriptor + */ +u8 usbd_alloc_string (struct usbd_function_instance *function_instance, const char *str) +{ + //const char *save = str; + //int i, j; + int i; + struct usbd_string_descriptor *string = NULL; + u8 bLength; + u16 *wData; + + TRACE_MSG2(USBD, "function_instance: %x str: %x", function_instance, str); + + RETURN_ZERO_IF(!str || !strlen (str)); + + /* find an empty string descriptor slot + * "0" is consumed by the language ID, "1" is the optional IP address + * and "2" is for the product string (as required by the PST group) + */ + for (i = 3; i < function_instance->bus->usbd_maxstrings; i++) { + + if (function_instance->bus->usb_strings[i]) { + UNLESS (strtest(function_instance->bus->usb_strings[i], str)) + return i; + continue; + } + + bLength = USBD_STRING_SIZEOF (struct usbd_string_descriptor) + 2 * strlen (str); + + RETURN_ZERO_IF(!(string = (struct usbd_string_descriptor *)CKMALLOC (bLength))); + + string->bLength = bLength; + string->bDescriptorType = USB_DT_STRING; + + for (wData = string->wData; *str;) + *wData++ = cpu_to_le16((u16) (*str++)); + + // store in string index array + function_instance->bus->usb_strings[i] = string; + //TRACE_MSG2(USBD, "STRING[%d] %s", i, save); + return i; + } + return 0; +} + + +/*! + * usbd_free_string_descriptor() - deallocate a string descriptor + * + * Find and remove an allocated string. + * @param bus + * @param index + * + */ +void usbd_free_string_descriptor (struct usbd_bus_instance *bus, u8 index) +{ + struct usbd_string_descriptor *string; + + if ((index < bus->usbd_maxstrings) && (string = bus->usb_strings[index])) { + bus->usb_strings[index] = NULL; + LKFREE (string); + } +} + +OTG_EXPORT_SYMBOL(usbd_realloc_string); +OTG_EXPORT_SYMBOL(usbd_alloc_string); +OTG_EXPORT_SYMBOL(usbd_free_string_descriptor); +OTG_EXPORT_SYMBOL(usbd_get_string_descriptor); +//OTG_EXPORT_SYMBOL(usbd_maxstrings); + + +/* ********************************************************************************************* */ +/* ********************************************************************************************* */ +/*! + * Device Information: + * usbd_high_speed() + * usbd_get_bMaxPower() + * usbd_endpoint_wMaxPacketsize() + * usbd_endpoint_wMaxPacketsize_ep0() + * usbd_endpoint_bEndpointAddress() + * + */ + + +/*! + * usbd_high_speed() - return high speed status + * @return true if operating at high speed + */ +BOOL usbd_high_speed(struct usbd_function_instance *function) +{ + // XXX TODO - per function modifications for composite devices + return BOOLEAN(function->bus->high_speed); +} + +/*! + * usbd_get_bMaxPower() - process a received urb + * + * Used by a USB Bus interface driver to pass received device request to + * the appropriate USB Function driver. + * + * @param function + * @return non-zero if errror + */ +int usbd_get_bMaxPower(struct usbd_function_instance *function) +{ + struct usbd_bus_instance *bus = function->bus; + return bus->driver->bMaxPower; +} + +/*! usbd_endpoint_map() + * + * @param function + * @return pointer to endpoint map + */ +struct usbd_endpoint_map *usbd_endpoint_map(struct usbd_function_instance *function) +{ + struct usbd_endpoint_map *endpoint_map = NULL; + RETURN_NULL_UNLESS(function); + switch(function->function_type) { + case function_simple: + RETURN_NULL_UNLESS((endpoint_map = ((struct usbd_simple_instance *)function)->endpoint_map_array)); + return endpoint_map; + case function_composite: + RETURN_NULL_UNLESS((endpoint_map = ((struct usbd_composite_instance *)function)->endpoint_map_array)); + return endpoint_map; + case function_interface: + RETURN_NULL_UNLESS((endpoint_map = ((struct usbd_interface_instance *)function)->endpoint_map_array)); + return endpoint_map; + case function_ep0: + RETURN_NULL_UNLESS((endpoint_map = ((struct usbd_interface_instance *)function)->endpoint_map_array)); + return endpoint_map; + default: + return NULL; + } +} + +/*! + * usbd_endpoint_wMaxPacketSize() - get maximum packet size for endpoint + * @param function + * @param endpoint_index + * @param hs highspeed flag + * @return endpoint size + */ +int usbd_endpoint_wMaxPacketSize(struct usbd_function_instance *function, int endpoint_index, int hs) +{ + struct usbd_endpoint_map *endpoint_map = usbd_endpoint_map(function); + //TRACE_MSG4(USBD, "function: %x index: %d endpoint_map: %x wMaxPacketSize: %02x", + // function, endpoint_index, endpoint_map, endpoint_map[endpoint_index].wMaxPacketSize[hs]); + //return le16_to_cpu(endpoint_map[endpoint_index].wMaxPacketSize[hs]); + return endpoint_map[endpoint_index].wMaxPacketSize[hs]; +} + +/*! + * usbd_endpoint_zero_wMaxPacketSize() - get maximum packet size for endpoint zero + * @param function + * @param hs highspeed flag + * @return endpoint size + */ +int usbd_endpoint_zero_wMaxPacketSize(struct usbd_function_instance *function, int hs) +{ + struct usbd_endpoint_map *endpoint_map; + RETURN_ZERO_IF(!(endpoint_map = function->bus->ep0->endpoint_map_array)); + //return le16_to_cpu(endpoint_map[0].wMaxPacketSize[hs]); + return endpoint_map[0].wMaxPacketSize[hs]; +} + +/*! + * usbd_endpoint_bEndpointAddress() - get endpoint addrsess + * @param function + * @param endpoint_index + * @param hs high speed flag + * @return endpoint address + */ +int usbd_endpoint_bEndpointAddress(struct usbd_function_instance *function, int endpoint_index, int hs) +{ + struct usbd_endpoint_map *endpoint_map = usbd_endpoint_map(function); + //TRACE_MSG3(USBD, "function: %x index: %d endpoint_map: %x", function, endpoint_index, endpoint_map); + return endpoint_map[endpoint_index].bEndpointAddress[hs]; +} + +OTG_EXPORT_SYMBOL(usbd_endpoint_map); +OTG_EXPORT_SYMBOL(usbd_endpoint_wMaxPacketSize); +OTG_EXPORT_SYMBOL(usbd_endpoint_zero_wMaxPacketSize); +OTG_EXPORT_SYMBOL(usbd_endpoint_bEndpointAddress); +OTG_EXPORT_SYMBOL(usbd_high_speed); + +/* ********************************************************************************************* */ +/* ********************************************************************************************* */ + +/*! + * @name STRUCT + * @brief + * Structure member address manipulation macros. + * + * These are used by client code (code using the urb_link routines), since + * the urb_link structure is embedded in the client data structures. + * + * Note: a macro offsetof equivalent to member_offset is defined in stddef.h + * but this is kept here for the sake of portability. + * + * p2surround returns a pointer to the surrounding structure given + * type of the surrounding structure, the name memb of the structure + * member pointed at by ptr. For example, if you have: + * + * struct foo { + * int x; + * float y; + * char z; + * } thingy; + * + * char *cp = &thingy.z; + * + * then + * &thingy == p2surround(struct foo, z, cp) + * + * @{ + */ + +#define _cv_(ptr) ((char*)(void*)(ptr)) +#define member_offset(type,memb) (_cv_(&(((type*)0)->memb))-(char*)0) +#define p2surround(type,memb,ptr) ((type*)(void*)(_cv_(ptr)-member_offset(type,memb))) + +/*! + * @name list + * @brief List support functions + * + * @{ + */ + +/*! + * @brief urb_link() - return first urb_link in list + * + * Return the first urb_link in a list with a distinguished + * head "hd", or NULL if the list is empty. This will also + * work as a predicate, returning NULL if empty, and non-NULL + * otherwise. + * + * Called from interrupt. + * + * @param hd + * @return link to urb + */ +static INLINE urb_link *first_urb_link (urb_link * hd) +{ + urb_link *nx; + return (!hd || !(nx = hd->next) || (nx == hd)) ? NULL : nx; +} + +/*! + * @brief _usbd_first_urb() - return first urb in list + * + * Return the first urb in a list with a distinguished + * head "hd", or NULL if the list is empty. + * + * Called from interrupt. + * + * @param hd linked list of urbs + * @return pointer to urb + */ +struct usbd_urb *_usbd_first_urb(urb_link * hd) +{ + urb_link *nx = first_urb_link (hd); + struct usbd_urb *urb = nx ? p2surround (struct usbd_urb, link, nx) : NULL; + return urb; +} + +#if 0 +/*! + * @brief _usbd_unlink_urb() - return first urb in list + * + * Return the first urb in a list with a distinguished + * head "hd", or NULL if the list is empty. + * + * Called from interrupt. + * + * @param urb linked list of urbs + * @return pointer to urb + */ +void INLINE _usbd_unlink_urb(struct usbd_urb *urb) +{ + urb_link *ul = &urb->link; + ul->next->prev = ul->prev; + ul->prev->next = ul->next; + ul->prev = ul->next = ul; +} +#endif + +/*! + * @brief usbd_first_urb_detached() - detach an return first urb + * + * Detach and return the first urb in a list with a distinguished + * head "hd", or NULL if the list is empty. + * @param endpoint pointer to endpoint instance + * @param hd linked list of urbs + * @return pointer to urb or NULL + */ +struct usbd_urb *usbd_first_urb_detached (struct usbd_endpoint_instance *endpoint, urb_link * hd) +{ + struct usbd_urb *urb; + otg_pthread_mutex_lock(&endpoint->mutex); /* lock mutex */ + if ((urb = _usbd_first_urb (hd))) { + usbd_unlink_urb(urb); + hd->total-=1; + } + otg_pthread_mutex_unlock(&endpoint->mutex); /* unlock mutex */ + return urb; +} + +/* @} */ + + +/*! + * @brief usbd_first_finished_urb_detached() - detach an return first urb + * + * Detach and return the first urb in a list with a distinguished + * head "hd", or NULL if the list is empty. + * @param endpoint pointer to endpoint instance + * @param hd linked list of urbs + * @return pointer to urb or NULL + */ +struct usbd_urb *usbd_first_finished_urb_detached (struct usbd_endpoint_instance *endpoint, urb_link * hd) +{ + struct usbd_urb *urb; + + otg_pthread_mutex_lock(&endpoint->mutex); /* lock mutex */ + if ((urb = _usbd_first_urb(hd))) { + //TRACE_MSG0(USBD,"enter1"); + if (urb->irq_flags == USBD_URB_FINISHED) { + //TRACE_MSG0(USBD,"enter2"); + usbd_unlink_urb(urb); + hd->total-=1; + otg_pthread_mutex_unlock(&endpoint->mutex); /* unlock mutex */ + //TRACE_MSG1(USBD,"return urb:%x",urb); + return urb; + } + } + //TRACE_MSG0(USBD,"return NULL"); + otg_pthread_mutex_unlock(&endpoint->mutex); /* unlock mutex */ + return NULL; +} + +/*! + * @brief usbd_first_ready_urb() - detach an return first urb + * + * Find the first ready urb, mark as active and return pointer. + * + * N.B. must be called with interrupts locked. + * @param endpoint pointer to endpoint instance + * @param hd linked list of urbs + * @return pointer to urb or NULL + */ +struct usbd_urb *usbd_first_ready_urb(struct usbd_endpoint_instance *endpoint, urb_link * hd) +{ + //struct usbd_urb *urb; + urb_link *ul; + otg_pthread_mutex_lock(&endpoint->mutex); /* lock mutex */ + for (ul = hd->next; ul != hd; ) { + struct usbd_urb *urb = p2surround (struct usbd_urb, link, ul); + ul = urb->link.next; + CONTINUE_UNLESS(urb->irq_flags & USBD_URB_READY); + urb->irq_flags |= USBD_URB_ACTIVE; + //hd->total-=1; + otg_pthread_mutex_unlock(&endpoint->mutex); /* unlock mutex */ + + return urb; + } + otg_pthread_mutex_unlock(&endpoint->mutex); /* unlock mutex */ + return NULL; +} + +/* ************************************************************************** */ +/*! + * Device I/O: + * usbd_urb_finished() + * usbd_first_urb_detached() + * usbd_find_endpoint_address() + */ + +/*! + * @brief urb_append() - append a linked list of urbs to another linked list + * @param hd link to urb list + * @param urb to append to list + */ +void urb_append (urb_link * hd, struct usbd_urb *urb) +{ + struct usbd_endpoint_instance *endpoint = urb->endpoint; + urb_link *new; + urb_link *pul; + RETURN_UNLESS (hd && urb); + otg_pthread_mutex_lock(&endpoint->mutex); /* lock mutex */ + new = &urb->link; + pul = hd->prev; + new->prev->next = hd; + hd->prev = new->prev; + new->prev = pul; + pul->next = new; + hd->total+=1; + otg_pthread_mutex_unlock(&endpoint->mutex); /* unlock mutex */ +} + + +/* ********************************************************************************************* */ +/*! + * Device Control: + * + * XXX usbd_request_endpoints() + * XXX usbd_configure_endpoints() + * + * usbd_get_device_state() + * usbd_get_device_status() + * usbd_framenum() + * usbd_ticks() + * usbd_elapsed() + */ + +/*! + * usbd_flush_endpoint() - flush urbs from endpoint + * + * Iterate across the approrpiate tx or rcv list and cancel any outstanding urbs. + * + * @param endpoint flush urbs on this endpoint + */ +void usbd_flush_endpoint (struct usbd_endpoint_instance *endpoint) +{ + struct usbd_urb *urb; + + if (TRACE_VERBOSE) + if (endpoint->bEndpointAddress) + TRACE_MSG1(USBD, "bEndpointAddress: %02x", endpoint->bEndpointAddress); + + while ((urb = endpoint->active_urb)) + usbd_cancel_urb(urb); + + for (; (urb = usbd_first_urb_detached (endpoint, &endpoint->rdy)); usbd_cancel_urb(urb)) + TRACE_MSG1(USBD, "cancelled urb:%x", urb); +} + + +/*! + * usbd_flush_endpoint_address() - flush endpoint + * @param function + * @param endpoint_index + */ +void usbd_flush_endpoint_index (struct usbd_function_instance *function, int endpoint_index) +{ + struct usbd_endpoint_map *endpoint_map = usbd_endpoint_map(function); + struct usbd_endpoint_instance *endpoint; + + RETURN_UNLESS(function && endpoint_map); + RETURN_IF(!(endpoint = endpoint_map[endpoint_index].endpoint)); + usbd_flush_endpoint (endpoint); +} + +/*! + * usbd_flush_endpoint_address() - flush endpoint + * @param function + * @param endpoint_index + */ +int usbd_endpoint_urb_num(struct usbd_function_instance *function, int endpoint_index) +{ + struct usbd_endpoint_map *endpoint_map = usbd_endpoint_map(function); + struct usbd_endpoint_instance *endpoint; + if (!(function && endpoint_map)) return -1; + if (!(endpoint = endpoint_map[endpoint_index].endpoint)) return -1; + return ((endpoint->rdy).total); +} + + + +/*! + * usbd_get_device_state() - return device state + * @param function + * @return current device state + */ +usbd_device_state_t usbd_get_device_state(struct usbd_function_instance *function) +{ + return (function && function->bus) ? function->bus->device_state : STATE_UNKNOWN; +} + +/*! + * usbd_get_device_status() - return device status + * @param function + * @return current device status + */ +usbd_device_status_t usbd_get_device_status(struct usbd_function_instance *function) +{ + return (function && function->bus) ? function->bus->status : USBD_UNKNOWN; +} + +OTG_EXPORT_SYMBOL(usbd_first_ready_urb); +OTG_EXPORT_SYMBOL(usbd_first_finished_urb_detached); +OTG_EXPORT_SYMBOL(usbd_get_device_state); +OTG_EXPORT_SYMBOL(usbd_get_device_status); +OTG_EXPORT_SYMBOL(usbd_flush_endpoint); +OTG_EXPORT_SYMBOL(usbd_flush_endpoint_index); +OTG_EXPORT_SYMBOL(usbd_endpoint_urb_num); + +/* ********************************************************************************************* */ +/* ********************************************************************************************* */ +/*! + * Endpoint I/O: + * usbd_alloc_urb() + * usbd_start_in_urb() + * usbd_start_out_urb() + * usbd_cancel_urb() + * + */ + +/*! + * usbd_alloc_urb() - allocate an URB appropriate for specified endpoint + * + * Allocate an urb structure. The usb device urb structure is used to + * contain all data associated with a transfer, including a setup packet for + * control transfers. + * + * @param function + * @param endpoint_index + * @param length + * @param notify + * @return urb + */ +struct usbd_urb *usbd_alloc_urb (struct usbd_function_instance *function, int endpoint_index, + int length, usbd_urb_notification *notify) +{ + struct usbd_urb *urb = NULL; + struct usbd_endpoint_map *endpoint_map = usbd_endpoint_map(function); + struct usbd_endpoint_instance *endpoint; + int hs = function->bus->high_speed; + + + RETURN_NULL_IF(!(endpoint = endpoint_map[endpoint_index].endpoint)); + + THROW_IF (!(urb = (struct usbd_urb *)CKMALLOC (sizeof (struct usbd_urb))), error); + urb->endpoint = endpoint; + urb->endpoint_index = endpoint_index; + urb->wMaxPacketSize = endpoint->wMaxPacketSize[hs]; + urb->bus = function->bus; + urb->function_instance = function; + urb->link.prev = urb->link.next = &urb->link; + urb->notify = notify; + urb->dma_addr = (dma_addr_t)NULL; + urb->alloc_length = urb->actual_length = urb->buffer_length = 0; + + if (length) { + + urb->buffer_length = length; + + /* For receive we always overallocate to ensure that receiving another + * full sized packet when we are only expecting a short packet will + * not overflow the buffer. Endpoint zero alloc's don't specify direction + * so always overallocate. + */ + UNLESS (endpoint->bEndpointAddress[hs] && (endpoint->bEndpointAddress[hs] & USB_ENDPOINT_DIR_MASK)) + length = ((length / urb->wMaxPacketSize) + 3) * urb->wMaxPacketSize; + + THROW_IF(!(urb->buffer = (u8 *)KMALLOC (length)), error); + urb->alloc_length = length; + } + + if (TRACE_VERBOSE) + TRACE_MSG7(USBD, "[%2x] urb:%x endpoint_index: %d " + "length: %d wMaxPacketSize: %d buffer_length: %d alloc_length: %d", + endpoint->bEndpointAddress[hs], + urb, endpoint_index, length, urb->wMaxPacketSize, urb->buffer_length, urb->alloc_length); + + CATCH(error) { + + #if defined(OTG_LINUX) + #endif /* defined(OTG_LINUX) */ + #if defined(OTG_WINCE) + DEBUGMSG(ZONE_INIT, (_T("usbd_alloc_urb: FAILED\n"))); + #endif /* defined(OTG_LINUX) */ + usbd_free_urb(urb); + urb = NULL; + TRACE_MSG0(USBD, "CATCH a error\n"); + } + return urb; +} + +/*! + * usbd_halt_endpoint() - + * + * @param function function that owns endpoint + * @param endpoint_index endpoint number + * @return non-zero if endpoint is halted. + */ +int usbd_halt_endpoint (struct usbd_function_instance *function, int endpoint_index) +{ + struct usbd_endpoint_map *endpoint_map = usbd_endpoint_map(function); + struct usbd_endpoint_instance *endpoint; + int hs = function->bus->high_speed; + + RETURN_ZERO_UNLESS(function && endpoint_map); + RETURN_ZERO_IF(!(endpoint = endpoint_map[endpoint_index].endpoint)); + + return function->bus->driver->bops->halt_endpoint (function->bus, endpoint->bEndpointAddress[hs], 1); +} + + +/*! + * usbd_alloc_urb_ep0() - allocate an urb for endpoint zero + * @param function + * @param length + * @param callback + * @return urb + */ +struct usbd_urb *usbd_alloc_urb_ep0 (struct usbd_function_instance *function, int length, + int (*callback) (struct usbd_urb *, int)) +{ + return usbd_alloc_urb((struct usbd_function_instance *)function->bus->ep0, 0, length, callback); +} + +/*! + * usbd_free_urb() - deallocate an URB and associated buffer + * + * Deallocate an urb structure and associated data. + * @param urb + */ +void usbd_free_urb (struct usbd_urb *urb) +{ +#if 0 + RETURN_IF (!urb); + if (urb->buffer) LKFREE ((void *) urb->buffer); + LKFREE (urb); +#else + RETURN_UNLESS(urb); + RETURN_IF(urb == urb->bus->ep0_urb); + if (urb->buffer) LKFREE(urb->buffer); + LKFREE (urb); +#endif + +} +/*! usbd_start_in_urb - called to send urb + * @param urb -pointer of usbd_urb to send + * @return send result or -EAGAIN on error + */ +int usbd_start_in_urb (struct usbd_urb *urb) +{ + int hs = urb->bus->high_speed; + struct usbd_bus_instance *bus = urb->bus; + struct usbd_endpoint_instance *endpoint= urb->endpoint; + + if (TRACE_VERBOSE) + TRACE_MSG6(USBD, "[%2x] urb: %x index: %d bus: %x status: %d length: %d", + urb->endpoint->bEndpointAddress[hs], urb, urb->endpoint_index, urb->bus, urb->bus->status, + urb->actual_length); + + if (urb->endpoint->feature_setting & FEATURE(USB_ENDPOINT_HALT)) { + urb->status = USBD_URB_STALLED; + TRACE_MSG1(USBD, "[%2x] USBD_URB_STALLED", urb->endpoint->bEndpointAddress[hs]); + return -EAGAIN; + } + if (urb->endpoint->bEndpointAddress[hs] && (USBD_OK != urb->bus->status)) { + urb->status = USBD_URB_NOT_READY; + TRACE_MSG1(USBD, "[%2x] USBD_URB_NOT_READY", urb->endpoint->bEndpointAddress[hs]); + return -EAGAIN; + } + //TRACE_MSG2(USBD, "endpoint: %02x length: %d", urb->endpoint->bEndpointAddress[hs], urb->actual_length); + urb->status = USBD_URB_QUEUED; + urb->flags |= USBD_URB_IN; + urb->irq_flags |= USBD_URB_READY; + + /* N.B. Race condition possible after this, once appended to the ready queue it + * is possible for an interrupt handler to start using *and* complete this urb + * so it cannot be referenced after this point. + */ + urb_append (&(urb->endpoint->rdy), urb); + + /* Use saved values for bus and endpoint to call appropriate start out function + */ + return bus->driver->bops->start_endpoint_in(bus, endpoint); +} + +/*! + * usbd_start_out_urb() - recycle a received urb + * + * Used by a USB Function interface driver to recycle an urb. + * + * @param urb to process + * @return non-zero if error + */ +int usbd_start_out_urb (struct usbd_urb *urb) +{ + int hs = urb->bus->high_speed; + struct usbd_bus_instance *bus = urb->bus; + struct usbd_endpoint_instance *endpoint= urb->endpoint; + + if (TRACE_VERBOSE) + TRACE_MSG6(USBD, "[%2x] urb: %x index: %d bus: %x status: %d length: %d", + urb->endpoint->bEndpointAddress[hs], + urb, urb->endpoint_index, urb->bus, urb->bus->status, + urb->buffer_length); + if (urb->endpoint->feature_setting & FEATURE(USB_ENDPOINT_HALT)) { + urb->status = USBD_URB_STALLED; + TRACE_MSG1(USBD, "[%2x] USBD_URB_STALLED", urb->endpoint->bEndpointAddress[hs]); + return -EAGAIN; + } + if (urb->endpoint->bEndpointAddress[hs] && (USBD_OK != urb->bus->status)) { + urb->status = USBD_URB_NOT_READY; + TRACE_MSG1(USBD, "[%2x] USBD_URB_NOT_READY", urb->endpoint->bEndpointAddress[hs]); + return -EAGAIN; + } + + urb->actual_length = 0; + urb->status = USBD_URB_QUEUED; + urb->flags |= USBD_URB_OUT; + urb->irq_flags = USBD_URB_READY; + + /* N.B. Race condition possible after this, once appended to the ready queue it + * is possible for an interrupt handler to start using *and* complete this urb + * so it cannot be referenced after this point. + */ + urb_append (&(urb->endpoint->rdy), urb); + + /* Use saved values for bus and endpoint to call appropriate start out function + */ + return bus->driver->bops->start_endpoint_out(bus, endpoint); +} + +/*! + * usbd_cancel_urb() - cancel an urb being sent + * + * @param urb to process + * @return non-zero if error + */ +int usbd_cancel_urb (struct usbd_urb *urb) +{ + RETURN_ZERO_UNLESS (urb && urb->bus && urb->bus->driver && urb->bus->driver->bops); + return urb->bus->driver->bops->cancel_urb_irq (urb); +} + + +/*! + * usbd_endpoint_halted() - return halt status + * + * @param function function that owns endpoint + * @param endpoint_index endpoint number + * @return non-zero if endpoint is halted. + */ +int usbd_endpoint_halted (struct usbd_function_instance *function, int endpoint_index) +{ + struct usbd_endpoint_instance *endpoint; + struct usbd_endpoint_map *endpoint_map = usbd_endpoint_map(function); + RETURN_ZERO_UNLESS(function && endpoint_map); + RETURN_ZERO_UNLESS((endpoint = endpoint_map[endpoint_index].endpoint)); + return function->bus->driver->bops->endpoint_halted (function->bus, + endpoint->bEndpointAddress[function->bus->high_speed]); +} + + +OTG_EXPORT_SYMBOL(usbd_alloc_urb); +OTG_EXPORT_SYMBOL(usbd_alloc_urb_ep0); +OTG_EXPORT_SYMBOL(usbd_free_urb); + +OTG_EXPORT_SYMBOL(usbd_start_in_urb); +OTG_EXPORT_SYMBOL(usbd_start_out_urb); +OTG_EXPORT_SYMBOL(usbd_cancel_urb); +OTG_EXPORT_SYMBOL(usbd_halt_endpoint); + +/* ********************************************************************************************* */ + +/*! + * usbd_function_get_privdata() - get private data pointer + * @param function + * @return void * pointer to private data + */ +void *usbd_function_get_privdata(struct usbd_function_instance *function) +{ + return(function->privdata); +} + +/*! + * usbd_function_set_privdata() - set private data structure in function + * @param function + * @param privdata + */ +void usbd_function_set_privdata(struct usbd_function_instance *function, void *privdata) +{ + function->privdata = privdata; +} + +/*! + * usbd_endpoint_transferSize() - get transferSize for endpoint + * @param function + * @param endpoint_index + * @param hs highspeed flag + * @return transfer size + */ +int usbd_endpoint_transferSize(struct usbd_function_instance *function, int endpoint_index, int hs) +{ + struct usbd_endpoint_map *endpoint_map = usbd_endpoint_map(function); + RETURN_ZERO_UNLESS(function && endpoint_map); + return endpoint_map[endpoint_index].transferSize[hs]; +} + +/*! + * usbd_endpoint_update() - update endpoint address and size + * @param function + * @param endpoint_index + * @param endpoint descriptor + * @param hs high speed flag + */ +void usbd_endpoint_update(struct usbd_function_instance *function, int endpoint_index, + struct usbd_endpoint_descriptor *endpoint, int hs) +{ + endpoint->bEndpointAddress = usbd_endpoint_bEndpointAddress(function, endpoint_index, hs); + endpoint->wMaxPacketSize = usbd_endpoint_wMaxPacketSize(function, endpoint_index, hs); +} + +/*! + * usbd_otg_bmattributes() - return attributes + * @param function + * @return endpoint attributes + */ +int usbd_otg_bmattributes(struct usbd_function_instance *function) +{ + // XXX TODO - per function modifications for composite devices + return function->bus->bmAttributes; +} + + + +/*! + * usbd_write_info_message() - + * + * Send a message to the otg management application. + * @param function - function instance pointer + * @param msg + */ +void usbd_write_info_message(struct usbd_function_instance *function, char *msg) +{ + struct usbd_bus_instance *bus = function->bus; + RETURN_UNLESS(bus); + otg_write_info_message(bus->privdata, msg); +} + +OTG_EXPORT_SYMBOL(usbd_function_get_privdata); +OTG_EXPORT_SYMBOL(usbd_function_set_privdata); +OTG_EXPORT_SYMBOL(usbd_endpoint_transferSize); +OTG_EXPORT_SYMBOL(usbd_otg_bmattributes); +OTG_EXPORT_SYMBOL(usbd_endpoint_update); +OTG_EXPORT_SYMBOL(usbd_write_info_message); diff --git a/drivers/otg/otgcore/usbp-procfs.c b/drivers/otg/otgcore/usbp-procfs.c new file mode 100644 index 000000000000..b2779f920a2b --- /dev/null +++ b/drivers/otg/otgcore/usbp-procfs.c @@ -0,0 +1,608 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * otg/otgcore/usbp-procfs.c - USB Device Core Layer + * @(#) balden@belcarra.com|otg/otgcore/usbp-procfs.c|20070327230504|28330 + * + * Copyright (c) 2004-2005 Belcarra Technologies Corp + * Copyright (c) 2005-2006 Belcarra Technologies 2005 Corp + * + * By: + * Stuart Lynne <sl@belcarra.com>, + * Bruce Balden <balden@belcarra.com> + * + */ +/*! + * @file otg/otgcore/usbp-procfs.c + * @brief Implements /proc/usbd-functions, which displays descriptors for the current selected function. + * + * + * @ingroup USBDCORE + */ + +#include <otg/otg-compat.h> + +//#ifdef LINUX24 +//EXPORT_NO_SYMBOLS; +//#endif + +#include <otg/usbp-chap9.h> +#include <otg/otg-trace.h> +#include <otg/otg-api.h> +#include <otg/otg-trace.h> +#include <otg/usbp-func.h> +#include <otg/usbp-bus.h> +#include <otg/otg-pcd.h> + + +#define MAX_INTERFACES 2 + +static struct usbd_bus_instance *procfs_usbd_bus_instance; +extern struct otg_list_node usbd_simple_drivers; // list of all registered configuration function modules +extern struct otg_list_node usbd_interface_drivers; // list of all registered interface function modules +extern struct otg_list_node usbd_class_drivers; // list of all registered composite function modules +extern struct otg_list_node usbd_composite_drivers; // list of all registered composite function modules + + +/*! + * list_function() - list registered usbd_function_driver from start information + * + * @param usbd_function_drivers - usbd function drivers table + * @param cp - buffer to store driver information + * @param start - start position of list + * @return information length + */ +int list_function_composite(struct otg_list_node *usbd_function_drivers, char *cp, int start) +{ + int total = 0; + int count = 0; + + struct otg_list_node *lhd = NULL; + + + LIST_FOR_EACH (lhd, usbd_function_drivers) { + struct usbd_composite_driver *composite_driver = + (struct usbd_composite_driver *) LIST_ENTRY (lhd, struct usbd_function_driver, drivers); + + const char **names = composite_driver->interface_function_names; + + CONTINUE_IF(count++ < start); + + total += sprintf(cp + total, "\t%20s", composite_driver->driver.name); + total += sprintf(cp + total, " %04x/%04x %02x/%02x/%02x ", + composite_driver->device_description->idVendor, + composite_driver->device_description->idProduct, + composite_driver->device_description->bDeviceClass, + composite_driver->device_description->bDeviceSubClass, + composite_driver->device_description->bDeviceProtocol + ); + while ( *names ) { + total += sprintf(cp + total, "%s", *names++); + if (*names) + total += sprintf(cp + total, ":"); + } + + + if (composite_driver ->class_name) + total += sprintf(cp + total, "; %s", composite_driver->class_name); + + total += sprintf(cp + total, "\n"); + break; + } + return total; +} + +/*! + * list_function() - list driver's name from usbd_function_drivers table + */ +int list_function(struct otg_list_node *usbd_function_drivers, char *cp, char *msg) +{ + int total = sprintf(cp, "\n%s\n", msg); + + struct otg_list_node *lhd = NULL; + LIST_FOR_EACH (lhd, usbd_function_drivers) { + struct usbd_function_driver *function_driver = LIST_ENTRY (lhd, struct usbd_function_driver, drivers); + total += sprintf(cp + total, "\t%s\n", function_driver->name); + } + return total; +} + + +/* Proc Filesystem *************************************************************************** */ +/*! + * dohexdigit - change a value to hexdecimal char + * @param cp - buffer to store transformed char + * @param val - value to process + * + */ +static void dohexdigit (char *cp, unsigned char val) +{ + if (val < 0xa) { + *cp = val + '0'; + } else if ((val >= 0x0a) && (val <= 0x0f)) { + *cp = val - 0x0a + 'a'; + } +} + +/*! + * dohex - using translate a unsigned char value to hex notation + * @param cp - buffer to save hex notation + * @param val - value to translate + * + */ +static void dohexval (char *cp, unsigned char val) +{ + dohexdigit (cp++, val >> 4); + dohexdigit (cp++, val & 0xf); +} + +/*! + * dump_descriptor - create a descriptor for string sp + * @param buf - buffer to store descriptor + * @param sp - + * @return created descriptor length + */ +static int dump_descriptor (char *buf, char *sp) +{ + int num; + int len = 0; + + RETURN_ZERO_UNLESS(sp); + + num = *sp; + + while (sp && num--) { + dohexval (buf, *sp++); + buf += 2; + *buf++ = ' '; + len += 3; + } + len++; + *buf = '\n'; + return len; +} + +/*! + * dump_string_descriptor - dispaly string descriptor information + * @param function_instance - function instance pointer + * @param buf - buffer to save information + * @param i - + * @param name - + * @return information buffer length + */ +static int dump_string_descriptor (struct usbd_function_instance *function_instance, char *buf, int i, char *name) +{ + int k; + int len = 0; + + struct usbd_string_descriptor *string_descriptor; + + + RETURN_ZERO_UNLESS(i); + + string_descriptor = usbd_get_string_descriptor (function_instance, i); + + RETURN_ZERO_UNLESS (string_descriptor); + + len += sprintf((char *)buf+len, " %-24s [%2d:%2d ] ", name, i, string_descriptor->bLength); + + for (k = 0; k < (string_descriptor->bLength / 2) - 1; k++) { + *(char *) (buf + len) = (char) string_descriptor->wData[k]; + len++; + } + len += sprintf ((char *) buf + len, "\n"); + + return len; +} + + + +/*! dump_device_descriptors - dump device descriptor + * @param function_instance - usbd_function_instance pointer + * @param buf - buffer for saving descriptor information + * @param sp - device descriptor pointer + * @return buffer length + */ +static int dump_device_descriptor(struct usbd_function_instance *function_instance, char *buf, char *sp) +{ + int total = 0; + struct usbd_device_descriptor *device_descriptor = (struct usbd_device_descriptor *) sp; + + TRACE_MSG2(USBD, "buf: %x sp: %x", buf, sp); + + total = sprintf(buf + total, "Device descriptor [ ] "); + total += dump_descriptor(buf + total, sp); + + total += sprintf(buf + total, " bcdUSB [ 2] %04x\n", device_descriptor->bcdUSB); + total += sprintf(buf + total, " bDevice[Class,Sub,Pro] [ 4] %02x %02x %02x\n", + device_descriptor->bDeviceClass, device_descriptor->bDeviceSubClass, device_descriptor->bDeviceProtocol); + + total += sprintf(buf + total, " bMaxPacketSize0 [ 7] %02x\n", device_descriptor->bMaxPacketSize0); + + total += sprintf(buf + total, " idVendor [ 8] %04x\n", device_descriptor->idVendor); + total += sprintf(buf + total, " idProduct [ 10] %04x\n", device_descriptor->idProduct); + total += sprintf(buf + total, " bcdDevice [ 12] %04x\n", device_descriptor->bcdDevice); + + total += sprintf(buf + total, " bNumConfigurations [ 17] %02x\n", device_descriptor->bNumConfigurations); + + total += dump_string_descriptor(function_instance, buf + total, device_descriptor->iManufacturer, "iManufacturer"); + total += dump_string_descriptor(function_instance, buf + total, device_descriptor->iProduct, "iProduct"); + total += dump_string_descriptor(function_instance, buf + total, device_descriptor->iSerialNumber, "iSerialNumber"); + + total += sprintf(buf + total, "\n"); + return total; +} + +/*! dump_device_qualifier_descriptors - dump device descriptor + * @param function_instance - usbd_function_instance pointer + * @param buf - buffer for saving descriptor information + * @param sp - device descriptor pointer + * @return buffer length + */ +static int dump_device_qualifier_descriptor(struct usbd_function_instance *function_instance, char *buf, char *sp) +{ + int total = 0; + struct usbd_device_qualifier_descriptor *device_qualifier_descriptor = (struct usbd_device_qualifier_descriptor *) sp; + + TRACE_MSG2(USBD, "buf: %x sp: %x", buf, sp); + + total = sprintf(buf + total, "Device qualifier [ ] "); + total += dump_descriptor(buf + total, sp); + + total += sprintf(buf + total, " bcdUSB [ 2] %04x\n", device_qualifier_descriptor->bcdUSB); + + total += sprintf(buf + total, " bDevice[Class,Sub,Pro] [ 4] %02x %02x %02x\n", + device_qualifier_descriptor->bDeviceClass, + device_qualifier_descriptor->bDeviceSubClass, device_qualifier_descriptor->bDeviceProtocol); + + total += sprintf(buf + total, " bMaxPacketSize0 [ 7] %02x\n", + device_qualifier_descriptor->bMaxPacketSize0); + + total += sprintf(buf + total, " bNumConfigurations [ 17] %02x\n", + device_qualifier_descriptor->bNumConfigurations); + + total += sprintf(buf + total, "\n"); + return total; +} + +/*! dump_config_descriptors - dump config descriptor information + * @param function_instance - + * @param buf + * @param sp - configuartion descriptor pointer + * @return information length + */ +static int dump_config_descriptor(struct usbd_function_instance *function_instance, char *buf, char *sp) +{ + struct usbd_configuration_descriptor *config = (struct usbd_configuration_descriptor *) sp; + struct usbd_endpoint_descriptor *endpoint; + struct usbd_interface_descriptor *interface; + struct usbd_interface_association_descriptor *iad; + + int wTotalLength = le16_to_cpu(config->wTotalLength); + int bConfigurationValue = config->bConfigurationValue; + int interface_num; + int class_num; + int endpoint_num; + int total; + + TRACE_MSG3(USBD, "buf: %x sp: %x length: %d", buf, sp, wTotalLength); + + interface_num = class_num = endpoint_num = 0; + + for (total = 0; wTotalLength > 0; ) { + BREAK_UNLESS(sp[0]); + switch (sp[1]) { + case USB_DT_CONFIGURATION: + case USB_DT_OTHER_SPEED_CONFIGURATION: + interface_num = class_num = endpoint_num = 0; + total += sprintf(buf + total, "Configuration descriptor [%d ] ", bConfigurationValue); + break; + case USB_DT_INTERFACE: + class_num = 0; + total += sprintf(buf + total, "\nInterface descriptor [%d:%d:%d ] ", + bConfigurationValue, interface_num++, class_num); + break; + case USB_DT_ENDPOINT: + class_num = endpoint_num = 0; + total += sprintf(buf + total, "Endpoint descriptor [%d:%d:%d:%d] ", + bConfigurationValue, interface_num - 1, class_num, ++endpoint_num); + break; + case USB_DT_OTG: + class_num = endpoint_num = 0; + total += sprintf(buf + total, "OTG descriptor [%d ] ", bConfigurationValue); + break; + case USB_DT_INTERFACE_ASSOCIATION: + class_num = endpoint_num = 0; + total += sprintf(buf + total, "\nIAD descriptor [%d:%d ] ", + bConfigurationValue, interface_num); + break; + default: + endpoint_num = 0; + total += sprintf(buf + total, " Class descriptor [%d:%d:%d ] ", + bConfigurationValue, interface_num-1 , ++class_num); + break; + } + total += dump_descriptor(buf + total, sp); + switch (sp[1]) { + case USB_DT_CONFIGURATION: + case USB_DT_OTHER_SPEED_CONFIGURATION: + config = (struct usbd_configuration_descriptor *)sp; + total += sprintf(buf + total, " wTotalLength [ 4] %02x\n", config->wTotalLength); + total += sprintf(buf + total, " bNumInterfaces [ 4] %02x\n", config->bNumInterfaces); + total += sprintf(buf + total, " bConfigurationValue [ 5] %02x\n", config->bConfigurationValue); + total += dump_string_descriptor(function_instance, buf + total, config->iConfiguration, "iConfiguration"); + total += sprintf(buf + total, " bmAttributes [ 7] %02x%s%s\n", + config->bmAttributes, + config->bmAttributes & USB_BMATTRIBUTE_SELF_POWERED ? " Self-Powered" : "", + config->bmAttributes & USB_BMATTRIBUTE_REMOTE_WAKEUP ? " Remote-Wakeup" : "" + ); + total += sprintf(buf + total, " bMaxPower [ 8] %02x\n", config->bMaxPower); + break; + case USB_DT_INTERFACE: + interface = (struct usbd_interface_descriptor *)sp; + total += sprintf(buf + total, " bInterfaceNumber [ 2] %02x\n", interface->bInterfaceNumber); + total += sprintf(buf + total, " bAlternateSetting [ 5] %02x\n", + interface->bAlternateSetting); + total += sprintf(buf + total, " bNumEndpoints [ 5] %02x\n", interface->bNumEndpoints); + total += sprintf(buf + total, " bInterface[Class,Sub,Pro][ 5] %02x %02x %02x\n", + interface->bInterfaceClass, interface->bInterfaceSubClass, interface->bInterfaceProtocol); + total += dump_string_descriptor(function_instance, buf + total, interface->iInterface, "iInterface"); + break; + case USB_DT_ENDPOINT: + endpoint = (struct usbd_endpoint_descriptor *)sp; + break; + case USB_DT_INTERFACE_ASSOCIATION: + iad = (struct usbd_interface_association_descriptor *)sp; + total += sprintf(buf + total, " bFirstInterface [ 2] %02x\n", iad->bFirstInterface); + total += sprintf(buf + total, " bInterfaceCount [ 3] %02x\n", iad->bInterfaceCount); + total += sprintf(buf + total, " bFunction[Class,Sub,Pro] [ 4] %02x %02x %02x\n", + iad->bFunctionClass, iad->bFunctionSubClass, iad->bFunctionProtocol); + total += dump_string_descriptor(function_instance, buf + total, iad->iFunction, "iFunction"); + break; + default: + break; + } + wTotalLength -= sp[0]; + sp += sp[0]; + } + total += sprintf(buf + total, "\n"); + return total; +} + +/*! + * usbd_device_proc_read - implement proc file system read. + * @param page + * @param count + * @param pos + * + * Standard proc file system read function. + * + * We let upper layers iterate for us, *pos will indicate which device to return + * statistics for. + */ +int usbd_device_proc_read (char *page, size_t count, int * pos) +{ + int len = 0; + int index; + + u8 config_descriptor[512]; + int config_size; + + struct usbd_function_instance *function_instance = + procfs_usbd_bus_instance && procfs_usbd_bus_instance->function_instance ? + procfs_usbd_bus_instance->function_instance : NULL; + + struct pcd_instance *pcd_instance = (struct pcd_instance *) + procfs_usbd_bus_instance && procfs_usbd_bus_instance->privdata ? + procfs_usbd_bus_instance->privdata : NULL; + + struct otg_instance *otg_instance = pcd_instance ? pcd_instance->otg : NULL; + + //struct list_head *lhd; + + len = 0; + index = (*pos)++; + + switch(index) { + + case 0: + #if defined(CONFIG_USB_PERIPHERAL) + len += sprintf ((char *) page + len, "USB Peripheral\n"); + #elif defined(CONFIG_USB_PERIPHERAL_OR_HOST) + len += sprintf ((char *) page + len, "USB Peripheral or Host\n"); + #elif defined(CONFIG_USB_WIRED_DEVICE) + len += sprintf ((char *) page + len, "USB Wired Device\n"); + #elif defined(CONFIG_USB_WIRED_DEVICE_OR_HOST) + len += sprintf ((char *) page + len, "USB Wired Device or Host\n"); + #elif defined(CONFIG_OTG_BDEVICE_WITH_SRP) + len += sprintf ((char *) page + len, "SRP Capable B-Device (only)\n"); + #elif defined(CONFIG_OTG_DEVICE) + len += sprintf ((char *) page + len, "OTG Device\n"); + #endif + len += sprintf ((char *) page + len, "usb-device list\n"); + break; + + case 1: + if ( function_instance) { + //int configuration = index; + //struct usbd_function_driver *function_driver; + int i; + + + if ((config_size = usbd_get_descriptor(function_instance, config_descriptor, + sizeof(config_descriptor), + USB_DT_DEVICE, 0)) > index) { + len += dump_device_descriptor(function_instance, (char *)page + len, config_descriptor ); + } + + if ((config_size = usbd_get_descriptor(function_instance, config_descriptor, + sizeof(config_descriptor), + USB_DT_CONFIGURATION, 0)) > index) { + len += dump_config_descriptor(function_instance, (char *)page + len, config_descriptor ); + } + + #ifdef CONFIG_OTG_HIGH_SPEED + len += sprintf ((char *) page + len, "High Speed\n"); + if ((config_size = usbd_get_descriptor(function_instance, config_descriptor, + sizeof(config_descriptor), + USB_DT_DEVICE_QUALIFIER, 0)) > index) { + len += dump_device_qualifier_descriptor(function_instance, + (char *)page + len, config_descriptor ); + } + len += sprintf ((char *) page + len, "Other Speed Descriptor\n"); + if ((config_size = usbd_get_descriptor(function_instance, config_descriptor, + sizeof(config_descriptor), + USB_DT_OTHER_SPEED_CONFIGURATION, 0)) > index) { + len += dump_config_descriptor(function_instance, (char *)page + len, config_descriptor ); + } + #endif /* CONFIG_OTG_HIGH_SPEED */ + + for (i = 0; i < procfs_usbd_bus_instance->usbd_maxstrings; i++) { + + struct usbd_string_descriptor *string_descriptor + = usbd_get_string_descriptor (function_instance, i); + struct usbd_langid_descriptor *langid_descriptor = + (struct usbd_langid_descriptor *) string_descriptor; + int k; + + CONTINUE_UNLESS (string_descriptor); + + switch (i) { + case 0: + len += sprintf((char *)page+len, + "LangID [ 0 ] %2x:%2x %02x%02x\n", + langid_descriptor->bLength, langid_descriptor->bDescriptorType, + langid_descriptor->bData[0], langid_descriptor->bData[1] + ); + break; + default: + len += sprintf((char *)page+len, "String [%2d:%2d ] ", + i, string_descriptor->bLength); + + // bLength = sizeof(struct usbd_string_descriptor) + 2*strlen(str)-2; + + for (k = 0; k < (string_descriptor->bLength / 2) - 1; k++) { + *(char *) (page + len) = (char) string_descriptor->wData[k]; + len++; + } + len += sprintf ((char *) page + len, "\n"); + break; + } + } + } + else + len += list_function(&usbd_simple_drivers, (char *)page + len, "Not Enabled"); + + break; + + case 2: + len += list_function(&usbd_simple_drivers, (char *)page + len, "Simple Drivers"); + break; + case 3: + len += list_function(&usbd_class_drivers, (char *)page + len, "Class Drivers"); + break; + case 4: + len += list_function(&usbd_interface_drivers, (char *)page + len, "Interface Drivers"); + break; + + case 5: + if (otg_instance && otg_instance->function_name && strlen(otg_instance->function_name)) + len += sprintf((char *)page + len, "\nActive: %s\n\n", otg_instance->function_name); + len += sprintf((char *)page + len, "\n%s\n", "Composite Drivers"); + break; + + default: + len += list_function_composite(&usbd_composite_drivers, (char *)page + len, index - 6); + break; + } + + return len; +} + +#if defined(CONFIG_OTG_LNX) +/*! * + * usbd_device_proc_read_lnx - implement proc file system read. + * @param file + * @param buf + * @param count + * @param pos + * + * Standard proc file system read function. + * + * We let upper layers iterate for us, *pos will indicate which device to return + * statistics for. + */ +static ssize_t usbd_device_proc_read_lnx (struct file *file, char *buf, size_t count, loff_t * pos) +{ + unsigned long page; + int len = 0; + int index; + + u8 config_descriptor[512]; + int config_size; + + if (!(page = GET_KERNEL_PAGE())) { + return -ENOMEM; + } + + len = 0; + index = (*pos)++; + + len = usbd_device_proc_read((char *)page, count, (int *)pos); + + if (len > count) { + len = -EINVAL; + } + else if ((len > 0) && copy_to_user (buf, (char *) page, len)) { + len = -EFAULT; + } + free_page (page); + return len; +} + +/* Module init ******************************************************************************* */ + +/*! + * usbd_device_proc_operations_functions - + */ +static struct file_operations usbd_device_proc_operations_functions = { + read:usbd_device_proc_read_lnx, +}; +#endif /* defined(CONFIG_OTG_LNX) */ + +/*! + * usbd_procfs_init () - + */ +int usbd_procfs_init (struct usbd_bus_instance *bus) +{ + #if defined(CONFIG_OTG_LNX) + /* create proc filesystem entries */ + struct proc_dir_entry *p; + RETURN_ENOMEM_UNLESS ((p = create_proc_entry ("usb-functions", 0, NULL))); + p->proc_fops = &usbd_device_proc_operations_functions; + #endif /* defined(CONFIG_OTG_LNX) */ + procfs_usbd_bus_instance = bus; + return 0; +} + +/*! + * usbd_procfs_exit () - + */ +void usbd_procfs_exit (struct usbd_bus_instance *bus) +{ + // remove proc filesystem entry + procfs_usbd_bus_instance = NULL; + #if defined(CONFIG_OTG_LNX) + remove_proc_entry ("usb-functions", NULL); + #endif /* defined(CONFIG_OTG_LNX) */ +} diff --git a/drivers/pcmcia/Kconfig b/drivers/pcmcia/Kconfig index 519b4ff79f7f..b6dacf89781c 100644 --- a/drivers/pcmcia/Kconfig +++ b/drivers/pcmcia/Kconfig @@ -269,6 +269,14 @@ config AT91_CF Say Y here to support the CompactFlash controller on AT91 chips. Or choose M to compile the driver as a module named "at91_cf". +config PCMCIA_MX31ADS + tristate "MX31ADS PCMCIA support" + depends on ARM && MACH_MX31ADS && PCMCIA + help + Say Y here to include support for the Freescale i.MX31 PCMCIA controller. + + This driver is also available as a module called mx31ads_pcmcia. + config ELECTRA_CF tristate "Electra CompactFlash Controller" depends on PCMCIA && PPC_PASEMI diff --git a/drivers/pcmcia/Makefile b/drivers/pcmcia/Makefile index 6f6478ba7174..8509609b3ba2 100644 --- a/drivers/pcmcia/Makefile +++ b/drivers/pcmcia/Makefile @@ -38,6 +38,7 @@ obj-$(CONFIG_PCMCIA_VRC4173) += vrc4173_cardu.o obj-$(CONFIG_OMAP_CF) += omap_cf.o obj-$(CONFIG_AT91_CF) += at91_cf.o obj-$(CONFIG_ELECTRA_CF) += electra_cf.o +obj-$(CONFIG_PCMCIA_MX31ADS) += mx31ads-pcmcia.o sa11xx_core-y += soc_common.o sa11xx_base.o pxa2xx_core-y += soc_common.o pxa2xx_base.o diff --git a/drivers/pcmcia/mx31ads-pcmcia.c b/drivers/pcmcia/mx31ads-pcmcia.c new file mode 100644 index 000000000000..d8d305b5001c --- /dev/null +++ b/drivers/pcmcia/mx31ads-pcmcia.c @@ -0,0 +1,1295 @@ +/*====================================================================== + drivers/pcmcia/mx31ads-pcmica.c + + Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + + Device driver for the PCMCIA control functionality of i.Mx31 + microprocessors. + + The contents of this file are subject to the Mozilla Public + License Version 1.1 (the "License"); you may not use this file + except in compliance with the License. You may obtain a copy of + the License at http://www.mozilla.org/MPL/ + + Software distributed under the License is distributed on an "AS + IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + implied. See the License for the specific language governing + rights and limitations under the License. + + The initial developer of the original code is John G. Dorsey + <john+@cs.cmu.edu>. Portions created by John G. Dorsey are + Copyright (C) 1999 John G. Dorsey. All Rights Reserved. + + Alternatively, the contents of this file may be used under the + terms of the GNU Public License version 2 (the "GPL"), in which + case the provisions of the GPL are applicable instead of the + above. If you wish to allow the use of your version of this file + only under the terms of the GPL and not to allow others to use + your version of this file under the MPL, indicate your decision + by deleting the provisions above and replace them with the notice + and other provisions required by the GPL. If you do not delete + the provisions above, a recipient may use your version of this + file under either the MPL or the GPL. + +======================================================================*/ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/clk.h> + +#include <pcmcia/version.h> +#include <pcmcia/cs_types.h> +#include <pcmcia/cs.h> +#include <pcmcia/ss.h> +#include <asm/mach-types.h> +#include <asm/arch/pcmcia.h> +#include <linux/interrupt.h> +#include <linux/delay.h> + +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/timer.h> +#include <linux/mm.h> +#include <linux/spinlock.h> + +#include <asm/hardware.h> +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/system.h> + +#include "mx31ads-pcmcia.h" +#include <linux/irq.h> + +#define MX31ADS_PCMCIA_IRQ INT_PCMCIA + +/* + * The mapping of window size to bank size value + */ +static bsize_map_t bsize_map[] = { + /* Window size Bank size */ + {POR_1, POR_BSIZE_1}, + {POR_2, POR_BSIZE_2}, + {POR_4, POR_BSIZE_4}, + {POR_8, POR_BSIZE_8}, + {POR_16, POR_BSIZE_16}, + {POR_32, POR_BSIZE_32}, + {POR_64, POR_BSIZE_64}, + {POR_128, POR_BSIZE_128}, + {POR_256, POR_BSIZE_256}, + {POR_512, POR_BSIZE_512}, + + {POR_1K, POR_BSIZE_1K}, + {POR_2K, POR_BSIZE_2K}, + {POR_4K, POR_BSIZE_4K}, + {POR_8K, POR_BSIZE_8K}, + {POR_16K, POR_BSIZE_16K}, + {POR_32K, POR_BSIZE_32K}, + {POR_64K, POR_BSIZE_64K}, + {POR_128K, POR_BSIZE_128K}, + {POR_256K, POR_BSIZE_256K}, + {POR_512K, POR_BSIZE_512K}, + + {POR_1M, POR_BSIZE_1M}, + {POR_2M, POR_BSIZE_2M}, + {POR_4M, POR_BSIZE_4M}, + {POR_8M, POR_BSIZE_8M}, + {POR_16M, POR_BSIZE_16M}, + {POR_32M, POR_BSIZE_32M}, + {POR_64M, POR_BSIZE_64M} +}; + +#define to_mx31ads_pcmcia_socket(x) container_of(x, struct mx31ads_pcmcia_socket, socket) + +/* mx31ads_pcmcia_find_bsize() + * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + * + * Find the bsize according to the window size passed in + * + * Return: + */ +static int mx31ads_pcmcia_find_bsize(unsigned long win_size) +{ + int i, nr = sizeof(bsize_map) / sizeof(bsize_map_t); + int bsize = -1; + + for (i = 0; i < nr; i++) { + if (bsize_map[i].win_size == win_size) { + bsize = bsize_map[i].bsize; + break; + } + } + + pr_debug(KERN_INFO "nr = %d bsize = 0x%0x\n", nr, bsize); + if (bsize < 0 || i > nr) { + pr_debug(KERN_INFO "No such bsize\n"); + return -ENODEV; + } + + return bsize; +} + +/* mx31ads_common_pcmcia_sock_init() + * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + * + * (Re-)Initialise the socket, turning on status interrupts + * and PCMCIA bus. This must wait for power to stabilise + * so that the card status signals report correctly. + * + * Returns: 0 + */ +static int mx31ads_common_pcmcia_sock_init(struct pcmcia_socket *sock) +{ + struct mx31ads_pcmcia_socket *skt = to_mx31ads_pcmcia_socket(sock); + + pr_debug(KERN_INFO "initializing socket\n"); + + skt->ops->socket_init(skt); + return 0; +} + +/* + * mx31ads_common_pcmcia_config_skt + * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + * + * Convert PCMCIA socket state to our socket configure structure. + */ +static int +mx31ads_common_pcmcia_config_skt(struct mx31ads_pcmcia_socket *skt, + socket_state_t * state) +{ + int ret; + + ret = skt->ops->configure_socket(skt, state); + if (ret == 0) { + /* + * This really needs a better solution. The IRQ + * may or may not be claimed by the driver. + */ + if (skt->irq_state != 1 && state->io_irq) { + skt->irq_state = 1; + set_irq_type(skt->irq, IRQF_TRIGGER_FALLING); + } else if (skt->irq_state == 1 && state->io_irq == 0) { + skt->irq_state = 0; + set_irq_type(skt->irq, IRQF_TRIGGER_RISING); + } + + skt->cs_state = *state; + } + + if (ret < 0) + pr_debug(KERN_ERR "mx31ads_common_pcmcia: unable to configure" + " socket\n"); + + return ret; +} + +/* + * mx31ads_common_pcmcia_suspend() + * ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + * + * Remove power on the socket, disable IRQs from the card. + * Turn off status interrupts, and disable the PCMCIA bus. + * + * Returns: 0 + */ +static int mx31ads_common_pcmcia_suspend(struct pcmcia_socket *sock) +{ + struct mx31ads_pcmcia_socket *skt = to_mx31ads_pcmcia_socket(sock); + int ret; + + pr_debug(KERN_INFO "suspending socket\n"); + + ret = mx31ads_common_pcmcia_config_skt(skt, &dead_socket); + if (ret == 0) + skt->ops->socket_suspend(skt); + + return ret; +} + +static unsigned int mx31ads_common_pcmcia_skt_state(struct mx31ads_pcmcia_socket + *skt) +{ + struct pcmcia_state state; + unsigned int stat; + + memset(&state, 0, sizeof(struct pcmcia_state)); + + skt->ops->socket_state(skt, &state); + + stat = state.detect ? SS_DETECT : 0; + stat |= state.ready ? SS_READY : 0; + stat |= state.wrprot ? SS_WRPROT : 0; + stat |= state.vs_3v ? SS_3VCARD : 0; + stat |= state.vs_Xv ? SS_XVCARD : 0; + + /* The power status of individual sockets is not available + * explicitly from the hardware, so we just remember the state + * and regurgitate it upon request: + */ + stat |= skt->cs_state.Vcc ? SS_POWERON : 0; + + if (skt->cs_state.flags & SS_IOCARD) + stat |= state.bvd1 ? SS_STSCHG : 0; + else { + if (state.bvd1 == 0) + stat |= SS_BATDEAD; + else if (state.bvd2 == 0) + stat |= SS_BATWARN; + } + + pr_debug(KERN_INFO "stat = 0x%08x\n", stat); + + return stat; +} + +/* + * Implements the get_status() operation for the in-kernel PCMCIA + * service (formerly SS_GetStatus in Card Services). Essentially just + * fills in bits in `status' according to internal driver state or + * the value of the voltage detect chipselect register. + * + * As a debugging note, during card startup, the PCMCIA core issues + * three set_socket() commands in a row the first with RESET deasserted, + * the second with RESET asserted, and the last with RESET deasserted + * again. Following the third set_socket(), a get_status() command will + * be issued. The kernel is looking for the SS_READY flag (see + * setup_socket(), reset_socket(), and unreset_socket() in cs.c). + * + * Returns: 0 + */ +static int mx31ads_common_pcmcia_get_status(struct pcmcia_socket *sock, + unsigned int *status) +{ + struct mx31ads_pcmcia_socket *skt = to_mx31ads_pcmcia_socket(sock); + + skt->status = mx31ads_common_pcmcia_skt_state(skt); + *status = skt->status; + + return 0; +} + +/* + * Implements the set_socket() operation for the in-kernel PCMCIA + * service (formerly SS_SetSocket in Card Services). We more or + * less punt all of this work and let the kernel handle the details + * of power configuration, reset, &c. We also record the value of + * `state' in order to regurgitate it to the PCMCIA core later. + * + * Returns: 0 + */ +static int mx31ads_common_pcmcia_set_socket(struct pcmcia_socket *sock, + socket_state_t * state) +{ + struct mx31ads_pcmcia_socket *skt = to_mx31ads_pcmcia_socket(sock); + + pr_debug(KERN_INFO + "mask: %s%s%s%s%s%sflags: %s%s%s%s%s%sVcc %d Vpp %d irq %d\n", + (state->csc_mask == 0) ? "<NONE> " : "", + (state->csc_mask & SS_DETECT) ? "DETECT " : "", + (state->csc_mask & SS_READY) ? "READY " : "", + (state->csc_mask & SS_BATDEAD) ? "BATDEAD " : "", + (state->csc_mask & SS_BATWARN) ? "BATWARN " : "", + (state->csc_mask & SS_STSCHG) ? "STSCHG " : "", + (state->flags == 0) ? "<NONE> " : "", + (state->flags & SS_PWR_AUTO) ? "PWR_AUTO " : "", + (state->flags & SS_IOCARD) ? "IOCARD " : "", + (state->flags & SS_RESET) ? "RESET " : "", + (state->flags & SS_SPKR_ENA) ? "SPKR_ENA " : "", + (state->flags & SS_OUTPUT_ENA) ? "OUTPUT_ENA " : "", + state->Vcc, state->Vpp, state->io_irq); + + pr_debug(KERN_INFO + "csc_mask: %08x flags: %08x Vcc: %d Vpp: %d io_irq: %d\n", + state->csc_mask, state->flags, state->Vcc, state->Vpp, + state->io_irq); + + return mx31ads_common_pcmcia_config_skt(skt, state); +} + +/* + * Set address and profile to window registers PBR, POR, POFR + */ +static int mx31ads_pcmcia_set_window_reg(ulong start, ulong end, u_int window) +{ + int bsize; + ulong size = end - start + 1; + + bsize = mx31ads_pcmcia_find_bsize(size); + if (bsize < 0) { + pr_debug("Cannot set the window register\n"); + return -1; + } + /* Disable the window */ + _reg_PCMCIA_POR(window) &= ~PCMCIA_POR_PV; + + /* Set PBR, POR, POFR */ + _reg_PCMCIA_PBR(window) = start; + _reg_PCMCIA_POR(window) &= ~(PCMCIA_POR_PRS_MASK + | PCMCIA_POR_WPEN + | PCMCIA_POR_WP + | PCMCIA_POR_BSIZE_MASK + | PCMCIA_POR_PPS_8); + _reg_PCMCIA_POR(window) |= bsize | PCMCIA_POR_PPS_16; + + switch (window) { + case IO_WINDOW: + _reg_PCMCIA_POR(window) |= PCMCIA_POR_PRS(PCMCIA_POR_PRS_IO); + break; + + case ATTRIBUTE_MEMORY_WINDOW: + _reg_PCMCIA_POR(window) |= + PCMCIA_POR_PRS(PCMCIA_POR_PRS_ATTRIBUTE); + break; + + case COMMON_MEMORY_WINDOW: + _reg_PCMCIA_POR(window) |= + PCMCIA_POR_PRS(PCMCIA_POR_PRS_COMMON); + break; + + default: + pr_debug("Window %d is not support\n", window); + return -1; + } + _reg_PCMCIA_POFR(window) = 0; + + /* Enable the window */ + _reg_PCMCIA_POR(window) |= PCMCIA_POR_PV; + + return 0; +} + +/* + * Implements the set_io_map() operation for the in-kernel PCMCIA + * service (formerly SS_SetIOMap in Card Services). We configure + * the map speed as requested, but override the address ranges + * supplied by Card Services. + * + * Returns: 0 on success, -1 on error + */ +static int +mx31ads_common_pcmcia_set_io_map(struct pcmcia_socket *sock, + struct pccard_io_map *map) +{ + struct mx31ads_pcmcia_socket *skt = to_mx31ads_pcmcia_socket(sock); + unsigned short speed = map->speed; + + pr_debug("map %u speed %u start 0x%08lx stop 0x%08lx\n", + map->map, map->speed, map->start, map->stop); + pr_debug("flags: %s%s%s%s%s%s%s%s\n", + (map->flags == 0) ? "<NONE>" : "", + (map->flags & MAP_ACTIVE) ? "ACTIVE " : "", + (map->flags & MAP_16BIT) ? "16BIT " : "", + (map->flags & MAP_AUTOSZ) ? "AUTOSZ " : "", + (map->flags & MAP_0WS) ? "0WS " : "", + (map->flags & MAP_WRPROT) ? "WRPROT " : "", + (map->flags & MAP_USE_WAIT) ? "USE_WAIT " : "", + (map->flags & MAP_PREFETCH) ? "PREFETCH " : ""); + + if (map->map >= MAX_IO_WIN) { + pr_debug(KERN_ERR "%s(): map (%d) out of range\n", __FUNCTION__, + map->map); + return -1; + } + + if (map->flags & MAP_ACTIVE) { + if (speed == 0) + speed = PCMCIA_IO_ACCESS; + } else { + speed = 0; + } + + skt->spd_io[map->map] = speed; + skt->ops->set_timing(skt); + + if (map->stop == 1) + map->stop = PAGE_SIZE - 1; + + skt->socket.io_offset = (unsigned long)skt->virt_io; + map->stop -= map->start; + map->stop += (unsigned long)skt->virt_io; + map->start = (unsigned long)skt->virt_io; + + mx31ads_pcmcia_set_window_reg(skt->res_io.start, skt->res_io.end, + IO_WINDOW); + + pr_debug(KERN_ERR "IO window: _reg_PCMCIA_PBR(%d) = %08x\n", + IO_WINDOW, _reg_PCMCIA_PBR(IO_WINDOW)); + pr_debug(KERN_ERR "IO window: _reg_PCMCIA_POR(%d) = %08x\n", + IO_WINDOW, _reg_PCMCIA_POR(IO_WINDOW)); + + return 0; +} + +/* + * Implements the set_mem_map() operation for the in-kernel PCMCIA + * service (formerly SS_SetMemMap in Card Services). We configure + * the map speed as requested, but override the address ranges + * supplied by Card Services. + * + * Returns: 0 on success, -1 on error + */ +static int +mx31ads_common_pcmcia_set_mem_map(struct pcmcia_socket *sock, + struct pccard_mem_map *map) +{ + struct mx31ads_pcmcia_socket *skt = to_mx31ads_pcmcia_socket(sock); + struct resource *res; + unsigned short speed = map->speed; + + pr_debug + (KERN_INFO + "map %u speed %u card_start %08x flags%08x static_start %08lx\n", + map->map, map->speed, map->card_start, map->flags, + map->static_start); + pr_debug(KERN_INFO "flags: %s%s%s%s%s%s%s%s\n", + (map->flags == 0) ? "<NONE>" : "", + (map->flags & MAP_ACTIVE) ? "ACTIVE " : "", + (map->flags & MAP_16BIT) ? "16BIT " : "", + (map->flags & MAP_AUTOSZ) ? "AUTOSZ " : "", + (map->flags & MAP_0WS) ? "0WS " : "", + (map->flags & MAP_WRPROT) ? "WRPROT " : "", + (map->flags & MAP_ATTRIB) ? "ATTRIB " : "", + (map->flags & MAP_USE_WAIT) ? "USE_WAIT " : ""); + + if (map->map >= MAX_WIN) + return -EINVAL; + + if (map->flags & MAP_ACTIVE) { + if (speed == 0) + speed = 300; + } else { + speed = 0; + } + + if (map->flags & MAP_ATTRIB) { + res = &skt->res_attr; + skt->spd_attr[map->map] = speed; + skt->spd_mem[map->map] = 0; + mx31ads_pcmcia_set_window_reg(res->start, res->end, + ATTRIBUTE_MEMORY_WINDOW); + + pr_debug(KERN_INFO "Attr window: _reg_PCMCIA_PBR(%d) = %08x\n", + ATTRIBUTE_MEMORY_WINDOW, + _reg_PCMCIA_PBR(ATTRIBUTE_MEMORY_WINDOW)); + pr_debug(KERN_INFO "_reg_PCMCIA_POR(%d) = %08x\n", + ATTRIBUTE_MEMORY_WINDOW, + _reg_PCMCIA_POR(ATTRIBUTE_MEMORY_WINDOW)); + + } else { + res = &skt->res_mem; + skt->spd_attr[map->map] = 0; + skt->spd_mem[map->map] = speed; + mx31ads_pcmcia_set_window_reg(res->start, res->end, + COMMON_MEMORY_WINDOW); + + pr_debug(KERN_INFO "Com window: _reg_PCMCIA_PBR(%d) = %08x\n", + COMMON_MEMORY_WINDOW, + _reg_PCMCIA_PBR(COMMON_MEMORY_WINDOW)); + pr_debug(KERN_INFO "Com window: _reg_PCMCIA_POR(%d) = %08x\n", + COMMON_MEMORY_WINDOW, + _reg_PCMCIA_POR(COMMON_MEMORY_WINDOW)); + } + + skt->ops->set_timing(skt); + + map->static_start = res->start + map->card_start; + + return 0; +} + +static struct pccard_operations mx31ads_common_pcmcia_operations = { + .init = mx31ads_common_pcmcia_sock_init, + .suspend = mx31ads_common_pcmcia_suspend, + .get_status = mx31ads_common_pcmcia_get_status, + .set_socket = mx31ads_common_pcmcia_set_socket, + .set_io_map = mx31ads_common_pcmcia_set_io_map, + .set_mem_map = mx31ads_common_pcmcia_set_mem_map, +}; + +/* ============================================================================== */ + +static inline void mx31ads_pcmcia_irq_config(void) +{ + /* Setup irq */ + _reg_PCMCIA_PER = + (PCMCIA_PER_RDYLE | PCMCIA_PER_CDE1 | PCMCIA_PER_CDE2); +} + +static inline void mx31ads_pcmcia_invalidate_windows(void) +{ + int i; + + for (i = 0; i < PCMCIA_WINDOWS; i++) { + _reg_PCMCIA_PBR(i) = 0; + _reg_PCMCIA_POR(i) = 0; + _reg_PCMCIA_POFR(i) = 0; + } +} + +extern void gpio_pcmcia_active(void); +extern void gpio_pcmcia_inactive(void); + +static int mx31ads_pcmcia_hw_init(struct mx31ads_pcmcia_socket *skt) +{ + /* Configure the pins for PCMCIA */ + gpio_pcmcia_active(); + + /* + * enabling interrupts at this time causes a flood of interrupts + * if a card is present, so wait for configure_socket + * to enable them when requested. + * + * mx31ads_pcmcia_irq_config(); + */ + mx31ads_pcmcia_invalidate_windows(); + + /* Register interrupt. */ + skt->irq = MX31ADS_PCMCIA_IRQ; + + return 0; +} + +static void mx31ads_pcmcia_free_irq(struct mx31ads_pcmcia_socket *skt, + unsigned int irq) +{ + free_irq(irq, skt); +} + +static void mx31ads_pcmcia_hw_shutdown(struct mx31ads_pcmcia_socket *skt) +{ + mx31ads_pcmcia_invalidate_windows(); + mx31ads_pcmcia_free_irq(skt, MX31ADS_PCMCIA_IRQ); + + /* Disable the pins */ + gpio_pcmcia_inactive(); +} + +/* + * Get the socket state + */ +static void +mx31ads_pcmcia_socket_state(struct mx31ads_pcmcia_socket *skt, + struct pcmcia_state *state) +{ + unsigned long pins; + + pins = _reg_PCMCIA_PIPR; + pr_debug(KERN_INFO "_reg_PCMCIA_PIPR = 0x%08lx\n", pins); + + state->ready = (pins & PCMCIA_PIPR_RDY) ? 1 : 0; + state->bvd2 = (pins & PCMCIA_PIPR_BVD2) ? 1 : 0; + state->bvd1 = (pins & PCMCIA_PIPR_BVD1) ? 1 : 0; + + if ((pins & PCMCIA_PIPR_CD) == PCMCIA_PIPR_CD) { + state->detect = 0; + skt->cs_state.csc_mask |= SS_INSERTION; + } else { + state->detect = 1; + } + state->detect = (pins & PCMCIA_PIPR_CD) ? 0 : 1; + state->wrprot = (pins & PCMCIA_PIPR_WP) ? 1 : 0; + state->poweron = (pins & PCMCIA_PIPR_POWERON) ? 1 : 0; +#if 0 + if ((pins & PCMCIA_PIPR_CD) == PCMCIA_PIPR_CD) { + state->detect = 0; + skt->cs_state.csc_mask |= SS_INSERTION; + } else { + state->detect = 1; + } + if (pins & PCMCIA_PIPR_VS_5V) { + state->vs_3v = 0; + skt->cs_state.Vcc = 33; + } else { + state->vs_3v = 1; + skt->cs_state.Vcc = 50; + } +#endif + state->vs_3v = (pins & PCMCIA_PIPR_VS_5V) ? 0 : 1; + state->vs_Xv = 0; +} + +static __inline__ void mx31ads_pcmcia_low_power(bool enable) +{ + if (enable) + _reg_PCMCIA_PGCR |= PCMCIA_PGCR_LPMEN; + else + _reg_PCMCIA_PGCR &= ~PCMCIA_PGCR_LPMEN; +} + +static __inline__ void mx31ads_pcmcia_soft_reset(void) +{ + _reg_PCMCIA_PGCR |= PCMCIA_PGCR_RESET; + msleep(2); + + _reg_PCMCIA_PGCR &= ~(PCMCIA_PGCR_RESET | PCMCIA_PGCR_LPMEN); + _reg_PCMCIA_PGCR |= PCMCIA_PGCR_POE; + msleep(2); + pr_debug(KERN_INFO "_reg_PCMCIA_PGCR = %08x\n", _reg_PCMCIA_PGCR); +} + +static int +mx31ads_pcmcia_configure_socket(struct mx31ads_pcmcia_socket *skt, + const socket_state_t * state) +{ + int ret = 0; + + if (state->Vcc != 0 && state->Vcc != 33 && state->Vcc != 50) { + pr_debug(KERN_ERR "mx31ads-pcmcia: unrecognized Vcc %d\n", + state->Vcc); + return -1; + } + + pr_debug(KERN_INFO "PIPR = %x, desired Vcc = %d.%dV\n", + _reg_PCMCIA_PIPR, state->Vcc / 10, state->Vcc % 10); + + if (!(skt->socket.state & SOCKET_PRESENT) && (skt->pre_stat == 1)) { + pr_debug(KERN_INFO "Socket enter low power mode\n"); + skt->pre_stat = 0; + mx31ads_pcmcia_low_power(1); + } + + if (state->flags & SS_RESET) { + mx31ads_pcmcia_soft_reset(); + + /* clean out previous tenant's trash */ + _reg_PCMCIA_PGSR = (PCMCIA_PGSR_NWINE + | PCMCIA_PGSR_LPE + | PCMCIA_PGSR_SE + | PCMCIA_PGSR_CDE | PCMCIA_PGSR_WPE); + } + /* enable interrupts if requested, else turn 'em off */ + if (skt->irq) + mx31ads_pcmcia_irq_config(); + else + _reg_PCMCIA_PER = 0; + + if (skt->socket.state & SOCKET_PRESENT) { + skt->pre_stat = 1; + } + return ret; +} + +static void mx31ads_pcmcia_enable_irq(struct mx31ads_pcmcia_socket *skt, + unsigned int irq) +{ + set_irq_type(irq, IRQF_TRIGGER_RISING); + set_irq_type(irq, IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING); +} + +static void mx31ads_pcmcia_disable_irq(struct mx31ads_pcmcia_socket *skt, + unsigned int irq) +{ + set_irq_type(irq, IRQF_TRIGGER_NONE); +} + +/* + * Enable card status IRQs on (re-)initialisation. This can + * be called at initialisation, power management event, or + * pcmcia event. + */ +static void mx31ads_pcmcia_socket_init(struct mx31ads_pcmcia_socket *skt) +{ + mx31ads_pcmcia_soft_reset(); + + mx31ads_pcmcia_enable_irq(skt, MX31ADS_PCMCIA_IRQ); +} + +/* + * Disable card status IRQ on suspend. + */ +static void mx31ads_pcmcia_socket_suspend(struct mx31ads_pcmcia_socket *skt) +{ + mx31ads_pcmcia_disable_irq(skt, MX31ADS_PCMCIA_IRQ); + mx31ads_pcmcia_low_power(1); +} + +/* ==================================================================================== */ + +/* + * PCMCIA strobe hold time + */ +static inline u_int mx31ads_pcmcia_por_psht(u_int pcmcia_cycle_ns, + u_int hclk_cycle_ns) +{ + u_int psht; + + return psht = pcmcia_cycle_ns / hclk_cycle_ns; +} + +/* + * PCMCIA strobe set up time + */ +static inline u_int mx31ads_pcmcia_por_psst(u_int pcmcia_cycle_ns, + u_int hclk_cycle_ns) +{ + u_int psst; + + return psst = pcmcia_cycle_ns / hclk_cycle_ns; +} + +/* + * PCMCIA strobe length time + */ +static inline u_int mx31ads_pcmcia_por_pslt(u_int pcmcia_cycle_ns, + u_int hclk_cycle_ns) +{ + u_int pslt; + + return pslt = pcmcia_cycle_ns / hclk_cycle_ns + 2; +} + +/* + * mx31ads_pcmcia_default_mecr_timing + * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + * + * Calculate MECR clock wait states for given CPU clock + * speed and command wait state. This function can be over- + * written by a board specific version. + * + * The default is to simply calculate the BS values as specified in + * the INTEL SA1100 development manual + * "Expansion Memory (PCMCIA) Configuration Register (MECR)" + * that's section 10.2.5 in _my_ version of the manual ;) + */ +static unsigned int mx31ads_pcmcia_default_mecr_timing(struct + mx31ads_pcmcia_socket + *skt, + unsigned int cpu_speed, + unsigned int cmd_time) +{ + return 0; +} + +/* + * Calculate the timing code + */ +static u_int mx31ads_pcmcia_cal_code(u_int speed_ns, u_int clk_ns) +{ + u_int code; + + code = PCMCIA_POR_PSHT(mx31ads_pcmcia_por_psht(speed_ns, clk_ns)) + | PCMCIA_POR_PSST(mx31ads_pcmcia_por_psst(speed_ns, clk_ns)) + | PCMCIA_POR_PSL(mx31ads_pcmcia_por_pslt(speed_ns, clk_ns)); + + return code; +} + +/* + * set MECR value for socket <sock> based on this sockets + * io, mem and attribute space access speed. + * Call board specific BS value calculation to allow boards + * to tweak the BS values. + */ +static int mx31ads_pcmcia_set_window_timing(u_int speed_ns, u_int window, + u_int clk_ns) +{ + u_int code = 0; + + switch (window) { + case IO_WINDOW: + code = mx31ads_pcmcia_cal_code(speed_ns, clk_ns); + break; + case COMMON_MEMORY_WINDOW: + code = mx31ads_pcmcia_cal_code(speed_ns, clk_ns); + break; + case ATTRIBUTE_MEMORY_WINDOW: + code = mx31ads_pcmcia_cal_code(speed_ns, clk_ns); + break; + default: + break; + } + + /* Disable the window */ + _reg_PCMCIA_POR(window) &= ~PCMCIA_POR_PV; + + /* Clear the register fisrt */ + _reg_PCMCIA_POR(window) &= ~(PCMCIA_POR_PSST_MASK + | PCMCIA_POR_PSL_MASK + | PCMCIA_POR_PSHT_MASK); + /* And then set the register */ + _reg_PCMCIA_POR(window) |= code; + + /* Enable the window */ + _reg_PCMCIA_POR(window) |= PCMCIA_POR_PV; + + return 0; +} + +static unsigned short calc_speed(unsigned short *spds, int num, + unsigned short dflt) +{ + unsigned short speed = 0; + int i; + + for (i = 0; i < num; i++) + if (speed < spds[i]) + speed = spds[i]; + if (speed == 0) + speed = dflt; + + return speed; +} + +static void +mx31ads_common_pcmcia_get_timing(struct mx31ads_pcmcia_socket *skt, + struct mx31ads_pcmcia_timing *timing) +{ + timing->io = calc_speed(skt->spd_io, MAX_IO_WIN, PCMCIA_IO_ACCESS); + timing->mem = calc_speed(skt->spd_mem, MAX_WIN, PCMCIA_3V_MEM_ACCESS); + timing->attr = + calc_speed(skt->spd_attr, MAX_WIN, PCMCIA_ATTR_MEM_ACCESS); +} + +static int mx31ads_pcmcia_set_timing(struct mx31ads_pcmcia_socket *skt) +{ + u_int clk_ns; + struct mx31ads_pcmcia_timing timing; + + /* How many nanoseconds */ + clk_ns = (1000 * 1000 * 1000) / clk_get_rate(skt->clk); + pr_debug(KERN_INFO "clk_ns = %d\n", clk_ns); + + mx31ads_common_pcmcia_get_timing(skt, &timing); + pr_debug(KERN_INFO "timing: io %d, mem %d, attr %d\n", timing.io, + timing.mem, timing.attr); + + mx31ads_pcmcia_set_window_timing(timing.io, IO_WINDOW, clk_ns); + mx31ads_pcmcia_set_window_timing(timing.mem, COMMON_MEMORY_WINDOW, + clk_ns); + mx31ads_pcmcia_set_window_timing(timing.attr, ATTRIBUTE_MEMORY_WINDOW, + clk_ns); + + return 0; +} + +static int mx31ads_pcmcia_show_timing(struct mx31ads_pcmcia_socket *skt, + char *buf) +{ + return 0; +} + +static struct pcmcia_low_level mx31ads_pcmcia_ops = { + .owner = THIS_MODULE, + .hw_init = mx31ads_pcmcia_hw_init, + .hw_shutdown = mx31ads_pcmcia_hw_shutdown, + .socket_state = mx31ads_pcmcia_socket_state, + .configure_socket = mx31ads_pcmcia_configure_socket, + + .socket_init = mx31ads_pcmcia_socket_init, + .socket_suspend = mx31ads_pcmcia_socket_suspend, + + .get_timing = mx31ads_pcmcia_default_mecr_timing, + .set_timing = mx31ads_pcmcia_set_timing, + .show_timing = mx31ads_pcmcia_show_timing, +}; + +/* =================================================================================== */ + +LIST_HEAD(mx31ads_pcmcia_sockets); +DECLARE_MUTEX(mx31ads_pcmcia_sockets_lock); + +static DEFINE_SPINLOCK(status_lock); + +struct bittbl { + unsigned int mask; + const char *name; +}; + +static struct bittbl status_bits[] = { + {SS_WRPROT, "SS_WRPROT"}, + {SS_BATDEAD, "SS_BATDEAD"}, + {SS_BATWARN, "SS_BATWARN"}, + {SS_READY, "SS_READY"}, + {SS_DETECT, "SS_DETECT"}, + {SS_POWERON, "SS_POWERON"}, + {SS_STSCHG, "SS_STSCHG"}, + {SS_3VCARD, "SS_3VCARD"}, + {SS_XVCARD, "SS_XVCARD"}, +}; + +static struct bittbl conf_bits[] = { + {SS_PWR_AUTO, "SS_PWR_AUTO"}, + {SS_IOCARD, "SS_IOCARD"}, + {SS_RESET, "SS_RESET"}, + {SS_DMA_MODE, "SS_DMA_MODE"}, + {SS_SPKR_ENA, "SS_SPKR_ENA"}, + {SS_OUTPUT_ENA, "SS_OUTPUT_ENA"}, +}; + +static void +dump_bits(char **p, const char *prefix, unsigned int val, struct bittbl *bits, + int sz) +{ + char *b = *p; + int i; + + b += sprintf(b, "%-9s:", prefix); + for (i = 0; i < sz; i++) + if (val & bits[i].mask) + b += sprintf(b, " %s", bits[i].name); + *b++ = '\n'; + *p = b; +} + +/* + * Implements the /sys/class/pcmcia_socket/??/status file. + * + * Returns: the number of characters added to the buffer + */ +static ssize_t show_status(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct mx31ads_pcmcia_socket *skt = + container_of(dev, struct mx31ads_pcmcia_socket, socket.dev); + char *p = buf; + + p += sprintf(p, "slot : %d\n", skt->nr); + + dump_bits(&p, "status", skt->status, + status_bits, ARRAY_SIZE(status_bits)); + dump_bits(&p, "csc_mask", skt->cs_state.csc_mask, + status_bits, ARRAY_SIZE(status_bits)); + dump_bits(&p, "cs_flags", skt->cs_state.flags, + conf_bits, ARRAY_SIZE(conf_bits)); + + p += sprintf(p, "Vcc : %d\n", skt->cs_state.Vcc); + p += sprintf(p, "Vpp : %d\n", skt->cs_state.Vpp); + p += sprintf(p, "IRQ : %d (%d)\n", skt->cs_state.io_irq, skt->irq); + if (skt->ops->show_timing) + p += skt->ops->show_timing(skt, p); + + return p - buf; +} + +static DEVICE_ATTR(status, S_IRUGO, show_status, NULL); + +static void mx31ads_common_check_status(struct mx31ads_pcmcia_socket *skt) +{ + unsigned int events; + + pr_debug(KERN_INFO "entering PCMCIA monitoring thread\n"); + + do { + unsigned int status; + unsigned long flags; + + status = mx31ads_common_pcmcia_skt_state(skt); + + spin_lock_irqsave(&status_lock, flags); + events = (status ^ skt->status) & skt->cs_state.csc_mask; + skt->status = status; + spin_unlock_irqrestore(&status_lock, flags); + + pr_debug(KERN_INFO "events: %s%s%s%s%s%s\n", + events == 0 ? "<NONE>" : "", + events & SS_DETECT ? "DETECT " : "", + events & SS_READY ? "READY " : "", + events & SS_BATDEAD ? "BATDEAD " : "", + events & SS_BATWARN ? "BATWARN " : "", + events & SS_STSCHG ? "STSCHG " : ""); + + if (events) + pcmcia_parse_events(&skt->socket, events); + } while (events); +} + +/* + * Service routine for socket driver interrupts (requested by the + * low-level PCMCIA init() operation via mx31ads_common_pcmcia_thread()). + * The actual interrupt-servicing work is performed by + * mx31ads_common_pcmcia_thread(), largely because the Card Services event- + * handling code performs scheduling operations which cannot be + * executed from within an interrupt context. + */ +static irqreturn_t mx31ads_common_pcmcia_interrupt(int irq, void *dev) +{ + struct mx31ads_pcmcia_socket *skt = dev; + volatile u32 pscr, pgsr; + + dev_dbg(dev, "servicing IRQ %d\n", irq); + + /* clear interrupt states */ + pscr = _reg_PCMCIA_PSCR; + _reg_PCMCIA_PSCR = pscr; + + pgsr = _reg_PCMCIA_PGSR; + _reg_PCMCIA_PGSR = pgsr; + + mx31ads_common_check_status(skt); + + return IRQ_HANDLED; +} + +/* Let's poll for events in addition to IRQs since IRQ only is unreliable... */ +static void mx31ads_common_pcmcia_poll_event(unsigned long dummy) +{ + struct mx31ads_pcmcia_socket *skt = + (struct mx31ads_pcmcia_socket *)dummy; + pr_debug(KERN_INFO "polling for events\n"); + + mod_timer(&skt->poll_timer, jiffies + PCMCIA_POLL_PERIOD); + + mx31ads_common_check_status(skt); +} + +#define mx31ads_pcmcia_cpufreq_register() +#define mx31ads_pcmcia_cpufreq_unregister() + +static int mx31ads_common_drv_pcmcia_probe(struct platform_device *pdev, + struct pcmcia_low_level *ops) +{ + struct mx31ads_pcmcia_socket *skt; + int vs, value, ret; + struct pccard_io_map map; + + down(&mx31ads_pcmcia_sockets_lock); + + skt = kmalloc(sizeof(struct mx31ads_pcmcia_socket), GFP_KERNEL); + if (!skt) { + ret = -ENOMEM; + goto out; + } + + memset(skt, 0, sizeof(struct mx31ads_pcmcia_socket)); + + /* + * Initialise the socket structure. + */ + skt->socket.ops = &mx31ads_common_pcmcia_operations; + skt->socket.owner = ops->owner; + skt->socket.driver_data = skt; + + init_timer(&skt->poll_timer); + skt->poll_timer.function = mx31ads_common_pcmcia_poll_event; + skt->poll_timer.data = (unsigned long)skt; + skt->poll_timer.expires = jiffies + PCMCIA_POLL_PERIOD; + + skt->irq = MX31ADS_PCMCIA_IRQ; + skt->dev = &pdev->dev; + skt->ops = ops; + + skt->clk = clk_get(NULL, "ahb_clk"); + + skt->res_skt.start = _PCMCIA(0); + skt->res_skt.end = _PCMCIA(0) + PCMCIASp - 1; + skt->res_skt.name = MX31ADS_PCMCIA; + skt->res_skt.flags = IORESOURCE_MEM; + + ret = request_resource(&iomem_resource, &skt->res_skt); + if (ret) + goto out_err_1; + + skt->res_io.start = _PCMCIAIO(0); + skt->res_io.end = _PCMCIAIO(0) + PCMCIAIOSp - 1; + skt->res_io.name = "io"; + skt->res_io.flags = IORESOURCE_MEM | IORESOURCE_BUSY; + + ret = request_resource(&skt->res_skt, &skt->res_io); + if (ret) + goto out_err_2; + + skt->res_mem.start = _PCMCIAMem(0); + skt->res_mem.end = _PCMCIAMem(0) + PCMCIAMemSp - 1; + skt->res_mem.name = "memory"; + skt->res_mem.flags = IORESOURCE_MEM; + + ret = request_resource(&skt->res_skt, &skt->res_mem); + if (ret) + goto out_err_3; + + skt->res_attr.start = _PCMCIAAttr(0); + skt->res_attr.end = _PCMCIAAttr(0) + PCMCIAAttrSp - 1; + skt->res_attr.name = "attribute"; + skt->res_attr.flags = IORESOURCE_MEM; + + ret = request_resource(&skt->res_skt, &skt->res_attr); + if (ret) + goto out_err_4; + + skt->virt_io = ioremap(skt->res_io.start, 0x10000); + if (skt->virt_io == NULL) { + ret = -ENOMEM; + goto out_err_5; + } + + if (list_empty(&mx31ads_pcmcia_sockets)) + mx31ads_pcmcia_cpufreq_register(); + + list_add(&skt->node, &mx31ads_pcmcia_sockets); + + /* + * We initialize default socket timing here, because + * we are not guaranteed to see a SetIOMap operation at + * runtime. + */ + ops->set_timing(skt); + + ret = ops->hw_init(skt); + if (ret) + goto out_err_6; + + ret = request_irq(skt->irq, mx31ads_common_pcmcia_interrupt, + IRQF_SHARED | IRQF_DISABLED, "PCMCIA IRQ", skt); + if (ret) + goto out_err_6; + set_irq_type(skt->irq, IRQT_NOEDGE); + + skt->socket.features = SS_CAP_STATIC_MAP | SS_CAP_PCCARD; + skt->socket.resource_ops = &pccard_static_ops; + skt->socket.irq_mask = 0; + skt->socket.map_size = PCMCIAPrtSp; + skt->socket.pci_irq = skt->irq; + skt->socket.io_offset = (unsigned long)skt->virt_io; + + skt->status = mx31ads_common_pcmcia_skt_state(skt); + skt->pre_stat = 0; + ret = pcmcia_register_socket(&skt->socket); + if (ret) + goto out_err_7; + /* FIXED ME workaround for binding with ide-cs. ide usage io port 0x100~0x107 and 0x10e */ + map.map = 0; + map.flags = MAP_ACTIVE | MAP_16BIT; + map.start = 0; + map.stop = PCMCIAIOSp - 1; + map.speed = 0; + mx31ads_common_pcmcia_set_io_map(&skt->socket, &map); + + vs = _reg_PCMCIA_PIPR & PCMCIA_PIPR_VS; + value = vs & PCMCIA_PIPR_VS_5V ? 50 : 33; + dev_dbg(&pdev->dev, "PCMCIA: Voltage the card supports: %d.%dV\n", + value / 10, value % 10); + + add_timer(&skt->poll_timer); + + ret = device_create_file(&skt->socket.dev, &dev_attr_status); + if (ret < 0) + goto out_err_8; + + platform_set_drvdata(pdev, skt); + ret = 0; + goto out; + + out_err_8: + del_timer_sync(&skt->poll_timer); + pcmcia_unregister_socket(&skt->socket); + + out_err_7: + flush_scheduled_work(); + free_irq(skt->irq, skt); + ops->hw_shutdown(skt); + out_err_6: + list_del(&skt->node); + iounmap(skt->virt_io); + out_err_5: + release_resource(&skt->res_attr); + out_err_4: + release_resource(&skt->res_mem); + out_err_3: + release_resource(&skt->res_io); + out_err_2: + release_resource(&skt->res_skt); + out_err_1: + + kfree(skt); + out: + up(&mx31ads_pcmcia_sockets_lock); + return ret; +} + +static int mx31ads_drv_pcmcia_remove(struct platform_device *pdev) +{ + struct mx31ads_pcmcia_socket *skt = platform_get_drvdata(pdev); + + platform_set_drvdata(pdev, NULL); + + down(&mx31ads_pcmcia_sockets_lock); + + del_timer_sync(&skt->poll_timer); + + pcmcia_unregister_socket(&skt->socket); + + flush_scheduled_work(); + + skt->ops->hw_shutdown(skt); + + mx31ads_common_pcmcia_config_skt(skt, &dead_socket); + + list_del(&skt->node); + iounmap(skt->virt_io); + skt->virt_io = NULL; + release_resource(&skt->res_attr); + release_resource(&skt->res_mem); + release_resource(&skt->res_io); + release_resource(&skt->res_skt); + + if (list_empty(&mx31ads_pcmcia_sockets)) + mx31ads_pcmcia_cpufreq_unregister(); + + up(&mx31ads_pcmcia_sockets_lock); + + kfree(skt); + + return 0; +} + +static int mx31ads_drv_pcmcia_probe(struct platform_device *pdev) +{ + if (!machine_is_mx31ads()) + return -ENODEV; + + return mx31ads_common_drv_pcmcia_probe(pdev, &mx31ads_pcmcia_ops); +} + +static int mx31ads_drv_pcmcia_suspend(struct platform_device *pdev, + pm_message_t state) +{ + return pcmcia_socket_dev_suspend(&pdev->dev, state); +} + +static int mx31ads_drv_pcmcia_resume(struct platform_device *pdev) +{ + return pcmcia_socket_dev_resume(&pdev->dev); +} + +/* + * Low level functions + */ +static struct platform_driver mx31ads_pcmcia_driver = { + .driver = { + .name = MX31ADS_PCMCIA, + }, + .probe = mx31ads_drv_pcmcia_probe, + .remove = mx31ads_drv_pcmcia_remove, + .suspend = mx31ads_drv_pcmcia_suspend, + .resume = mx31ads_drv_pcmcia_resume, +}; + +/* mx31ads_pcmcia_init() + * + */ +static int __init mx31ads_pcmcia_init(void) +{ + int ret; + + if ((ret = platform_driver_register(&mx31ads_pcmcia_driver))) + return ret; + pr_debug(KERN_INFO "PCMCIA: Initialize i.Mx31 pcmcia socket\n"); + + return ret; +} + +/* mx31ads_pcmcia_exit() + * + */ +static void __exit mx31ads_pcmcia_exit(void) +{ + platform_driver_unregister(&mx31ads_pcmcia_driver); +} + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("i.MX31 PCMCIA Socket Controller"); +MODULE_LICENSE("GPL"); + +module_init(mx31ads_pcmcia_init); +module_exit(mx31ads_pcmcia_exit); diff --git a/drivers/pcmcia/mx31ads-pcmcia.h b/drivers/pcmcia/mx31ads-pcmcia.h new file mode 100644 index 000000000000..50b74868bd1e --- /dev/null +++ b/drivers/pcmcia/mx31ads-pcmcia.h @@ -0,0 +1,157 @@ +/* + * linux/drivers/pcmcia/mx31ads-pcmcia.h + * + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + * + * This file contains definitions for the PCMCIA support code common to + * integrated SOCs like the i.Mx31 microprocessors. + */ +#ifndef _ASM_ARCH_PCMCIA +#define _ASM_ARCH_PCMCIA + +/* include the world */ +#include <linux/cpufreq.h> +#include <pcmcia/version.h> +#include <pcmcia/cs_types.h> +#include <pcmcia/cs.h> +#include <pcmcia/ss.h> +#include <pcmcia/bulkmem.h> +#include <pcmcia/cistpl.h> +#include "cs_internal.h" + +#define MX31ADS_PCMCIA "Mx31ads_pcmcia_socket" + +struct device; +struct pcmcia_low_level; + +/* + * This structure encapsulates per-socket state which we might need to + * use when responding to a Card Services query of some kind. + */ +struct mx31ads_pcmcia_socket { + struct pcmcia_socket socket; + + /* + * Info from low level handler + */ + struct device *dev; + unsigned int nr; + unsigned int irq; + + struct clk *clk; + + /* + * Core PCMCIA state + */ + struct pcmcia_low_level *ops; + + unsigned int status; + unsigned int pre_stat; + socket_state_t cs_state; + + unsigned short spd_io[MAX_IO_WIN]; + unsigned short spd_mem[MAX_WIN]; + unsigned short spd_attr[MAX_WIN]; + + struct resource res_skt; + struct resource res_io; + struct resource res_mem; + struct resource res_attr; + void *virt_io; + + unsigned int irq_state; + + struct timer_list poll_timer; + struct list_head node; +}; + +struct pcmcia_state { + unsigned detect:1, + ready:1, bvd1:1, bvd2:1, wrprot:1, vs_3v:1, vs_Xv:1, poweron:1; +}; + +struct pcmcia_low_level { + struct module *owner; + + /* first socket in system */ + int first; + /* nr of sockets */ + int nr; + + int (*hw_init) (struct mx31ads_pcmcia_socket *); + void (*hw_shutdown) (struct mx31ads_pcmcia_socket *); + + void (*socket_state) (struct mx31ads_pcmcia_socket *, + struct pcmcia_state *); + int (*configure_socket) (struct mx31ads_pcmcia_socket *, + const socket_state_t *); + + /* + * Enable card status IRQs on (re-)initialisation. This can + * be called at initialisation, power management event, or + * pcmcia event. + */ + void (*socket_init) (struct mx31ads_pcmcia_socket *); + + /* + * Disable card status IRQs and PCMCIA bus on suspend. + */ + void (*socket_suspend) (struct mx31ads_pcmcia_socket *); + + /* + * Hardware specific timing routines. + * If provided, the get_timing routine overrides the SOC default. + */ + unsigned int (*get_timing) (struct mx31ads_pcmcia_socket *, + unsigned int, unsigned int); + int (*set_timing) (struct mx31ads_pcmcia_socket *); + int (*show_timing) (struct mx31ads_pcmcia_socket *, char *); + +#ifdef CONFIG_CPU_FREQ + /* + * CPUFREQ support. + */ + int (*frequency_change) (struct mx31ads_pcmcia_socket *, unsigned long, + struct cpufreq_freqs *); +#endif +}; + +struct mx31ads_pcmcia_timing { + unsigned short io; + unsigned short mem; + unsigned short attr; +}; + +typedef struct { + ulong win_size; + int bsize; +} bsize_map_t; + +/* + * The PC Card Standard, Release 7, section 4.13.4, says that twIORD + * has a minimum value of 165ns. Section 4.13.5 says that twIOWR has + * a minimum value of 165ns, as well. Section 4.7.2 (describing + * common and attribute memory write timing) says that twWE has a + * minimum value of 150ns for a 250ns cycle time (for 5V operation; + * see section 4.7.4), or 300ns for a 600ns cycle time (for 3.3V + * operation, also section 4.7.4). Section 4.7.3 says that taOE + * has a maximum value of 150ns for a 300ns cycle time (for 5V + * operation), or 300ns for a 600ns cycle time (for 3.3V operation). + * + * When configuring memory maps, Card Services appears to adopt the policy + * that a memory access time of "0" means "use the default." The default + * PCMCIA I/O command width time is 165ns. The default PCMCIA 5V attribute + * and memory command width time is 150ns; the PCMCIA 3.3V attribute and + * memory command width time is 300ns. + */ +#define PCMCIA_IO_ACCESS (165) +#define PCMCIA_5V_MEM_ACCESS (150) +#define PCMCIA_3V_MEM_ACCESS (300) +#define PCMCIA_ATTR_MEM_ACCESS (300) + +/* + * The socket driver actually works nicely in interrupt-driven form, + * so the (relatively infrequent) polling is "just to be sure." + */ +#define PCMCIA_POLL_PERIOD (2*HZ) +#endif diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index 1e6715ec51ef..4f9e43b3f9ac 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -461,4 +461,11 @@ config RTC_DRV_RS5C313 help If you say yes here you get support for the Ricoh RS5C313 RTC chips. +config RTC_MXC + tristate "Freescale MXC Real Time Clock" + depends on ARCH_MXC + depends on RTC_CLASS + help + Support for Freescale RTC MXC + endif # RTC_CLASS diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile index 465db4dd50b2..7249dfdb6974 100644 --- a/drivers/rtc/Makefile +++ b/drivers/rtc/Makefile @@ -49,3 +49,4 @@ obj-$(CONFIG_RTC_DRV_TEST) += rtc-test.o obj-$(CONFIG_RTC_DRV_V3020) += rtc-v3020.o obj-$(CONFIG_RTC_DRV_VR41XX) += rtc-vr41xx.o obj-$(CONFIG_RTC_DRV_X1205) += rtc-x1205.o +obj-$(CONFIG_RTC_MXC) += rtc-mxc.o diff --git a/drivers/rtc/rtc-mxc.c b/drivers/rtc/rtc-mxc.c new file mode 100644 index 000000000000..c3997d76a0db --- /dev/null +++ b/drivers/rtc/rtc-mxc.c @@ -0,0 +1,815 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * Implementation based on rtc-ds1553.c + */ + +/*! + * @defgroup RTC Real Time Clock (RTC) Driver + */ +/*! + * @file rtc-mxc.c + * @brief Real Time Clock interface + * + * This file contains Real Time Clock interface for Linux. + * + * @ingroup RTC + */ + +#include <linux/rtc.h> +#include <linux/module.h> +#include <linux/fs.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <linux/uaccess.h> + +#include <asm/hardware.h> +#define RTC_INPUT_CLK_32768HZ (0x00 << 5) +#define RTC_INPUT_CLK_32000HZ (0x01 << 5) +#define RTC_INPUT_CLK_38400HZ (0x02 << 5) + +#define RTC_SW_BIT (1 << 0) +#define RTC_ALM_BIT (1 << 2) +#define RTC_1HZ_BIT (1 << 4) +#define RTC_2HZ_BIT (1 << 7) +#define RTC_SAM0_BIT (1 << 8) +#define RTC_SAM1_BIT (1 << 9) +#define RTC_SAM2_BIT (1 << 10) +#define RTC_SAM3_BIT (1 << 11) +#define RTC_SAM4_BIT (1 << 12) +#define RTC_SAM5_BIT (1 << 13) +#define RTC_SAM6_BIT (1 << 14) +#define RTC_SAM7_BIT (1 << 15) +#define PIT_ALL_ON (RTC_2HZ_BIT | RTC_SAM0_BIT | RTC_SAM1_BIT | \ + RTC_SAM2_BIT | RTC_SAM3_BIT | RTC_SAM4_BIT | \ + RTC_SAM5_BIT | RTC_SAM6_BIT | RTC_SAM7_BIT) + +#define RTC_ENABLE_BIT (1 << 7) + +#define MAX_PIE_NUM 9 +#define MAX_PIE_FREQ 512 +const u32 PIE_BIT_DEF[MAX_PIE_NUM][2] = { + {2, RTC_2HZ_BIT}, + {4, RTC_SAM0_BIT}, + {8, RTC_SAM1_BIT}, + {16, RTC_SAM2_BIT}, + {32, RTC_SAM3_BIT}, + {64, RTC_SAM4_BIT}, + {128, RTC_SAM5_BIT}, + {256, RTC_SAM6_BIT}, + {MAX_PIE_FREQ, RTC_SAM7_BIT}, +}; + +/* Those are the bits from a classic RTC we want to mimic */ +#define RTC_IRQF 0x80 /* any of the following 3 is active */ +#define RTC_PF 0x40 /* Periodic interrupt */ +#define RTC_AF 0x20 /* Alarm interrupt */ +#define RTC_UF 0x10 /* Update interrupt for 1Hz RTC */ + +#define MXC_RTC_TIME 0 +#define MXC_RTC_ALARM 1 + +#define RTC_HOURMIN 0x00 /* 32bit rtc hour/min counter reg */ +#define RTC_SECOND 0x04 /* 32bit rtc seconds counter reg */ +#define RTC_ALRM_HM 0x08 /* 32bit rtc alarm hour/min reg */ +#define RTC_ALRM_SEC 0x0C /* 32bit rtc alarm seconds reg */ +#define RTC_RTCCTL 0x10 /* 32bit rtc control reg */ +#define RTC_RTCISR 0x14 /* 32bit rtc interrupt status reg */ +#define RTC_RTCIENR 0x18 /* 32bit rtc interrupt enable reg */ +#define RTC_STPWCH 0x1C /* 32bit rtc stopwatch min reg */ +#define RTC_DAYR 0x20 /* 32bit rtc days counter reg */ +#define RTC_DAYALARM 0x24 /* 32bit rtc day alarm reg */ +#define RTC_TEST1 0x28 /* 32bit rtc test reg 1 */ +#define RTC_TEST2 0x2C /* 32bit rtc test reg 2 */ +#define RTC_TEST3 0x30 /* 32bit rtc test reg 3 */ + +struct rtc_plat_data { + struct rtc_device *rtc; + void __iomem *ioaddr; + unsigned long baseaddr; + int irq; + struct clk *clk; + unsigned int irqen; + int alrm_sec; + int alrm_min; + int alrm_hour; + int alrm_mday; +}; + +/*! + * @defgroup RTC Real Time Clock (RTC) Driver + */ +/*! + * @file rtc-mxc.c + * @brief Real Time Clock interface + * + * This file contains Real Time Clock interface for Linux. + * + * @ingroup RTC + */ + +#if defined (CONFIG_MXC_PMIC_SC55112_RTC) || defined (CONFIG_MXC_MC13783_RTC) +#include <asm/arch/pmic_rtc.h> +#else +#define pmic_rtc_get_time(args) MXC_EXTERNAL_RTC_NONE +#define pmic_rtc_set_time(args) MXC_EXTERNAL_RTC_NONE +#define pmic_rtc_loaded() 0 +#endif + +#define RTC_VERSION "1.0" +#define MXC_EXTERNAL_RTC_OK 0 +#define MXC_EXTERNAL_RTC_ERR -1 +#define MXC_EXTERNAL_RTC_NONE -2 + +/*! + * This function reads the RTC value from some external source. + * + * @param second pointer to the returned value in second + * + * @return 0 if successful; non-zero otherwise + */ +int get_ext_rtc_time(u32 * second) +{ + int ret = 0; + struct timeval tmp; + if (!pmic_rtc_loaded()) { + return MXC_EXTERNAL_RTC_NONE; + } + + ret = pmic_rtc_get_time(&tmp); + + if (0 == ret) + *second = tmp.tv_sec; + else + ret = MXC_EXTERNAL_RTC_ERR; + + return ret; +} + +/*! + * This function sets external RTC + * + * @param second value in second to be set to external RTC + * + * @return 0 if successful; non-zero otherwise + */ +int set_ext_rtc_time(u32 second) +{ + int ret = 0; + struct timeval tmp; + + if (!pmic_rtc_loaded()) { + return MXC_EXTERNAL_RTC_NONE; + } + + tmp.tv_sec = second; + + ret = pmic_rtc_set_time(&tmp); + + if (0 != ret) + ret = MXC_EXTERNAL_RTC_ERR; + + return ret; +} + +static u32 rtc_freq = 2; /* minimun value for PIE */ +static unsigned long rtc_status; + +static struct rtc_time g_rtc_alarm = { + .tm_year = 0, + .tm_mon = 0, + .tm_mday = 0, + .tm_hour = 0, + .tm_mon = 0, + .tm_sec = 0, +}; + +static DEFINE_SPINLOCK(rtc_lock); + +/*! + * This function is used to obtain the RTC time or the alarm value in + * second. + * + * @param time_alarm use MXC_RTC_TIME for RTC time value; MXC_RTC_ALARM for alarm value + * + * @return The RTC time or alarm time in second. + */ +static u32 get_alarm_or_time(struct device *dev, int time_alarm) +{ + struct platform_device *pdev = to_platform_device(dev); + struct rtc_plat_data *pdata = platform_get_drvdata(pdev); + void __iomem *ioaddr = pdata->ioaddr; + u32 day, hr, min, sec, hr_min; + if (time_alarm == MXC_RTC_TIME) { + day = readw(ioaddr + RTC_DAYR); + hr_min = readw(ioaddr + RTC_HOURMIN); + sec = readw(ioaddr + RTC_SECOND); + } else if (time_alarm == MXC_RTC_ALARM) { + day = readw(ioaddr + RTC_DAYALARM); + hr_min = (0x0000FFFF) & readw(ioaddr + RTC_ALRM_HM); + sec = readw(ioaddr + RTC_ALRM_SEC); + } else { + panic("wrong value for time_alarm=%d\n", time_alarm); + } + + hr = hr_min >> 8; + min = hr_min & 0x00FF; + + return ((((day * 24 + hr) * 60) + min) * 60 + sec); +} + +/*! + * This function sets the RTC alarm value or the time value. + * + * @param time_alarm the new alarm value to be updated in the RTC + * @param time use MXC_RTC_TIME for RTC time value; MXC_RTC_ALARM for alarm value + */ +static void set_alarm_or_time(struct device *dev, int time_alarm, u32 time) +{ + u32 day, hr, min, sec, temp; + struct platform_device *pdev = to_platform_device(dev); + struct rtc_plat_data *pdata = platform_get_drvdata(pdev); + void __iomem *ioaddr = pdata->ioaddr; + day = time / 86400; + time -= day * 86400; + /* time is within a day now */ + hr = time / 3600; + time -= hr * 3600; + /* time is within an hour now */ + min = time / 60; + sec = time - min * 60; + + temp = (hr << 8) + min; + + if (time_alarm == MXC_RTC_TIME) { + writew(day, ioaddr + RTC_DAYR); + writew(sec, ioaddr + RTC_SECOND); + writew(temp, ioaddr + RTC_HOURMIN); + } else if (time_alarm == MXC_RTC_ALARM) { + writew(day, ioaddr + RTC_DAYALARM); + writew(sec, ioaddr + RTC_ALRM_SEC); + writew(temp, ioaddr + RTC_ALRM_HM); + } else { + panic("wrong value for time_alarm=%d\n", time_alarm); + } +} + +/*! + * This function updates the RTC alarm registers and then clears all the + * interrupt status bits. + * + * @param alrm the new alarm value to be updated in the RTC + * + * @return 0 if successful; non-zero otherwise. + */ +static int rtc_update_alarm(struct device *dev, struct rtc_time *alrm) +{ + struct rtc_time alarm_tm, now_tm; + unsigned long now, time; + int ret; + struct platform_device *pdev = to_platform_device(dev); + struct rtc_plat_data *pdata = platform_get_drvdata(pdev); + void __iomem *ioaddr = pdata->ioaddr; + + now = get_alarm_or_time(dev, MXC_RTC_TIME); + rtc_time_to_tm(now, &now_tm); + alarm_tm.tm_year = now_tm.tm_year; + alarm_tm.tm_mon = now_tm.tm_mon; + alarm_tm.tm_mday = now_tm.tm_mday; + alarm_tm.tm_hour = alrm->tm_hour; + alarm_tm.tm_min = alrm->tm_min; + alarm_tm.tm_sec = alrm->tm_sec; + rtc_tm_to_time(&now_tm, &now); + rtc_tm_to_time(&alarm_tm, &time); + if (time < now) { + time += 60 * 60 * 24; + rtc_time_to_tm(time, &alarm_tm); + } + ret = rtc_tm_to_time(&alarm_tm, &time); + + /* clear all the interrupt status bits */ + writew(readw(ioaddr + RTC_RTCISR), ioaddr + RTC_RTCISR); + + set_alarm_or_time(dev, MXC_RTC_ALARM, time); + + return ret; +} + +/*! + * This function is the RTC interrupt service routine. + * + * @param irq RTC IRQ number + * @param dev_id device ID which is not used + * + * @return IRQ_HANDLED as defined in the include/linux/interrupt.h file. + */ +static irqreturn_t mxc_rtc_interrupt(int irq, void *dev_id) +{ + struct platform_device *pdev = dev_id; + struct rtc_plat_data *pdata = platform_get_drvdata(pdev); + void __iomem *ioaddr = pdata->ioaddr; + u32 status; + u32 events = 0; + spin_lock(&rtc_lock); + status = readw(ioaddr + RTC_RTCISR) & readw(ioaddr + RTC_RTCIENR); + /* clear interrupt sources */ + writew(status, ioaddr + RTC_RTCISR); + + /* clear alarm interrupt if it has occurred */ + if (status & RTC_ALM_BIT) { + status &= ~RTC_ALM_BIT; + } + + /* update irq data & counter */ + if (status & RTC_ALM_BIT) { + events |= (RTC_AF | RTC_IRQF); + } + if (status & RTC_1HZ_BIT) { + events |= (RTC_UF | RTC_IRQF); + } + if (status & PIT_ALL_ON) { + events |= (RTC_PF | RTC_IRQF); + } + + if ((status & RTC_ALM_BIT) && rtc_valid_tm(&g_rtc_alarm)) { + rtc_update_alarm(&pdev->dev, &g_rtc_alarm); + } + + spin_unlock(&rtc_lock); + rtc_update_irq(pdata->rtc, 1, events); + return IRQ_HANDLED; +} + +/*! + * This function is used to open the RTC driver by registering the RTC + * interrupt service routine. + * + * @return 0 if successful; non-zero otherwise. + */ +static int mxc_rtc_open(struct device *dev) +{ + if (test_and_set_bit(1, &rtc_status)) + return -EBUSY; + return 0; +} + +/*! + * clear all interrupts and release the IRQ + */ +static void mxc_rtc_release(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct rtc_plat_data *pdata = platform_get_drvdata(pdev); + void __iomem *ioaddr = pdata->ioaddr; + + spin_lock_irq(&rtc_lock); + writew(0, ioaddr + RTC_RTCIENR); /* Disable all rtc interrupts */ + writew(0xFFFFFFFF, ioaddr + RTC_RTCISR); /* Clear all interrupt status */ + spin_unlock_irq(&rtc_lock); + rtc_status = 0; +} + +/*! + * This function is used to support some ioctl calls directly. + * Other ioctl calls are supported indirectly through the + * arm/common/rtctime.c file. + * + * @param cmd ioctl command as defined in include/linux/rtc.h + * @param arg value for the ioctl command + * + * @return 0 if successful or negative value otherwise. + */ +static int mxc_rtc_ioctl(struct device *dev, unsigned int cmd, + unsigned long arg) +{ + struct platform_device *pdev = to_platform_device(dev); + struct rtc_plat_data *pdata = platform_get_drvdata(pdev); + void __iomem *ioaddr = pdata->ioaddr; + int i; + switch (cmd) { + case RTC_PIE_OFF: + writew((readw(ioaddr + RTC_RTCIENR) & ~PIT_ALL_ON), + ioaddr + RTC_RTCIENR); + return 0; + case RTC_IRQP_SET: + if (arg < 2 || arg > MAX_PIE_FREQ || (arg % 2) != 0) + return -EINVAL; /* Also make sure a power of 2Hz */ + if ((arg > 64) && (!capable(CAP_SYS_RESOURCE))) + return -EACCES; + rtc_freq = arg; + return 0; + case RTC_IRQP_READ: + return put_user(rtc_freq, (u32 *) arg); + case RTC_PIE_ON: + for (i = 0; i < MAX_PIE_NUM; i++) { + if (PIE_BIT_DEF[i][0] == rtc_freq) { + break; + } + } + if (i == MAX_PIE_NUM) { + return -EACCES; + } + spin_lock_irq(&rtc_lock); + writew((readw(ioaddr + RTC_RTCIENR) | PIE_BIT_DEF[i][1]), + ioaddr + RTC_RTCIENR); + spin_unlock_irq(&rtc_lock); + return 0; + case RTC_AIE_OFF: + spin_lock_irq(&rtc_lock); + writew((readw(ioaddr + RTC_RTCIENR) & ~RTC_ALM_BIT), + ioaddr + RTC_RTCIENR); + spin_unlock_irq(&rtc_lock); + return 0; + + case RTC_AIE_ON: + spin_lock_irq(&rtc_lock); + writew((readw(ioaddr + RTC_RTCIENR) | RTC_ALM_BIT), + ioaddr + RTC_RTCIENR); + spin_unlock_irq(&rtc_lock); + return 0; + + case RTC_UIE_OFF: /* UIE is for the 1Hz interrupt */ + spin_lock_irq(&rtc_lock); + writew((readw(ioaddr + RTC_RTCIENR) & ~RTC_1HZ_BIT), + ioaddr + RTC_RTCIENR); + spin_unlock_irq(&rtc_lock); + return 0; + + case RTC_UIE_ON: + spin_lock_irq(&rtc_lock); + writew((readw(ioaddr + RTC_RTCIENR) | RTC_1HZ_BIT), + ioaddr + RTC_RTCIENR); + spin_unlock_irq(&rtc_lock); + return 0; + } + return -ENOIOCTLCMD; +} + +/*! + * This function reads the current RTC time into tm in Gregorian date. + * + * @param tm contains the RTC time value upon return + * + * @return 0 if successful; non-zero otherwise. + */ +static int mxc_rtc_read_time(struct device *dev, struct rtc_time *tm) +{ + u32 val; + + /* Avoid roll-over from reading the different registers */ + do { + val = get_alarm_or_time(dev, MXC_RTC_TIME); + } while (val != get_alarm_or_time(dev, MXC_RTC_TIME)); + + rtc_time_to_tm(val, tm); + return 0; +} + +/*! + * This function sets the internal RTC time based on tm in Gregorian date. + * + * @param tm the time value to be set in the RTC + * + * @return 0 if successful; non-zero otherwise. + */ +static int mxc_rtc_set_time(struct device *dev, struct rtc_time *tm) +{ + unsigned long time; + int ret; + ret = rtc_tm_to_time(tm, &time); + if (ret != 0) { + return ret; + } + + /* Avoid roll-over from reading the different registers */ + do { + set_alarm_or_time(dev, MXC_RTC_TIME, time); + } while (time != get_alarm_or_time(dev, MXC_RTC_TIME)); + + ret = set_ext_rtc_time(time); + + if (ret != MXC_EXTERNAL_RTC_OK) { + if (ret == MXC_EXTERNAL_RTC_NONE) { + pr_info("No external RTC\n"); + ret = 0; + } else + pr_info("Failed to set external RTC\n"); + } + + return ret; +} + +/*! + * This function reads the current alarm value into the passed in \b alrm + * argument. It updates the \b alrm's pending field value based on the whether + * an alarm interrupt occurs or not. + * + * @param alrm contains the RTC alarm value upon return + * + * @return 0 if successful; non-zero otherwise. + */ +static int mxc_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + struct platform_device *pdev = to_platform_device(dev); + struct rtc_plat_data *pdata = platform_get_drvdata(pdev); + void __iomem *ioaddr = pdata->ioaddr; + + rtc_time_to_tm(get_alarm_or_time(dev, MXC_RTC_ALARM), &alrm->time); + alrm->pending = + ((readw(ioaddr + RTC_RTCISR) & RTC_ALM_BIT) != 0) ? 1 : 0; + + return 0; +} + +/*! + * This function sets the RTC alarm based on passed in alrm. + * + * @param alrm the alarm value to be set in the RTC + * + * @return 0 if successful; non-zero otherwise. + */ +static int mxc_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + struct platform_device *pdev = to_platform_device(dev); + struct rtc_plat_data *pdata = platform_get_drvdata(pdev); + void __iomem *ioaddr = pdata->ioaddr; + int ret; + + spin_lock_irq(&rtc_lock); + if (rtc_valid_tm(&alrm->time)) { + if (alrm->time.tm_sec > 59 || + alrm->time.tm_hour > 23 || alrm->time.tm_min > 59) { + ret = -EINVAL; + goto out; + } + ret = rtc_update_alarm(dev, &alrm->time); + } else { + if ((ret = rtc_valid_tm(&alrm->time))) + goto out; + ret = rtc_update_alarm(dev, &alrm->time); + } + + if (ret == 0) { + memcpy(&g_rtc_alarm, &alrm->time, sizeof(struct rtc_time)); + + if (alrm->enabled) { + writew((readw(ioaddr + RTC_RTCIENR) | RTC_ALM_BIT), + ioaddr + RTC_RTCIENR); + } else { + writew((readw(ioaddr + RTC_RTCIENR) & ~RTC_ALM_BIT), + ioaddr + RTC_RTCIENR); + } + } + out: + spin_unlock_irq(&rtc_lock); + + return ret; +} + +/*! + * This function is used to provide the content for the /proc/driver/rtc + * file. + * + * @param buf the buffer to hold the information that the driver wants to write + * + * @return The number of bytes written into the rtc file. + */ +static int mxc_rtc_proc(struct device *dev, struct seq_file *sq) +{ + struct platform_device *pdev = to_platform_device(dev); + struct rtc_plat_data *pdata = platform_get_drvdata(pdev); + void __iomem *ioaddr = pdata->ioaddr; + char *p = sq->buf; + + p += sprintf(p, "alarm_IRQ\t: %s\n", + (((readw(ioaddr + RTC_RTCIENR)) & RTC_ALM_BIT) != + 0) ? "yes" : "no"); + p += sprintf(p, "update_IRQ\t: %s\n", + (((readw(ioaddr + RTC_RTCIENR)) & RTC_1HZ_BIT) != + 0) ? "yes" : "no"); + p += sprintf(p, "periodic_IRQ\t: %s\n", + (((readw(ioaddr + RTC_RTCIENR)) & PIT_ALL_ON) != + 0) ? "yes" : "no"); + p += sprintf(p, "periodic_freq\t: %d\n", rtc_freq); + + return p - (sq->buf); +} + +/*! + * The RTC driver structure + */ +static struct rtc_class_ops mxc_rtc_ops = { + .open = mxc_rtc_open, + .release = mxc_rtc_release, + .ioctl = mxc_rtc_ioctl, + .read_time = mxc_rtc_read_time, + .set_time = mxc_rtc_set_time, + .read_alarm = mxc_rtc_read_alarm, + .set_alarm = mxc_rtc_set_alarm, + .proc = mxc_rtc_proc, +}; + +/*! MXC RTC Power management control */ + +static struct timespec mxc_rtc_delta; + +static int mxc_rtc_probe(struct platform_device *pdev) +{ + struct clk *clk; + struct timespec tv; + struct resource *res; + struct rtc_time temp_time; + struct rtc_device *rtc; + struct rtc_plat_data *pdata = NULL; + u32 sec, reg; + int ret; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENODEV; + + pdata = kzalloc(sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + pdata->clk = clk_get(&pdev->dev, "rtc_clk"); + clk_enable(pdata->clk); + + pdata->baseaddr = res->start; + pdata->ioaddr = ((void *)(IO_ADDRESS(pdata->baseaddr))); + /* Configure and enable the RTC */ + pdata->irq = platform_get_irq(pdev, 0); + if (pdata->irq >= 0) { + if (request_irq(pdata->irq, mxc_rtc_interrupt, IRQF_SHARED, + pdev->name, pdev) < 0) { + dev_warn(&pdev->dev, "interrupt not available.\n"); + pdata->irq = -1; + } + } + rtc = + rtc_device_register(pdev->name, &pdev->dev, &mxc_rtc_ops, + THIS_MODULE); + if (IS_ERR(rtc)) { + ret = PTR_ERR(rtc); + if (pdata->irq >= 0) + free_irq(pdata->irq, pdev); + kfree(pdata); + return ret; + } + pdata->rtc = rtc; + platform_set_drvdata(pdev, pdata); + ret = get_ext_rtc_time(&sec); + if (ret == MXC_EXTERNAL_RTC_OK) { + rtc_time_to_tm(sec, &temp_time); + mxc_rtc_set_time(&pdev->dev, &temp_time); + + } else if (ret == MXC_EXTERNAL_RTC_NONE) { + pr_info("No external RTC clock\n"); + } else { + pr_info("Reading external RTC failed\n"); + } + tv.tv_nsec = 0; + tv.tv_sec = get_alarm_or_time(&pdev->dev, MXC_RTC_TIME); + clk = clk_get(NULL, "ckil"); + if (clk_get_rate(clk) == 32768) + reg = RTC_INPUT_CLK_32768HZ; + else if (clk_get_rate(clk) == 32000) + reg = RTC_INPUT_CLK_32000HZ; + else if (clk_get_rate(clk) == 38400) + reg = RTC_INPUT_CLK_38400HZ; + else { + printk(KERN_ALERT "rtc clock is not valid"); + return -EINVAL; + } + clk_put(clk); + reg |= RTC_ENABLE_BIT; + writew(reg, (pdata->ioaddr + RTC_RTCCTL)); + if (((readw(pdata->ioaddr + RTC_RTCCTL)) & RTC_ENABLE_BIT) == 0) { + printk(KERN_ALERT "rtc : hardware module can't be enabled!\n"); + return -EPERM; + } + printk("Real TIme clock Driver v%s \n", RTC_VERSION); + return ret; +} + +static int __exit mxc_rtc_remove(struct platform_device *pdev) +{ + struct rtc_plat_data *pdata = platform_get_drvdata(pdev); + rtc_device_unregister(pdata->rtc); + if (pdata->irq >= 0) { + free_irq(pdata->irq, pdev); + } + clk_disable(pdata->clk); + clk_put(pdata->clk); + kfree(pdata); + mxc_rtc_release(NULL); + return 0; +} + +/*! + * This function is called to save the system time delta relative to + * the MXC RTC when enterring a low power state. This time delta is + * then used on resume to adjust the system time to account for time + * loss while suspended. + * + * @param pdev not used + * @param state Power state to enter. + * + * @return The function always returns 0. + */ +static int mxc_rtc_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct timespec tv; + + /* calculate time delta for suspend */ + /* RTC precision is 1 second; adjust delta for avg 1/2 sec err */ + tv.tv_nsec = NSEC_PER_SEC >> 1; + tv.tv_sec = get_alarm_or_time(&pdev->dev, MXC_RTC_TIME); + set_normalized_timespec(&mxc_rtc_delta, + xtime.tv_sec - tv.tv_sec, + xtime.tv_nsec - tv.tv_nsec); + + return 0; +} + +/*! + * This function is called to correct the system time based on the + * current MXC RTC time relative to the time delta saved during + * suspend. + * + * @param pdev not used + * + * @return The function always returns 0. + */ +static int mxc_rtc_resume(struct platform_device *pdev) +{ + struct timespec tv; + struct timespec ts; + + tv.tv_nsec = 0; + tv.tv_sec = get_alarm_or_time(&pdev->dev, MXC_RTC_TIME); + + /* restore wall clock using delta against this RTC; + * adjust again for avg 1/2 second RTC sampling error + */ + set_normalized_timespec(&ts, + tv.tv_sec + mxc_rtc_delta.tv_sec, + (NSEC_PER_SEC >> 1) + mxc_rtc_delta.tv_nsec); + do_settimeofday(&ts); + + return 0; +} + +/*! + * Contains pointers to the power management callback functions. + */ +static struct platform_driver mxc_rtc_driver = { + .driver = { + .name = "mxc_rtc", + }, + .probe = mxc_rtc_probe, + .remove = __exit_p(mxc_rtc_remove), + .suspend = mxc_rtc_suspend, + .resume = mxc_rtc_resume, +}; + +/*! + * This function creates the /proc/driver/rtc file and registers the device RTC + * in the /dev/misc directory. It also reads the RTC value from external source + * and setup the internal RTC properly. + * + * @return -1 if RTC is failed to initialize; 0 is successful. + */ +static int __init mxc_rtc_init(void) +{ + return platform_driver_register(&mxc_rtc_driver); +} + +/*! + * This function removes the /proc/driver/rtc file and un-registers the + * device RTC from the /dev/misc directory. + */ +static void __exit mxc_rtc_exit(void) +{ + platform_driver_unregister(&mxc_rtc_driver); + +} + +module_init(mxc_rtc_init); +module_exit(mxc_rtc_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("Realtime Clock Driver (RTC)"); +MODULE_LICENSE("GPL"); diff --git a/drivers/serial/8250.c b/drivers/serial/8250.c index f94109cbb46e..717860873fa7 100644 --- a/drivers/serial/8250.c +++ b/drivers/serial/8250.c @@ -850,6 +850,10 @@ static void autoconfig_16550a(struct uart_8250_port *up) * Check for presence of the EFR when DLAB is set. * Only ST16C650V1 UARTs pass this test. */ +#ifndef CONFIG_ARCH_MXC + /* This test fails as EFR reads 0, but our uart requires LCR=0xBF + * to access EFR. + */ serial_outp(up, UART_LCR, UART_LCR_DLAB); if (serial_in(up, UART_EFR) == 0) { serial_outp(up, UART_EFR, 0xA8); @@ -863,6 +867,7 @@ static void autoconfig_16550a(struct uart_8250_port *up) serial_outp(up, UART_EFR, 0); return; } +#endif /* * Maybe it requires 0xbf to be written to the LCR. @@ -1374,7 +1379,12 @@ static void transmit_chars(struct uart_8250_port *up) count = up->tx_loadsz; do { +#ifdef CONFIG_ARCH_MXC + /* Seems like back-to-back accesses are a problem */ + serial_out_sync(up, UART_TX, xmit->buf[xmit->tail]); +#else serial_out(up, UART_TX, xmit->buf[xmit->tail]); +#endif xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1); up->port.icount.tx++; if (uart_circ_empty(xmit)) diff --git a/drivers/serial/Kconfig b/drivers/serial/Kconfig index d7e1996e2fec..2f53e7417f45 100644 --- a/drivers/serial/Kconfig +++ b/drivers/serial/Kconfig @@ -307,6 +307,31 @@ config SERIAL_AMBA_PL010_CONSOLE your boot loader (lilo or loadlin) about how to pass options to the kernel at boot time.) +config SERIAL_MXC + tristate "MXC Internal serial port support" + depends on ARCH_MXC + select SERIAL_CORE + help + This selects the Freescale Semiconductor MXC Internal UART driver. + If unsure, say N. + +config SERIAL_MXC_CONSOLE + bool "Support for console on a MXC/MX27/MX21 Internal serial port" + depends on SERIAL_MXC=y + select SERIAL_CORE_CONSOLE + help + Say Y here if you wish to use an MXC Internal UART as the system + console (the system console is the device which receives all kernel + messages and warnings and which allows logins in single user mode). + + Even if you say Y here, the currently visible framebuffer console + (/dev/tty0) will still be used as the system console by default, but + you can alter that using a kernel command line option such as + "console=ttymxc". (Try "man bootparam" or see the documentation of + your boot loader (lilo or loadlin) about how to pass options to the + kernel at boot time.) + + config SERIAL_AMBA_PL011 tristate "ARM AMBA PL011 serial port support" depends on ARM_AMBA diff --git a/drivers/serial/Makefile b/drivers/serial/Makefile index af6377d480d7..122f32d77e71 100644 --- a/drivers/serial/Makefile +++ b/drivers/serial/Makefile @@ -62,5 +62,7 @@ obj-$(CONFIG_SERIAL_SGI_IOC3) += ioc3_serial.o obj-$(CONFIG_SERIAL_ATMEL) += atmel_serial.o obj-$(CONFIG_SERIAL_UARTLITE) += uartlite.o obj-$(CONFIG_SERIAL_NETX) += netx-serial.o +obj-$(CONFIG_SERIAL_MXC) += mxc_uart.o +obj-$(CONFIG_SERIAL_MXC_CONSOLE) += mxc_uart_early.o obj-$(CONFIG_SERIAL_OF_PLATFORM) += of_serial.o obj-$(CONFIG_SERIAL_KS8695) += serial_ks8695.o diff --git a/drivers/serial/mxc_uart.c b/drivers/serial/mxc_uart.c new file mode 100644 index 000000000000..642a5603c5c9 --- /dev/null +++ b/drivers/serial/mxc_uart.c @@ -0,0 +1,1942 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file drivers/serial/mxc_uart.c + * + * @brief Driver for the Freescale Semiconductor MXC serial ports based on + * drivers/char/serial.c, by Linus Torvalds, Theodore Ts'o. + * + * @ingroup UART + */ + +/* + * Include Files + */ +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/tty.h> +#include <linux/string.h> +#include <linux/ioport.h> +#include <linux/init.h> +#include <linux/serial.h> +#include <linux/console.h> +#include <linux/platform_device.h> +#include <linux/sysrq.h> +#include <linux/dma-mapping.h> +#include <linux/clk.h> +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/dma.h> +#include <asm/arch/hardware.h> +#include <asm/arch/mxc_uart.h> + +#if defined(CONFIG_SERIAL_MXC_CONSOLE) && defined(CONFIG_MAGIC_SYSRQ) +#define SUPPORT_SYSRQ +#endif +#define SERIAL_MXC_MAJOR 207 +#define SERIAL_MXC_MINOR 16 +#define MXC_ISR_PASS_LIMIT 256 +#define UART_CREAD_BIT 256 + +/* IRDA minimum pulse duration in micro seconds */ +#define MIN_PULSE_DUR 2 +/* + * Transmit DMA buffer size is set to 1024 bytes, this is limited + * by UART_XMIT_SIZE. + */ +#define TXDMA_BUFF_SIZE UART_XMIT_SIZE +/* + * Receive DMA sub-buffer size + */ +#define RXDMA_BUFF_SIZE 128 + +/*! + * This structure is used to store the information for DMA data transfer. + */ +typedef struct { + /*! + * Holds the read channel number. + */ + int rd_channel; + /*! + * Holds the write channel number. + */ + int wr_channel; + /*! + * UART Transmit Event ID + */ + int tx_event_id; + /*! + * UART Receive Event ID + */ + int rx_event_id; + /*! + * DMA Transmit tasklet + */ + struct tasklet_struct dma_tx_tasklet; + /*! + * Flag indicates if the channel is in use + */ + int dma_txchnl_inuse; +} dma_info; + +/*! + * This is used to indicate if we want echo cancellation in the Irda mode. + */ +static int echo_cancel; +extern void gpio_uart_active(int port, int no_irda); +extern void gpio_uart_inactive(int port, int no_irda); +extern void config_uartdma_event(int port); + +static uart_mxc_port *mxc_ports[MXC_UART_NR]; + +/*! + * This array holds the DMA channel information for each MXC UART + */ +static dma_info dma_list[MXC_UART_NR]; + +/*! + * This function is called by the core driver to stop UART transmission. + * This might be due to the TTY layer indicating that the user wants to stop + * transmission. + * + * @param port the port structure for the UART passed in by the core + * driver + */ +static void mxcuart_stop_tx(struct uart_port *port) +{ + uart_mxc_port *umxc = (uart_mxc_port *) port; + volatile unsigned int cr1; + + cr1 = readl(port->membase + MXC_UARTUCR1); + /* Disable Transmitter rdy interrupt */ + if (umxc->dma_enabled == 1) { + cr1 &= ~MXC_UARTUCR1_TXDMAEN; + } else { + cr1 &= ~MXC_UARTUCR1_TRDYEN; + } + writel(cr1, port->membase + MXC_UARTUCR1); +} + +/*! + * DMA Transmit tasklet method is scheduled on completion of a DMA transmit + * to send out any more data that is available in the UART xmit buffer. + * + * @param arg driver private data + */ +static void dma_tx_do_tasklet(unsigned long arg) +{ + uart_mxc_port *umxc = (uart_mxc_port *) arg; + struct circ_buf *xmit = &umxc->port.info->xmit; + mxc_dma_requestbuf_t writechnl_request; + int tx_num; + unsigned long flags; + + spin_lock_irqsave(&umxc->port.lock, flags); + tx_num = uart_circ_chars_pending(xmit); + if (tx_num > 0) { + if (xmit->tail > xmit->head) { + memcpy(umxc->tx_buf, xmit->buf + xmit->tail, + UART_XMIT_SIZE - xmit->tail); + memcpy(umxc->tx_buf + (UART_XMIT_SIZE - xmit->tail), + xmit->buf, xmit->head); + } else { + memcpy(umxc->tx_buf, xmit->buf + xmit->tail, tx_num); + } + umxc->tx_handle = dma_map_single(umxc->port.dev, umxc->tx_buf, + TXDMA_BUFF_SIZE, + DMA_TO_DEVICE); + + writechnl_request.dst_addr = umxc->port.mapbase + MXC_UARTUTXD; + writechnl_request.src_addr = umxc->tx_handle; + writechnl_request.num_of_bytes = tx_num; + + if ((mxc_dma_config(dma_list[umxc->port.line].wr_channel, + &writechnl_request, 1, + MXC_DMA_MODE_WRITE)) == 0) { + mxc_dma_enable(dma_list[umxc->port.line].wr_channel); + } + } else { + /* No more data available in the xmit queue, clear the flag */ + dma_list[umxc->port.line].dma_txchnl_inuse = 0; + } + spin_unlock_irqrestore(&umxc->port.lock, flags); +} + +/*! + * DMA Write callback is called by the SDMA controller after it has sent out all + * the data from the user buffer. This function updates the xmit buffer pointers. + * + * @param arg driver private data + * @param error any DMA error + * @param count amount of data that was transferred + */ +static void mxcuart_dma_writecallback(void *arg, int error, unsigned int count) +{ + uart_mxc_port *umxc = arg; + struct circ_buf *xmit = &umxc->port.info->xmit; + int tx_num; + + if (error != MXC_DMA_TRANSFER_ERROR) { + tx_num = count; + umxc->port.icount.tx += tx_num; + xmit->tail = (xmit->tail + tx_num) & (UART_XMIT_SIZE - 1); + } + + dma_unmap_single(umxc->port.dev, umxc->tx_handle, TXDMA_BUFF_SIZE, + DMA_TO_DEVICE); + tx_num = uart_circ_chars_pending(xmit); + /* Schedule a tasklet to send out the pending characters */ + if (tx_num > 0) { + tasklet_schedule(&dma_list[umxc->port.line].dma_tx_tasklet); + } else { + dma_list[umxc->port.line].dma_txchnl_inuse = 0; + } + if (tx_num < WAKEUP_CHARS) { + uart_write_wakeup(&umxc->port); + } +} + +/*! + * This function is called by the core driver to start transmitting characters. + * This function enables the transmit interrupts. + * + * @param port the port structure for the UART passed in by the core + * driver + */ +static void mxcuart_start_tx(struct uart_port *port) +{ + uart_mxc_port *umxc = (uart_mxc_port *) port; + struct circ_buf *xmit = &umxc->port.info->xmit; + volatile unsigned int cr1; + mxc_dma_requestbuf_t writechnl_request; + int tx_num; + + cr1 = readl(port->membase + MXC_UARTUCR1); + /* Enable Transmitter rdy interrupt */ + if (umxc->dma_enabled == 1) { + /* + * If the channel is in use then return immediately and use + * the dma_tx tasklet to transfer queued data when current DMA + * transfer is complete + */ + if (dma_list[umxc->port.line].dma_txchnl_inuse == 1) { + return; + } + tx_num = uart_circ_chars_pending(xmit); + if (tx_num > 0) { + dma_list[umxc->port.line].dma_txchnl_inuse = 1; + if (xmit->tail > xmit->head) { + memcpy(umxc->tx_buf, xmit->buf + xmit->tail, + UART_XMIT_SIZE - xmit->tail); + memcpy(umxc->tx_buf + + (UART_XMIT_SIZE - xmit->tail), xmit->buf, + xmit->head); + } else { + memcpy(umxc->tx_buf, xmit->buf + xmit->tail, + tx_num); + } + umxc->tx_handle = + dma_map_single(umxc->port.dev, umxc->tx_buf, + TXDMA_BUFF_SIZE, DMA_TO_DEVICE); + + writechnl_request.dst_addr = + umxc->port.mapbase + MXC_UARTUTXD; + writechnl_request.src_addr = umxc->tx_handle; + writechnl_request.num_of_bytes = tx_num; + if ((mxc_dma_config + (dma_list[umxc->port.line].wr_channel, + &writechnl_request, 1, + MXC_DMA_MODE_WRITE)) == 0) { + mxc_dma_enable(dma_list[umxc->port.line]. + wr_channel); + } + cr1 |= MXC_UARTUCR1_TXDMAEN; + } + } else { + cr1 |= MXC_UARTUCR1_TRDYEN; + } + writel(cr1, port->membase + MXC_UARTUCR1); +} + +/*! + * This function is called by the core driver to stop receiving characters; the + * port is in the process of being closed. + * + * @param port the port structure for the UART passed in by the core driver + */ +static void mxcuart_stop_rx(struct uart_port *port) +{ + uart_mxc_port *umxc = (uart_mxc_port *) port; + volatile unsigned int cr1; + + cr1 = readl(port->membase + MXC_UARTUCR1); + if (umxc->dma_enabled == 1) { + cr1 &= ~MXC_UARTUCR1_RXDMAEN; + } else { + cr1 &= ~MXC_UARTUCR1_RRDYEN; + } + writel(cr1, port->membase + MXC_UARTUCR1); +} + +/*! + * This function is called by the core driver to enable the modem status + * interrupts. If the port is configured to be in DTE mode then it enables the + * DCDDELT and RIDELT interrupts in addition to the DTRDEN interrupt. The RTSDEN + * interrupt is enabled only for interrupt-driven hardware flow control. + * + * @param port the port structure for the UART passed in by the core driver + */ +static void mxcuart_enable_ms(struct uart_port *port) +{ + uart_mxc_port *umxc = (uart_mxc_port *) port; + volatile unsigned int cr1, cr3; + + /* + * RTS interrupt is enabled only if we are using interrupt-driven + * software controlled hardware flow control + */ + if (umxc->hardware_flow == 0) { + cr1 = readl(umxc->port.membase + MXC_UARTUCR1); + cr1 |= MXC_UARTUCR1_RTSDEN; + writel(cr1, umxc->port.membase + MXC_UARTUCR1); + } + cr3 = readl(umxc->port.membase + MXC_UARTUCR3); + cr3 |= MXC_UARTUCR3_DTRDEN; + if (umxc->mode == MODE_DTE) { + cr3 |= MXC_UARTUCR3_DCD | MXC_UARTUCR3_RI; + } + writel(cr3, umxc->port.membase + MXC_UARTUCR3); +} + +/*! + * This function is called from the interrupt service routine if the status bit + * indicates that the receive fifo data level is above the set threshold. The + * function reads the character and queues them into the TTY layers read + * buffer. The function also looks for break characters, parity and framing + * errors in the received character and sets the appropriate flag in the TTY + * receive buffer. + * + * @param umxc the MXC UART port structure, this includes the \b uart_port + * structure and other members that are specific to MXC UARTs + */ +static void mxcuart_rx_chars(uart_mxc_port * umxc) +{ + struct tty_struct *tty = umxc->port.info->tty; + volatile unsigned int ch, sr2; + unsigned int status, flag, max_count = 256; + + sr2 = readl(umxc->port.membase + MXC_UARTUSR2); + while (((sr2 & MXC_UARTUSR2_RDR) == 1) && (max_count-- > 0)) { + ch = readl(umxc->port.membase + MXC_UARTURXD); + + flag = TTY_NORMAL; + status = ch | UART_CREAD_BIT; + ch &= 0xFF; /* Clear the upper bits */ + umxc->port.icount.rx++; + + /* + * Check to see if there is an error in the received + * character. Perform the appropriate actions based on the + * error bit that was set. + */ + if (status & MXC_UARTURXD_ERR) { + if (status & MXC_UARTURXD_BRK) { + /* + * Clear the frame and parity error bits + * as these always get set on receiving a + * break character + */ + status &= ~(MXC_UARTURXD_FRMERR | + MXC_UARTURXD_PRERR); + umxc->port.icount.brk++; + if (uart_handle_break(&umxc->port)) { + goto ignore_char; + } + } else if (status & MXC_UARTURXD_FRMERR) { + umxc->port.icount.frame++; + } else if (status & MXC_UARTURXD_PRERR) { + umxc->port.icount.parity++; + } + if (status & MXC_UARTURXD_OVRRUN) { + umxc->port.icount.overrun++; + } + + status &= umxc->port.read_status_mask; + + if (status & MXC_UARTURXD_BRK) { + flag = TTY_BREAK; + } else if (status & MXC_UARTURXD_FRMERR) { + flag = TTY_FRAME; + } else if (status & MXC_UARTURXD_PRERR) { + flag = TTY_PARITY; + } + } + + if (uart_handle_sysrq_char(&umxc->port, ch)) { + goto ignore_char; + } + + uart_insert_char(&umxc->port, status, MXC_UARTURXD_OVRRUN, ch, + flag); + ignore_char: + sr2 = readl(umxc->port.membase + MXC_UARTUSR2); + } + tty_flip_buffer_push(tty); +} + +/*! + * This function is called from the interrupt service routine if the status bit + * indicates that the transmit fifo is emptied below its set threshold and + * requires data. The function pulls characters from the TTY layers write + * buffer and writes it out to the UART transmit fifo. + * + * @param umxc the MXC UART port structure, this includes the \b uart_port + * structure and other members that are specific to MXC UARTs + */ +static void mxcuart_tx_chars(uart_mxc_port * umxc) +{ + struct circ_buf *xmit = &umxc->port.info->xmit; + int count; + + /* + * Transmit the XON/XOFF character if required + */ + if (umxc->port.x_char) { + writel(umxc->port.x_char, umxc->port.membase + MXC_UARTUTXD); + umxc->port.icount.tx++; + umxc->port.x_char = 0; + return; + } + + /* + * Check to see if there is any data to be sent and that the + * port has not been currently stopped by anything. + */ + if (uart_circ_empty(xmit) || uart_tx_stopped(&umxc->port)) { + mxcuart_stop_tx(&umxc->port); + return; + } + + count = umxc->port.fifosize - umxc->tx_threshold; + do { + writel(xmit->buf[xmit->tail], + umxc->port.membase + MXC_UARTUTXD); + xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1); + umxc->port.icount.tx++; + if (uart_circ_empty(xmit)) { + break; + } + } while (--count > 0); + + /* + * Check to see if we have flushed enough characters to ask for more + * to be sent to us, if so, we notify the user space that we can + * accept more data + */ + if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) { + uart_write_wakeup(&umxc->port); + } + + if (uart_circ_empty(xmit)) { + mxcuart_stop_tx(&umxc->port); + } +} + +/*! + * This function is called from the interrupt service routine if there is a + * change in the modem signals. This function handles these signal changes and + * also clears the appropriate status register bits. + * + * @param umxc the MXC UART port structure, this includes the \b uart_port + * structure and other members that are specific to MXC UARTs + * @param sr1 contents of status register 1 + * @param sr2 contents of status register 2 + */ +static void mxcuart_modem_status(uart_mxc_port * umxc, unsigned int sr1, + unsigned int sr2) +{ + if (umxc->mode == MODE_DTE) { + if (sr2 & MXC_UARTUSR2_DCDDELT) { + uart_handle_dcd_change(&umxc->port, + !(sr2 & MXC_UARTUSR2_DCDIN)); + } + if (sr2 & MXC_UARTUSR2_RIDELT) { + umxc->port.icount.rng++; + } + } + if (sr1 & MXC_UARTUSR1_DTRD) { + umxc->port.icount.dsr++; + } + if ((umxc->hardware_flow == 0) && (sr1 & MXC_UARTUSR1_RTSD)) { + uart_handle_cts_change(&umxc->port, sr1 & MXC_UARTUSR1_RTSS); + } + + wake_up_interruptible(&umxc->port.info->delta_msr_wait); +} + +/*! + * Interrupt service routine registered to handle the muxed ANDed interrupts. + * This routine is registered only in the case where the UART interrupts are + * muxed. + * + * @param irq the interrupt number + * @param dev_id driver private data + * + * @return The function returns \b IRQ_RETVAL(1) if interrupt was handled, + * returns \b IRQ_RETVAL(0) if the interrupt was not handled. + * \b IRQ_RETVAL is defined in \b include/linux/interrupt.h. + */ +static irqreturn_t mxcuart_int(int irq, void *dev_id) +{ + uart_mxc_port *umxc = dev_id; + volatile unsigned int sr1, sr2, cr1, cr; + unsigned int pass_counter = MXC_ISR_PASS_LIMIT; + unsigned int term_cond = 0; + int handled = 0; + + sr1 = readl(umxc->port.membase + MXC_UARTUSR1); + sr2 = readl(umxc->port.membase + MXC_UARTUSR2); + cr1 = readl(umxc->port.membase + MXC_UARTUCR1); + + do { + /* Clear the bits that triggered the interrupt */ + writel(sr1, umxc->port.membase + MXC_UARTUSR1); + writel(sr2, umxc->port.membase + MXC_UARTUSR2); + /* + * Read if there is data available + */ + if (sr2 & MXC_UARTUSR2_RDR) { + mxcuart_rx_chars(umxc); + } + + if ((sr1 & (MXC_UARTUSR1_RTSD | MXC_UARTUSR1_DTRD)) || + (sr2 & (MXC_UARTUSR2_DCDDELT | MXC_UARTUSR2_RIDELT))) { + mxcuart_modem_status(umxc, sr1, sr2); + } + + /* + * Send data if there is data to be sent + */ + if ((cr1 & MXC_UARTUCR1_TRDYEN) && (sr1 & MXC_UARTUSR1_TRDY)) { + /* Echo cancellation for IRDA Transmit chars */ + if (umxc->ir_mode == IRDA && echo_cancel) { + /* Disable the receiver */ + cr = readl(umxc->port.membase + MXC_UARTUCR2); + cr &= ~MXC_UARTUCR2_RXEN; + writel(cr, umxc->port.membase + MXC_UARTUCR2); + /* Enable Transmit complete intr to reenable RX */ + cr = readl(umxc->port.membase + MXC_UARTUCR4); + cr |= MXC_UARTUCR4_TCEN; + writel(cr, umxc->port.membase + MXC_UARTUCR4); + } + mxcuart_tx_chars(umxc); + } + + if (pass_counter-- == 0) { + break; + } + + sr1 = readl(umxc->port.membase + MXC_UARTUSR1); + sr2 = readl(umxc->port.membase + MXC_UARTUSR2); + + /* Is the transmit complete to reenable the receiver? */ + if (umxc->ir_mode == IRDA && echo_cancel) { + if (sr2 & MXC_UARTUSR2_TXDC) { + cr = readl(umxc->port.membase + MXC_UARTUCR2); + cr |= MXC_UARTUCR2_RXEN; + writel(cr, umxc->port.membase + MXC_UARTUCR2); + /* Disable the Transmit complete interrupt bit */ + cr = readl(umxc->port.membase + MXC_UARTUCR4); + cr &= ~MXC_UARTUCR4_TCEN; + writel(cr, umxc->port.membase + MXC_UARTUCR4); + } + } + + /* + * If there is no data to send or receive and if there is no + * change in the modem status signals then quit the routine + */ + term_cond = sr1 & (MXC_UARTUSR1_RTSD | MXC_UARTUSR1_DTRD); + term_cond |= sr2 & (MXC_UARTUSR2_RDR | MXC_UARTUSR2_DCDDELT); + term_cond |= !(sr2 & MXC_UARTUSR2_TXFE); + } while (term_cond > 0); + + handled = 1; + return IRQ_RETVAL(handled); +} + +/*! + * Interrupt service routine registered to handle the transmit interrupts. This + * routine is registered only in the case where the UART interrupts are not + * muxed. + * + * @param irq the interrupt number + * @param dev_id driver private data + * + * @return The function returns \b IRQ_RETVAL(1) if interrupt was handled, + * returns \b IRQ_RETVAL(0) if the interrupt was not handled. + * \b IRQ_RETVAL is defined in include/linux/interrupt.h. + */ +static irqreturn_t mxcuart_tx_int(int irq, void *dev_id) +{ + uart_mxc_port *umxc = dev_id; + int handled = 0; + volatile unsigned int sr2, cr; + + /* Echo cancellation for IRDA Transmit chars */ + if (umxc->ir_mode == IRDA && echo_cancel) { + /* Disable the receiver */ + cr = readl(umxc->port.membase + MXC_UARTUCR2); + cr &= ~MXC_UARTUCR2_RXEN; + writel(cr, umxc->port.membase + MXC_UARTUCR2); + /* Enable Transmit complete to reenable receiver */ + cr = readl(umxc->port.membase + MXC_UARTUCR4); + cr |= MXC_UARTUCR4_TCEN; + writel(cr, umxc->port.membase + MXC_UARTUCR4); + } + + mxcuart_tx_chars(umxc); + + /* Is the transmit complete to reenable the receiver? */ + if (umxc->ir_mode == IRDA && echo_cancel) { + sr2 = readl(umxc->port.membase + MXC_UARTUSR2); + if (sr2 & MXC_UARTUSR2_TXDC) { + cr = readl(umxc->port.membase + MXC_UARTUCR2); + cr |= MXC_UARTUCR2_RXEN; + writel(cr, umxc->port.membase + MXC_UARTUCR2); + /* Disable the Transmit complete interrupt bit */ + cr = readl(umxc->port.membase + MXC_UARTUCR4); + cr &= ~MXC_UARTUCR4_TCEN; + writel(cr, umxc->port.membase + MXC_UARTUCR4); + } + } + + handled = 1; + + return IRQ_RETVAL(handled); +} + +/*! + * Interrupt service routine registered to handle the receive interrupts. This + * routine is registered only in the case where the UART interrupts are not + * muxed. + * + * @param irq the interrupt number + * @param dev_id driver private data + * + * @return The function returns \b IRQ_RETVAL(1) if interrupt was handled, + * returns \b IRQ_RETVAL(0) if the interrupt was not handled. + * \b IRQ_RETVAL is defined in include/linux/interrupt.h. + */ +static irqreturn_t mxcuart_rx_int(int irq, void *dev_id) +{ + uart_mxc_port *umxc = dev_id; + int handled = 0; + + /* Clear the aging timer bit */ + writel(MXC_UARTUSR1_AGTIM, umxc->port.membase + MXC_UARTUSR1); + mxcuart_rx_chars(umxc); + handled = 1; + + return IRQ_RETVAL(handled); +} + +/*! + * Interrupt service routine registered to handle the master interrupts. This + * routine is registered only in the case where the UART interrupts are not + * muxed. + * + * @param irq the interrupt number + * @param dev_id driver private data + * + * @return The function returns \b IRQ_RETVAL(1) if interrupt was handled, + * returns \b IRQ_RETVAL(0) if the interrupt was not handled. + * \b IRQ_RETVAL is defined in include/linux/interrupt.h. + */ +static irqreturn_t mxcuart_mint_int(int irq, void *dev_id) +{ + uart_mxc_port *umxc = dev_id; + int handled = 0; + volatile unsigned int sr1, sr2; + + sr1 = readl(umxc->port.membase + MXC_UARTUSR1); + sr2 = readl(umxc->port.membase + MXC_UARTUSR2); + /* Clear the modem status interrupt bits */ + writel(MXC_UARTUSR1_RTSD | MXC_UARTUSR1_DTRD, + umxc->port.membase + MXC_UARTUSR1); + writel(MXC_UARTUSR2_DCDDELT | MXC_UARTUSR2_RIDELT, + umxc->port.membase + MXC_UARTUSR2); + mxcuart_modem_status(umxc, sr1, sr2); + handled = 1; + + return IRQ_RETVAL(handled); +} + +/*! + * This function is called by the core driver to test whether the transmitter + * fifo and shift register for the UART port are empty. + * + * @param port the port structure for the UART passed in by the core driver + * + * @return The function returns TIOCSER_TEMT if it is empty, else returns 0. + */ +static unsigned int mxcuart_tx_empty(struct uart_port *port) +{ + volatile unsigned int sr2; + unsigned long flags; + + spin_lock_irqsave(&port->lock, flags); + sr2 = readl(port->membase + MXC_UARTUSR2); + spin_unlock_irqrestore(&port->lock, flags); + + return sr2 & MXC_UARTUSR2_TXDC ? TIOCSER_TEMT : 0; +} + +/*! + * This function is called by the core driver to get the current status of the + * modem input signals. The state of the output signals is not collected. + * + * @param port the port structure for the UART passed in by the core driver + * + * @return The function returns an integer that contains the ORed value of the + * status of all the modem input signals or error. + */ +static unsigned int mxcuart_get_mctrl(struct uart_port *port) +{ + uart_mxc_port *umxc = (uart_mxc_port *) port; + unsigned int result = 0; + volatile unsigned int sr1, sr2; + + sr1 = readl(umxc->port.membase + MXC_UARTUSR1); + sr2 = readl(umxc->port.membase + MXC_UARTUSR2); + + if (sr1 & MXC_UARTUSR1_RTSS) { + result |= TIOCM_CTS; + } + if (umxc->mode == MODE_DTE) { + if (!(sr2 & MXC_UARTUSR2_DCDIN)) { + result |= TIOCM_CAR; + } + if (!(sr2 & MXC_UARTUSR2_RIIN)) { + result |= TIOCM_RI; + } + } + return result; +} + +/*! + * This function is called by the core driver to set the state of the modem + * control lines. + * + * @param port the port structure for the UART passed in by the core driver + * @param mctrl the state that the modem control lines should be changed to + */ +static void mxcuart_set_mctrl(struct uart_port *port, unsigned int mctrl) +{ + uart_mxc_port *umxc = (uart_mxc_port *) port; + volatile unsigned int cr2 = 0, cr3 = 0, uts = 0; + + cr2 = readl(port->membase + MXC_UARTUCR2); + cr3 = readl(port->membase + MXC_UARTUCR3); + uts = readl(port->membase + MXC_UARTUTS); + + if (mctrl & TIOCM_RTS) { + /* + * Return to hardware-driven hardware flow control if the + * option is enabled + */ + if (umxc->hardware_flow == 1) { + cr2 |= MXC_UARTUCR2_CTSC; + } else { + cr2 |= MXC_UARTUCR2_CTS; + cr2 &= ~MXC_UARTUCR2_CTSC; + } + } else { + cr2 &= ~(MXC_UARTUCR2_CTS | MXC_UARTUCR2_CTSC); + } + writel(cr2, port->membase + MXC_UARTUCR2); + + if (mctrl & TIOCM_DTR) { + cr3 |= MXC_UARTUCR3_DSR; + } else { + cr3 &= ~MXC_UARTUCR3_DSR; + } + writel(cr3, port->membase + MXC_UARTUCR3); + + if (mctrl & TIOCM_LOOP) { + if (umxc->ir_mode == IRDA) { + echo_cancel = 0; + } else { + uts |= MXC_UARTUTS_LOOP; + } + } else { + if (umxc->ir_mode == IRDA) { + echo_cancel = 1; + } else { + uts &= ~MXC_UARTUTS_LOOP; + } + } + writel(uts, port->membase + MXC_UARTUTS); +} + +/*! + * This function is called by the core driver to control the transmission of + * the break signal. If break_state is non-zero, the break signal is + * transmitted, the signal is terminated when another call is made with + * break_state set to 0. + * + * @param port the port structure for the UART passed in by the core + * driver + * @param break_state the requested state of the break signal + */ +static void mxcuart_break_ctl(struct uart_port *port, int break_state) +{ + volatile unsigned int cr1; + unsigned long flags; + + spin_lock_irqsave(&port->lock, flags); + cr1 = readl(port->membase + MXC_UARTUCR1); + if (break_state == -1) { + cr1 |= MXC_UARTUCR1_SNDBRK; + } else { + cr1 &= ~MXC_UARTUCR1_SNDBRK; + } + writel(cr1, port->membase + MXC_UARTUCR1); + spin_unlock_irqrestore(&port->lock, flags); +} + +/*! + * The read DMA callback, this method is called when the DMA buffer has received its + * data. This functions copies the data to the tty buffer and updates the tty buffer + * pointers. It also queues the DMA buffer back to the DMA system. + * + * @param arg driver private data + * @param error any DMA error + * @param cnt amount of data that was transferred + */ +static void mxcuart_dmaread_callback(void *arg, int error, unsigned int cnt) +{ + uart_mxc_port *umxc = arg; + struct tty_struct *tty = umxc->port.info->tty; + int buff_id, flip_cnt, num_bufs; + mxc_dma_requestbuf_t readchnl_request; + mxc_uart_rxdmamap *rx_buf_elem = NULL; + unsigned int sr1, sr2; + char flag; + + num_bufs = umxc->dma_rxbuf_size / RXDMA_BUFF_SIZE; + /* Clear the aging timer bit */ + writel(MXC_UARTUSR1_AGTIM, umxc->port.membase + MXC_UARTUSR1); + + buff_id = umxc->dma_rxbuf_id; + flag = TTY_NORMAL; + + if ((umxc->dma_rxbuf_id += 1) >= num_bufs) { + umxc->dma_rxbuf_id = 0; + } + + rx_buf_elem = (mxc_uart_rxdmamap *) (umxc->rx_dmamap + buff_id); + + if (error == MXC_DMA_TRANSFER_ERROR) { + + sr1 = __raw_readl(umxc->port.membase + MXC_UARTUSR1); + sr2 = __raw_readl(umxc->port.membase + MXC_UARTUSR2); + + if (sr2 & MXC_UARTUSR2_BRCD) { + umxc->port.icount.brk++; + if (uart_handle_break(&umxc->port)) { + goto drop_data; + } + } else if (sr1 & MXC_UARTUSR1_PARITYERR) { + umxc->port.icount.parity++; + } else if (sr1 & MXC_UARTUSR1_FRAMERR) { + umxc->port.icount.frame++; + } else if (sr2 & MXC_UARTUSR2_ORE) { + umxc->port.icount.overrun++; + + } + + if (umxc->port.read_status_mask & MXC_UARTURXD_BRK) { + if (sr2 & MXC_UARTUSR2_BRCD) + flag = TTY_BREAK; + } else if (umxc->port.read_status_mask & MXC_UARTURXD_PRERR) { + if (sr1 & MXC_UARTUSR1_PARITYERR) + flag = TTY_PARITY; + } else if (umxc->port.read_status_mask & MXC_UARTURXD_FRMERR) { + if (sr1 & MXC_UARTUSR1_FRAMERR) + flag = TTY_FRAME; + } else if (umxc->port.read_status_mask & MXC_UARTURXD_OVRRUN) { + if (sr2 & MXC_UARTUSR2_ORE) + flag = TTY_OVERRUN; + } +/* By default clearing all error bits in status reg */ + __raw_writel((MXC_UARTUSR2_BRCD | MXC_UARTUSR2_ORE), + umxc->port.membase + MXC_UARTUSR2); + __raw_writel((MXC_UARTUSR1_PARITYERR | MXC_UARTUSR1_FRAMERR), + umxc->port.membase + MXC_UARTUSR1); + } + + flip_cnt = tty_buffer_request_room(tty, cnt); + + /* Check for space availability in the TTY Flip buffer */ + if (flip_cnt <= 0) { + goto drop_data; + } + umxc->port.icount.rx += flip_cnt; + + tty_insert_flip_string(tty, rx_buf_elem->rx_buf, flip_cnt); + + if (flag != TTY_NORMAL) { + tty_insert_flip_char(tty, 0, flag); + } + + tty_flip_buffer_push(tty); + umxc->port.info->tty->real_raw = 1; + + drop_data: + readchnl_request.src_addr = umxc->port.mapbase; + readchnl_request.dst_addr = rx_buf_elem->rx_handle; + readchnl_request.num_of_bytes = RXDMA_BUFF_SIZE; + mxc_dma_config(dma_list[umxc->port.line].rd_channel, &readchnl_request, + 1, MXC_DMA_MODE_READ); + mxc_dma_enable(dma_list[umxc->port.line].rd_channel); +} + +/*! + * Allocates DMA read and write channels, creates DMA read and write buffers and + * sets the channel specific parameters. + * + * @param d_info the structure that holds all the DMA information for a + * particular MXC UART + * @param umxc the MXC UART port structure, this includes the \b uart_port + * structure and other members that are specific to MXC UARTs + * + * @return The function returns 0 on success and a non-zero value on failure. + */ +static int mxcuart_initdma(dma_info * d_info, uart_mxc_port * umxc) +{ + int ret = 0, rxbufs, i, j; + mxc_dma_requestbuf_t *readchnl_reqelem; + mxc_uart_rxdmamap *rx_buf_elem; + + /* Request for the read and write channels */ + d_info->rd_channel = mxc_dma_request(umxc->dma_rx_id, "MXC UART Read"); + if (d_info->rd_channel < 0) { + printk(KERN_ERR "MXC UART: Cannot allocate DMA read channel\n"); + return -1; + } else { + d_info->wr_channel = + mxc_dma_request(umxc->dma_tx_id, "MXC UART Write"); + if (d_info->wr_channel < 0) { + mxc_dma_free(d_info->rd_channel); + printk(KERN_ERR + "MXC UART: Cannot allocate DMA write channel\n"); + return -1; + } + } + + /* Allocate the DMA Transmit Buffer */ + if ((umxc->tx_buf = kmalloc(TXDMA_BUFF_SIZE, GFP_KERNEL)) == NULL) { + ret = -1; + goto err_dma_tx_buff; + } + rxbufs = umxc->dma_rxbuf_size / RXDMA_BUFF_SIZE; + /* Allocate the DMA Virtual Receive Buffer */ + if ((umxc->rx_dmamap = kmalloc(rxbufs * sizeof(mxc_uart_rxdmamap), + GFP_KERNEL)) == NULL) { + ret = -1; + goto err_dma_rx_buff; + } + + /* Allocate the DMA Receive Request structures */ + if ((readchnl_reqelem = + kmalloc(rxbufs * sizeof(mxc_dma_requestbuf_t), + GFP_KERNEL)) == NULL) { + ret = -1; + goto err_request; + } + + for (i = 0; i < rxbufs; i++) { + rx_buf_elem = (mxc_uart_rxdmamap *) (umxc->rx_dmamap + i); + rx_buf_elem->rx_buf = + dma_alloc_coherent(NULL, RXDMA_BUFF_SIZE, + &rx_buf_elem->rx_handle, GFP_DMA); + if (rx_buf_elem->rx_buf == NULL) { + for (j = 0; j < i; j++) { + rx_buf_elem = + (mxc_uart_rxdmamap *) (umxc->rx_dmamap + j); + dma_free_coherent(NULL, RXDMA_BUFF_SIZE, + rx_buf_elem->rx_buf, + rx_buf_elem->rx_handle); + } + ret = -1; + goto cleanup; + } + } + + umxc->dma_rxbuf_id = 0; + /* Setup the DMA read request structures */ + for (i = 0; i < rxbufs; i++) { + rx_buf_elem = (mxc_uart_rxdmamap *) (umxc->rx_dmamap + i); + (readchnl_reqelem + i)->src_addr = umxc->port.mapbase; + (readchnl_reqelem + i)->dst_addr = rx_buf_elem->rx_handle; + (readchnl_reqelem + i)->num_of_bytes = RXDMA_BUFF_SIZE; + } + mxc_dma_config(d_info->rd_channel, readchnl_reqelem, rxbufs, + MXC_DMA_MODE_READ); + mxc_dma_callback_set(d_info->rd_channel, mxcuart_dmaread_callback, + umxc); + mxc_dma_callback_set(d_info->wr_channel, mxcuart_dma_writecallback, + umxc); + + /* Start the read channel */ + mxc_dma_enable(d_info->rd_channel); + kfree(readchnl_reqelem); + tasklet_init(&d_info->dma_tx_tasklet, dma_tx_do_tasklet, + (unsigned long)umxc); + d_info->dma_txchnl_inuse = 0; + return ret; + cleanup: + kfree(readchnl_reqelem); + err_request: + kfree(umxc->rx_dmamap); + err_dma_rx_buff: + kfree(umxc->tx_buf); + err_dma_tx_buff: + mxc_dma_free(d_info->rd_channel); + mxc_dma_free(d_info->wr_channel); + + return ret; +} + +/*! + * Stops DMA and frees the DMA resources + * + * @param d_info the structure that holds all the DMA information for a + * particular MXC UART + * @param umxc the MXC UART port structure, this includes the \b uart_port + * structure and other members that are specific to MXC UARTs + */ +static void mxcuart_freedma(dma_info * d_info, uart_mxc_port * umxc) +{ + int i, rxbufs; + mxc_uart_rxdmamap *rx_buf_elem; + + rxbufs = umxc->dma_rxbuf_size / RXDMA_BUFF_SIZE; + + for (i = 0; i < rxbufs; i++) { + rx_buf_elem = (mxc_uart_rxdmamap *) (umxc->rx_dmamap + i); + dma_free_coherent(NULL, RXDMA_BUFF_SIZE, + rx_buf_elem->rx_buf, rx_buf_elem->rx_handle); + } + kfree(umxc->rx_dmamap); + kfree(umxc->tx_buf); + mxc_dma_free(d_info->rd_channel); + mxc_dma_free(d_info->wr_channel); +} + +/*! + * This function is called to free the interrupts. + * + * @param umxc the MXC UART port structure, this includes the \b uart_port + * structure and other members that are specific to MXC UARTs + */ +static void mxcuart_free_interrupts(uart_mxc_port * umxc) +{ + free_irq(umxc->port.irq, umxc); + if (umxc->ints_muxed == 0) { + free_irq(umxc->irqs[0], umxc); + free_irq(umxc->irqs[1], umxc); + } +} + +/*! + * Calculate and set the UART port clock value + * + * @param umxc the MXC UART port structure, this includes the \b uart_port + * structure and other members that are specific to MXC UARTs + * @param per_clk peripheral clock coming into the MXC UART module + * @param req_baud current baudrate requested + * @param div returns the reference frequency divider value + */ +static void mxcuart_set_ref_freq(uart_mxc_port * umxc, unsigned long per_clk, + unsigned int req_baud, int *div) +{ + int d = 1; + + d = per_clk / ((req_baud * 16) + 1000); + if (d > 6) { + d = 6; + } + + umxc->port.uartclk = per_clk / d; + /* + * Set the ONEMS register that is used by IR special case bit and + * the Escape character detect logic + */ + writel(umxc->port.uartclk / 1000, umxc->port.membase + MXC_UARTONEMS); + *div = d; +} + +/*! + * This function is called by the core driver to initialize the low-level + * driver. The function grabs the interrupt resources and registers its + * interrupt service routines. It then initializes the IOMUX registers to + * configure the pins for UART signals and finally initializes the various + * UART registers and enables the port for reception. + * + * @param port the port structure for the UART passed in by the core driver + * + * @return The function returns 0 on success and a non-zero value on failure. + */ +static int mxcuart_startup(struct uart_port *port) +{ + uart_mxc_port *umxc = (uart_mxc_port *) port; + int retval; + volatile unsigned int cr, cr1 = 0, cr2 = 0, ufcr = 0; + + /* + * Some UARTs need separate registrations for the interrupts as + * they do not take the muxed interrupt output to the ARM core + */ + if (umxc->ints_muxed == 1) { + retval = request_irq(umxc->port.irq, mxcuart_int, 0, + "mxcintuart", umxc); + if (retval != 0) { + return retval; + } + } else { + retval = request_irq(umxc->port.irq, mxcuart_tx_int, + 0, "mxcintuart", umxc); + if (retval != 0) { + return retval; + } else { + retval = request_irq(umxc->irqs[0], mxcuart_rx_int, + 0, "mxcintuart", umxc); + if (retval != 0) { + free_irq(umxc->port.irq, umxc); + return retval; + } else { + retval = + request_irq(umxc->irqs[1], mxcuart_mint_int, + 0, "mxcintuart", umxc); + if (retval != 0) { + free_irq(umxc->port.irq, umxc); + free_irq(umxc->irqs[0], umxc); + return retval; + } + } + } + } + + /* Initialize the DMA if we need SDMA data transfer */ + if (umxc->dma_enabled == 1) { + retval = mxcuart_initdma(dma_list + umxc->port.line, umxc); + if (retval != 0) { + printk + (KERN_ERR + "MXC UART: Failed to initialize DMA for UART %d\n", + umxc->port.line); + mxcuart_free_interrupts(umxc); + return retval; + } + /* Configure the GPR register to receive SDMA events */ + config_uartdma_event(umxc->port.line); + } + + /* + * Clear Status Registers 1 and 2 + */ + writel(0xFFFF, umxc->port.membase + MXC_UARTUSR1); + writel(0xFFFF, umxc->port.membase + MXC_UARTUSR2); + + /* Configure the IOMUX for the UART */ + gpio_uart_active(umxc->port.line, umxc->ir_mode); + + /* + * Set the transceiver invert bits if required + */ + if (umxc->ir_mode == IRDA) { + echo_cancel = 1; + writel(umxc->ir_rx_inv | MXC_UARTUCR4_IRSC, umxc->port.membase + + MXC_UARTUCR4); + writel(umxc->rxd_mux | umxc->ir_tx_inv, + umxc->port.membase + MXC_UARTUCR3); + } else { + writel(umxc->rxd_mux, umxc->port.membase + MXC_UARTUCR3); + } + + /* + * Initialize UCR1,2 and UFCR registers + */ + if (umxc->dma_enabled == 1) { + cr2 = (MXC_UARTUCR2_TXEN | MXC_UARTUCR2_RXEN); + } else { + cr2 = + (MXC_UARTUCR2_ATEN | MXC_UARTUCR2_TXEN | MXC_UARTUCR2_RXEN); + } + + writel(cr2, umxc->port.membase + MXC_UARTUCR2); + /* Wait till we are out of software reset */ + do { + cr = readl(umxc->port.membase + MXC_UARTUCR2); + } while (!(cr & MXC_UARTUCR2_SRST)); + + if (umxc->mode == MODE_DTE) { + ufcr |= ((umxc->tx_threshold << MXC_UARTUFCR_TXTL_OFFSET) | + MXC_UARTUFCR_DCEDTE | MXC_UARTUFCR_RFDIV | umxc-> + rx_threshold); + } else { + ufcr |= ((umxc->tx_threshold << MXC_UARTUFCR_TXTL_OFFSET) | + MXC_UARTUFCR_RFDIV | umxc->rx_threshold); + } + writel(ufcr, umxc->port.membase + MXC_UARTUFCR); + + /* + * Finally enable the UART and the Receive interrupts + */ + if (umxc->ir_mode == IRDA) { + cr1 |= MXC_UARTUCR1_IREN; + } + if (umxc->dma_enabled == 1) { + cr1 |= (MXC_UARTUCR1_RXDMAEN | MXC_UARTUCR1_ATDMAEN | + MXC_UARTUCR1_UARTEN); + } else { + cr1 |= (MXC_UARTUCR1_RRDYEN | MXC_UARTUCR1_UARTEN); + } + writel(cr1, umxc->port.membase + MXC_UARTUCR1); + + return 0; +} + +/*! + * This function is called by the core driver for the low-level driver to free + * its resources. The function frees all its interrupts and disables the UART. + * + * @param port the port structure for the UART passed in by the core driver + */ +static void mxcuart_shutdown(struct uart_port *port) +{ + uart_mxc_port *umxc = (uart_mxc_port *) port; + + /* Disable the IOMUX for the UART */ + gpio_uart_inactive(umxc->port.line, umxc->ir_mode); + mxcuart_free_interrupts(umxc); + /* Disable all interrupts, port and break condition */ + writel(0, umxc->port.membase + MXC_UARTUCR1); + writel(0, umxc->port.membase + MXC_UARTUCR3); + if (umxc->dma_enabled == 1) { + mxcuart_freedma(dma_list + umxc->port.line, umxc); + } +} + +/*! + * This function is called while changing the UART parameters. It is called to + * check if the Infrared special case bit (IRSC) in control register 4 should + * be set. + * + * @param baudrate the desired baudrate + * + * @return The functions returns 0 if the IRSC bit does not have to be set, + * else it returns a 1. + */ +/* +static int mxcuart_setir_special(u_int baudrate) +{ + u_int thresh_val; + + thresh_val = 1000000 / (8 * MIN_PULSE_DUR); + if (baudrate > thresh_val) { + return 0; + } + + return 1; +} +*/ + +/*! + * This function is called by the core driver to change the UART parameters, + * including baudrate, word length, parity, stop bits. The function also updates + * the port structures mask registers to indicate the types of events the user is + * interested in receiving. + * + * @param port the port structure for the UART passed in by the core driver + * @param termios the desired termios settings + * @param old old termios + */ +static void mxcuart_set_termios(struct uart_port *port, + struct ktermios *termios, struct ktermios *old) +{ + uart_mxc_port *umxc = (uart_mxc_port *) port; + volatile unsigned int cr4 = 0, cr2 = 0, ufcr; + u_int num, denom, baud; + u_int cr2_mask; /* Used to add the changes to CR2 */ + unsigned long flags, per_clk; + int div; + + cr2_mask = ~(MXC_UARTUCR2_IRTS | MXC_UARTUCR2_CTSC | MXC_UARTUCR2_PREN | + MXC_UARTUCR2_PROE | MXC_UARTUCR2_STPB | MXC_UARTUCR2_WS); + + per_clk = clk_get_rate(umxc->clk); + + /* + * Ask the core to get the baudrate, if requested baudrate is not + * between max and min, then either use the baudrate in old termios + * setting. If it's still invalid, we try 9600 baud. + */ + baud = uart_get_baud_rate(&umxc->port, termios, old, 0, per_clk / 16); + /* Set the Reference frequency divider */ + mxcuart_set_ref_freq(umxc, per_clk, baud, &div); + + /* Byte size, default is 8-bit mode */ + switch (termios->c_cflag & CSIZE) { + case CS7: + cr2 = 0; + break; + default: + cr2 = MXC_UARTUCR2_WS; + break; + } + /* Check to see if we need 2 Stop bits */ + if (termios->c_cflag & CSTOPB) { + cr2 |= MXC_UARTUCR2_STPB; + } + + /* Check to see if we need Parity checking */ + if (termios->c_cflag & PARENB) { + cr2 |= MXC_UARTUCR2_PREN; + if (termios->c_cflag & PARODD) { + cr2 |= MXC_UARTUCR2_PROE; + } + } + spin_lock_irqsave(&umxc->port.lock, flags); + + ufcr = readl(umxc->port.membase + MXC_UARTUFCR); + ufcr = (ufcr & (~MXC_UARTUFCR_RFDIV_MASK)) | + ((6 - div) << MXC_UARTUFCR_RFDIV_OFFSET); + writel(ufcr, umxc->port.membase + MXC_UARTUFCR); + + /* + * Update the per-port timeout + */ + uart_update_timeout(&umxc->port, termios->c_cflag, baud); + + umxc->port.read_status_mask = MXC_UARTURXD_OVRRUN; + /* + * Enable appropriate events to be passed to the TTY layer + */ + if (termios->c_iflag & INPCK) { + umxc->port.read_status_mask |= MXC_UARTURXD_FRMERR | + MXC_UARTURXD_PRERR; + } + if (termios->c_iflag & (BRKINT | PARMRK)) { + umxc->port.read_status_mask |= MXC_UARTURXD_BRK; + } + + /* + * Characters to ignore + */ + umxc->port.ignore_status_mask = 0; + if (termios->c_iflag & IGNPAR) { + umxc->port.ignore_status_mask |= MXC_UARTURXD_FRMERR | + MXC_UARTURXD_PRERR; + } + if (termios->c_iflag & IGNBRK) { + umxc->port.ignore_status_mask |= MXC_UARTURXD_BRK; + /* + * If we are ignoring parity and break indicators, + * ignore overruns too (for real raw support) + */ + if (termios->c_iflag & IGNPAR) { + umxc->port.ignore_status_mask |= MXC_UARTURXD_OVRRUN; + } + } + + /* + * Ignore all characters if CREAD is not set, still receive characters + * from the port, but throw them away. + */ + if ((termios->c_cflag & CREAD) == 0) { + umxc->port.ignore_status_mask |= UART_CREAD_BIT; + } + + cr4 = readl(umxc->port.membase + MXC_UARTUCR4); + if (UART_ENABLE_MS(port, termios->c_cflag)) { + mxcuart_enable_ms(port); + if (umxc->hardware_flow == 1) { + cr4 = (cr4 & (~MXC_UARTUCR4_CTSTL_MASK)) | + (umxc->cts_threshold << MXC_UARTUCR4_CTSTL_OFFSET); + cr2 |= MXC_UARTUCR2_CTSC; + umxc->port.info->tty->hw_stopped = 0; + } else { + cr2 |= MXC_UARTUCR2_IRTS; + } + } else { + cr2 |= MXC_UARTUCR2_IRTS; + } + + /* Add Parity, character length and stop bits information */ + cr2 |= (readl(umxc->port.membase + MXC_UARTUCR2) & cr2_mask); + writel(cr2, umxc->port.membase + MXC_UARTUCR2); + /* + if (umxc->ir_mode == IRDA) { + ret = mxcuart_setir_special(baud); + if (ret == 0) { + cr4 &= ~MXC_UARTUCR4_IRSC; + } else { + cr4 |= MXC_UARTUCR4_IRSC; + } + } */ + writel(cr4, umxc->port.membase + MXC_UARTUCR4); + /* Set baud rate */ + num = (baud / 100) - 1; + denom = (umxc->port.uartclk / 1600) - 1; + if ((denom < 65536) && (umxc->port.uartclk > 1600)) { + writel(num, umxc->port.membase + MXC_UARTUBIR); + writel(denom, umxc->port.membase + MXC_UARTUBMR); + } + spin_unlock_irqrestore(&umxc->port.lock, flags); +} + +/*! + * This function is called by the core driver to know the UART type. + * + * @param port the port structure for the UART passed in by the core driver + * + * @return The function returns a pointer to a string describing the UART port. + */ +static const char *mxcuart_type(struct uart_port *port) +{ + return port->type == PORT_MXC ? "Freescale MXC" : NULL; +} + +/*! + * This function is called by the core driver to release the memory resources + * currently in use by the UART port. + * + * @param port the port structure for the UART passed in by the core driver + */ +static void mxcuart_release_port(struct uart_port *port) +{ + release_mem_region(port->mapbase, SZ_4K); +} + +/*! + * This function is called by the core driver to request memory resources for + * the UART port. + * + * @param port the port structure for the UART passed in by the core driver + * + * @return The function returns \b -EBUSY on failure, else it returns 0. + */ +static int mxcuart_request_port(struct uart_port *port) +{ + return request_mem_region(port->mapbase, SZ_4K, "serial_mxc") + != NULL ? 0 : -EBUSY; +} + +/*! + * This function is called by the core driver to perform any autoconfiguration + * steps required for the UART port. This function sets the port->type field. + * + * @param port the port structure for the UART passed in by the core driver + * @param flags bit mask of the required configuration + */ +static void mxcuart_config_port(struct uart_port *port, int flags) +{ + if ((flags & UART_CONFIG_TYPE) && (mxcuart_request_port(port) == 0)) { + port->type = PORT_MXC; + } +} + +/*! + * This function is called by the core driver to verify that the new serial + * port information contained within \a ser is suitable for this UART port type. + * The function checks to see if the UART port type specified by the user + * application while setting the UART port information matches what is stored + * in the define \b PORT_MXC found in the header file include/linux/serial_core.h + * + * @param port the port structure for the UART passed in by the core driver + * @param ser the new serial port information + * + * @return The function returns 0 on success or \b -EINVAL if the port type + * specified is not equal to \b PORT_MXC. + */ +static int mxcuart_verify_port(struct uart_port *port, + struct serial_struct *ser) +{ + int ret = 0; + if (ser->type != PORT_UNKNOWN && ser->type != PORT_MXC) { + ret = -EINVAL; + } + return ret; +} + +/*! + * This function is used to send a high priority XON/XOFF character + * + * @param port the port structure for the UART passed in by the core driver + * @param ch the character to send + */ +static void mxcuart_send_xchar(struct uart_port *port, char ch) +{ + unsigned long flags; + + port->x_char = ch; + if (port->info->tty->hw_stopped) { + return; + } + + if (ch) { + spin_lock_irqsave(&port->lock, flags); + port->ops->start_tx(port); + spin_unlock_irqrestore(&port->lock, flags); + } +} + +/*! + * This function is used enable/disable the MXC UART clocks + * + * @param port the port structure for the UART passed in by the core driver + * @param state New PM state + * @param oldstate Current PM state + */ +static void +mxcuart_pm(struct uart_port *port, unsigned int state, unsigned int oldstate) +{ + uart_mxc_port *umxc = (uart_mxc_port *) port; + + if (state) + clk_disable(umxc->clk); + else + clk_enable(umxc->clk); +} + +/*! + * This structure contains the pointers to the control functions that are + * invoked by the core serial driver to access the UART hardware. The + * structure is passed to serial_core.c file during registration. + */ +static struct uart_ops mxc_ops = { + .tx_empty = mxcuart_tx_empty, + .set_mctrl = mxcuart_set_mctrl, + .get_mctrl = mxcuart_get_mctrl, + .stop_tx = mxcuart_stop_tx, + .start_tx = mxcuart_start_tx, + .stop_rx = mxcuart_stop_rx, + .enable_ms = mxcuart_enable_ms, + .break_ctl = mxcuart_break_ctl, + .startup = mxcuart_startup, + .shutdown = mxcuart_shutdown, + .set_termios = mxcuart_set_termios, + .type = mxcuart_type, + .pm = mxcuart_pm, + .release_port = mxcuart_release_port, + .request_port = mxcuart_request_port, + .config_port = mxcuart_config_port, + .verify_port = mxcuart_verify_port, + .send_xchar = mxcuart_send_xchar, +}; + +#ifdef CONFIG_SERIAL_MXC_CONSOLE + +/* + * Write out a character once the UART is ready + */ +static inline void mxcuart_console_write_char(struct uart_port *port, char ch) +{ + volatile unsigned int status; + + do { + status = readl(port->membase + MXC_UARTUSR1); + } while ((status & MXC_UARTUSR1_TRDY) == 0); + writel(ch, port->membase + MXC_UARTUTXD); +} + +/*! + * This function is called to write the console messages through the UART port. + * + * @param co the console structure + * @param s the log message to be written to the UART + * @param count length of the message + */ +static void mxcuart_console_write(struct console *co, const char *s, + u_int count) +{ + struct uart_port *port = &mxc_ports[co->index]->port; + volatile unsigned int status, oldcr1, oldcr2, oldcr3, cr2, cr3; + int i; + + /* + * First save the control registers and then disable the interrupts + */ + oldcr1 = readl(port->membase + MXC_UARTUCR1); + oldcr2 = readl(port->membase + MXC_UARTUCR2); + oldcr3 = readl(port->membase + MXC_UARTUCR3); + cr2 = + oldcr2 & ~(MXC_UARTUCR2_ATEN | MXC_UARTUCR2_RTSEN | + MXC_UARTUCR2_ESCI); + cr3 = + oldcr3 & ~(MXC_UARTUCR3_DCD | MXC_UARTUCR3_RI | + MXC_UARTUCR3_DTRDEN); + writel(MXC_UARTUCR1_UARTEN, port->membase + MXC_UARTUCR1); + writel(cr2, port->membase + MXC_UARTUCR2); + writel(cr3, port->membase + MXC_UARTUCR3); + /* + * Do each character + */ + for (i = 0; i < count; i++) { + mxcuart_console_write_char(port, s[i]); + if (s[i] == '\n') { + mxcuart_console_write_char(port, '\r'); + } + } + /* + * Finally, wait for the transmitter to become empty + */ + do { + status = readl(port->membase + MXC_UARTUSR2); + } while (!(status & MXC_UARTUSR2_TXDC)); + + /* + * Restore the control registers + */ + writel(oldcr1, port->membase + MXC_UARTUCR1); + writel(oldcr2, port->membase + MXC_UARTUCR2); + writel(oldcr3, port->membase + MXC_UARTUCR3); +} + +/*! + * Initializes the UART port to be used to print console message with the + * options specified. If no options are specified, then the function + * initializes the UART with the default options of baudrate=115200, 8 bit + * word size, no parity, no flow control. + * + * @param co The console structure + * @param options Any console options passed in from the command line + * + * @return The function returns 0 on success or error. + */ +static int __init mxcuart_console_setup(struct console *co, char *options) +{ + uart_mxc_port *umxc; + int baud = 115200; + int bits = 8; + int parity = 'n'; + int flow = 'n'; + volatile unsigned int cr = 0; + + /* + * Check whether an invalid uart number had been specified, and if + * so, search for the first available port that does have console + * support + */ + if (co->index >= MXC_UART_NR) { + co->index = 0; + } + umxc = mxc_ports[co->index]; + + if (umxc == NULL) { + return -ENODEV; + } + + clk_enable(umxc->clk); + + /* initialize port.lock else oops */ + spin_lock_init(&umxc->port.lock); + + /* + * Initialize the UART registers + */ + writel(MXC_UARTUCR1_UARTEN, umxc->port.membase + MXC_UARTUCR1); + /* Enable the transmitter and do a software reset */ + writel(MXC_UARTUCR2_TXEN, umxc->port.membase + MXC_UARTUCR2); + /* Wait till we are out of software reset */ + do { + cr = readl(umxc->port.membase + MXC_UARTUCR2); + } while (!(cr & MXC_UARTUCR2_SRST)); + + writel(0x0, umxc->port.membase + MXC_UARTUCR3); + writel(0x0, umxc->port.membase + MXC_UARTUCR4); + /* Set TXTL to 2, RXTL to 1 and RFDIV to 2 */ + cr = 0x0800 | MXC_UARTUFCR_RFDIV | 0x1; + if (umxc->mode == MODE_DTE) { + cr |= MXC_UARTUFCR_DCEDTE; + } + writel(cr, umxc->port.membase + MXC_UARTUFCR); + writel(0xFFFF, umxc->port.membase + MXC_UARTUSR1); + writel(0xFFFF, umxc->port.membase + MXC_UARTUSR2); + + if (options != NULL) { + uart_parse_options(options, &baud, &parity, &bits, &flow); + } + gpio_uart_active(umxc->port.line, umxc->ir_mode); + return uart_set_options(&umxc->port, co, baud, parity, bits, flow); +} + +static struct uart_driver mxc_reg; + +/*! + * This structure contains the pointers to the UART console functions. It is + * passed as an argument when registering the console. + */ +static struct console mxc_console = { + .name = "ttymxc", + .write = mxcuart_console_write, + .device = uart_console_device, + .setup = mxcuart_console_setup, + .flags = CON_PRINTBUFFER, + .index = -1, + .data = &mxc_reg, +}; + +/*! + * This function registers the console callback functions with the kernel. + */ +static int __init mxcuart_console_init(void) +{ + register_console(&mxc_console); + return 0; +} + +console_initcall(mxcuart_console_init); + +static int __init find_port(struct uart_port *p) +{ + int line; + struct uart_port *port; + for (line = 0; line < MXC_UART_NR; line++) { + if (!mxc_ports[line]) + continue; + port = &mxc_ports[line]->port; + if (uart_match_port(p, port)) + return line; + } + return -ENODEV; +} + +int __init mxc_uart_start_console(struct uart_port *port, char *options) +{ + int line; + line = find_port(port); + if (line < 0) + return -ENODEV; + + add_preferred_console("ttymxc", line, options); + printk("Switching Console to ttymxc%d at %s 0x%lx (options '%s')\n", + line, port->iotype == UPIO_MEM ? "MMIO" : "I/O port", + port->iotype == + UPIO_MEM ? (unsigned long)port->mapbase : (unsigned long)port-> + iobase, options); + + if (!(mxc_console.flags & CON_ENABLED)) { + mxc_console.flags &= ~CON_PRINTBUFFER; + register_console(&mxc_console); + } + return 0; +} + +#define MXC_CONSOLE &mxc_console +#else +#define MXC_CONSOLE NULL +#endif /* CONFIG_SERIAL_MXC_CONSOLE */ + +/*! + * This structure contains the information such as the name of the UART driver + * that appears in the /dev folder, major and minor numbers etc. This structure + * is passed to the serial_core.c file. + */ +static struct uart_driver mxc_reg = { + .owner = THIS_MODULE, + .driver_name = "ttymxc", + .dev_name = "ttymxc", + .major = SERIAL_MXC_MAJOR, + .minor = SERIAL_MXC_MINOR, + .nr = MXC_UART_NR, + .cons = MXC_CONSOLE, +}; + +/*! + * This function is called to put the UART in a low power state. Refer to the + * document driver-model/driver.txt in the kernel source tree for more + * information. + * + * @param pdev the device structure used to give information on which UART + * to suspend + * @param state the power state the device is entering + * + * @return The function returns 0 on success and -1 on failure + */ +static int mxcuart_suspend(struct platform_device *pdev, pm_message_t state) +{ + uart_mxc_port *umxc = platform_get_drvdata(pdev); + + if (umxc == NULL) { + return 0; /* skip disabled ports */ + } + uart_suspend_port(&mxc_reg, &umxc->port); + if (umxc->port.info && umxc->port.info->flags & UIF_INITIALIZED) { + umxc->port.info->tty->hw_stopped = 1; + } + + if (device_may_wakeup(&pdev->dev)) { + /* UART RTS signal is used as wakeup source */ + writel(MXC_UARTUCR1_RTSDEN, umxc->port.membase + MXC_UARTUCR1); + gpio_uart_active(umxc->port.line, umxc->ir_mode); + } + + return 0; +} + +/*! + * This function is called to bring the UART back from a low power state. Refer + * to the document driver-model/driver.txt in the kernel source tree for more + * information. + * + * @param pdev the device structure used to give information on which UART + * to resume + * + * @return The function returns 0 on success and -1 on failure + */ +static int mxcuart_resume(struct platform_device *pdev) +{ + uart_mxc_port *umxc = platform_get_drvdata(pdev); + + if (umxc == NULL) { + return 0; /* skip disabled ports */ + } + if (umxc->port.info && umxc->port.info->flags & UIF_INITIALIZED) { + umxc->port.info->tty->hw_stopped = 0; + } + if (device_may_wakeup(&pdev->dev)) { + writel(0, umxc->port.membase + MXC_UARTUCR1); + gpio_uart_inactive(umxc->port.line, umxc->ir_mode); + } + uart_resume_port(&mxc_reg, &umxc->port); + + return 0; +} + +/*! + * This function is called during the driver binding process. Based on the UART + * that is being probed this function adds the appropriate UART port structure + * in the core driver. + * + * @param pdev the device structure used to store device specific + * information that is used by the suspend, resume and remove + * functions + * + * @return The function returns 0 if successful; -1 otherwise. + */ +static int mxcuart_probe(struct platform_device *pdev) +{ + int id = pdev->id; + + mxc_ports[id] = pdev->dev.platform_data; + mxc_ports[id]->port.ops = &mxc_ops; + + /* Do not use UARTs that are disabled during integration */ + if (mxc_ports[id]->enabled == 1) { + mxc_ports[id]->port.dev = &pdev->dev; + spin_lock_init(&mxc_ports[id]->port.lock); + /* Enable the low latency flag for DMA UART ports */ + if (mxc_ports[id]->dma_enabled == 1) { + mxc_ports[id]->port.flags |= UPF_LOW_LATENCY; + } + + mxc_ports[id]->clk = clk_get(&pdev->dev, "uart_clk"); + clk_enable(mxc_ports[id]->clk); + if (mxc_ports[id]->clk == NULL) + return -1; + + uart_add_one_port(&mxc_reg, &mxc_ports[id]->port); + platform_set_drvdata(pdev, mxc_ports[id]); + pdev->dev.power.can_wakeup = 1; + } + return 0; +} + +/*! + * Dissociates the driver from the UART device. Removes the appropriate UART + * port structure from the core driver. + * + * @param pdev the device structure used to give information on which UART + * to remove + * + * @return The function always returns 0. + */ +static int mxcuart_remove(struct platform_device *pdev) +{ + uart_mxc_port *umxc = platform_get_drvdata(pdev); + + platform_set_drvdata(pdev, NULL); + + if (umxc) { + uart_remove_one_port(&mxc_reg, &umxc->port); + } + return 0; +} + +/*! + * This structure contains pointers to the power management callback functions. + */ +static struct platform_driver mxcuart_driver = { + .driver = { + .name = "mxcintuart", + }, + .probe = mxcuart_probe, + .remove = mxcuart_remove, + .suspend = mxcuart_suspend, + .resume = mxcuart_resume, +}; + +/*! + * This function is used to initialize the UART driver module. The function + * registers the power management callback functions with the kernel and also + * registers the UART callback functions with the core serial driver. + * + * @return The function returns 0 on success and a non-zero value on failure. + */ +static int __init mxcuart_init(void) +{ + int ret = 0; + + printk(KERN_INFO "Serial: MXC Internal UART driver\n"); + ret = uart_register_driver(&mxc_reg); + if (ret == 0) { + /* Register the device driver structure. */ + ret = platform_driver_register(&mxcuart_driver); + if (ret != 0) { + uart_unregister_driver(&mxc_reg); + } + } + return ret; +} + +/*! + * This function is used to cleanup all resources before the driver exits. + */ +static void __exit mxcuart_exit(void) +{ + platform_driver_unregister(&mxcuart_driver); + uart_unregister_driver(&mxc_reg); +} + +module_init(mxcuart_init); +module_exit(mxcuart_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MXC serial port driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/serial/mxc_uart_early.c b/drivers/serial/mxc_uart_early.c new file mode 100644 index 000000000000..275f3a08b2ed --- /dev/null +++ b/drivers/serial/mxc_uart_early.c @@ -0,0 +1,252 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file drivers/serial/mxc_uart_early.c + * + * @brief Driver for the Freescale Semiconductor MXC serial ports based on + * drivers/char/8250_early.c, Copyright 2004 Hewlett-Packard Development Company, + * L.P. by Bjorn Helgaasby. + * + * Early serial console for MXC UARTS. + * + * This is for use before the serial driver has initialized, in + * particular, before the UARTs have been discovered and named. + * Instead of specifying the console device as, e.g., "ttymxc0", + * we locate the device directly by its MMIO or I/O port address. + * + * The user can specify the device directly, e.g., + * console=mxcuart,0x43f90000,115200n8 + * or platform code can call early_uart_console_init() to set + * the early UART device. + * + * After the normal serial driver starts, we try to locate the + * matching ttymxc device and start a console there. + */ + +/* + * Include Files + */ + +#include <linux/tty.h> +#include <linux/init.h> +#include <linux/console.h> +#include <linux/serial_core.h> +#include <linux/serial_reg.h> +#include <linux/clk.h> +#include <asm/arch/mxc_uart.h> + +struct mxc_early_uart_device { + struct uart_port port; + char options[16]; /* e.g., 115200n8 */ + unsigned int baud; +}; + +int __init mxc_uart_start_console(struct uart_port *, char *); +static struct mxc_early_uart_device mxc_early_device __initdata; +static int mxc_early_uart_registered __initdata; +static struct clk *clk; + +/* + * Write out a character once the UART is ready + */ +static void __init mxcuart_console_write_char(struct uart_port *port, int ch) +{ + unsigned int status; + + do { + status = readl(port->membase + MXC_UARTUSR1); + } while ((status & MXC_UARTUSR1_TRDY) == 0); + writel(ch, port->membase + MXC_UARTUTXD); +} + +/*! + * This function is called to write the console messages through the UART port. + * + * @param co the console structure + * @param s the log message to be written to the UART + * @param count length of the message + */ +void __init early_mxcuart_console_write(struct console *co, const char *s, + u_int count) +{ + struct uart_port *port = &mxc_early_device.port; + volatile unsigned int status, oldcr1, oldcr2, oldcr3, cr2, cr3; + + /* + * First save the control registers and then disable the interrupts + */ + oldcr1 = readl(port->membase + MXC_UARTUCR1); + oldcr2 = readl(port->membase + MXC_UARTUCR2); + oldcr3 = readl(port->membase + MXC_UARTUCR3); + cr2 = + oldcr2 & ~(MXC_UARTUCR2_ATEN | MXC_UARTUCR2_RTSEN | + MXC_UARTUCR2_ESCI); + cr3 = + oldcr3 & ~(MXC_UARTUCR3_DCD | MXC_UARTUCR3_RI | + MXC_UARTUCR3_DTRDEN); + writel(MXC_UARTUCR1_UARTEN, port->membase + MXC_UARTUCR1); + writel(cr2, port->membase + MXC_UARTUCR2); + writel(cr3, port->membase + MXC_UARTUCR3); + + /* Transmit string */ + uart_console_write(port, s, count, mxcuart_console_write_char); + + /* + * Finally, wait for the transmitter to become empty + */ + do { + status = readl(port->membase + MXC_UARTUSR2); + } while (!(status & MXC_UARTUSR2_TXDC)); + + /* + * Restore the control registers + */ + writel(oldcr1, port->membase + MXC_UARTUCR1); + writel(oldcr2, port->membase + MXC_UARTUCR2); + writel(oldcr3, port->membase + MXC_UARTUCR3); +} + +static unsigned int __init probe_baud(struct uart_port *port) +{ + /* FIXME Return Default Baud Rate */ + return 115200; +} + +static int __init parse_options(struct mxc_early_uart_device *device, + char *options) +{ + struct uart_port *port = &device->port; + int mapsize = 64; + int length; + + if (!options) + return -ENODEV; + + port->uartclk = 5600000; + port->iotype = UPIO_MEM; + port->mapbase = simple_strtoul(options, &options, 0); + port->membase = ioremap(port->mapbase, mapsize); + + if ((options = strchr(options, ','))) { + options++; + device->baud = simple_strtoul(options, NULL, 0); + length = min(strcspn(options, " "), sizeof(device->options)); + strncpy(device->options, options, length); + } else { + device->baud = probe_baud(port); + snprintf(device->options, sizeof(device->options), "%u", + device->baud); + } + printk(KERN_INFO + "MXC_Early serial console at MMIO 0x%lx (options '%s')\n", + port->mapbase, device->options); + return 0; +} + +static int __init mxc_early_uart_setup(struct console *console, char *options) +{ + struct mxc_early_uart_device *device = &mxc_early_device; + int err; + if (device->port.membase || device->port.iobase) + return 0; + if ((err = parse_options(device, options)) < 0) + return err; + return 0; +} + +static struct console mxc_early_uart_console __initdata = { + .name = "mxcuart", + .write = early_mxcuart_console_write, + .setup = mxc_early_uart_setup, + .flags = CON_PRINTBUFFER, + .index = -1, +}; + +static int __init mxc_early_uart_console_init(void) +{ + + if (!mxc_early_uart_registered) { + register_console(&mxc_early_uart_console); + mxc_early_uart_registered = 1; + } + + return 0; +} + +int __init mxc_early_serial_console_init(char *cmdline) +{ + char *options; + int err; + int uart_paddr; + + options = strstr(cmdline, "console=mxcuart"); + if (!options) + return -ENODEV; + + /* Extracting MXC UART Uart Port Address from cmdline */ + options = strchr(cmdline, ',') + 1; + uart_paddr = simple_strtoul(options, NULL, 16); + +#ifdef UART1_BASE_ADDR + if (uart_paddr == UART1_BASE_ADDR) + clk = clk_get(NULL, "uart_clk.0"); +#endif +#ifdef UART2_BASE_ADDR + if (uart_paddr == UART2_BASE_ADDR) + clk = clk_get(NULL, "uart_clk.1"); +#endif +#ifdef UART3_BASE_ADDR + if (uart_paddr == UART3_BASE_ADDR) + clk = clk_get(NULL, "uart_clk.2"); +#endif + if (clk == NULL) + return -1; + + /* Enable Early MXC UART Clock */ + clk_enable(clk); + + options = strchr(cmdline, ',') + 1; + if ((err = mxc_early_uart_setup(NULL, options)) < 0) + return err; + return mxc_early_uart_console_init(); +} + +int __init mxc_early_uart_console_switch(void) +{ + struct mxc_early_uart_device *device = &mxc_early_device; + struct uart_port *port = &device->port; + int mmio, line; + + if (!(mxc_early_uart_console.flags & CON_ENABLED)) + return 0; + /* Try to start the normal driver on a matching line. */ + mmio = (port->iotype == UPIO_MEM); + line = mxc_uart_start_console(port, device->options); + + if (line < 0) + printk("No ttymxc device at %s 0x%lx for console\n", + mmio ? "MMIO" : "I/O port", + mmio ? port->mapbase : (unsigned long)port->iobase); + + unregister_console(&mxc_early_uart_console); + if (mmio) + iounmap(port->membase); + + clk_disable(clk); + clk_put(clk); + + return 0; +} + +late_initcall(mxc_early_uart_console_switch); diff --git a/drivers/serial/mxc_uart_reg.h b/drivers/serial/mxc_uart_reg.h new file mode 100644 index 000000000000..c0d1e812fe6a --- /dev/null +++ b/drivers/serial/mxc_uart_reg.h @@ -0,0 +1,128 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef __MXC_UART_REG_H__ +#define __MXC_UART_REG_H__ + +/* Address offsets of the UART registers */ +#define MXC_UARTURXD 0x000 /* Receive reg */ +#define MXC_UARTUTXD 0x040 /* Transmitter reg */ +#define MXC_UARTUCR1 0x080 /* Control reg 1 */ +#define MXC_UARTUCR2 0x084 /* Control reg 2 */ +#define MXC_UARTUCR3 0x088 /* Control reg 3 */ +#define MXC_UARTUCR4 0x08C /* Control reg 4 */ +#define MXC_UARTUFCR 0x090 /* FIFO control reg */ +#define MXC_UARTUSR1 0x094 /* Status reg 1 */ +#define MXC_UARTUSR2 0x098 /* Status reg 2 */ +#define MXC_UARTUESC 0x09C /* Escape character reg */ +#define MXC_UARTUTIM 0x0A0 /* Escape timer reg */ +#define MXC_UARTUBIR 0x0A4 /* BRM incremental reg */ +#define MXC_UARTUBMR 0x0A8 /* BRM modulator reg */ +#define MXC_UARTUBRC 0x0AC /* Baud rate count reg */ +#define MXC_UARTONEMS 0x0B0 /* One millisecond reg */ +#define MXC_UARTUTS 0x0B4 /* Test reg */ + +/* Bit definations of UCR1 */ +#define MXC_UARTUCR1_ADEN 0x8000 +#define MXC_UARTUCR1_ADBR 0x4000 +#define MXC_UARTUCR1_TRDYEN 0x2000 +#define MXC_UARTUCR1_IDEN 0x1000 +#define MXC_UARTUCR1_RRDYEN 0x0200 +#define MXC_UARTUCR1_RXDMAEN 0x0100 +#define MXC_UARTUCR1_IREN 0x0080 +#define MXC_UARTUCR1_TXMPTYEN 0x0040 +#define MXC_UARTUCR1_RTSDEN 0x0020 +#define MXC_UARTUCR1_SNDBRK 0x0010 +#define MXC_UARTUCR1_TXDMAEN 0x0008 +#define MXC_UARTUCR1_ATDMAEN 0x0004 +#define MXC_UARTUCR1_DOZE 0x0002 +#define MXC_UARTUCR1_UARTEN 0x0001 + +/* Bit definations of UCR2 */ +#define MXC_UARTUCR2_ESCI 0x8000 +#define MXC_UARTUCR2_IRTS 0x4000 +#define MXC_UARTUCR2_CTSC 0x2000 +#define MXC_UARTUCR2_CTS 0x1000 +#define MXC_UARTUCR2_PREN 0x0100 +#define MXC_UARTUCR2_PROE 0x0080 +#define MXC_UARTUCR2_STPB 0x0040 +#define MXC_UARTUCR2_WS 0x0020 +#define MXC_UARTUCR2_RTSEN 0x0010 +#define MXC_UARTUCR2_ATEN 0x0008 +#define MXC_UARTUCR2_TXEN 0x0004 +#define MXC_UARTUCR2_RXEN 0x0002 +#define MXC_UARTUCR2_SRST 0x0001 + +/* Bit definations of UCR3 */ +#define MXC_UARTUCR3_DTREN 0x2000 +#define MXC_UARTUCR3_PARERREN 0x1000 +#define MXC_UARTUCR3_FRAERREN 0x0800 +#define MXC_UARTUCR3_DSR 0x0400 +#define MXC_UARTUCR3_DCD 0x0200 +#define MXC_UARTUCR3_RI 0x0100 +#define MXC_UARTUCR3_RXDSEN 0x0040 +#define MXC_UARTUCR3_AWAKEN 0x0010 +#define MXC_UARTUCR3_DTRDEN 0x0008 +#define MXC_UARTUCR3_RXDMUXSEL 0x0004 +#define MXC_UARTUCR3_INVT 0x0002 + +/* Bit definations of UCR4 */ +#define MXC_UARTUCR4_CTSTL_OFFSET 10 +#define MXC_UARTUCR4_CTSTL_MASK (0x3F << 10) +#define MXC_UARTUCR4_INVR 0x0200 +#define MXC_UARTUCR4_ENIRI 0x0100 +#define MXC_UARTUCR4_REF16 0x0040 +#define MXC_UARTUCR4_IRSC 0x0020 +#define MXC_UARTUCR4_TCEN 0x0008 +#define MXC_UARTUCR4_OREN 0x0002 +#define MXC_UARTUCR4_DREN 0x0001 + +/* Bit definations of UFCR */ +#define MXC_UARTUFCR_RFDIV 0x0200 /* Ref freq div is set to 2 */ +#define MXC_UARTUFCR_RFDIV_OFFSET 7 +#define MXC_UARTUFCR_RFDIV_MASK (0x7 << 7) +#define MXC_UARTUFCR_TXTL_OFFSET 10 +#define MXC_UARTUFCR_DCEDTE 0x0040 + +/* Bit definations of URXD */ +#define MXC_UARTURXD_ERR 0x4000 +#define MXC_UARTURXD_OVRRUN 0x2000 +#define MXC_UARTURXD_FRMERR 0x1000 +#define MXC_UARTURXD_BRK 0x0800 +#define MXC_UARTURXD_PRERR 0x0400 + +/* Bit definations of USR1 */ +#define MXC_UARTUSR1_PARITYERR 0x8000 +#define MXC_UARTUSR1_RTSS 0x4000 +#define MXC_UARTUSR1_TRDY 0x2000 +#define MXC_UARTUSR1_RTSD 0x1000 +#define MXC_UARTUSR1_FRAMERR 0x0400 +#define MXC_UARTUSR1_RRDY 0x0200 +#define MXC_UARTUSR1_AGTIM 0x0100 +#define MXC_UARTUSR1_DTRD 0x0080 +#define MXC_UARTUSR1_AWAKE 0x0010 + +/* Bit definations of USR2 */ +#define MXC_UARTUSR2_TXFE 0x4000 +#define MXC_UARTUSR2_IDLE 0x1000 +#define MXC_UARTUSR2_RIDELT 0x0400 +#define MXC_UARTUSR2_RIIN 0x0200 +#define MXC_UARTUSR2_DCDDELT 0x0040 +#define MXC_UARTUSR2_DCDIN 0x0020 +#define MXC_UARTUSR2_TXDC 0x0008 +#define MXC_UARTUSR2_ORE 0x0002 +#define MXC_UARTUSR2_RDR 0x0001 + +/* Bit definations of UTS */ +#define MXC_UARTUTS_LOOP 0x1000 + +#endif /* __MXC_UART_REG_H__ */ diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index abf05048c638..13d10aa34cb2 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -191,6 +191,33 @@ config SPI_XILINX See the "OPB Serial Peripheral Interface (SPI) (v1.00e)" Product Specification document (DS464) for hardware details. +config SPI_MXC + tristate "MXC CSPI controller as SPI Master" + depends on ARCH_MXC && SPI_MASTER + select SPI_BITBANG + help + This implements the SPI master mode using MXC CSPI. + +config SPI_MXC_TEST_LOOPBACK + bool "LOOPBACK Testing of CSPIs" + depends on SPI_MXC + default n + +config SPI_MXC_SELECT1 + bool "CSPI1" + depends on SPI_MXC + default y + +config SPI_MXC_SELECT2 + bool "CSPI2" + depends on SPI_MXC && (!ARCH_MXC91221) + default n + +config SPI_MXC_SELECT3 + bool "CSPI3" + depends on SPI_MXC && (ARCH_MX3 || ARCH_MX27) + default n + # # Add new SPI master controllers in alphabetical order above this line # diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index 41fbac45c323..6ed524fea90e 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -26,6 +26,7 @@ obj-$(CONFIG_SPI_MPC83xx) += spi_mpc83xx.o obj-$(CONFIG_SPI_S3C24XX_GPIO) += spi_s3c24xx_gpio.o obj-$(CONFIG_SPI_S3C24XX) += spi_s3c24xx.o obj-$(CONFIG_SPI_TXX9) += spi_txx9.o +obj-$(CONFIG_SPI_MXC) += mxc_spi.o obj-$(CONFIG_SPI_XILINX) += xilinx_spi.o # ... add above this line ... diff --git a/drivers/spi/mxc_spi.c b/drivers/spi/mxc_spi.c new file mode 100644 index 000000000000..54ffc54c10f1 --- /dev/null +++ b/drivers/spi/mxc_spi.c @@ -0,0 +1,913 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-licensisr_locke.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @defgroup SPI Configurable Serial Peripheral Interface (CSPI) Driver + */ + +/*! + * @file mxc_spi.c + * @brief This file contains the implementation of the SPI master controller services + * + * + * @ingroup SPI + */ + +#include <linux/completion.h> +#include <linux/platform_device.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/types.h> +#include <linux/clk.h> + +#include <linux/spi/spi.h> +#include <linux/spi/spi_bitbang.h> + +#include <asm/hardware.h> +#include <asm/irq.h> +#include <asm/io.h> +#include <asm/arch/gpio.h> + +#ifdef CONFIG_ARCH_MX27 +#include "mxc_spi_mx27.h" +#else +#include "mxc_spi.h" +#endif + +#ifdef CONFIG_SPI_MXC_TEST_LOOPBACK +struct spi_chip_info { + int lb_enable; +}; + +static struct spi_chip_info lb_chip_info = { + .lb_enable = 1, +}; + +static struct spi_board_info loopback_info[] = { +#ifdef CONFIG_SPI_MXC_SELECT1 + { + .modalias = "loopback_spi", + .controller_data = &lb_chip_info, + .irq = 0, + .max_speed_hz = 4000000, + .bus_num = 1, + .chip_select = 4, + }, +#endif +#ifdef CONFIG_SPI_MXC_SELECT2 + { + .modalias = "loopback_spi", + .controller_data = &lb_chip_info, + .irq = 0, + .max_speed_hz = 4000000, + .bus_num = 2, + .chip_select = 4, + }, +#endif +#ifdef CONFIG_SPI_MXC_SELECT3 + { + .modalias = "loopback_spi", + .controller_data = &lb_chip_info, + .irq = 0, + .max_speed_hz = 4000000, + .bus_num = 3, + .chip_select = 4, + }, +#endif +}; +#endif + +extern void gpio_spi_active(int cspi_mod); +extern void gpio_spi_inactive(int cspi_mod); + +static struct mxc_spi_unique_def spi_ver_0_7 = { + .intr_bit_shift = MXC_CSPIINT_IRQSHIFT_0_7, + .cs_shift = MXC_CSPICTRL_CSSHIFT_0_7, + .bc_shift = MXC_CSPICTRL_BCSHIFT_0_7, + .bc_mask = MXC_CSPICTRL_BCMASK_0_7, + .drctrl_shift = MXC_CSPICTRL_DRCTRLSHIFT_0_7, + .xfer_complete = MXC_CSPISTAT_TC_0_7, + .bc_overflow = MXC_CSPISTAT_BO_0_7, +}; + +static struct mxc_spi_unique_def spi_ver_0_5 = { + .intr_bit_shift = MXC_CSPIINT_IRQSHIFT_0_5, + .cs_shift = MXC_CSPICTRL_CSSHIFT_0_5, + .bc_shift = MXC_CSPICTRL_BCSHIFT_0_5, + .bc_mask = MXC_CSPICTRL_BCMASK_0_5, + .drctrl_shift = MXC_CSPICTRL_DRCTRLSHIFT_0_5, + .xfer_complete = MXC_CSPISTAT_TC_0_5, + .bc_overflow = MXC_CSPISTAT_BO_0_5, +}; + +static struct mxc_spi_unique_def spi_ver_0_4 = { + .intr_bit_shift = MXC_CSPIINT_IRQSHIFT_0_4, + .cs_shift = MXC_CSPICTRL_CSSHIFT_0_4, + .bc_shift = MXC_CSPICTRL_BCSHIFT_0_4, + .bc_mask = MXC_CSPICTRL_BCMASK_0_4, + .drctrl_shift = MXC_CSPICTRL_DRCTRLSHIFT_0_4, + .xfer_complete = MXC_CSPISTAT_TC_0_4, + .bc_overflow = MXC_CSPISTAT_BO_0_4, +}; + +static struct mxc_spi_unique_def spi_ver_0_0 = { + .intr_bit_shift = MXC_CSPIINT_IRQSHIFT_0_0, + .cs_shift = MXC_CSPICTRL_CSSHIFT_0_0, + .bc_shift = MXC_CSPICTRL_BCSHIFT_0_0, + .bc_mask = MXC_CSPICTRL_BCMASK_0_0, + .drctrl_shift = MXC_CSPICTRL_DRCTRLSHIFT_0_0, + .xfer_complete = MXC_CSPISTAT_TC_0_0, + .bc_overflow = MXC_CSPISTAT_BO_0_0, +}; + +struct mxc_spi; +/*! + * Structure to group together all the data buffers and functions + * used in data transfers. + */ +struct mxc_spi_xfer { + /*! + * Transmit buffer. + */ + const void *tx_buf; + /*! + * Receive buffer. + */ + void *rx_buf; + /*! + * Data transfered count. + */ + unsigned int count; + + /*! + * Function to read the FIFO data to rx_buf. + */ + void (*rx_get) (struct mxc_spi *, u32 val); + /*! + * Function to get the data to be written to FIFO. + */ + u32(*tx_get) (struct mxc_spi *); +}; + +/*! + * This structure is a way for the low level driver to define their own + * \b spi_master structure. This structure includes the core \b spi_master + * structure that is provided by Linux SPI Framework/driver as an + * element and has other elements that are specifically required by this + * low-level driver. + */ +struct mxc_spi { + /*! + * SPI Master and a simple I/O queue runner. + */ + struct spi_bitbang mxc_bitbang; + /*! + * Completion flags used in data transfers. + */ + struct completion xfer_done; + /*! + * Data transfer structure. + */ + struct mxc_spi_xfer transfer; + /*! + * Resource structure, which will maintain base addresses and IRQs. + */ + struct resource *res; + /*! + * Base address of CSPI, used in readl and writel. + */ + void *base; + /*! + * CSPI IRQ number. + */ + int irq; + /*! + * CSPI Clock id. + */ + struct clk *clk; + /*! + * CSPI input clock SCLK. + */ + unsigned long spi_ipg_clk; + /*! + * CSPI registers' bit pattern. + */ + struct mxc_spi_unique_def *spi_ver_def; +}; + +#define MXC_SPI_BUF_RX(type) \ +void mxc_spi_buf_rx_##type(struct mxc_spi *master_drv_data, u32 val)\ +{\ + type *rx = master_drv_data->transfer.rx_buf;\ + *rx++ = (type)val;\ + master_drv_data->transfer.rx_buf = rx;\ +} + +#define MXC_SPI_BUF_TX(type) \ +u32 mxc_spi_buf_tx_##type(struct mxc_spi *master_drv_data)\ +{\ + u32 val;\ + const type *tx = master_drv_data->transfer.tx_buf;\ + val = *tx++;\ + master_drv_data->transfer.tx_buf = tx;\ + return val;\ +} +MXC_SPI_BUF_RX(u8) + MXC_SPI_BUF_TX(u8) + MXC_SPI_BUF_RX(u16) + MXC_SPI_BUF_TX(u16) + MXC_SPI_BUF_RX(u32) + MXC_SPI_BUF_TX(u32) + +/*! + * This function enables CSPI interrupt(s) + * + * @param master_data the pointer to mxc_spi structure + * @param irqs the irq(s) to set (can be a combination) + * + * @return This function returns 0 if successful, -1 otherwise. + */ +static int spi_enable_interrupt(struct mxc_spi *master_data, unsigned int irqs) +{ + if (irqs & ~((1 << master_data->spi_ver_def->intr_bit_shift) - 1)) { + return -1; + } + + __raw_writel((irqs | __raw_readl(master_data->base + MXC_CSPIINT)), + master_data->base + MXC_CSPIINT); + + return 0; +} + +/*! + * This function disables CSPI interrupt(s) + * + * @param master_data the pointer to mxc_spi structure + * @param irqs the irq(s) to reset (can be a combination) + * + * @return This function returns 0 if successful, -1 otherwise. + */ +static int spi_disable_interrupt(struct mxc_spi *master_data, unsigned int irqs) +{ + if (irqs & ~((1 << master_data->spi_ver_def->intr_bit_shift) - 1)) { + return -1; + } + + __raw_writel((~irqs & __raw_readl(master_data->base + MXC_CSPIINT)), + master_data->base + MXC_CSPIINT); + return 0; +} + +/*! + * This function sets the baud rate for the SPI module. + * + * @param master_data the pointer to mxc_spi structure + * @param baud the baud rate + * + * @return This function returns the baud rate divisor. + */ +static unsigned int spi_find_baudrate(struct mxc_spi *master_data, + unsigned int baud) +{ + unsigned int divisor; + unsigned int shift = 0; + + /* Calculate required divisor (rounded) */ + divisor = (master_data->spi_ipg_clk + baud / 2) / baud; + while (divisor >>= 1) + shift++; + MXC_CSPICTRL_ADJUST_SHIFT(shift); + if (shift > MXC_CSPICTRL_MAXDATRATE) + shift = MXC_CSPICTRL_MAXDATRATE; + + return (shift << MXC_CSPICTRL_DATASHIFT); +} + +/*! + * This function gets the received data. + * + * @param base the CSPI base address + * + * @return This function returns Rx FIFO data read. + */ +static unsigned int spi_get_rx_data(void *base) +{ + return __raw_readl(base + MXC_CSPIRXDATA); +} + +/*! + * This function loads the transmit fifo. + * + * @param base the CSPI base address + * @param val the data to put in the TxFIFO + */ +static void spi_put_tx_data(void *base, unsigned int val) +{ + unsigned int ctrl_reg; + + __raw_writel(val, base + MXC_CSPITXDATA); + + ctrl_reg = __raw_readl(base + MXC_CSPICTRL); + + ctrl_reg |= MXC_CSPICTRL_XCH; + + __raw_writel(ctrl_reg, base + MXC_CSPICTRL); + + return; +} + +/*! + * This function configures the hardware CSPI for the current SPI device. + * It sets the word size, transfer mode, data rate for this device. + * + * @param spi the current SPI device + * @param is_active indicates whether to active/deactivate the current device + */ +void mxc_spi_chipselect(struct spi_device *spi, int is_active) +{ + struct mxc_spi *master_drv_data; + struct mxc_spi_xfer *ptransfer; + struct mxc_spi_unique_def *spi_ver_def; + unsigned int ctrl_reg; + unsigned int ctrl_mask; + unsigned int xfer_len; + + if (is_active == BITBANG_CS_INACTIVE) { + /*Need to deselect the slave */ + return; + } + + /* Get the master controller driver data from spi device's master */ + + master_drv_data = spi_master_get_devdata(spi->master); + spi_ver_def = master_drv_data->spi_ver_def; + + xfer_len = spi->bits_per_word; + + /* Control Register Settings for transfer to this slave */ + + ctrl_reg = __raw_readl(master_drv_data->base + MXC_CSPICTRL); + + ctrl_mask = + (MXC_CSPICTRL_LOWPOL | MXC_CSPICTRL_PHA | MXC_CSPICTRL_HIGHSSPOL | + MXC_CSPICTRL_CSMASK << spi_ver_def->cs_shift | + MXC_CSPICTRL_DATAMASK << MXC_CSPICTRL_DATASHIFT | + spi_ver_def->bc_mask << spi_ver_def->bc_shift); + ctrl_reg &= ~ctrl_mask; + + ctrl_reg |= + ((spi->chip_select & MXC_CSPICTRL_CSMASK) << spi_ver_def->cs_shift); + ctrl_reg |= spi_find_baudrate(master_drv_data, spi->max_speed_hz); + ctrl_reg |= + (((xfer_len - 1) & spi_ver_def->bc_mask) << spi_ver_def->bc_shift); + if (spi->mode & SPI_CPHA) + ctrl_reg |= MXC_CSPICTRL_PHA; + if (!(spi->mode & SPI_CPOL)) + ctrl_reg |= MXC_CSPICTRL_LOWPOL; + if (spi->mode & SPI_CS_HIGH) + ctrl_reg |= MXC_CSPICTRL_HIGHSSPOL; + + __raw_writel(ctrl_reg, master_drv_data->base + MXC_CSPICTRL); + + /* Initialize the functions for transfer */ + ptransfer = &master_drv_data->transfer; + if (xfer_len <= 8) { + ptransfer->rx_get = mxc_spi_buf_rx_u8; + ptransfer->tx_get = mxc_spi_buf_tx_u8; + } else if (xfer_len <= 16) { + ptransfer->rx_get = mxc_spi_buf_rx_u16; + ptransfer->tx_get = mxc_spi_buf_tx_u16; + } else if (xfer_len <= 32) { + ptransfer->rx_get = mxc_spi_buf_rx_u32; + ptransfer->tx_get = mxc_spi_buf_tx_u32; + } +#ifdef CONFIG_SPI_MXC_TEST_LOOPBACK + { + struct spi_chip_info *lb_chip = + (struct spi_chip_info *)spi->controller_data; + if (!lb_chip) + __raw_writel(0, master_drv_data->base + MXC_CSPITEST); + else if (lb_chip->lb_enable) + __raw_writel(MXC_CSPITEST_LBC, + master_drv_data->base + MXC_CSPITEST); + } +#endif + return; +} + +/*! + * This function is called when an interrupt occurs on the SPI modules. + * It is the interrupt handler for the SPI modules. + * + * @param irq the irq number + * @param dev_id the pointer on the device + * + * @return The function returns IRQ_HANDLED when handled. + */ +static irqreturn_t mxc_spi_isr(int irq, void *dev_id) +{ + struct mxc_spi *master_drv_data = dev_id; + irqreturn_t ret = IRQ_NONE; + unsigned int status; + + /* Read the interrupt status register to determine the source */ + status = __raw_readl(master_drv_data->base + MXC_CSPISTAT); + + /* Rx is given higher priority - Handle it first */ + if (status & MXC_CSPISTAT_RR) { + u32 rx_tmp = spi_get_rx_data(master_drv_data->base); + + if (master_drv_data->transfer.rx_buf) + master_drv_data->transfer.rx_get(master_drv_data, + rx_tmp); + + ret = IRQ_HANDLED; + } + + (master_drv_data->transfer.count)--; + /* Handle the Tx now */ + if (master_drv_data->transfer.count) { + if (master_drv_data->transfer.tx_buf) { + u32 tx_tmp = + master_drv_data->transfer.tx_get(master_drv_data); + + spi_put_tx_data(master_drv_data->base, tx_tmp); + } + } else { + complete(&master_drv_data->xfer_done); + } + + /* Clear the interrupt status */ + //__raw_writel(spi_ver_def->spi_status_transfer_complete, + // master_drv_data->base + MXC_CSPISTAT); + + return ret; +} + +/*! + * This function initialize the current SPI device. + * + * @param spi the current SPI device. + * + */ +int mxc_spi_setup(struct spi_device *spi) +{ + struct mxc_spi *master_data = spi_master_get_devdata(spi->master); + + if ((spi->max_speed_hz < 0) + || (spi->max_speed_hz > (master_data->spi_ipg_clk / 4))) + return -EINVAL; + + if (!spi->bits_per_word) + spi->bits_per_word = 8; + + pr_debug("%s: mode %d, %u bpw, %d hz\n", __FUNCTION__, + spi->mode, spi->bits_per_word, spi->max_speed_hz); + + return 0; +} + +/*! + * This function is called when the data has to transfer from/to the + * current SPI device. It enables the Rx interrupt, initiates the transfer. + * When Rx interrupt occurs, the completion flag is set. It then disables + * the Rx interrupt. + * + * @param spi the current spi device + * @param t the transfer request - read/write buffer pairs + * + * @return Returns 0 on success -1 on failure. + */ +int mxc_spi_transfer(struct spi_device *spi, struct spi_transfer *t) +{ + struct mxc_spi *master_drv_data = NULL; + + /* Get the master controller driver data from spi device's master */ + + master_drv_data = spi_master_get_devdata(spi->master); + + /* Modify the Tx, Rx, Count */ + + master_drv_data->transfer.tx_buf = t->tx_buf; + master_drv_data->transfer.rx_buf = t->rx_buf; + master_drv_data->transfer.count = t->len; + INIT_COMPLETION(master_drv_data->xfer_done); + + /* Enable the Rx Interrupts */ + + spi_enable_interrupt(master_drv_data, MXC_CSPIINT_RREN); + + /* Perform single Tx transaction */ + + spi_put_tx_data(master_drv_data->base, + master_drv_data->transfer.tx_get(master_drv_data)); + + /* Wait for transfer completion */ + + wait_for_completion(&master_drv_data->xfer_done); + + /* Disable the Rx Interrupts */ + + spi_disable_interrupt(master_drv_data, MXC_CSPIINT_RREN); + + return (t->len - master_drv_data->transfer.count); +} + +/*! + * This function releases the current SPI device's resources. + * + * @param spi the current SPI device. + * + */ +void mxc_spi_cleanup(struct spi_device *spi) +{ +} + +/*! + * This function is called during the driver binding process. Based on the CSPI + * hardware module that is being probed this function adds the appropriate SPI module + * structure in the SPI core driver. + * + * @param pdev the device structure used to store device specific + * information that is used by the suspend, resume and remove + * functions. + * + * @return The function returns 0 on successful registration and initialization + * of CSPI module. Otherwise returns specific error code. + */ +static int mxc_spi_probe(struct platform_device *pdev) +{ + struct mxc_spi_master *mxc_platform_info; + struct spi_master *master; + struct mxc_spi *master_drv_data = NULL; + unsigned int spi_ver; + int ret = -ENODEV; + + /* Get the platform specific data for this master device */ + + mxc_platform_info = (struct mxc_spi_master *)pdev->dev.platform_data; + if (!mxc_platform_info) { + dev_err(&pdev->dev, "can't get the platform data for CSPI\n"); + return -EINVAL; + } + + /* Allocate SPI master controller */ + + master = spi_alloc_master(&pdev->dev, sizeof(struct mxc_spi)); + if (!master) { + dev_err(&pdev->dev, "can't alloc for spi_master\n"); + return -ENOMEM; + } + + /* Set this device's driver data to master */ + + platform_set_drvdata(pdev, master); + + /* Set this master's data from platform_info */ + + master->bus_num = pdev->id + 1; + master->num_chipselect = mxc_platform_info->maxchipselect; + + /* Set the master controller driver data for this master */ + + master_drv_data = spi_master_get_devdata(master); + master_drv_data->mxc_bitbang.master = spi_master_get(master); + + /* Set the master bitbang data */ + + master_drv_data->mxc_bitbang.chipselect = mxc_spi_chipselect; + master_drv_data->mxc_bitbang.txrx_bufs = mxc_spi_transfer; + master_drv_data->mxc_bitbang.master->setup = mxc_spi_setup; + master_drv_data->mxc_bitbang.master->cleanup = mxc_spi_cleanup; + + /* Initialize the completion object */ + + init_completion(&master_drv_data->xfer_done); + + /* Set the master controller register addresses and irqs */ + + master_drv_data->res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!master_drv_data->res) { + dev_err(&pdev->dev, "can't get platform resource for CSPI%d\n", + master->bus_num); + ret = -ENOMEM; + goto err; + } + + if (!request_mem_region(master_drv_data->res->start, + master_drv_data->res->end - + master_drv_data->res->start + 1, pdev->name)) { + dev_err(&pdev->dev, "request_mem_region failed for CSPI%d\n", + master->bus_num); + ret = -ENOMEM; + goto err; + } + + master_drv_data->base = (void *)IO_ADDRESS(master_drv_data->res->start); + if (!master_drv_data->base) { + dev_err(&pdev->dev, "invalid base address for CSPI%d\n", + master->bus_num); + ret = -EINVAL; + goto err1; + } + + master_drv_data->irq = platform_get_irq(pdev, 0); + if (!master_drv_data->irq) { + dev_err(&pdev->dev, "can't get IRQ for CSPI%d\n", + master->bus_num); + ret = -EINVAL; + goto err1; + } + + /* Register for SPI Interrupt */ + + ret = request_irq(master_drv_data->irq, mxc_spi_isr, + 0, "CSPI_IRQ", master_drv_data); + if (ret != 0) { + dev_err(&pdev->dev, "request_irq failed for CSPI%d\n", + master->bus_num); + goto err1; + } + + /* Setup any GPIO active */ + + gpio_spi_active(master->bus_num - 1); + + /* Identify SPI version */ + + spi_ver = mxc_platform_info->spi_version; + if (spi_ver == 7) { + master_drv_data->spi_ver_def = &spi_ver_0_7; + } else if (spi_ver == 5) { + master_drv_data->spi_ver_def = &spi_ver_0_5; + } else if (spi_ver == 4) { + master_drv_data->spi_ver_def = &spi_ver_0_4; + } else if (spi_ver == 0) { + master_drv_data->spi_ver_def = &spi_ver_0_0; + } + + dev_dbg(&pdev->dev, "SPI_REV 0.%d\n", spi_ver); + + /* Enable the CSPI Clock, CSPI Module, set as a master */ + + master_drv_data->clk = clk_get(&pdev->dev, "cspi_clk"); + clk_enable(master_drv_data->clk); + master_drv_data->spi_ipg_clk = clk_get_rate(master_drv_data->clk); + + __raw_writel(MXC_CSPIRESET_START, + master_drv_data->base + MXC_CSPIRESET); + udelay(1); + __raw_writel(MXC_CSPICTRL_ENABLE | MXC_CSPICTRL_MASTER, + master_drv_data->base + MXC_CSPICTRL); + __raw_writel(MXC_CSPIPERIOD_32KHZ, + master_drv_data->base + MXC_CSPIPERIOD); + __raw_writel(0, master_drv_data->base + MXC_CSPIINT); + + /* Start the SPI Master Controller driver */ + + ret = spi_bitbang_start(&master_drv_data->mxc_bitbang); + if (ret != 0) + goto err2; + + printk(KERN_INFO "CSPI: %s-%d probed\n", pdev->name, pdev->id); + +#ifdef CONFIG_SPI_MXC_TEST_LOOPBACK + { + int i; + struct spi_board_info *bi = &loopback_info[0]; + for (i = 0; i < ARRAY_SIZE(loopback_info); i++, bi++) { + if (bi->bus_num != master->bus_num) + continue; + + dev_info(&pdev->dev, + "registering loopback device '%s'\n", + bi->modalias); + + spi_new_device(master, bi); + } + } +#endif + return ret; + + err2: + gpio_spi_inactive(master->bus_num - 1); + clk_disable(master_drv_data->clk); + clk_put(master_drv_data->clk); + free_irq(master_drv_data->irq, master_drv_data); + err1: + release_mem_region(pdev->resource[0].start, + pdev->resource[0].end - pdev->resource[0].start + 1); + err: + spi_master_put(master); + kfree(master); + platform_set_drvdata(pdev, NULL); + return ret; +} + +/*! + * Dissociates the driver from the SPI master controller. Disables the CSPI module. + * It handles the release of SPI resources like IRQ, memory,..etc. + * + * @param pdev the device structure used to give information on which SPI + * to remove + * + * @return The function always returns 0. + */ +static int mxc_spi_remove(struct platform_device *pdev) +{ + struct spi_master *master = platform_get_drvdata(pdev); + + if (master) { + struct mxc_spi *master_drv_data = + spi_master_get_devdata(master); + + gpio_spi_inactive(master->bus_num - 1); + clk_disable(master_drv_data->clk); + + /* Disable the CSPI module */ + + __raw_writel(MXC_CSPICTRL_DISABLE, + master_drv_data->base + MXC_CSPICTRL); + + /* Unregister for SPI Interrupt */ + + free_irq(master_drv_data->irq, master_drv_data); + + release_mem_region(master_drv_data->res->start, + master_drv_data->res->end - + master_drv_data->res->start + 1); + + /* Stop the SPI Master Controller driver */ + + spi_bitbang_stop(&master_drv_data->mxc_bitbang); + + spi_master_put(master); + } + + printk(KERN_INFO "CSPI: %s-%d removed\n", pdev->name, pdev->id); + platform_set_drvdata(pdev, NULL); + + return 0; +} + +#ifdef CONFIG_PM +static int suspend_devices(struct device *dev, void *pm_message) +{ + pm_message_t *state = pm_message; + + if (dev->power.power_state.event != state->event) { + dev_warn(dev, "mismatch in pm state request\n"); + return -1; + } + + return 0; +} + +static int spi_bitbang_suspend(struct spi_bitbang *bitbang) +{ + unsigned long flags; + unsigned limit = 500; + + spin_lock_irqsave(&bitbang->lock, flags); + while (!list_empty(&bitbang->queue) && limit--) { + spin_unlock_irqrestore(&bitbang->lock, flags); + + dev_dbg(bitbang->master->cdev.dev, "wait for queue\n"); + msleep(10); + + spin_lock_irqsave(&bitbang->lock, flags); + } + if (!list_empty(&bitbang->queue)) { + dev_err(bitbang->master->cdev.dev, "queue didn't empty\n"); + return -EBUSY; + } + spin_unlock_irqrestore(&bitbang->lock, flags); + + return 0; +} + +static void spi_bitbang_resume(struct spi_bitbang *bitbang) +{ + spin_lock_init(&bitbang->lock); + INIT_LIST_HEAD(&bitbang->queue); + + bitbang->busy = 0; +} + +/*! + * This function puts the SPI master controller in low-power mode/state. + * + * @param pdev the device structure used to give information on which SDHC + * to suspend + * @param state the power state the device is entering + * + * @return The function always returns 0. + */ +static int mxc_spi_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct spi_master *master = platform_get_drvdata(pdev); + struct mxc_spi *master_drv_data = spi_master_get_devdata(master); + int ret = 0; + + if (device_for_each_child(&pdev->dev, &state, suspend_devices) != 0) { + dev_warn(&pdev->dev, "suspend aborted\n"); + return -EINVAL; + } + + spi_bitbang_suspend(&master_drv_data->mxc_bitbang); + __raw_writel(MXC_CSPICTRL_DISABLE, + master_drv_data->base + MXC_CSPICTRL); + + clk_disable(master_drv_data->clk); + gpio_spi_inactive(master->bus_num - 1); + + return ret; +} + +/*! + * This function brings the SPI master controller back from low-power state. + * + * @param pdev the device structure used to give information on which SDHC + * to resume + * + * @return The function always returns 0. + */ +static int mxc_spi_resume(struct platform_device *pdev) +{ + struct spi_master *master = platform_get_drvdata(pdev); + struct mxc_spi *master_drv_data = spi_master_get_devdata(master); + + gpio_spi_active(master->bus_num - 1); + clk_enable(master_drv_data->clk); + + spi_bitbang_resume(&master_drv_data->mxc_bitbang); + __raw_writel(MXC_CSPICTRL_ENABLE | MXC_CSPICTRL_MASTER, + master_drv_data->base + MXC_CSPICTRL); + + return 0; +} +#else +#define mxc_spi_suspend NULL +#define mxc_spi_resume NULL +#endif /* CONFIG_PM */ + +/*! + * This structure contains pointers to the power management callback functions. + */ +static struct platform_driver mxc_spi_driver = { + .driver = { + .name = "mxc_spi", + .bus = &platform_bus_type, + .owner = THIS_MODULE, + }, + .probe = mxc_spi_probe, + .remove = mxc_spi_remove, + .suspend = mxc_spi_suspend, + .resume = mxc_spi_resume, +}; + +/*! + * This function implements the init function of the SPI device. + * It is called when the module is loaded. It enables the required + * clocks to CSPI module(if any) and activates necessary GPIO pins. + * + * @return This function returns 0. + */ +static int __init mxc_spi_init(void) +{ + pr_debug("Registering the SPI Controller Driver\n"); + return platform_driver_register(&mxc_spi_driver); +} + +/*! + * This function implements the exit function of the SPI device. + * It is called when the module is unloaded. It deactivates the + * the GPIO pin associated with CSPI hardware modules. + * + */ +static void __exit mxc_spi_exit(void) +{ + pr_debug("Unregistering the SPI Controller Driver\n"); + platform_driver_unregister(&mxc_spi_driver); +} + +subsys_initcall(mxc_spi_init); +module_exit(mxc_spi_exit); + +MODULE_DESCRIPTION("SPI Master Controller driver"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/spi/mxc_spi.h b/drivers/spi/mxc_spi.h new file mode 100644 index 000000000000..42b177493f79 --- /dev/null +++ b/drivers/spi/mxc_spi.h @@ -0,0 +1,153 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mxc_spi.h + * @brief This header file contains SPI driver low level register definitions. + * + * @ingroup SPI + */ + +#ifndef __MXC_SPI_H__ +#define __MXC_SPI_H__ + +#include <asm/hardware.h> +#include <asm/mach-types.h> + +#define MXC_CSPIRXDATA 0x00 +#define MXC_CSPITXDATA 0x04 +#define MXC_CSPICTRL 0x08 +#define MXC_CSPIINT 0x0C +#define MXC_CSPIDMA 0x10 +#define MXC_CSPISTAT 0x14 +#define MXC_CSPIPERIOD 0x18 +#define MXC_CSPITEST 0x1C +#define MXC_CSPIRESET 0x00 + +#define MXC_CSPICTRL_ENABLE 0x1 +#define MXC_CSPICTRL_DISABLE 0x0 +#define MXC_CSPICTRL_MASTER (1 << 1) +#define MXC_CSPICTRL_SLAVE 0x0 +#define MXC_CSPICTRL_XCH (1 << 2) +#define MXC_CSPICTRL_SMC (1 << 3) +#define MXC_CSPICTRL_LOWPOL (1 << 4) +#define MXC_CSPICTRL_HIGHPOL 0x0 +#define MXC_CSPICTRL_PHA (1 << 5) +#define MXC_CSPICTRL_NOPHA 0x0 +#define MXC_CSPICTRL_SSCTL (1 << 6) +#define MXC_CSPICTRL_HIGHSSPOL (1 << 7) +#define MXC_CSPICTRL_LOWSSPOL 0x0 +#define MXC_CSPICTRL_CSMASK 0x3 +#define MXC_CSPICTRL_MAXDATRATE 0x7 +#define MXC_CSPICTRL_DATAMASK 0x7 +#define MXC_CSPICTRL_DATASHIFT 16 +#define MXC_CSPICTRL_ADJUST_SHIFT(x) ((x) -= 2) + +#define MXC_CSPICTRL_CSSHIFT_0_7 12 +#define MXC_CSPICTRL_BCSHIFT_0_7 20 +#define MXC_CSPICTRL_BCMASK_0_7 0xFFF +#define MXC_CSPICTRL_DRCTRLSHIFT_0_7 8 + +#define MXC_CSPICTRL_CSSHIFT_0_5 12 +#define MXC_CSPICTRL_BCSHIFT_0_5 20 +#define MXC_CSPICTRL_BCMASK_0_5 0xFFF +#define MXC_CSPICTRL_DRCTRLSHIFT_0_5 8 + +#define MXC_CSPICTRL_CSSHIFT_0_4 24 +#define MXC_CSPICTRL_BCSHIFT_0_4 8 +#define MXC_CSPICTRL_BCMASK_0_4 0x1F +#define MXC_CSPICTRL_DRCTRLSHIFT_0_4 20 + +#define MXC_CSPICTRL_CSSHIFT_0_0 19 +#define MXC_CSPICTRL_BCSHIFT_0_0 0 +#define MXC_CSPICTRL_BCMASK_0_0 0x1F +#define MXC_CSPICTRL_DRCTRLSHIFT_0_0 12 + +#define MXC_CSPIINT_IRQSHIFT_0_7 8 +#define MXC_CSPIINT_IRQSHIFT_0_5 9 +#define MXC_CSPIINT_IRQSHIFT_0_4 9 +#define MXC_CSPIINT_IRQSHIFT_0_0 18 +#define MXC_CSPIINT_TEEN (1 << 0) +#define MXC_CSPIINT_THEN (1 << 1) +#define MXC_CSPIINT_TFEN (1 << 2) +#define MXC_CSPIINT_RREN (1 << 3) +#define MXC_CSPIINT_RHEN (1 << 4) +#define MXC_CSPIINT_RFEN (1 << 5) +#define MXC_CSPIINT_ROEN (1 << 6) +#define MXC_CSPIINT_TCEN_0_7 (1 << 7) +#define MXC_CSPIINT_TCEN_0_5 (1 << 8) +#define MXC_CSPIINT_TCEN_0_4 (1 << 8) +#define MXC_CSPIINT_TCEN_0_0 (1 << 3) +#define MXC_CSPIINT_BOEN_0_7 0 +#define MXC_CSPIINT_BOEN_0_5 (1 << 7) +#define MXC_CSPIINT_BOEN_0_4 (1 << 7) +#define MXC_CSPIINT_BOEN_0_0 (1 << 8) + +#define MXC_CSPISTAT_TE (1 << 0) +#define MXC_CSPISTAT_TH (1 << 1) +#define MXC_CSPISTAT_TF (1 << 2) +#define MXC_CSPISTAT_RR (1 << 3) +#define MXC_CSPISTAT_RH (1 << 4) +#define MXC_CSPISTAT_RF (1 << 5) +#define MXC_CSPISTAT_RO (1 << 6) +#define MXC_CSPISTAT_TC_0_7 (1 << 7) +#define MXC_CSPISTAT_TC_0_5 (1 << 8) +#define MXC_CSPISTAT_TC_0_4 (1 << 8) +#define MXC_CSPISTAT_TC_0_0 (1 << 3) +#define MXC_CSPISTAT_BO_0_7 0 +#define MXC_CSPISTAT_BO_0_5 (1 << 7) +#define MXC_CSPISTAT_BO_0_4 (1 << 7) +#define MXC_CSPISTAT_BO_0_0 (1 << 8) + +#define MXC_CSPIPERIOD_32KHZ (1 << 15) + +#define MXC_CSPITEST_LBC (1 << 14) + +#define MXC_CSPIRESET_START 1 + +/*! + * @struct mxc_spi_unique_def + * @brief This structure contains information that differs with + * SPI master controller hardware version + */ +struct mxc_spi_unique_def { + /*! + * Width of valid bits in MXC_CSPIINT. + */ + unsigned int intr_bit_shift; + /*! + * Chip Select shift. + */ + unsigned int cs_shift; + /*! + * Bit count shift. + */ + unsigned int bc_shift; + /*! + * Bit count mask. + */ + unsigned int bc_mask; + /*! + * Data Control shift. + */ + unsigned int drctrl_shift; + /*! + * Transfer Complete shift. + */ + unsigned int xfer_complete; + /*! + * Bit counnter overflow shift. + */ + unsigned int bc_overflow; +}; +#endif //__MXC_SPI_H__ diff --git a/drivers/spi/mxc_spi_mx27.h b/drivers/spi/mxc_spi_mx27.h new file mode 100644 index 000000000000..06921f3dacd9 --- /dev/null +++ b/drivers/spi/mxc_spi_mx27.h @@ -0,0 +1,155 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mxc_spi_mx27.h + * @brief This header file contains SPI driver low level register definitions for MX27. + * + * @ingroup SPI + */ + +#ifndef __MXC_SPI_MX27_H__ +#define __MXC_SPI_MX27_H__ + +#include <asm/hardware.h> +#include <asm/mach-types.h> + +#define MXC_CSPIRXDATA 0x00 +#define MXC_CSPITXDATA 0x04 +#define MXC_CSPICTRL 0x08 +#define MXC_CSPIINT 0x0C +#define MXC_CSPIDMA 0x18 +#define MXC_CSPISTAT 0x0C +#define MXC_CSPIPERIOD 0x14 +#define MXC_CSPITEST 0x10 +#define MXC_CSPIRESET 0x1C + +#define MXC_CSPICTRL_ENABLE (1 << 10) +#define MXC_CSPICTRL_DISABLE 0x0 +#define MXC_CSPICTRL_MASTER (1 << 11) +#define MXC_CSPICTRL_SLAVE 0x0 +#define MXC_CSPICTRL_XCH (1 << 9) +#define MXC_CSPICTRL_LOWPOL (1 << 5) +#define MXC_CSPICTRL_HIGHPOL 0x0 +#define MXC_CSPICTRL_PHA (1 << 6) +#define MXC_CSPICTRL_NOPHA 0x0 +#define MXC_CSPICTRL_SSCTL (1 << 7) +#define MXC_CSPICTRL_HIGHSSPOL (1 << 8) +#define MXC_CSPICTRL_LOWSSPOL 0x0 +#define MXC_CSPICTRL_CSMASK 0x3 +#define MXC_CSPICTRL_MAXDATRATE 0x10 +#define MXC_CSPICTRL_DATAMASK 0x1F +#define MXC_CSPICTRL_DATASHIFT 14 +/* This adjustment in the shift is valid only for even states only(i.e. divide + ratio of 2). SDHC_SPIEN is not set by default. If SDHC_SPIEN bit is set in + MXC_CSPICTRL, then divide ratio is 3, this shift adjustment is invalid. */ +#define MXC_CSPICTRL_ADJUST_SHIFT(x) ((x) = ((x) - 1) * 2) + +#define MXC_CSPICTRL_CSSHIFT_0_7 12 +#define MXC_CSPICTRL_BCSHIFT_0_7 20 +#define MXC_CSPICTRL_BCMASK_0_7 0xFFF +#define MXC_CSPICTRL_DRCTRLSHIFT_0_7 8 + +#define MXC_CSPICTRL_CSSHIFT_0_5 12 +#define MXC_CSPICTRL_BCSHIFT_0_5 20 +#define MXC_CSPICTRL_BCMASK_0_5 0xFFF +#define MXC_CSPICTRL_DRCTRLSHIFT_0_5 8 + +#define MXC_CSPICTRL_CSSHIFT_0_4 24 +#define MXC_CSPICTRL_BCSHIFT_0_4 8 +#define MXC_CSPICTRL_BCMASK_0_4 0x1F +#define MXC_CSPICTRL_DRCTRLSHIFT_0_4 20 + +#define MXC_CSPICTRL_CSSHIFT_0_0 19 +#define MXC_CSPICTRL_BCSHIFT_0_0 0 +#define MXC_CSPICTRL_BCMASK_0_0 0x1F +#define MXC_CSPICTRL_DRCTRLSHIFT_0_0 12 + +#define MXC_CSPIINT_IRQSHIFT_0_7 8 +#define MXC_CSPIINT_IRQSHIFT_0_5 9 +#define MXC_CSPIINT_IRQSHIFT_0_4 9 +#define MXC_CSPIINT_IRQSHIFT_0_0 18 +#define MXC_CSPIINT_TEEN (1 << 9) +#define MXC_CSPIINT_THEN (1 << 10) +#define MXC_CSPIINT_TFEN (1 << 11) +#define MXC_CSPIINT_RREN (1 << 13) +#define MXC_CSPIINT_RHEN (1 << 14) +#define MXC_CSPIINT_RFEN (1 << 15) +#define MXC_CSPIINT_ROEN (1 << 16) +#define MXC_CSPIINT_TCEN_0_7 (1 << 7) +#define MXC_CSPIINT_TCEN_0_5 (1 << 8) +#define MXC_CSPIINT_TCEN_0_4 (1 << 8) +#define MXC_CSPIINT_TCEN_0_0 (1 << 12) +#define MXC_CSPIINT_BOEN_0_7 0 +#define MXC_CSPIINT_BOEN_0_5 (1 << 7) +#define MXC_CSPIINT_BOEN_0_4 (1 << 7) +#define MXC_CSPIINT_BOEN_0_0 (1 << 17) + +#define MXC_CSPISTAT_TE (1 << 0) +#define MXC_CSPISTAT_TH (1 << 1) +#define MXC_CSPISTAT_TF (1 << 2) +#define MXC_CSPISTAT_RR (1 << 3) +#define MXC_CSPISTAT_RH (1 << 4) +#define MXC_CSPISTAT_RF (1 << 5) +#define MXC_CSPISTAT_RO (1 << 6) +#define MXC_CSPISTAT_TC_0_7 (1 << 7) +#define MXC_CSPISTAT_TC_0_5 (1 << 8) +#define MXC_CSPISTAT_TC_0_4 (1 << 8) +#define MXC_CSPISTAT_TC_0_0 (1 << 3) +#define MXC_CSPISTAT_BO_0_7 0 +#define MXC_CSPISTAT_BO_0_5 (1 << 7) +#define MXC_CSPISTAT_BO_0_4 (1 << 7) +#define MXC_CSPISTAT_BO_0_0 (1 << 8) + +#define MXC_CSPIPERIOD_32KHZ (1 << 15) + +#define MXC_CSPITEST_LBC (1 << 14) + +#define MXC_CSPIRESET_START 1 + +/*! + * @struct mxc_spi_unique_def + * @brief This structure contains information that differs with + * SPI master controller hardware version + */ +struct mxc_spi_unique_def { + /*! + * Width of valid bits in MXC_CSPIINT. + */ + unsigned int intr_bit_shift; + /*! + * Chip Select shift. + */ + unsigned int cs_shift; + /*! + * Bit count shift. + */ + unsigned int bc_shift; + /*! + * Bit count mask. + */ + unsigned int bc_mask; + /*! + * Data Control shift. + */ + unsigned int drctrl_shift; + /*! + * Transfer Complete shift. + */ + unsigned int xfer_complete; + /*! + * Bit counnter overflow shift. + */ + unsigned int bc_overflow; +}; +#endif //__MXC_SPI_MX27_H__ diff --git a/drivers/usb/Kconfig b/drivers/usb/Kconfig index 7580aa5da0f8..a072eafb5509 100644 --- a/drivers/usb/Kconfig +++ b/drivers/usb/Kconfig @@ -89,6 +89,8 @@ config USB source "drivers/usb/core/Kconfig" +source "drivers/usb/otg/Kconfig" + source "drivers/usb/host/Kconfig" source "drivers/usb/class/Kconfig" @@ -99,6 +101,8 @@ source "drivers/usb/image/Kconfig" source "drivers/usb/mon/Kconfig" +source "drivers/usb/usblan/Kconfig" + comment "USB port drivers" depends on USB diff --git a/drivers/usb/Makefile b/drivers/usb/Makefile index 516a6400db43..f9ba676e1733 100644 --- a/drivers/usb/Makefile +++ b/drivers/usb/Makefile @@ -32,3 +32,6 @@ obj-$(CONFIG_USB) += misc/ obj-$(CONFIG_USB_ATM) += atm/ obj-$(CONFIG_USB_SPEEDTOUCH) += atm/ + +obj-$(CONFIG_USB_OTG) += otg/ +obj-$(CONFIG_USB_USBLAN) += usblan/ diff --git a/drivers/usb/core/Kconfig b/drivers/usb/core/Kconfig index 97b09f282705..77af764e7904 100644 --- a/drivers/usb/core/Kconfig +++ b/drivers/usb/core/Kconfig @@ -112,11 +112,14 @@ config USB_PERSIST If you are unsure, say N here. config USB_OTG - bool + bool "Enable host-side support for On-The-Go (OTG)" depends on USB && EXPERIMENTAL select USB_SUSPEND default n - + help + Say y here if you want support for the Host Negotiation Protocol + (HNP) and the Session Request Protocol (SRP), parts of the OTG + supplement to the USB protocol, to be included in the USB host core. config USB_OTG_WHITELIST bool "Rely on OTG Targeted Peripherals List" diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index b04d232d4c65..6fd435fff671 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -929,6 +929,14 @@ static int hub_probe(struct usb_interface *intf, const struct usb_device_id *id) desc = intf->cur_altsetting; hdev = interface_to_usbdev(intf); + /* With OTG enabled, suspending root hub results in gadget not + * working because gadget uses the same root hub. We disable + * this feature when OTG is selected. + */ +#ifdef CONFIG_USB_EHCI_ARC_OTG + hdev->autosuspend_disabled = 1; +#endif + #ifdef CONFIG_USB_OTG_BLACKLIST_HUB if (hdev->parent) { dev_warn(&intf->dev, "ignoring external hub\n"); diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig index f81d08d6538b..4f2ccf6cdbf9 100644 --- a/drivers/usb/gadget/Kconfig +++ b/drivers/usb/gadget/Kconfig @@ -274,16 +274,25 @@ config USB_OMAP default USB_GADGET select USB_GADGET_SELECTED -config USB_OTG - boolean "OTG Support" - depends on USB_GADGET_OMAP && ARCH_OMAP_OTG && USB_OHCI_HCD +config USB_GADGET_ARC + boolean "ARC USB Device Controller" + depends on ARCH_MXC + select USB_GADGET_DUALSPEED + select USB_GADGET_ARC_OTGHS if USB_EHCI_ARC_OTGHS + select USB_GADGET_ARC_OTGFS if USB_EHCI_ARC_OTGFS help - The most notable feature of USB OTG is support for a - "Dual-Role" device, which can act as either a device - or a host. The initial role choice can be changed - later, when two dual-role devices talk to each other. + Some Freescale processors have an ARC High Speed + USBOTG controller, which supports device mode. - Select this only if your OMAP board has a Mini-AB connector. + Say "y" to link the driver statically, or "m" to build a + dynamically linked module called "arc_udc" and force all + gadget drivers to also be dynamically linked. + +config USB_ARC + tristate + depends on USB_GADGET_ARC + default USB_GADGET + select USB_GADGET_SELECTED config USB_GADGET_S3C2410 boolean "S3C2410 USB Device Controller" @@ -358,6 +367,18 @@ config USB_DUMMY_HCD endchoice +config USB_OTG + boolean "OTG Support" + depends on (USB_GADGET_OMAP && ARCH_OMAP_OTG && USB_OHCI_HCD) || \ + (USB_GADGET_ARC && (ARCH_MX3 || ARCH_MX27 || ARCH_MXC91221) && USB_EHCI_HCD) + help + The most notable feature of USB OTG is support for a + "Dual-Role" device, which can act as either a device + or a host. The initial role choice can be changed + later, when two dual-role devices talk to each other. + + Select this only if your board has a Mini-AB connector. + config USB_GADGET_DUALSPEED bool depends on USB_GADGET @@ -366,6 +387,38 @@ config USB_GADGET_DUALSPEED Means that gadget drivers should include extra descriptors and code to handle dual-speed controllers. +config USB_GADGET_ARC_OTG + bool "Support for OTG prepheral port on ARC controller" + depends on USB_GADGET_ARC + default y + ---help--- + Enable support for the USB OTG port in HS/FS prepheral mode. + +choice + prompt "Select transceiver" + depends on USB_GADGET_ARC_OTG + +config USB_GADGET_ARC_OTGFS + bool "Full Speed" + depends on !USB_EHCI_ARC_OTGHS + help + The ARC OTG controller can be connected to either a FS or + a HS transceiver. + + Enable this configuration option if you want to use the FS + transceiver. + +config USB_GADGET_ARC_OTGHS + bool "High Speed" + depends on !USB_EHCI_ARC_OTGFS + help + The ARC OTG controller can be connected to either a FS or + a HS transceiver. + + Enable this configuration option if you want to use the HS + transceiver. +endchoice + # # USB Gadget Drivers # @@ -418,7 +471,7 @@ config USB_ZERO config USB_ZERO_HNPTEST boolean "HNP Test Device" - depends on USB_ZERO && USB_OTG + depends on USB_ZERO && USB_OTG2 help You can configure this device to enumerate using the device identifiers of the USB-OTG test device. That means that when diff --git a/drivers/usb/gadget/Makefile b/drivers/usb/gadget/Makefile index 904e57bf6112..ac95b08a7179 100644 --- a/drivers/usb/gadget/Makefile +++ b/drivers/usb/gadget/Makefile @@ -17,6 +17,7 @@ obj-$(CONFIG_USB_AT91) += at91_udc.o obj-$(CONFIG_USB_ATMEL_USBA) += atmel_usba_udc.o obj-$(CONFIG_USB_FSL_USB2) += fsl_usb2_udc.o obj-$(CONFIG_USB_M66592) += m66592-udc.o +obj-$(CONFIG_USB_ARC) += arcotg_udc.o # # USB gadget drivers diff --git a/drivers/usb/gadget/arcotg_udc.c b/drivers/usb/gadget/arcotg_udc.c new file mode 100644 index 000000000000..9f365abdbae6 --- /dev/null +++ b/drivers/usb/gadget/arcotg_udc.c @@ -0,0 +1,3126 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file arcotg_udc.c + * @brief arc otg device controller driver + * @ingroup USB + */ + +/*! + * Include files + */ + +#if 0 +#define DEBUG +#define VERBOSE +#define DUMP_QUEUES +#endif + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/ioport.h> +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/delay.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/timer.h> +#include <linux/list.h> +#include <linux/interrupt.h> +#include <linux/proc_fs.h> +#include <linux/mm.h> +#include <linux/platform_device.h> +#include <linux/usb/ch9.h> +#include <linux/usb_gadget.h> +#include <linux/dma-mapping.h> +#include <linux/dmapool.h> +#include <linux/fsl_devices.h> +#include <linux/usb/fsl_xcvr.h> +#include <linux/usb/otg.h> + +#include <asm/byteorder.h> +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/system.h> +#include <asm/dma.h> + +#include "arcotg_udc.h" +#include <asm/arch/arc_otg.h> + +extern void gpio_usbotg_hs_active(void); +extern void gpio_usbotg_fs_active(void); + +static void Ep0Stall(struct arcotg_udc *); +static int ep0_prime_status(struct arcotg_udc *, int); + +static int timeout; + +#undef USB_TRACE + +#define DRIVER_DESC "ARC USBOTG Device Controller driver" +#define DRIVER_AUTHOR "Freescale Semiconductor" +#define DRIVER_VERSION "1 August 2005" + +#define DMA_ADDR_INVALID (~(dma_addr_t)0) + +/*#define DEBUG_FORCE_FS 1 */ + +static const char driver_name[] = "arc_udc"; +static const char driver_desc[] = DRIVER_DESC; + +volatile static struct usb_dr_device *usb_slave_regs; + +/* it is initialized in probe() */ +static struct arcotg_udc *udc_controller; + +/*ep name is important in gadget, it should obey the convention of ep_match()*/ +/* even numbered EPs are OUT or setup, odd are IN/INTERRUPT */ +static const char *const ep_name[] = { + "ep0-control", NULL, /* everyone has ep0 */ + /* 7 configurable endpoints */ + "ep1out", + "ep1in", + "ep2out", + "ep2in", + "ep3out", + "ep3in", + "ep4out", + "ep4in", + "ep5out", + "ep5in", + "ep6out", + "ep6in", + "ep7out", + "ep7in" +}; +static const struct usb_endpoint_descriptor arcotg_ep0_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = 0, + .bmAttributes = USB_ENDPOINT_XFER_CONTROL, + .wMaxPacketSize = USB_MAX_CTRL_PAYLOAD, +}; + +static int udc_suspend(struct arcotg_udc *udc); +static int arcotg_udc_suspend(struct device *dev, pm_message_t state); +static int arcotg_udc_resume(struct device *dev); + +/******************************************************************** + * Internal Used Function +********************************************************************/ + +#ifdef DUMP_QUEUES +static void dump_ep_queue(struct arcotg_ep *ep) +{ + int ep_index; + struct arcotg_req *req; + struct ep_td_struct *dtd; + + if (list_empty(&ep->queue)) { + pr_debug("udc: empty\n"); + return; + } + + ep_index = ep_index(ep) * 2 + ep_is_in(ep); + pr_debug("udc: ep=0x%p index=%d\n", ep, ep_index); + + list_for_each_entry(req, &ep->queue, queue) { + pr_debug("udc: req=0x%p dTD count=%d\n", req, req->dtd_count); + pr_debug("udc: dTD head=0x%p tail=0x%p\n", req->head, + req->tail); + + dtd = req->head; + + while (dtd) { + if (le32_to_cpu(dtd->next_td_ptr) & DTD_NEXT_TERMINATE) + break; /* end of dTD list */ + + dtd = dtd->next_td_virt; + } + } +} +#else +static inline void dump_ep_queue(struct arcotg_ep *ep) +{ +} +#endif + +/*! + * done() - retire a request; caller blocked irqs + * @param ep endpoint pointer + * @param req require pointer + * @param status when req->req.status is -EINPROGRESSS, it is input para + * else it will be a output parameter + * req->req.status : in ep_queue() it will be set as -EINPROGRESS + */ +static void done(struct arcotg_ep *ep, struct arcotg_req *req, int status) +{ + struct arcotg_udc *udc = NULL; + unsigned char stopped = ep->stopped; + + udc = (struct arcotg_udc *)ep->udc; + + pr_debug("udc: req=0x%p\n", req); + if (req->head) { + pr_debug("udc: freeing head=0x%p\n", req->head); + dma_pool_free(udc->dtd_pool, req->head, req->head->td_dma); + } + + /* the req->queue pointer is used by ep_queue() func, in which + * the request will be added into a udc_ep->queue 'd tail + * so here the req will be dropped from the ep->queue + */ + list_del_init(&req->queue); + + /* req.status should be set as -EINPROGRESS in ep_queue() */ + if (req->req.status == -EINPROGRESS) + req->req.status = status; + else + status = req->req.status; + + pr_debug("udc: req=0x%p mapped=%x\n", req, req->mapped); + + if (req->mapped) { + pr_debug("udc: calling dma_unmap_single(buf,%s) req=0x%p " + "a=0x%x len=%d\n", + ep_is_in(ep) ? "to_dvc" : "from_dvc", + req, req->req.dma, req->req.length); + + dma_unmap_single(ep->udc->gadget.dev.parent, + req->req.dma, req->req.length, + ep_is_in(ep) ? DMA_TO_DEVICE : + DMA_FROM_DEVICE); + + req->req.dma = DMA_ADDR_INVALID; + req->mapped = 0; + pr_debug("udc: req=0x%p set req.dma=0x%x\n", req, req->req.dma); + } else { + if ((req->req.length != 0) + && (req->req.dma != DMA_ADDR_INVALID)) { + pr_debug("udc: calling dma_sync_single_for_cpu(buf,%s) " + "req=0x%p dma=0x%x len=%d\n", + ep_is_in(ep) ? "to_dvc" : "from_dvc", req, + req->req.dma, req->req.length); + + dma_sync_single_for_cpu(ep->udc->gadget.dev.parent, + req->req.dma, req->req.length, + ep_is_in(ep) ? DMA_TO_DEVICE : + DMA_FROM_DEVICE); + } + } + + if (status && (status != -ESHUTDOWN)) { + pr_debug("udc: complete %s req 0c%p stat %d len %u/%u\n", + ep->ep.name, &req->req, status, + req->req.actual, req->req.length); + } + + /* don't modify queue heads during completion callback */ + ep->stopped = 1; + + spin_unlock(&ep->udc->lock); + + /* this complete() should a func implemented by gadget layer, + * eg fsg->bulk_in_complete() */ + if (req->req.complete) { + pr_debug("udc: calling gadget's complete() req=0x%p\n", req); + req->req.complete(&ep->ep, &req->req); + pr_debug("udc: back from gadget's complete()\n"); + } + + spin_lock(&ep->udc->lock); + ep->stopped = stopped; +} + +/*! + * nuke(): delete all requests related to this ep + * called by ep_disable() within spinlock held + * add status paramter? + * @param ep endpoint pointer + * @param status current status + */ +static void nuke(struct arcotg_ep *ep, int status) +{ + pr_debug("udc: ep=0x%p\n", ep); + ep->stopped = 1; + + /* Whether this eq has request linked */ + while (!list_empty(&ep->queue)) { + struct arcotg_req *req = NULL; + + req = list_entry(ep->queue.next, struct arcotg_req, queue); + + done(ep, req, status); + } + dump_ep_queue(ep); +} + +/*------------------------------------------------------------------ + Internal Hardware related function + ------------------------------------------------------------------*/ +extern void fsl_platform_set_vbus_power(struct fsl_usb2_platform_data *pdata, + int on); + +/* + * init device controller + * @param qh_addr the aligned virt addr of ep QH addr + * @param dev device controller pointer + */ +static int dr_controller_setup(struct arcotg_udc *udc) +{ + unsigned int tmp = 0, portctrl = 0; + void *qh_addr = udc->ep_qh; + struct device *dev __attribute((unused)) = udc->gadget.dev.parent; + struct fsl_usb2_platform_data *pdata; + + pdata = udc->pdata; + + pr_debug("udc: dev=0x%p\n", dev); + + /* before here, make sure usb_slave_regs has been initialized */ + if (!qh_addr) + return -EINVAL; + + /* Stop and reset the usb controller */ + tmp = le32_to_cpu(usb_slave_regs->usbcmd); + tmp &= ~USB_CMD_RUN_STOP; + usb_slave_regs->usbcmd = cpu_to_le32(tmp); + + tmp = le32_to_cpu(usb_slave_regs->usbcmd); + tmp |= USB_CMD_CTRL_RESET; + usb_slave_regs->usbcmd = cpu_to_le32(tmp); + + /* Wait for reset to complete */ + timeout = 10000000; + while ((le32_to_cpu(usb_slave_regs->usbcmd) & USB_CMD_CTRL_RESET) && + --timeout) { + continue; + } + if (timeout == 0) { + printk(KERN_ERR "%s: TIMEOUT\n", __FUNCTION__); + return -ETIMEDOUT; + } + + /* Set the controller as device mode */ + tmp = le32_to_cpu(usb_slave_regs->usbmode); + tmp |= USB_MODE_CTRL_MODE_DEVICE; + /* Disable Setup Lockout */ + tmp |= USB_MODE_SETUP_LOCK_OFF; + usb_slave_regs->usbmode = cpu_to_le32(tmp); + + if (pdata->xcvr_ops && pdata->xcvr_ops->set_device) + pdata->xcvr_ops->set_device(); + + /* Clear the setup status */ + usb_slave_regs->usbsts = 0; + + tmp = udc->ep_qh_dma; + tmp &= USB_EP_LIST_ADDRESS_MASK; + usb_slave_regs->endpointlistaddr = cpu_to_le32(tmp); + + pr_debug("udc: vir[qh_base]=0x%p phy[qh_base]=0x%8x epla_reg=0x%8x\n", + qh_addr, (int)tmp, + le32_to_cpu(usb_slave_regs->endpointlistaddr)); + + portctrl = le32_to_cpu(usb_slave_regs->portsc1); + portctrl &= ~PORTSCX_PHY_TYPE_SEL; + + portctrl |= udc->xcvr_type; + +#ifdef DEBUG_FORCE_FS + portctrl |= 0x1000000; +#endif + + usb_slave_regs->portsc1 = cpu_to_le32(portctrl); + + fsl_platform_set_vbus_power(pdata, 0); + + return 0; +} + +/*! + * just Enable DR irq reg and Set Dr controller Run + * @param udc device controller pointer + */ + +static void dr_controller_run(struct arcotg_udc *udc) +{ + u32 tmp; + + pr_debug("%s\n", __FUNCTION__); + + /*Enable DR irq reg */ + tmp = USB_INTR_INT_EN | USB_INTR_ERR_INT_EN | + USB_INTR_PTC_DETECT_EN | USB_INTR_RESET_EN | + USB_INTR_DEVICE_SUSPEND | USB_INTR_SYS_ERR_EN; + + usb_slave_regs->usbintr = le32_to_cpu(tmp); + + /* Clear stopped bit */ + udc->stopped = 0; + + /* Set the controller as device mode */ + tmp = le32_to_cpu(usb_slave_regs->usbmode); + tmp |= USB_MODE_CTRL_MODE_DEVICE; + usb_slave_regs->usbmode = cpu_to_le32(tmp); + + /* Set controller to Run */ + tmp = le32_to_cpu(usb_slave_regs->usbcmd); + tmp |= USB_CMD_RUN_STOP; + usb_slave_regs->usbcmd = le32_to_cpu(tmp); + + return; +} + +/* + * just Disable DR irq reg and Set Dr controller Stop + * @param udc device controller pointer + */ +static void dr_controller_stop(struct arcotg_udc *udc) +{ + unsigned int tmp; + + pr_debug("%s\n", __FUNCTION__); + + /* if we're in OTG mode, and the Host is currently using the port, + * stop now and don't rip the controller out from under the + * ehci driver + */ + if (udc->gadget.is_otg) { + if (!(usb_slave_regs->otgsc & OTGSC_STS_USB_ID)) { + pr_debug("udc: Leaving early\n"); + return; + } + } + + /* disable all INTR */ + usb_slave_regs->usbintr = 0; + + /* Set stopped bit for isr */ + udc->stopped = 1; + + /* set controller to Stop */ + tmp = le32_to_cpu(usb_slave_regs->usbcmd); + tmp &= ~USB_CMD_RUN_STOP; + usb_slave_regs->usbcmd = le32_to_cpu(tmp); +} + +void dr_ep_setup(unsigned char ep_num, unsigned char dir, unsigned char ep_type) +{ + unsigned int tmp_epctrl = 0; + + tmp_epctrl = le32_to_cpu(usb_slave_regs->endptctrl[ep_num]); + if (dir) { + if (ep_num) + tmp_epctrl |= EPCTRL_TX_DATA_TOGGLE_RST; + tmp_epctrl |= EPCTRL_TX_ENABLE; + tmp_epctrl |= + ((unsigned int)(ep_type) << EPCTRL_TX_EP_TYPE_SHIFT); + } else { + if (ep_num) + tmp_epctrl |= EPCTRL_RX_DATA_TOGGLE_RST; + tmp_epctrl |= EPCTRL_RX_ENABLE; + tmp_epctrl |= + ((unsigned int)(ep_type) << EPCTRL_RX_EP_TYPE_SHIFT); + } + + usb_slave_regs->endptctrl[ep_num] = cpu_to_le32(tmp_epctrl); + + /* wait for the write reg to finish */ + timeout = 10000000; + while ((!(le32_to_cpu(usb_slave_regs->endptctrl[ep_num]) & + (tmp_epctrl & (EPCTRL_TX_ENABLE | EPCTRL_RX_ENABLE)))) + && --timeout) { + continue; + } + if (timeout == 0) { + printk(KERN_ERR "%s: TIMEOUT\n", __FUNCTION__); + } +} + +static void dr_ep_change_stall(unsigned char ep_num, unsigned char dir, + int value) +{ + unsigned int tmp_epctrl = 0; + + tmp_epctrl = le32_to_cpu(usb_slave_regs->endptctrl[ep_num]); + + if (value) { + /* set the stall bit */ + if (dir) + tmp_epctrl |= EPCTRL_TX_EP_STALL; + else + tmp_epctrl |= EPCTRL_RX_EP_STALL; + } else { + /* clear the stall bit and reset data toggle */ + if (dir) { + tmp_epctrl &= ~EPCTRL_TX_EP_STALL; + tmp_epctrl |= EPCTRL_TX_DATA_TOGGLE_RST; + } else { + tmp_epctrl &= ~EPCTRL_RX_EP_STALL; + tmp_epctrl |= EPCTRL_RX_DATA_TOGGLE_RST; + } + + } + usb_slave_regs->endptctrl[ep_num] = cpu_to_le32(tmp_epctrl); +} + +/******************************************************************** + Internal Structure Build up functions +********************************************************************/ + +/*! + * set the Endpoint Capabilites field of QH + * @param handle udc handler + * @param ep_num endpoint number + * @param dir in or out + * @param ep_type USB_ENDPOINT_XFER_CONTROL or other + * @param max_pkt_len max packet length of this endpoint + * @param zlt Zero Length Termination Select + * @param mult Mult field + */ +static void struct_ep_qh_setup(void *handle, unsigned char ep_num, + unsigned char dir, unsigned char ep_type, + unsigned int max_pkt_len, + unsigned int zlt, unsigned char mult) +{ + struct arcotg_udc *udc = NULL; + struct ep_queue_head *p_QH = NULL; + unsigned int tmp = 0; + + udc = (struct arcotg_udc *)handle; + + p_QH = &udc->ep_qh[2 * ep_num + dir]; + + /* set the Endpoint Capabilites Reg of QH */ + switch (ep_type) { + case USB_ENDPOINT_XFER_CONTROL: + /* Interrupt On Setup (IOS). for control ep */ + tmp = (max_pkt_len << EP_QUEUE_HEAD_MAX_PKT_LEN_POS) | + EP_QUEUE_HEAD_IOS; + break; + case USB_ENDPOINT_XFER_ISOC: + tmp = (max_pkt_len << EP_QUEUE_HEAD_MAX_PKT_LEN_POS) | + (mult << EP_QUEUE_HEAD_MULT_POS); + break; + case USB_ENDPOINT_XFER_BULK: + case USB_ENDPOINT_XFER_INT: + tmp = max_pkt_len << EP_QUEUE_HEAD_MAX_PKT_LEN_POS; + if (zlt) + tmp |= EP_QUEUE_HEAD_ZLT_SEL; + break; + default: + pr_debug("udc: error ep type is %d\n", ep_type); + return; + } + p_QH->max_pkt_length = le32_to_cpu(tmp); + + return; +} + +/*! + * This function only to make code looks good + * it is a collection of struct_ep_qh_setup and dr_ep_setup for ep0 + * ep0 should set OK before the bind() of gadget layer + * @param udc device controller pointer + */ +static void ep0_dr_and_qh_setup(struct arcotg_udc *udc) +{ + /* the intialization of an ep includes: fields in QH, Regs, + * arcotg_ep struct */ + struct_ep_qh_setup(udc, 0, USB_RECV, + USB_ENDPOINT_XFER_CONTROL, USB_MAX_CTRL_PAYLOAD, + 0, 0); + struct_ep_qh_setup(udc, 0, USB_SEND, + USB_ENDPOINT_XFER_CONTROL, USB_MAX_CTRL_PAYLOAD, + 0, 0); + dr_ep_setup(0, USB_RECV, USB_ENDPOINT_XFER_CONTROL); + dr_ep_setup(0, USB_SEND, USB_ENDPOINT_XFER_CONTROL); + + return; + +} + +/*********************************************************************** + Endpoint Management Functions +***********************************************************************/ + +/*! + * when configurations are set, or when interface settings change + * for example the do_set_interface() in gadget layer, + * the driver will enable or disable the relevant endpoints + * ep0 will not use this func it is enable in probe() + * @param _ep endpoint pointer + * @param desc endpoint descriptor pointer + * @return The function returns 0 on success or -1 if failed + */ +static int arcotg_ep_enable(struct usb_ep *_ep, + const struct usb_endpoint_descriptor *desc) +{ + struct arcotg_udc *udc = NULL; + struct arcotg_ep *ep = NULL; + unsigned short max = 0; + unsigned char mult = 0, zlt = 0; + int retval = 0; + unsigned long flags = 0; + char *val = NULL; /* for debug */ + + ep = container_of(_ep, struct arcotg_ep, ep); + + pr_debug("udc: %s ep.name=%s\n", __FUNCTION__, ep->ep.name); + /* catch various bogus parameters */ + if (!_ep || !desc || ep->desc || _ep->name == ep_name[0] || + (desc->bDescriptorType != USB_DT_ENDPOINT)) + /* FIXME: add judge for ep->bEndpointAddress */ + return -EINVAL; + + udc = ep->udc; + + if (!udc->driver || (udc->gadget.speed == USB_SPEED_UNKNOWN)) + return -ESHUTDOWN; + + max = le16_to_cpu(desc->wMaxPacketSize); + retval = -EINVAL; + + /* check the max package size validate for this endpoint */ + /* Refer to USB2.0 spec table 9-13, + */ + switch (desc->bmAttributes & 0x03) { + case USB_ENDPOINT_XFER_BULK: + if (strstr(ep->ep.name, "-iso") + || strstr(ep->ep.name, "-int")) + goto en_done; + mult = 0; + zlt = 1; + switch (udc->gadget.speed) { + case USB_SPEED_HIGH: + if ((max == 128) || (max == 256) || (max == 512)) + break; + default: + switch (max) { + case 4: + case 8: + case 16: + case 32: + case 64: + break; + default: + case USB_SPEED_LOW: + goto en_done; + } + } + break; + case USB_ENDPOINT_XFER_INT: + if (strstr(ep->ep.name, "-iso")) /* bulk is ok */ + goto en_done; + mult = 0; + zlt = 1; + switch (udc->gadget.speed) { + case USB_SPEED_HIGH: + if (max <= 1024) + break; + case USB_SPEED_FULL: + if (max <= 64) + break; + default: + if (max <= 8) + break; + goto en_done; + } + break; + case USB_ENDPOINT_XFER_ISOC: + if (strstr(ep->ep.name, "-bulk") || strstr(ep->ep.name, "-int")) + goto en_done; + mult = (unsigned char) + (1 + ((le16_to_cpu(desc->wMaxPacketSize) >> 11) & 0x03)); + zlt = 0; + switch (udc->gadget.speed) { + case USB_SPEED_HIGH: + if (max <= 1024) + break; + case USB_SPEED_FULL: + if (max <= 1023) + break; + default: + goto en_done; + } + break; + case USB_ENDPOINT_XFER_CONTROL: + if (strstr(ep->ep.name, "-iso") || strstr(ep->ep.name, "-int")) + goto en_done; + mult = 0; + zlt = 1; + switch (udc->gadget.speed) { + case USB_SPEED_HIGH: + case USB_SPEED_FULL: + switch (max) { + case 1: + case 2: + case 4: + case 8: + case 16: + case 32: + case 64: + break; + default: + goto en_done; + } + case USB_SPEED_LOW: + switch (max) { + case 1: + case 2: + case 4: + case 8: + break; + default: + goto en_done; + } + default: + goto en_done; + } + break; + + default: + goto en_done; + } + + /* here initialize variable of ep */ + spin_lock_irqsave(&udc->lock, flags); + ep->ep.maxpacket = max; + ep->desc = desc; + ep->stopped = 0; + + /* hardware special operation */ + + /* Init EPx Queue Head (Ep Capabilites field in QH + * according to max, zlt, mult) */ + struct_ep_qh_setup((void *)udc, (unsigned char)ep_index(ep), + (unsigned char) + ((desc->bEndpointAddress & USB_DIR_IN) ? + USB_SEND : USB_RECV), (unsigned char) + (desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK), + max, zlt, mult); + + /* Init endpoint x at here */ + /* 83xx RM chapter 16.3.2.24, here init the endpoint ctrl reg */ + dr_ep_setup((unsigned char)ep_index(ep), + (unsigned char)((desc->bEndpointAddress & USB_DIR_IN) + ? USB_SEND : USB_RECV), + (unsigned char)(desc->bmAttributes & + USB_ENDPOINT_XFERTYPE_MASK)); + + /* Now HW will be NAKing transfers to that EP, + * until a buffer is queued to it. */ + + /* should have stop the lock */ + spin_unlock_irqrestore(&udc->lock, flags); + retval = 0; + switch (desc->bmAttributes & 0x03) { + case USB_ENDPOINT_XFER_BULK: + val = "bulk"; + break; + case USB_ENDPOINT_XFER_ISOC: + val = "iso"; + break; + case USB_ENDPOINT_XFER_INT: + val = "intr"; + break; + default: + val = "ctrl"; + break; + } + + pr_debug("udc: enabled %s (ep%d%s-%s) maxpacket %d\n", ep->ep.name, + ep->desc->bEndpointAddress & 0x0f, + (desc->bEndpointAddress & USB_DIR_IN) ? "in" : "out", + val, max); + en_done: + return retval; +} + +/*! + * disable endpoint + * Any pending and uncomplete req will complete with status (-ESHUTDOWN) + * @param _ep the ep being unconfigured. May not be ep0 + * @return The function returns 0 on success or -1 if failed + */ +static int arcotg_ep_disable(struct usb_ep *_ep) +{ + struct arcotg_udc *udc = NULL; + struct arcotg_ep *ep = NULL; + unsigned long flags = 0; + + ep = container_of(_ep, struct arcotg_ep, ep); + if (!_ep || !ep->desc) { + pr_debug("udc: %s not enabled\n", _ep ? ep->ep.name : NULL); + return -EINVAL; + } + + udc = (struct arcotg_udc *)ep->udc; + + spin_lock_irqsave(&udc->lock, flags); + + /* Nuke all pending requests (does flush) */ + nuke(ep, -ESHUTDOWN); + + ep->desc = 0; + ep->stopped = 1; + spin_unlock_irqrestore(&udc->lock, flags); + + pr_debug("udc: disabled %s OK\n", _ep->name); + return 0; +} + +/*! + * allocate a request object used by this endpoint + * the main operation is to insert the req->queue to the eq->queue + * @param _ep the ep being unconfigured. May not be ep0 + * @param gfp_flags mem flags + * @return Returns the request, or null if one could not be allocated + */ +static struct usb_request *arcotg_alloc_request(struct usb_ep *_ep, + gfp_t gfp_flags) +{ + struct arcotg_req *req = NULL; + + req = kmalloc(sizeof *req, gfp_flags); + if (!req) + return NULL; + + memset(req, 0, sizeof *req); + req->req.dma = DMA_ADDR_INVALID; + pr_debug("udc: req=0x%p set req.dma=0x%x\n", req, req->req.dma); + INIT_LIST_HEAD(&req->queue); + + return &req->req; +} + +/*! + * free request memory + * @param _ep the ep being unconfigured. May not be ep0 + * @param _req usb request pointer + */ +static void arcotg_free_request(struct usb_ep *_ep, struct usb_request *_req) +{ + struct arcotg_req *req; + + req = container_of(_req, struct arcotg_req, req); + pr_debug("udc: req=0x%p\n", req); + + if (_req) + kfree(req); +} + +/*! + * Allocate an I/O buffer for the ep->req->buf. + * @param _ep endpoint pointer + * @param bytes length of the desired buffer + * @param dma pointer to the buffer's DMA address; must be valid + * when gadget layer calls this function, ma is &req->dma + * @param gfp_flags GFP_* flags to use + * @return Returns a new buffer, or null if one could not be allocated + */ +static void *arcotg_alloc_buffer(struct usb_ep *_ep, unsigned bytes, + dma_addr_t * dma, gfp_t gfp_flags) +{ + void *retval = NULL; + + if (!bytes) + return 0; + + retval = kmalloc(bytes, gfp_flags); + + if (retval) + *dma = virt_to_phys(retval); + + pr_debug("udc: ep=%s buffer=0x%p dma=0x%x len=%d\n", + _ep->name, retval, *dma, bytes); + + return retval; +} + +/*! + * Free an I/O buffer for the ep->req->buf + * @param _ep endpoint pointer + * @param buf data buf pointer + * @param dma for 834x, we will not touch dma field + * @param bytes buffer size + */ +static void arcotg_free_buffer(struct usb_ep *_ep, void *buf, + dma_addr_t dma, unsigned bytes) +{ + pr_debug("udc: buf=0x%p dma=0x%x\n", buf, dma); + if (buf) + kfree(buf); +} + +/*-------------------------------------------------------------------------*/ + +/*! + * link req's dTD queue to the end of ep's QH's dTD queue. + * @param ep endpoint pointer + * @param req request pointer + * @return The function returns 0 on success or -1 if failed + */ +static int arcotg_queue_td(struct arcotg_ep *ep, struct arcotg_req *req) +{ + int i = ep_index(ep) * 2 + ep_is_in(ep); + u32 temp, bitmask, tmp_stat; + struct ep_queue_head *dQH = &ep->udc->ep_qh[i]; + + pr_debug("udc: queue req=0x%p to ep index %d\n", req, i); + bitmask = (ep_is_in(ep)) ? (1 << (ep_index(ep) + 16)) : + (1 << (ep_index(ep))); + + /* check if the pipe is empty */ + if (!(list_empty(&ep->queue))) { + /* Add td to the end */ + struct arcotg_req *lastreq; + lastreq = list_entry(ep->queue.prev, struct arcotg_req, queue); + + WARN_ON(req->head->td_dma & 31); + lastreq->tail->next_td_ptr = req->head->td_dma; + lastreq->tail->next_td_virt = req->head; + + pr_debug("udc: ep's queue not empty. lastreq=0x%p\n", lastreq); + + /* Read prime bit, if 1 goto done */ + if (usb_slave_regs->endpointprime & cpu_to_le32(bitmask)) { + pr_debug("udc: ep's already primed\n"); + goto out; + } + + timeout = 10000000; + do { + /* Set ATDTW bit in USBCMD */ + usb_slave_regs->usbcmd |= cpu_to_le32(USB_CMD_ATDTW); + + /* Read correct status bit */ + tmp_stat = le32_to_cpu(usb_slave_regs->endptstatus) & + bitmask; + + } while ((!(usb_slave_regs->usbcmd & + cpu_to_le32(USB_CMD_ATDTW))) + && --timeout); + + if (timeout == 0) { + printk(KERN_ERR "%s: TIMEOUT\n", __FUNCTION__); + } + + /* Write ATDTW bit to 0 */ + usb_slave_regs->usbcmd &= cpu_to_le32(~USB_CMD_ATDTW); + + if (tmp_stat) + goto out; + + /* fall through to Case 1: List is empty */ + } + + /* Write dQH next pointer and terminate bit to 0 */ + WARN_ON(req->head->td_dma & 31); + dQH->next_dtd_ptr = cpu_to_le32(req->head->td_dma); + + /* Clear active and halt bit */ + temp = cpu_to_le32(~(EP_QUEUE_HEAD_STATUS_ACTIVE | + EP_QUEUE_HEAD_STATUS_HALT)); + dQH->size_ioc_int_sts &= temp; + + /* Prime endpoint by writing 1 to ENDPTPRIME */ + temp = (ep_is_in(ep)) ? (1 << (ep_index(ep) + 16)) : + (1 << (ep_index(ep))); + + pr_debug("udc: setting endpointprime. temp=0x%x (bitmask=0x%x)\n", + temp, bitmask); + usb_slave_regs->endpointprime |= cpu_to_le32(temp); + + out: + return 0; +} + +static int arcotg_build_dtd(struct arcotg_req *req, unsigned max, + struct ep_td_struct **address, + struct arcotg_udc *udc) +{ + unsigned length; + u32 swap_temp; + struct ep_td_struct *dtd; + dma_addr_t handle; + + /* how big will this packet be? */ + length = min(req->req.length - req->req.actual, max); + + dtd = dma_pool_alloc(udc->dtd_pool, GFP_KERNEL, &handle); + pr_debug("udc: dma_pool_alloc() ep(0x%p)=%s virt=0x%p dma=0x%x\n", + req->ep, req->ep->name, dtd, handle); + + /* check alignment - must be 32 byte aligned (bits 4:0 == 0) */ + BUG_ON((u32) dtd & 31); + + memset(dtd, 0, sizeof(struct ep_td_struct)); + dtd->td_dma = handle; + + /* Fill in the transfer size; set interrupt on every dtd; + set active bit */ + swap_temp = ((length << DTD_LENGTH_BIT_POS) | DTD_IOC + | DTD_STATUS_ACTIVE); + + dtd->size_ioc_sts = cpu_to_le32(swap_temp); + + /* Clear reserved field */ + swap_temp = cpu_to_le32(dtd->size_ioc_sts); + swap_temp &= ~DTD_RESERVED_FIELDS; + dtd->size_ioc_sts = cpu_to_le32(swap_temp); + + pr_debug("udc: req=0x%p dtd=0x%p req.dma=0x%x req.length=%d " + "length=%d size_ioc_sts=0x%x\n", + req, dtd, req->req.dma, req->req.length, + length, dtd->size_ioc_sts); + + /* Init all of buffer page pointers */ + swap_temp = (u32) (req->req.dma + req->req.actual); + dtd->buff_ptr0 = cpu_to_le32(swap_temp); + dtd->buff_ptr1 = cpu_to_le32(swap_temp + 0x1000); + dtd->buff_ptr2 = cpu_to_le32(swap_temp + 0x2000); + dtd->buff_ptr3 = cpu_to_le32(swap_temp + 0x3000); + dtd->buff_ptr4 = cpu_to_le32(swap_temp + 0x4000); + + req->req.actual += length; + *address = dtd; + + return length; +} + +/*! + * add USB request to dtd queue + * @param req USB request + * @param dev device pointer + * @return Returns zero on success , or a negative error code + */ +static int arcotg_req_to_dtd(struct arcotg_req *req, struct arcotg_udc *udc) +{ + unsigned max; + unsigned count; + int is_last; + int is_first = 1; + struct ep_td_struct *last_addr = NULL, *addr; + + pr_debug("udc: req=0x%p\n", req); + + max = EP_MAX_LENGTH_TRANSFER; + do { + count = arcotg_build_dtd(req, max, &addr, udc); + + if (is_first) { + is_first = 0; + req->head = addr; + } else { + if (!last_addr) { + /* FIXME last_addr not set. iso only + * case, which we don't do yet + */ + pr_debug("udc: wiping out something at 0!!\n"); + } + + last_addr->next_td_ptr = cpu_to_le32(addr->td_dma); + last_addr->next_td_virt = addr; + last_addr = addr; + } + + /* last packet is usually short (or a zlp) */ + if (unlikely(count != max)) + is_last = 1; + else if (likely(req->req.length != req->req.actual) || + req->req.zero) + is_last = 0; + else + is_last = 1; + + req->dtd_count++; + } while (!is_last); + + addr->next_td_ptr = cpu_to_le32(DTD_NEXT_TERMINATE); + addr->next_td_virt = NULL; + req->tail = addr; + + return 0; +} + +/*! + * add transfer request to queue + * @param _ep endpoint pointer + * @param _req request pointer + * @param gfp_flags GFP_* flags to use + * @return Returns zero on success , or a negative error code + */ +static int arcotg_ep_queue(struct usb_ep *_ep, struct usb_request *_req, + gfp_t gfp_flags) +{ + struct arcotg_ep *ep = container_of(_ep, struct arcotg_ep, ep); + struct arcotg_req *req = container_of(_req, struct arcotg_req, req); + struct arcotg_udc *udc; + unsigned long flags; + int is_iso = 0; + + pr_debug("udc: _req=0x%p len=%d\n", _req, _req->length); + + /* catch various bogus parameters */ + if (!_req || !req->req.complete || !req->req.buf + || !list_empty(&req->queue)) { + pr_debug("udc: %s, bad params\n", __FUNCTION__); + return -EINVAL; + } + if (!_ep || (!ep->desc && ep_index(ep))) { + pr_debug("udc: %s, bad ep\n", __FUNCTION__); + return -EINVAL; + } + if (ep->desc->bmAttributes == USB_ENDPOINT_XFER_ISOC) { + if (req->req.length > ep->ep.maxpacket) + return -EMSGSIZE; + is_iso = 1; + } + + udc = ep->udc; + if (!udc->driver || udc->gadget.speed == USB_SPEED_UNKNOWN) + return -ESHUTDOWN; + + req->ep = ep; + + /* if data phase is absent send the status phase */ + if ((ep_index(ep) == 0)) { + if (udc->ep0_state != DATA_STATE_XMIT && + udc->ep0_state != DATA_STATE_RECV && + (udc->local_setup_buff).wLength == 0) { + if (ep0_prime_status(udc, EP_DIR_IN)) + Ep0Stall(udc); + else + return 0; + } + } + + /* map virtual address to hardware */ + if (req->req.dma == DMA_ADDR_INVALID) { + req->req.dma = dma_map_single(ep->udc->gadget.dev.parent, + req->req.buf, + req->req.length, ep_is_in(ep) + ? DMA_TO_DEVICE : + DMA_FROM_DEVICE); + req->mapped = 1; + pr_debug("udc: called dma_map_single(buffer,%s) req=0x%p " + "buf=0x%p dma=0x%x len=%d\n", + ep_is_in(ep) ? "to_dvc" : "from_dvc", + req, req->req.buf, req->req.dma, req->req.length); + pr_debug("udc: req=0x%p set req.dma=0x%x\n", req, req->req.dma); + } else { + dma_sync_single_for_device(ep->udc->gadget.dev.parent, + req->req.dma, req->req.length, + ep_is_in(ep) ? DMA_TO_DEVICE : + DMA_FROM_DEVICE); + + req->mapped = 0; + pr_debug("udc: called dma_sync_single_for_device(buffer,%s) " + "req=0x%p buf=0x%p dma=0x%x len=%d\n", + ep_is_in(ep) ? "to_dvc" : "from_dvc", + req, req->req.buf, req->req.dma, req->req.length); + } + + req->req.status = -EINPROGRESS; + req->req.actual = 0; + req->dtd_count = 0; + + spin_lock_irqsave(&udc->lock, flags); + + /* push the dtds to device queue */ + if (!arcotg_req_to_dtd(req, udc)) + arcotg_queue_td(ep, req); + else + return -ENOMEM; + + /* EP0 */ + if ((ep_index(ep) == 0)) { + udc->ep0_state = DATA_STATE_XMIT; + pr_debug("udc: ep0_state now DATA_STATE_XMIT\n"); + } + + /* put this req at the end of the ep's queue */ + /* irq handler advances the queue */ + if (req != NULL) + list_add_tail(&req->queue, &ep->queue); + + dump_ep_queue(ep); + spin_unlock_irqrestore(&udc->lock, flags); + + return 0; +} + +/*! + * remove the endpoint buffer + * @param _ep endpoint pointer + * @param _req usb request pointer + * @return Returns zero on success , or a negative error code + */ +static int arcotg_ep_dequeue(struct usb_ep *_ep, struct usb_request *_req) +{ + struct arcotg_ep *ep = container_of(_ep, struct arcotg_ep, ep); + struct arcotg_req *req; + unsigned long flags; + + pr_debug("%s\n", __FUNCTION__); + if (!_ep || !_req) + return -EINVAL; + + spin_lock_irqsave(&ep->udc->lock, flags); + + /* make sure it's actually queued on this endpoint */ + list_for_each_entry(req, &ep->queue, queue) { + if (&req->req == _req) + break; + } + if (&req->req != _req) { + spin_unlock_irqrestore(&ep->udc->lock, flags); + return -EINVAL; + } + pr_debug("udc: req=0x%p\n", req); + + done(ep, req, -ECONNRESET); + + spin_unlock_irqrestore(&ep->udc->lock, flags); + return 0; + +} + +/*-------------------------------------------------------------------------*/ + +/*! + * modify the endpoint halt feature + * @param _ep the non-isochronous endpoint being stalled + * @param value 1--set halt 0--clear halt + * @return Returns zero, or a negative error code + */ +static int _arcotg_ep_set_halt(struct usb_ep *_ep, int value) +{ + + struct arcotg_ep *ep = NULL; + unsigned long flags = 0; + int status = -EOPNOTSUPP; /* operation not supported */ + unsigned char ep_dir = 0, ep_num = 0; + struct arcotg_udc *udc = NULL; + + ep = container_of(_ep, struct arcotg_ep, ep); + udc = ep->udc; + if (!_ep || !ep->desc) { + status = -EINVAL; + goto out; + } + + if (ep->desc->bmAttributes == USB_ENDPOINT_XFER_ISOC) { + status = -EOPNOTSUPP; + goto out; + } + + /* Attemp to halt IN ep will fail if any transfer requests + are still queue */ + if (value && ep_is_in(ep) && !list_empty(&ep->queue)) { + status = -EAGAIN; + goto out; + } + + status = 0; + ep_dir = ep_is_in(ep) ? USB_SEND : USB_RECV; + ep_num = (unsigned char)(ep_index(ep)); + spin_lock_irqsave(&ep->udc->lock, flags); + dr_ep_change_stall(ep_num, ep_dir, value); + spin_unlock_irqrestore(&ep->udc->lock, flags); + + if (ep_index(ep) == 0) { + udc->ep0_state = WAIT_FOR_SETUP; + pr_debug("udc: ep0_state now WAIT_FOR_SETUP\n"); + udc->ep0_dir = 0; + } + out: + pr_debug("udc: %s %s halt rc=%d\n", + ep->ep.name, value ? "set" : "clear", status); + + return status; +} + +static int arcotg_ep_set_halt(struct usb_ep *_ep, int value) +{ + return (_arcotg_ep_set_halt(_ep, value)); +} + +static int arcotg_fifo_status(struct usb_ep *_ep) +{ + struct arcotg_ep *ep; + struct arcotg_udc *udc; + int size = 0; + u32 bitmask; + struct ep_queue_head *d_qh; + + ep = container_of(_ep, struct arcotg_ep, ep); + if (!_ep || (!ep->desc && ep_index(ep) != 0)) + return -ENODEV; + + udc = (struct arcotg_udc *)ep->udc; + + if (!udc->driver || udc->gadget.speed == USB_SPEED_UNKNOWN) + return -ESHUTDOWN; + + d_qh = &ep->udc->ep_qh[ep_index(ep) * 2 + ep_is_in(ep)]; + + bitmask = (ep_is_in(ep)) ? (1 << (ep_index(ep) + 16)) : + (1 << (ep_index(ep))); + + if (le32_to_cpu(usb_slave_regs->endptstatus) & bitmask) + size = (d_qh->size_ioc_int_sts & DTD_PACKET_SIZE) + >> DTD_LENGTH_BIT_POS; + + pr_debug("%s %u\n", __FUNCTION__, size); + return size; +} + +static void arcotg_fifo_flush(struct usb_ep *_ep) +{ + struct arcotg_ep *ep; + struct arcotg_udc *udc; + u32 bitmask; + int loops = 0; + + ep = container_of(_ep, struct arcotg_ep, ep); + if (!_ep || (!ep->desc && ep_index(ep) != 0)) + return; + + udc = (struct arcotg_udc *)ep->udc; + + if (!udc->driver || udc->gadget.speed == USB_SPEED_UNKNOWN) + return; + + bitmask = (ep_is_in(ep)) ? (1 << (ep_index(ep) + 16)) : + (1 << (ep_index(ep))); + + do { + /* set the flush bit, and wait for it to clear */ + usb_slave_regs->endptflush = cpu_to_le32(bitmask); + while (usb_slave_regs->endptflush) + continue; + + /* if ENDPTSTAT bit is set, the flush failed. Retry. */ + if (!(cpu_to_le32(usb_slave_regs->endptstatus) && bitmask)) + break; + } while (++loops > 3); +} + +/*! + * endpoint callback functions + */ +static const struct usb_ep_ops arcotg_ep_ops = { + .enable = arcotg_ep_enable, + .disable = arcotg_ep_disable, + + .alloc_request = arcotg_alloc_request, + .free_request = arcotg_free_request, + + .alloc_buffer = arcotg_alloc_buffer, + .free_buffer = arcotg_free_buffer, + + .queue = arcotg_ep_queue, + .dequeue = arcotg_ep_dequeue, + + .set_halt = arcotg_ep_set_halt, + + .fifo_status = arcotg_fifo_status, + .fifo_flush = arcotg_fifo_flush, +}; + +/*------------------------------------------------------------------------- + Gadget Driver Layer Operations +-------------------------------------------------------------------------*/ + +/************************************************************************* + Gadget Driver Layer Operations +*************************************************************************/ + +/*! + * Get the current frame number (from DR frame_index Reg ) + * @param gadget gadget pointer + * @return return frame count + */ +static int arcotg_get_frame(struct usb_gadget *gadget) +{ + return (int)(le32_to_cpu(usb_slave_regs->frindex) & USB_FRINDEX_MASKS); +} + +/*----------------------------------------------------------------------- + * Tries to wake up the host connected to this gadget + * + * Return : 0-success + * Negative-this feature not enabled by host or not supported by device hw + * FIXME: RM 16.6.2.2.1 DR support this wake-up feature? + -----------------------------------------------------------------------*/ +static int arcotg_wakeup(struct usb_gadget *gadget) +{ + pr_debug("%s\n", __FUNCTION__); + return -ENOTSUPP; +} + +/*! + * sets the device selfpowered feature + * this affects the device status reported by the hw driver + * to reflect that it now has a local power supply + * usually device hw has register for this feature + * @param gadget gadget pointer + * @param is_selfpowered self powered? + * @return if selfpowered + */ +static int arcotg_set_selfpowered(struct usb_gadget *gadget, int is_selfpowered) +{ + pr_debug("%s\n", __FUNCTION__); + return -ENOTSUPP; +} + +static int can_pullup(struct arcotg_udc *udc) +{ + return udc->driver && udc->softconnect && udc->vbus_active; +} + +/*! + * Notify controller that VBUS is powered, Called by whatever + * detects VBUS sessions + * @param gadger gadger pointer + * @param is_active is active? + * @return Returns zero on success , or a negative error code + */ +static int arcotg_vbus_session(struct usb_gadget *gadget, int is_active) +{ + struct arcotg_udc *udc; + unsigned long flags; + + pr_debug("%s\n", __FUNCTION__); + + udc = container_of(gadget, struct arcotg_udc, gadget); + spin_lock_irqsave(&udc->lock, flags); + + pr_debug("udc: VBUS %s\n", is_active ? "on" : "off"); + udc->vbus_active = (is_active != 0); + +#if 0 /* FIXME manipulate pullups?? check other platforms. */ + if (can_pullup(udc)) + usb_slave_regs->usbcmd |= USB_CMD_RUN_STOP; + else + usb_slave_regs->usbcmd &= ~USB_CMD_RUN_STOP; +#endif + + spin_unlock_irqrestore(&udc->lock, flags); + return 0; +} + +/*! + * constrain controller's VBUS power usage + * This call is used by gadget drivers during SET_CONFIGURATION calls, + * reporting how much power the device may consume. For example, this + * could affect how quickly batteries are recharged. + * Returns zero on success, else negative errno. + * @param gadget gadger pointer + * @param mA power + * @return Returns zero on success , or a negative error code + */ +static int arcotg_vbus_draw(struct usb_gadget *gadget, unsigned mA) +{ + struct arcotg_udc *udc; + + pr_debug("%s\n", __FUNCTION__); + + udc = container_of(gadget, struct arcotg_udc, gadget); + + if (udc->transceiver) + return otg_set_power(udc->transceiver, mA); + + return -ENOTSUPP; +} + +/*! + * Change Data+ pullup status + * this func is used by usb_gadget_connect/disconnet + * @param gadget gadger pointer + * @param is_on on or off + * @return Returns zero on success , or a negative error code + */ +static int arcotg_pullup(struct usb_gadget *gadget, int is_on) +{ + struct arcotg_udc *udc; + + pr_debug("%s\n", __FUNCTION__); + + udc = container_of(gadget, struct arcotg_udc, gadget); + udc->softconnect = (is_on != 0); + if (can_pullup(udc)) + usb_slave_regs->usbcmd |= USB_CMD_RUN_STOP; + else + usb_slave_regs->usbcmd &= ~USB_CMD_RUN_STOP; + + return 0; +} + +static const struct usb_gadget_ops arcotg_gadget_ops = { + .get_frame = arcotg_get_frame, + .wakeup = arcotg_wakeup, + .set_selfpowered = arcotg_set_selfpowered, + .vbus_session = arcotg_vbus_session, + .vbus_draw = arcotg_vbus_draw, + .pullup = arcotg_pullup, +}; + +static void Ep0Stall(struct arcotg_udc *udc) +{ + u32 tmp; + + pr_debug("%s\n", __FUNCTION__); + /* a protocol stall */ + tmp = le32_to_cpu(usb_slave_regs->endptctrl[0]); + tmp |= EPCTRL_TX_EP_STALL | EPCTRL_RX_EP_STALL; + usb_slave_regs->endptctrl[0] = cpu_to_le32(tmp); + udc->ep0_state = WAIT_FOR_SETUP; + pr_debug("udc: ep0_state now WAIT_FOR_SETUP\n"); + udc->ep0_dir = 0; +} + +/*! + * if direction is EP_IN, the status is Device->Host + * if direction is EP_OUT, the status transaction is Device<-Host + * @param udc device controller pointer + * @param direction in or out + * @return Returns zero on success , or a negative error code + */ +static int ep0_prime_status(struct arcotg_udc *udc, int direction) +{ + + struct arcotg_req *req = udc->status_req; + struct arcotg_ep *ep; + int status = 0; + unsigned long flags; + + pr_debug("%s\n", __FUNCTION__); + if (direction == EP_DIR_IN) + udc->ep0_dir = USB_DIR_IN; + else + udc->ep0_dir = USB_DIR_OUT; + + ep = &udc->eps[0]; + udc->ep0_state = WAIT_FOR_OUT_STATUS; + pr_debug("udc: ep0_state now WAIT_FOR_OUT_STATUS\n"); + + req->ep = ep; + req->req.length = 0; + req->req.status = -EINPROGRESS; + req->req.actual = 0; + req->req.complete = NULL; + req->dtd_count = 0; + + spin_lock_irqsave(&udc->lock, flags); + + if ((arcotg_req_to_dtd(req, udc) == 0)) + status = arcotg_queue_td(ep, req); + else + return -ENOMEM; + + if (status) + printk(KERN_ERR "Can't get control status request \n"); + + list_add_tail(&req->queue, &ep->queue); + dump_ep_queue(ep); + + spin_unlock_irqrestore(&udc->lock, flags); + + return status; +} + +static int udc_reset_ep_queue(struct arcotg_udc *udc, u8 pipe) +{ + struct arcotg_ep *ep = get_ep_by_pipe(udc, pipe); + + pr_debug("%s\n", __FUNCTION__); + /* FIXME: collect completed requests? */ + if (!ep->name) + return 0; + + nuke(ep, -ECONNRESET); + + return 0; +} +static void ch9SetAddress(struct arcotg_udc *udc, u16 value, u16 index, + u16 length) +{ + pr_debug("udc: new address=%d\n", value); + + /* Save the new address to device struct */ + udc->device_address = (u8) value; + + /* Update usb state */ + udc->usb_state = USB_STATE_ADDRESS; + + /* Status phase */ + if (ep0_prime_status(udc, EP_DIR_IN)) + Ep0Stall(udc); +} + +static void ch9GetStatus(struct arcotg_udc *udc, u16 value, u16 index, + u16 length) +{ + u16 usb_status = 0; /* fix me to give correct status */ + + struct arcotg_req *req; + struct arcotg_ep *ep; + int status = 0; + unsigned long flags; + + pr_debug("%s\n", __FUNCTION__); + ep = &udc->eps[0]; + + req = container_of(arcotg_alloc_request(&ep->ep, GFP_KERNEL), + struct arcotg_req, req); + req->req.length = 2; + req->req.buf = &usb_status; + req->req.status = -EINPROGRESS; + req->req.actual = 0; + + spin_lock_irqsave(&udc->lock, flags); + + /* data phase */ + if ((arcotg_req_to_dtd(req, udc) == 0)) + status = arcotg_queue_td(ep, req); + else /* no mem */ + goto stall; + + if (status) { + printk(KERN_ERR "Can't respond to getstatus request \n"); + Ep0Stall(udc); + } else { + udc->ep0_state = DATA_STATE_XMIT; + pr_debug("udc: ep0_state now DATA_STATE_XMIT\n"); + } + + list_add_tail(&req->queue, &ep->queue); + dump_ep_queue(ep); + + spin_unlock_irqrestore(&udc->lock, flags); + return; + + stall: + Ep0Stall(udc); +} + +static void ch9SetConfig(struct arcotg_udc *udc, u16 value, u16 index, + u16 length) +{ + pr_debug("udc: 1 calling gadget driver->setup\n"); + udc->ep0_dir = USB_DIR_IN; + if (udc->driver->setup(&udc->gadget, &udc->local_setup_buff) >= 0) { + /* gadget layer deal with the status phase */ + udc->usb_state = USB_STATE_CONFIGURED; + udc->ep0_state = WAIT_FOR_OUT_STATUS; + pr_debug("udc: ep0_state now WAIT_FOR_OUT_STATUS\n"); + } +} + +static void setup_received_irq(struct arcotg_udc *udc, + struct usb_ctrlrequest *setup) +{ + u16 ptc = 0; /* port test control */ + int handled = 1; /* set to zero if we do not handle the message, */ + /* and should pass it to the gadget driver */ + + pr_debug("udc: request=0x%x\n", setup->bRequest); + /* Fix Endian (udc->local_setup_buff is cpu Endian now) */ + setup->wValue = le16_to_cpu(setup->wValue); + setup->wIndex = le16_to_cpu(setup->wIndex); + setup->wLength = le16_to_cpu(setup->wLength); + + udc_reset_ep_queue(udc, 0); + + /* We asume setup only occurs on EP0 */ + if (setup->bRequestType & USB_DIR_IN) + udc->ep0_dir = USB_DIR_IN; + else + udc->ep0_dir = USB_DIR_OUT; + + if ((setup->bRequestType & USB_TYPE_MASK) == USB_TYPE_CLASS) { + /* handle class requests */ + switch (setup->bRequest) { + + case USB_BULK_RESET_REQUEST: + udc->ep0_dir = USB_DIR_IN; + if (udc->driver->setup(&udc->gadget, + &udc->local_setup_buff) >= 0) { + udc->ep0_state = WAIT_FOR_SETUP; + pr_debug("udc: ep0_state now WAIT_FOR_SETUP\n"); + } + break; + + default: + handled = 0; + break; + } + } else if ((setup->bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) { + /* handle standard requests */ + switch (setup->bRequest) { + + case USB_REQ_GET_STATUS: + if ((setup-> + bRequestType & (USB_DIR_IN | USB_TYPE_STANDARD)) + != (USB_DIR_IN | USB_TYPE_STANDARD)) + break; + ch9GetStatus(udc, setup->wValue, setup->wIndex, + setup->wLength); + break; + + case USB_REQ_SET_ADDRESS: + if (setup->bRequestType != + (USB_DIR_OUT | USB_TYPE_STANDARD | + USB_RECIP_DEVICE)) + break; + ch9SetAddress(udc, setup->wValue, setup->wIndex, + setup->wLength); + break; + + case USB_REQ_SET_CONFIGURATION: + if (setup->bRequestType != + (USB_DIR_OUT | USB_TYPE_STANDARD | + USB_RECIP_DEVICE)) + break; + /* gadget layer take over the status phase */ + ch9SetConfig(udc, setup->wValue, setup->wIndex, + setup->wLength); + break; + case USB_REQ_SET_INTERFACE: + if (setup->bRequestType != + (USB_DIR_OUT | USB_TYPE_STANDARD | + USB_RECIP_INTERFACE)) + break; + udc->ep0_dir = USB_DIR_IN; + if (udc->driver->setup(&udc->gadget, + &udc->local_setup_buff) >= 0) + /* gadget layer take over the status phase */ + break; + /* Requests with no data phase */ + case USB_REQ_CLEAR_FEATURE: + case USB_REQ_SET_FEATURE: + { /* status transaction */ + int rc = -EOPNOTSUPP; + + if ((setup->bRequestType & USB_TYPE_MASK) != + USB_TYPE_STANDARD) + break; + + /* we only support set/clear feature for endpoint */ + if (setup->bRequestType == USB_RECIP_ENDPOINT) { + int dir = (setup->wIndex & 0x0080) ? + EP_DIR_IN : EP_DIR_OUT; + int num = (setup->wIndex & 0x000f); + struct arcotg_ep *ep; + + if (setup->wValue != 0 + || setup->wLength != 0 + || (num * 2 + dir) > USB_MAX_PIPES) + break; + ep = &udc->eps[num * 2 + dir]; + + if (setup->bRequest == + USB_REQ_SET_FEATURE) { + pr_debug("udc: udc: SET_FEATURE" + " doing set_halt\n"); + rc = _arcotg_ep_set_halt(&ep-> + ep, 1); + } else { + pr_debug("udc: CLEAR_FEATURE" + " doing clear_halt\n"); + rc = _arcotg_ep_set_halt(&ep-> + ep, 0); + } + + } else if (setup->bRequestType == + USB_RECIP_DEVICE) { + if (setup->bRequest == + USB_REQ_SET_FEATURE) { + ptc = setup->wIndex >> 8; + rc = 0; + } + if (!udc->gadget.is_otg) + break; + else if (setup->bRequest == + USB_DEVICE_B_HNP_ENABLE) + udc->gadget.b_hnp_enable = 1; + else if (setup->bRequest == + USB_DEVICE_A_HNP_SUPPORT) + udc->gadget.a_hnp_support = 1; + else if (setup->bRequest == + USB_DEVICE_A_ALT_HNP_SUPPORT) + udc->gadget.a_alt_hnp_support = + 1; + rc = 0; + } + if (rc == 0) { + /* send status only if _arcotg_ep_set_halt success */ + if (ep0_prime_status(udc, EP_DIR_IN)) + Ep0Stall(udc); + } + break; + } + default: + handled = 0; + break; + } + } else { + /* vendor requests */ + handled = 0; + } + + if (!handled) { + if (udc->driver->setup(&udc->gadget, &udc->local_setup_buff) + != 0) { + Ep0Stall(udc); + } else if (setup->bRequestType & USB_DIR_IN) { + udc->ep0_state = DATA_STATE_XMIT; + pr_debug("udc: ep0_state now DATA_STATE_XMIT\n"); + } else { + if (setup->wLength != 0) { + udc->ep0_state = DATA_STATE_RECV; + pr_debug + ("udc: ep0_state now DATA_STATE_RECV\n"); + } + } + } + + if (ptc) { + usb_slave_regs->portsc1 |= ptc << 16; + pr_debug("udc: switch to test mode.\n"); + } +} + +static void ep0_req_complete(struct arcotg_udc *udc, struct arcotg_ep *ep0, + struct arcotg_req *req) +{ + pr_debug("udc: req=0x%p ep0_state=0x%x\n", req, udc->ep0_state); + if (udc->usb_state == USB_STATE_ADDRESS) { + /* Set the new address */ + u32 new_address = (u32) udc->device_address; + usb_slave_regs->deviceaddr = cpu_to_le32(new_address << + USB_DEVICE_ADDRESS_BIT_POS); + pr_debug("udc: set deviceaddr to %d\n", + usb_slave_regs-> + deviceaddr >> USB_DEVICE_ADDRESS_BIT_POS); + } + + switch (udc->ep0_state) { + case DATA_STATE_XMIT: + + done(ep0, req, 0); + /* receive status phase */ + if (ep0_prime_status(udc, EP_DIR_OUT)) + Ep0Stall(udc); + break; + + case DATA_STATE_RECV: + + done(ep0, req, 0); + /* send status phase */ + if (ep0_prime_status(udc, EP_DIR_IN)) + Ep0Stall(udc); + break; + + case WAIT_FOR_OUT_STATUS: + done(ep0, req, 0); + udc->ep0_state = WAIT_FOR_SETUP; + pr_debug("udc: ep0_state now WAIT_FOR_SETUP\n"); + break; + + case WAIT_FOR_SETUP: + pr_debug("udc: Unexpected interrupt\n"); + break; + + default: + Ep0Stall(udc); + break; + } +} + +static void tripwire_handler(struct arcotg_udc *udc, u8 ep_num, u8 * buffer_ptr) +{ + u32 temp; + struct ep_queue_head *qh; + + qh = &udc->ep_qh[ep_num * 2 + EP_DIR_OUT]; + + /* Clear bit in ENDPTSETUPSTAT */ + temp = cpu_to_le32(1 << ep_num); + usb_slave_regs->endptsetupstat |= temp; + + /* while a hazard exists when setup package arrives */ + do { + /* Set Setup Tripwire */ + temp = cpu_to_le32(USB_CMD_SUTW); + usb_slave_regs->usbcmd |= temp; + + /* Copy the setup packet to local buffer */ + pr_debug("udc: qh=0x%p copy setup buffer from 0x%p to 0x%p\n", + qh, qh->setup_buffer, buffer_ptr); + memcpy(buffer_ptr, (u8 *) qh->setup_buffer, 8); + } while (!(le32_to_cpu(usb_slave_regs->usbcmd) & USB_CMD_SUTW)); + + /* Clear Setup Tripwire */ + temp = le32_to_cpu(usb_slave_regs->usbcmd); + temp &= ~USB_CMD_SUTW; + usb_slave_regs->usbcmd = le32_to_cpu(temp); + + timeout = 10000000; + while ((usb_slave_regs->endptsetupstat & 1) && --timeout) { + continue; + } + if (timeout == 0) { + printk(KERN_ERR "%s: TIMEOUT\n", __FUNCTION__); + } +} + +/*! + * process_ep_req(): free the completed Tds for this req + * FIXME: ERROR handling for multi-dtd requests + * @param udc device controller pointer + * @param pipe endpoint pipe + * @param req request pointer + * @return Returns zero on success , or a negative error code + */ +static int process_ep_req(struct arcotg_udc *udc, int pipe, + struct arcotg_req *curr_req) +{ + struct ep_td_struct *curr_td, *tmp_td; + int td_complete, actual, remaining_length, j, tmp; + int status = 0; + int errors = 0; + struct ep_queue_head *curr_qh = &udc->ep_qh[pipe]; + int direction = pipe % 2; + + curr_td = curr_req->head; + td_complete = 0; + actual = curr_req->req.length; + + pr_debug + ("udc: curr_req=0x%p curr_td=0x%p actual=%d size_ioc_sts=0x%x\n", + curr_req, curr_td, actual, curr_td->size_ioc_sts); + + for (j = 0; j < curr_req->dtd_count; j++) { + remaining_length = ((le32_to_cpu(curr_td->size_ioc_sts) + & DTD_PACKET_SIZE) >> DTD_LENGTH_BIT_POS); + actual -= remaining_length; + + if ((errors = le32_to_cpu(curr_td->size_ioc_sts) & + DTD_ERROR_MASK)) { + if (errors & DTD_STATUS_HALTED) { + printk(KERN_ERR "dTD error %08x \n", errors); + /* Clear the errors and Halt condition */ + tmp = le32_to_cpu(curr_qh->size_ioc_int_sts); + tmp &= ~errors; + curr_qh->size_ioc_int_sts = cpu_to_le32(tmp); + status = -EPIPE; + /*FIXME clearing active bit, update + * nextTD pointer re-prime ep */ + + break; + } + if (errors & DTD_STATUS_DATA_BUFF_ERR) { + pr_debug("udc: Transfer overflow\n"); + status = -EPROTO; + break; + } else if (errors & DTD_STATUS_TRANSACTION_ERR) { + pr_debug("udc: ISO error\n"); + status = -EILSEQ; + break; + } else + printk(KERN_ERR + "Unknown error has occured (0x%x)!\r\n", + errors); + + } else if (le32_to_cpu(curr_td->size_ioc_sts) & + DTD_STATUS_ACTIVE) { + pr_debug("udc: Request not wholly complete dtd=0x%p\n", + curr_td); + status = REQ_UNCOMPLETE; + return status; + } else if (remaining_length) + if (direction) { + pr_debug + ("udc: Transmit dTD remaining length not zero " + "(rl=%d)\n", remaining_length); + status = -EPROTO; + break; + } else { + td_complete += 1; + break; + } else { + td_complete += 1; + pr_debug("udc: dTD transmitted successful\n"); + } + + if (j != curr_req->dtd_count - 1) + curr_td = curr_td->next_td_virt; + } + + if (status) + return status; + + curr_req->req.actual = actual; + + /* Free dtd for completed/error request */ + curr_td = curr_req->head; + for (j = 0; j < curr_req->dtd_count; j++) { + tmp_td = curr_td; + if (j != curr_req->dtd_count - 1) + curr_td = curr_td->next_td_virt; + pr_debug("udc: freeing dtd 0x%p curr_req=0x%p\n", tmp_td, + curr_req); + dma_pool_free(udc->dtd_pool, tmp_td, tmp_td->td_dma); + } + + return status; +} + +static void dtd_complete_irq(struct arcotg_udc *udc) +{ + u32 bit_pos; + int i, ep_num, direction, bit_mask, status; + struct arcotg_ep *curr_ep; + struct arcotg_req *curr_req, *temp_req; + + pr_debug("%s\n", __FUNCTION__); + /* Clear the bits in the register */ + bit_pos = usb_slave_regs->endptcomplete; + usb_slave_regs->endptcomplete = bit_pos; + bit_pos = le32_to_cpu(bit_pos); + + if (!bit_pos) + return; + + for (i = 0; i < USB_MAX_ENDPOINTS * 2; i++) { + ep_num = i >> 1; + direction = i % 2; + + bit_mask = 1 << (ep_num + 16 * direction); + + if (!(bit_pos & bit_mask)) + continue; + + curr_ep = get_ep_by_pipe(udc, i); + + /* If the ep is configured */ + if (curr_ep->name == NULL) { + printk(KERN_WARNING "Invalid EP?\n"); + continue; + } + + dump_ep_queue(curr_ep); + + /* search all arcotg_reqs of ep */ + list_for_each_entry_safe(curr_req, temp_req, &curr_ep->queue, + queue) { + status = process_ep_req(udc, i, curr_req); + if (status == REQ_UNCOMPLETE) { + pr_debug + ("udc: Not all tds are completed in the req\n"); + break; + } + + if (ep_num == 0) { + ep0_req_complete(udc, curr_ep, curr_req); + break; + } else + done(curr_ep, curr_req, status); + + } + + dump_ep_queue(curr_ep); + } +} + +static void port_change_irq(struct arcotg_udc *udc) +{ + u32 speed; + + if (udc->bus_reset) + udc->bus_reset = FALSE; + + /* Bus resetting is finished */ + if (!(le32_to_cpu(usb_slave_regs->portsc1) & PORTSCX_PORT_RESET)) { + /* Get the speed */ + speed = (le32_to_cpu(usb_slave_regs->portsc1) & + PORTSCX_PORT_SPEED_MASK); + switch (speed) { + case PORTSCX_PORT_SPEED_HIGH: + udc->gadget.speed = USB_SPEED_HIGH; + break; + case PORTSCX_PORT_SPEED_FULL: + udc->gadget.speed = USB_SPEED_FULL; + break; + case PORTSCX_PORT_SPEED_LOW: + udc->gadget.speed = USB_SPEED_LOW; + break; + default: + udc->gadget.speed = USB_SPEED_UNKNOWN; + break; + } + } + pr_debug("udc: speed now %d\n", udc->gadget.speed); + + /* Update USB state */ + if (!udc->resume_state) + udc->usb_state = USB_STATE_DEFAULT; +} + +static void suspend_irq(struct arcotg_udc *udc) +{ + pr_debug("%s\n", __FUNCTION__); + + udc->resume_state = udc->usb_state; + udc->usb_state = USB_STATE_SUSPENDED; + + /* report suspend to the driver ,serial.c not support this */ + if (udc->driver->suspend) + udc->driver->suspend(&udc->gadget); +} + +static void resume_irq(struct arcotg_udc *udc) +{ + pr_debug("%s\n", __FUNCTION__); + + udc->usb_state = udc->resume_state; + udc->resume_state = 0; + + /* report resume to the driver , serial.c not support this */ + if (udc->driver->resume) + udc->driver->resume(&udc->gadget); + +} + +static int reset_queues(struct arcotg_udc *udc) +{ + u8 pipe; + + pr_debug("udc: disconnect\n"); + for (pipe = 0; pipe < udc->max_pipes; pipe++) + udc_reset_ep_queue(udc, pipe); + + /* report disconnect; the driver is already quiesced */ + udc->driver->disconnect(&udc->gadget); + + return 0; +} + +static void reset_irq(struct arcotg_udc *udc) +{ + u32 temp; + + /* Clear the device address */ + temp = le32_to_cpu(usb_slave_regs->deviceaddr); + temp &= ~USB_DEVICE_ADDRESS_MASK; + usb_slave_regs->deviceaddr = cpu_to_le32(temp); + pr_debug("udc: set deviceaddr to %d\n", + usb_slave_regs->deviceaddr >> USB_DEVICE_ADDRESS_BIT_POS); + udc->device_address = 0; + + /* Clear usb state */ + udc->usb_state = USB_STATE_DEFAULT; + + /* Clear all the setup token semaphores */ + temp = le32_to_cpu(usb_slave_regs->endptsetupstat); + usb_slave_regs->endptsetupstat = cpu_to_le32(temp); + + /* Clear all the endpoint complete status bits */ + temp = le32_to_cpu(usb_slave_regs->endptcomplete); + usb_slave_regs->endptcomplete = cpu_to_le32(temp); + + timeout = 10000000; + /* Wait until all endptprime bits cleared */ + while ((usb_slave_regs->endpointprime) && --timeout) { + continue; + } + if (timeout == 0) { + printk(KERN_ERR "%s: TIMEOUT\n", __FUNCTION__); + } + + /* Write 1s to the Flush register */ + usb_slave_regs->endptflush = 0xFFFFFFFF; + + if (le32_to_cpu(usb_slave_regs->portsc1) & PORTSCX_PORT_RESET) { + pr_debug("udc: Bus RESET\n"); + /* Bus is reseting */ + udc->bus_reset = TRUE; + udc->ep0_state = WAIT_FOR_SETUP; + pr_debug("udc: ep0_state now WAIT_FOR_SETUP\n"); + udc->ep0_dir = 0; + /* Reset all the queues, include XD, dTD, EP queue + * head and TR Queue */ + reset_queues(udc); + } else { + pr_debug("udc: Controller reset\n"); + /* initialize usb hw reg except for regs for EP, not + * touch usbintr reg */ + dr_controller_setup(udc); + + /* FIXME: Reset all internal used Queues */ + reset_queues(udc); + + ep0_dr_and_qh_setup(udc); + + /* Enable DR IRQ reg, Set Run bit, change udc state */ + dr_controller_run(udc); + udc->usb_state = USB_STATE_ATTACHED; + udc->ep0_state = WAIT_FOR_SETUP; + pr_debug("udc: ep0_state now WAIT_FOR_SETUP\n"); + udc->ep0_dir = 0; + } +} + +static irqreturn_t arcotg_udc_irq(int irq, void *_udc) +{ + struct arcotg_udc *udc = _udc; + u32 irq_src; + irqreturn_t status = IRQ_NONE; + unsigned long flags; + + if (udc->stopped) + return IRQ_NONE; /* ignore irq if we're not running */ + + spin_lock_irqsave(&udc->lock, flags); + irq_src = usb_slave_regs->usbsts & usb_slave_regs->usbintr; + /* Clear notification bits */ + usb_slave_regs->usbsts &= irq_src; + + irq_src = le32_to_cpu(irq_src); + pr_debug("udc: irq_src [0x%08x]\n", irq_src); + + /* USB Interrupt */ + if (irq_src & USB_STS_INT) { + /* Setup packet, we only support ep0 as control ep */ + pr_debug("udc: endptsetupstat=0x%x endptcomplete=0x%x\n", + usb_slave_regs->endptsetupstat, + usb_slave_regs->endptcomplete); + if (usb_slave_regs-> + endptsetupstat & cpu_to_le32(EP_SETUP_STATUS_EP0)) { + tripwire_handler(udc, 0, + (u8 *) (&udc->local_setup_buff)); + setup_received_irq(udc, &udc->local_setup_buff); + status = IRQ_HANDLED; + } + + /* completion of dtd */ + if (usb_slave_regs->endptcomplete) { + dtd_complete_irq(udc); + status = IRQ_HANDLED; + } + } + + /* SOF (for ISO transfer) */ + if (irq_src & USB_STS_SOF) { + status = IRQ_HANDLED; + } + + /* Port Change */ + if (irq_src & USB_STS_PORT_CHANGE) { + port_change_irq(udc); + status = IRQ_HANDLED; + } + + /* Reset Received */ + if (irq_src & USB_STS_RESET) { + reset_irq(udc); + status = IRQ_HANDLED; + } + + /* Sleep Enable (Suspend) */ + if (irq_src & USB_STS_SUSPEND) { + suspend_irq(udc); + status = IRQ_HANDLED; + } else if (udc->resume_state) { + resume_irq(udc); + status = IRQ_HANDLED; + } + + if (irq_src & (USB_STS_ERR | USB_STS_SYS_ERR)) { + pr_debug("udc: Error IRQ %x\n", irq_src); + status = IRQ_HANDLED; + } + + if (status != IRQ_HANDLED) { + pr_debug("udc: not handled irq_src=0x%x\n", irq_src); + } + + pr_debug("udc: irq_src [0x%08x] done. regs now=0x%08x\n", irq_src, + usb_slave_regs->usbsts & usb_slave_regs->usbintr); + pr_debug("-\n"); + pr_debug("-\n"); + spin_unlock_irqrestore(&udc->lock, flags); + + return status; +} + +/*! + * tell the controller driver about gadget layer driver + * The driver's bind function will be called to bind it to a gadget. + * @param driver for example fsg_driver from file_storage.c + * @return Returns zero on success , or a negative error code + */ +int usb_gadget_register_driver(struct usb_gadget_driver *driver) +{ + int retval = -ENODEV; + unsigned long flags = 0; + struct arcotg_udc *udc = udc_controller; + + pr_debug("udc: udc=0x%p\n", udc); + + /* standard operations */ + if (!udc) + return -ENODEV; + + if (!driver || (driver->speed != USB_SPEED_FULL + && driver->speed != USB_SPEED_HIGH) + || !driver->bind || !driver->unbind || + !driver->disconnect || !driver->setup) + return -EINVAL; + + if (udc->driver) + return -EBUSY; + + /* lock is needed but whether should use this lock or another */ + spin_lock_irqsave(&udc->lock, flags); + + driver->driver.bus = 0; + /* hook up the driver */ + udc->driver = driver; + udc->gadget.dev.driver = &driver->driver; + spin_unlock_irqrestore(&udc->lock, flags); + + retval = driver->bind(&udc->gadget); + if (retval) { + pr_debug("bind to %s --> %d\n", driver->driver.name, retval); + udc->gadget.dev.driver = 0; + udc->driver = 0; + goto out; + } + + if (udc->transceiver) { + /* Suspend the controller until OTG enables it */ + udc_suspend(udc); + pr_debug("udc: suspend udc for OTG auto detect \n"); + + /* export udc suspend/resume call to OTG */ + udc->gadget.dev.parent->driver->suspend = arcotg_udc_suspend; + udc->gadget.dev.parent->driver->resume = arcotg_udc_resume; + + /* connect to bus through transceiver */ + retval = otg_set_peripheral(udc->transceiver, &udc->gadget); + if (retval < 0) { + pr_debug("udc: can't bind to transceiver\n"); + driver->unbind(&udc->gadget); + udc->gadget.dev.driver = 0; + udc->driver = 0; + return retval; + } + } else { + /* Enable DR IRQ reg and Set usbcmd reg Run bit */ + dr_controller_run(udc); + udc->usb_state = USB_STATE_ATTACHED; + udc->ep0_state = WAIT_FOR_SETUP; + pr_debug("udc: ep0_state now WAIT_FOR_SETUP\n"); + udc->ep0_dir = 0; + } + + printk(KERN_INFO "arcotg_udc: gadget %s bound to driver %s\n", + udc->gadget.name, driver->driver.name); + + out: + return retval; +} + +EXPORT_SYMBOL(usb_gadget_register_driver); + +int usb_gadget_unregister_driver(struct usb_gadget_driver *driver) +{ + struct arcotg_ep *loop_ep; + unsigned long flags; + struct arcotg_udc *udc = udc_controller; + + pr_debug("usb_gadget_unregister_driver: udc=0x%p\n", udc); + if (!udc) + return -ENODEV; + + if (!driver || driver != udc->driver) + return -EINVAL; + + if (udc->transceiver) { + (void)otg_set_peripheral(udc->transceiver, 0); + pr_debug("udc: set peripheral=NULL\n"); + } else { + /* FIXME + pullup_disable(udc); + */ + } + + /* stop DR, disable intr */ + dr_controller_stop(udc); + + /* in fact, no needed */ + udc->usb_state = USB_STATE_ATTACHED; + udc->ep0_state = WAIT_FOR_SETUP; + pr_debug("udc: ep0_state now WAIT_FOR_SETUP\n"); + udc->ep0_dir = 0; + + /* stand operation */ + spin_lock_irqsave(&udc->lock, flags); + udc->gadget.speed = USB_SPEED_UNKNOWN; + nuke(&udc->eps[0], -ESHUTDOWN); + list_for_each_entry(loop_ep, &udc->gadget.ep_list, ep.ep_list) + nuke(loop_ep, -ESHUTDOWN); + + /* report disconnect to free up endpoints */ + pr_debug("udc: disconnect\n"); + driver->disconnect(&udc->gadget); + + spin_unlock_irqrestore(&udc->lock, flags); + + /* unbind gadget and unhook driver. */ + pr_debug("udc: unbind\n"); + driver->unbind(&udc->gadget); + udc->gadget.dev.driver = 0; + udc->driver = 0; + + printk(KERN_INFO "unregistered gadget driver '%s'\r\n", + driver->driver.name); + return 0; +} + +EXPORT_SYMBOL(usb_gadget_unregister_driver); + +/*------------------------------------------------------------------------- + PROC File System Support +-------------------------------------------------------------------------*/ +#ifdef CONFIG_USB_GADGET_DEBUG_FILES + +#include <linux/seq_file.h> + +static const char proc_filename[] = "driver/arcotg_udc"; + +static int arcotg_proc_read(char *page, char **start, off_t off, int count, + int *eof, void *_dev) +{ + char *buf = page; + char *next = buf; + unsigned size = count; + unsigned long flags; + int t, i; + u32 tmp_reg; + struct arcotg_ep *ep = NULL; + struct arcotg_req *req; + + struct arcotg_udc *udc = udc_controller; + if (off != 0) + return 0; + + spin_lock_irqsave(&udc->lock, flags); + + /* ------basic driver infomation ---- */ + t = scnprintf(next, size, + DRIVER_DESC "\n" "%s version: %s\n" + "Gadget driver: %s\n\n", driver_name, DRIVER_VERSION, + udc->driver ? udc->driver->driver.name : "(none)"); + size -= t; + next += t; + + /* ------ DR Registers ----- */ + tmp_reg = le32_to_cpu(usb_slave_regs->usbcmd); + t = scnprintf(next, size, + "USBCMD reg:\n" "SetupTW: %d\n" "Run/Stop: %s\n\n", + (tmp_reg & USB_CMD_SUTW) ? 1 : 0, + (tmp_reg & USB_CMD_RUN_STOP) ? "Run" : "Stop"); + size -= t; + next += t; + + tmp_reg = le32_to_cpu(usb_slave_regs->usbsts); + t = scnprintf(next, size, + "USB Status Reg:\n" "Dr Suspend: %d" + "Reset Received: %d" "System Error: %s" + "USB Error Interrupt: %s\n\n", + (tmp_reg & USB_STS_SUSPEND) ? 1 : 0, + (tmp_reg & USB_STS_RESET) ? 1 : 0, + (tmp_reg & USB_STS_SYS_ERR) ? "Err" : "Normal", + (tmp_reg & USB_STS_ERR) ? "Err detected" : "No err"); + size -= t; + next += t; + + tmp_reg = le32_to_cpu(usb_slave_regs->usbintr); + t = scnprintf(next, size, + "USB Intrrupt Enable Reg:\n" + "Sleep Enable: %d" "SOF Received Enable: %d" + "Reset Enable: %d\n" "System Error Enable: %d" + "Port Change Dectected Enable: %d\n" + "USB Error Intr Enable: %d" + "USB Intr Enable: %d\n\n", + (tmp_reg & USB_INTR_DEVICE_SUSPEND) ? 1 : 0, + (tmp_reg & USB_INTR_SOF_EN) ? 1 : 0, + (tmp_reg & USB_INTR_RESET_EN) ? 1 : 0, + (tmp_reg & USB_INTR_SYS_ERR_EN) ? 1 : 0, + (tmp_reg & USB_INTR_PTC_DETECT_EN) ? 1 : 0, + (tmp_reg & USB_INTR_ERR_INT_EN) ? 1 : 0, + (tmp_reg & USB_INTR_INT_EN) ? 1 : 0); + size -= t; + next += t; + + tmp_reg = le32_to_cpu(usb_slave_regs->frindex); + t = scnprintf(next, size, + "USB Frame Index Reg:" "Frame Number is 0x%x\n\n", + (tmp_reg & USB_FRINDEX_MASKS)); + size -= t; + next += t; + + tmp_reg = le32_to_cpu(usb_slave_regs->deviceaddr); + t = scnprintf(next, size, + "USB Device Address Reg:" "Device Addr is 0x%x\n\n", + (tmp_reg & USB_DEVICE_ADDRESS_MASK)); + size -= t; + next += t; + + tmp_reg = le32_to_cpu(usb_slave_regs->endpointlistaddr); + t = scnprintf(next, size, + "USB Endpoint List Address Reg:" + "Device Addr is 0x%x\n\n", + (tmp_reg & USB_EP_LIST_ADDRESS_MASK)); + size -= t; + next += t; + + tmp_reg = le32_to_cpu(usb_slave_regs->portsc1); + t = scnprintf(next, size, + "USB Port Status&Control Reg:\n" + "Port Transceiver Type : %s" "Port Speed: %s \n" + "PHY Low Power Suspend: %s" "Port Reset: %s" + "Port Suspend Mode: %s \n" "Over-current Change: %s" + "Port Enable/Disable Change: %s\n" + "Port Enabled/Disabled: %s" + "Current Connect Status: %s\n\n", ( { + char *s; + switch (tmp_reg & + PORTSCX_PTS_FSLS) + { +case PORTSCX_PTS_UTMI: +s = "UTMI"; break; case PORTSCX_PTS_ULPI: +s = "ULPI "; break; case PORTSCX_PTS_FSLS: +s = "FS/LS Serial"; break; default: + s = "None"; break;} + s;} + ), ( { + char *s; switch (tmp_reg & PORTSCX_PORT_SPEED_UNDEF) { +case PORTSCX_PORT_SPEED_FULL: +s = "Full Speed"; break; case PORTSCX_PORT_SPEED_LOW: +s = "Low Speed"; break; case PORTSCX_PORT_SPEED_HIGH: +s = "High Speed"; break; default: + s = "Undefined"; break;} + s;} + ), + (tmp_reg & PORTSCX_PHY_LOW_POWER_SPD) ? + "Normal PHY mode" : "Low power mode", + (tmp_reg & PORTSCX_PORT_RESET) ? "In Reset" : + "Not in Reset", + (tmp_reg & PORTSCX_PORT_SUSPEND) ? "In " : "Not in", + (tmp_reg & PORTSCX_OVER_CURRENT_CHG) ? "Dected" : + "No", + (tmp_reg & PORTSCX_PORT_EN_DIS_CHANGE) ? "Disable" : + "Not change", + (tmp_reg & PORTSCX_PORT_ENABLE) ? "Enable" : + "Not correct", + (tmp_reg & PORTSCX_CURRENT_CONNECT_STATUS) ? + "Attached" : "Not-Att") ; + size -= t; + next += t; + + tmp_reg = le32_to_cpu(usb_slave_regs->usbmode); + t = scnprintf(next, size, "USB Mode Reg:" "Controller Mode is : %s\n\n", ( { + char + *s; + switch + (tmp_reg + & + USB_MODE_CTRL_MODE_HOST) + { +case USB_MODE_CTRL_MODE_IDLE: +s = "Idle"; break; case USB_MODE_CTRL_MODE_DEVICE: +s = "Device Controller"; break; case USB_MODE_CTRL_MODE_HOST: +s = "Host Controller"; break; default: + s + = + "None"; + break;} + s;} + )) ; + size -= t; + next += t; + + tmp_reg = le32_to_cpu(usb_slave_regs->endptsetupstat); + t = scnprintf(next, size, + "Endpoint Setup Status Reg:" "SETUP on ep 0x%x\n\n", + (tmp_reg & EP_SETUP_STATUS_MASK)); + size -= t; + next += t; + + for (i = 0; i < USB_MAX_ENDPOINTS; i++) { + tmp_reg = le32_to_cpu(usb_slave_regs->endptctrl[i]); + t = scnprintf(next, size, "EP Ctrl Reg [0x%x]: = [0x%x]\n", + i, tmp_reg); + size -= t; + next += t; + } + tmp_reg = le32_to_cpu(usb_slave_regs->endpointprime); + t = scnprintf(next, size, "EP Prime Reg = [0x%x]\n", tmp_reg); + size -= t; + next += t; + + /* ------arcotg_udc, arcotg_ep, arcotg_request structure information ----- */ + ep = &udc->eps[0]; + t = scnprintf(next, size, "For %s Maxpkt is 0x%x index is 0x%x\n", + ep->ep.name, ep_maxpacket(ep), ep_index(ep)); + size -= t; + next += t; + + if (list_empty(&ep->queue)) { + t = scnprintf(next, size, "its req queue is empty\n\n"); + size -= t; + next += t; + } else { + list_for_each_entry(req, &ep->queue, queue) { + t = scnprintf(next, size, + "req %p actual 0x%x length 0x%x buf %p\n", + &req->req, req->req.actual, + req->req.length, req->req.buf); + size -= t; + next += t; + } + } + /* other gadget->eplist ep */ + list_for_each_entry(ep, &udc->gadget.ep_list, ep.ep_list) { + if (ep->desc) { + t = scnprintf(next, size, + "\nFor %s Maxpkt is 0x%x index is 0x%x\n", + ep->ep.name, + ep_maxpacket(ep), ep_index(ep)); + size -= t; + next += t; + + if (list_empty(&ep->queue)) { + t = scnprintf(next, size, + "its req queue is empty\n\n"); + size -= t; + next += t; + } else { + list_for_each_entry(req, &ep->queue, queue) { + t = scnprintf(next, size, + "req %p actual 0x%x length" + "0x%x buf %p\n", + &req->req, + req->req.actual, + req->req.length, + req->req.buf); + size -= t; + next += t; + } + } + } +#if 0 /* DDD debug */ + else { + t = scnprintf(next, size, "\nno desc for %s\n", + ep->ep.name); + size -= t; + next += t; + } +#endif + } + + spin_unlock_irqrestore(&udc->lock, flags); + + *eof = 1; + return count - size; +} + +#define create_proc_file() create_proc_read_entry(proc_filename, \ + 0, NULL, arcotg_proc_read, NULL) + +#define remove_proc_file() remove_proc_entry(proc_filename, NULL) + +#else /* !CONFIG_USB_GADGET_DEBUG_FILES */ + +#define create_proc_file() do {} while (0) +#define remove_proc_file() do {} while (0) + +#endif /*CONFIG_USB_GADGET_DEBUG_FILES */ + +/*-------------------------------------------------------------------------*/ + +/*! + * Release the ARC OTG specific udc structure + * it is not stand gadget function + * it is called when the last reference to the device is removed; + * it is called from the embedded kobject's release method. + * All device structures registered with the core must have a + * release method, or the kernel prints out scary complaints + * @param dev device controller pointer + */ +static void arcotg_gadget_release(struct device *dev) +{ + struct device *udc_dev = dev->parent; + + struct arcotg_udc *udc = (struct arcotg_udc *)dev_get_drvdata(udc_dev); + + complete(udc->done); + dma_free_coherent(dev, udc->ep_qh_size, udc->ep_qh, udc->ep_qh_dma); + kfree(udc); +} + +/****************************************************************** + Internal Structure Build up functions -2 +*******************************************************************/ +/*! + * this func will init resource for globle controller + * Return the udc handle on success or Null on failing + * @param pdev device controller pointer + */ +static void *struct_udc_setup(struct platform_device *pdev) +{ + struct arcotg_udc *udc = NULL; + + udc = (struct arcotg_udc *) + kmalloc(sizeof(struct arcotg_udc), GFP_KERNEL); + pr_debug("udc: kmalloc(ucd)=0x%p\n", udc); + if (udc == NULL) { + printk(KERN_ERR "malloc udc failed\n"); + goto cleanup; + } + + /* Zero out the internal USB state structure */ + memset(udc, 0, sizeof(struct arcotg_udc)); + + /* initialized QHs, take care the 2K align */ + udc->ep_qh_size = USB_MAX_PIPES * sizeof(struct ep_queue_head); + + /* Arc OTG IP-core requires 2K alignment of queuehead + * this if fullfilled by per page allocation + * by dma_alloc_coherent(...) + */ + udc->ep_qh = (struct ep_queue_head *) + dma_alloc_coherent(&pdev->dev, udc->ep_qh_size, + &udc->ep_qh_dma, GFP_KERNEL); + if (!udc->ep_qh) { + printk(KERN_ERR "malloc QHs for udc failed\n"); + goto cleanup; + } + pr_debug("udc: udc->ep_qh=0x%p\n", udc->ep_qh); + + memset(udc->ep_qh, 0, udc->ep_qh_size); + + /* need 32 byte alignment, don't cross 4K boundary */ + udc->dtd_pool = dma_pool_create("arcotg_dtd", &pdev->dev, + sizeof(struct ep_td_struct), 32, 4096); + if (!udc->dtd_pool) { + printk(KERN_ERR "dtd_pool alloc failed\n"); + goto cleanup; + } + + /* Initialize ep0 status request structure */ + /* FIXME: arcotg_alloc_request() ignores ep argument */ + udc->status_req = + container_of(arcotg_alloc_request(NULL, GFP_KERNEL), + struct arcotg_req, req); + + /* allocate a small amount of memory to get valid address */ + udc->status_req->req.buf = kmalloc(8, GFP_KERNEL); + udc->status_req->req.dma = virt_to_phys(udc->status_req->req.buf); + + pr_debug("udc: status_req=0x%p status_req->req.buf=0x%p " + "status_req->req.dma=0x%x", + udc->status_req, udc->status_req->req.buf, + udc->status_req->req.dma); + + udc->resume_state = USB_STATE_NOTATTACHED; + udc->usb_state = USB_STATE_POWERED; + udc->ep0_dir = 0; + /* initliaze the arcotg_udc lock */ + spin_lock_init(&udc->lock); + + return udc; + + cleanup: + kfree(udc); + return NULL; +} + +/*! + * set up the arcotg_ep struct for eps + * ep0out isnot used so do nothing here + * ep0in should be taken care + * It also link this arcotg_ep->ep to gadget->ep_list + * @param udc device controller pointer + * @param pipe_num pipe number + * @return Returns zero on success , or a negative error code + */ +static int struct_ep_setup(struct arcotg_udc *udc, unsigned char pipe_num) +{ + struct arcotg_ep *ep = get_ep_by_pipe(udc, pipe_num); + + /* + VDBG("pipe_num=%d name[%d]=%s", + pipe_num, pipe_num, ep_name[pipe_num]); + */ + ep->udc = udc; + strcpy(ep->name, ep_name[pipe_num]); + ep->ep.name = ep_name[pipe_num]; + ep->ep.ops = &arcotg_ep_ops; + ep->stopped = 0; + + /* for ep0: the desc defined here; + * for other eps, gadget layer called ep_enable with defined desc + */ + /* for ep0: maxP defined in desc + * for other eps, maxP is set by epautoconfig() called by gadget layer + */ + if (pipe_num == 0) { + ep->desc = &arcotg_ep0_desc; + ep->ep.maxpacket = USB_MAX_CTRL_PAYLOAD; + } else { + ep->ep.maxpacket = (unsigned short)~0; + ep->desc = NULL; + } + + /* the queue lists any req for this ep */ + INIT_LIST_HEAD(&ep->queue); + + /* arcotg_ep->ep.ep_list: gadget ep_list hold all of its eps + * so only the first should init--it is ep0' */ + + /* gagdet.ep_list used for ep_autoconfig so no ep0 */ + if (pipe_num != 0) + list_add_tail(&ep->ep.ep_list, &udc->gadget.ep_list); + + ep->gadget = &udc->gadget; + + return 0; +} + +static int board_init(struct platform_device *pdev) +{ + struct fsl_usb2_platform_data *pdata; + pdata = (struct fsl_usb2_platform_data *)pdev->dev.platform_data; + + pr_debug("%s: pdev=0x%p pdata=0x%p\n", __FUNCTION__, pdev, pdata); + + /* + * do platform specific init: check the clock, grab/config pins, etc. + */ + if (pdata->platform_init && pdata->platform_init(pdev) != 0) + return -EINVAL; + + return 0; +} + +/* Driver probe functions */ + + /*! + * all intialize operations implemented here except Enable usb_intr reg + * @param dev device controller pointer + * @return Returns zero on success , or a negative error code + */ +static int __devinit arcotg_udc_probe(struct platform_device *pdev) +{ + struct fsl_usb2_platform_data *pdata = pdev->dev.platform_data; + struct arcotg_udc *udc; + + unsigned int tmp_status = -ENODEV; + unsigned int i; + u32 id; + u64 rsrc_start, rsrc_len; + + if (strcmp(pdev->name, "arc_udc")) { + pr_debug("udc: Wrong device\n"); + return -ENODEV; + } + + pr_debug("%s: pdev=0x%p pdata=0x%p\n", __FUNCTION__, pdev, pdata); + + if (board_init(pdev) != 0) + return -EINVAL; + + /* Initialize the udc structure including QH member and other member */ + udc = (struct arcotg_udc *)struct_udc_setup(pdev); + udc_controller = udc; + + if (!udc) { + pr_debug("udc: udc is NULL\n"); + return -ENOMEM; + } + + dev_set_drvdata(&pdev->dev, udc); + + udc->pdata = pdata; + udc->xcvr_type = pdata->xcvr_type; + +#ifdef CONFIG_USB_OTG + udc->transceiver = otg_get_transceiver(); + pr_debug("udc: otg_get_transceiver returns 0x%p", udc->transceiver); +#endif + + if (pdev->resource[1].flags != IORESOURCE_IRQ) { + return -ENODEV; + } + + rsrc_start = pdev->resource[0].start; + rsrc_len = pdev->resource[0].end - pdev->resource[0].start + 1; + + pr_debug("start=0x%x end=0x%x\n", + pdev->resource[0].start, pdev->resource[0].end); + pr_debug("rsrc_start=0x%llx rsrc_len=0x%llx\n", rsrc_start, rsrc_len); + +#if 0 /* DDD */ + pr_debug("doing request_mem_region(start=0x%llx, len=0x%llx)\n", + rsrc_start, rsrc_len); + if (!request_mem_region(rsrc_start, rsrc_len, driver_name)) { + printk(KERN_ERR "request_mem_region failed\n"); + return -EBUSY; + } +#endif + usb_slave_regs = (struct usb_dr_device *)(int)IO_ADDRESS(rsrc_start); + + pr_debug("udc: usb_slave_regs = 0x%p\n", usb_slave_regs); + pr_debug("udc: hci_version=0x%x\n", usb_slave_regs->hciversion); + pr_debug("udc: otgsc at 0x%p\n", &usb_slave_regs->otgsc); + + id = usb_slave_regs->id; + printk(KERN_INFO "ARC USBOTG h/w ID=0x%x revision=0x%x\n", + id & 0x3f, id >> 16); + + /* request irq and disable DR */ + tmp_status = request_irq(pdev->resource[1].start, arcotg_udc_irq, + IRQF_SHARED, driver_name, udc); + if (tmp_status != 0) { + printk(KERN_ERR "cannot request irq %d err %d \n", + (int)pdev->resource[1].start, tmp_status); + /* DDD free mem_region here */ + return tmp_status; + } + + if (!udc->transceiver) { + /* initialize usb hw reg except for regs for EP, + * leave usbintr reg untouched*/ + dr_controller_setup(udc); + } + + /* here comes the stand operations for probe + * set the arcotg_udc->gadget.xxx + */ + udc->gadget.ops = &arcotg_gadget_ops; + udc->gadget.is_dualspeed = 1; + + /* gadget.ep0 is a pointer */ + udc->gadget.ep0 = &udc->eps[0].ep; + + INIT_LIST_HEAD(&udc->gadget.ep_list); + + udc->gadget.speed = USB_SPEED_UNKNOWN; + + /* name: Identifies the controller hardware type. */ + udc->gadget.name = driver_name; + + device_initialize(&udc->gadget.dev); + + strcpy(udc->gadget.dev.bus_id, "gadget"); + + udc->gadget.dev.release = arcotg_gadget_release; + udc->gadget.dev.parent = &pdev->dev; + + if (udc->transceiver) { + udc->gadget.is_otg = 1; + } + + /* for an EP, the intialization includes: fields in QH, Regs, + * arcotg_ep struct */ + ep0_dr_and_qh_setup(udc); + for (i = 0; i < USB_MAX_PIPES; i++) { + /* because the ep type is not decide here so + * struct_ep_qh_setup() and dr_ep_setup() + * should be called in ep_enable() + */ + if (ep_name[i] != NULL) + /* setup the arcotg_ep struct and link ep.ep.list + * into gadget.ep_list */ + struct_ep_setup(udc, i); + } + + create_proc_file(); + tmp_status = device_add(&udc->gadget.dev); + pr_debug("udc: back from device_add "); + + return tmp_status; +} + +/*! + * Driver removal functions + * Free resources + * Finish pending transaction + * @param dev device controller pointer + * @return Returns zero on success , or a negative error code + */ +static int __devexit arcotg_udc_remove(struct platform_device *pdev) +{ + struct arcotg_udc *udc = + (struct arcotg_udc *)platform_get_drvdata(pdev); + struct fsl_usb2_platform_data *pdata = pdev->dev.platform_data; + + DECLARE_COMPLETION(done); + + if (!udc) + return -ENODEV; + + udc->done = &done; + + if (udc->transceiver) { + put_device(udc->transceiver->dev); + udc->transceiver = 0; + } + + /* DR has been stopped in usb_gadget_unregister_driver() */ + + /* remove proc */ + remove_proc_file(); + + /* free irq */ + free_irq(pdev->resource[1].start, udc); + + /* deinitialize all ep: strcut */ + /* deinitialize ep0: reg and QH */ + + /* Free allocated memory */ + pr_debug("status_req->head = 0x%p\n", udc->status_req->head); + if (udc->status_req->head) { + pr_debug("freeing head=0x%p\n", udc->status_req->head); + dma_pool_free(udc->dtd_pool, + udc->status_req->head, + udc->status_req->head->td_dma); + } + + kfree(udc->status_req->req.buf); + kfree(udc->status_req); + + if (udc->dtd_pool) + dma_pool_destroy(udc->dtd_pool); + + device_unregister(&udc->gadget.dev); + /* free udc --wait for the release() finished */ + wait_for_completion(&done); + +#if 0 /* DDD */ + release_mem_region(pdev->resource[0].start, + pdev->resource[0].end - pdev->resource[0].start + 1); +#endif + + /* + * do platform specific un-initialization: + * release iomux pins, etc. + */ + if (pdata->platform_uninit) + pdata->platform_uninit(pdata); + + return 0; +} + +static int udc_suspend(struct arcotg_udc *udc) +{ + udc->stopped = 1; + return 0; +} + +/*! + * Modify Power management attributes + * Here we stop the DR controller and disable the irq + * @param dev device controller pointer + * @param state current state + * @return The function returns 0 on success or -1 if failed + */ +static int arcotg_udc_suspend(struct device *dev, pm_message_t state) +{ + struct arcotg_udc *udc = (struct arcotg_udc *)dev_get_drvdata(dev); + pr_debug("udc: Suspend. state=%d\n", state.event); + return udc_suspend(udc); +} + +static int udc_resume(struct arcotg_udc *udc) +{ + /*Enable DR irq reg and set controller Run */ + if (udc->stopped) { + dr_controller_setup(udc); + dr_controller_run(udc); + } + udc->usb_state = USB_STATE_ATTACHED; + udc->ep0_state = WAIT_FOR_SETUP; + udc->ep0_dir = 0; + return 0; +} + +/*! + * Invoked on USB resume. May be called in_interrupt. + * Here we start the DR controller and enable the irq + * @param dev device controller pointer + * @return The function returns 0 on success or -1 if failed + */ +static int arcotg_udc_resume(struct device *dev) +{ + struct arcotg_udc *udc = (struct arcotg_udc *)dev_get_drvdata(dev); + pr_debug("udc: Resume dev=0x%p udc=0x%p\n", dev, udc); + + return udc_resume(udc); +} + +/*! + * Register entry point for the peripheral controller driver + */ +static struct platform_driver udc_driver = { + .probe = arcotg_udc_probe, + .remove = __exit_p(arcotg_udc_remove), + .driver = { + .name = driver_name, + }, +}; + +static int __init udc_init(void) +{ + int rc; + + printk(KERN_INFO "%s version %s init \n", driver_desc, DRIVER_VERSION); + rc = platform_driver_register(&udc_driver); + pr_debug("udc: %s() driver_register returns %d\n", __FUNCTION__, rc); + return rc; +} + +module_init(udc_init); + +static void __exit udc_exit(void) +{ + platform_driver_unregister(&udc_driver); +} + +module_exit(udc_exit); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/gadget/arcotg_udc.h b/drivers/usb/gadget/arcotg_udc.h new file mode 100644 index 000000000000..fad34300b5ba --- /dev/null +++ b/drivers/usb/gadget/arcotg_udc.h @@ -0,0 +1,598 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file arcotg_udc.h + * @brief Freescale USB device/endpoint management registers + * @ingroup USB + */ + +#ifndef __ARCOTG_UDC_H +#define __ARCOTG_UDC_H + +#define TRUE 1 +#define FALSE 0 + +/* ### define USB registers here + */ +#define USB_MAX_ENDPOINTS 8 +#define USB_MAX_PIPES (USB_MAX_ENDPOINTS*2) +#define USB_MAX_CTRL_PAYLOAD 64 +#define USB_DR_SYS_OFFSET 0x400 + +#define USB_DR_OFFSET 0x3100 + +struct usb_dr_device { + /* Capability register */ + u32 id; + u32 res1[63]; + u16 caplength; /* Capability Register Length */ + u16 hciversion; /* Host Controller Interface Version */ + u32 hcsparams; /* Host Controller Structual Parameters */ + u32 hccparams; /* Host Controller Capability Parameters */ + u32 res2[5]; + u32 dciversion; /* Device Controller Interface Version */ + u32 dccparams; /* Device Controller Capability Parameters */ + u32 res3[6]; + /* Operation register */ + u32 usbcmd; /* USB Command Register */ + u32 usbsts; /* USB Status Register */ + u32 usbintr; /* USB Interrupt Enable Register */ + u32 frindex; /* Frame Index Register */ + u32 res4; + u32 deviceaddr; /* Device Address */ + u32 endpointlistaddr; /* Endpoint List Address Register */ + u32 res5; + u32 burstsize; /* Master Interface Data Burst Size Register */ + u32 txttfilltuning; /* Transmit FIFO Tuning Controls Register */ + u32 res6[6]; + u32 configflag; /* Configure Flag Register */ + u32 portsc1; /* Port 1 Status and Control Register */ + u32 res7[7]; + u32 otgsc; /* On-The-Go Status and Control */ + u32 usbmode; /* USB Mode Register */ + u32 endptsetupstat; /* Endpoint Setup Status Register */ + u32 endpointprime; /* Endpoint Initialization Register */ + u32 endptflush; /* Endpoint Flush Register */ + u32 endptstatus; /* Endpoint Status Register */ + u32 endptcomplete; /* Endpoint Complete Register */ + u32 endptctrl[8 * 2]; /* Endpoint Control Registers */ +} __attribute__ ((packed)); + +/* ep0 transfer state */ +#define WAIT_FOR_SETUP 0 +#define DATA_STATE_XMIT 1 +#define DATA_STATE_NEED_ZLP 2 +#define WAIT_FOR_OUT_STATUS 3 +#define DATA_STATE_RECV 4 + +/* Frame Index Register Bit Masks */ +#define USB_FRINDEX_MASKS (0x3fff) +/* USB CMD Register Bit Masks */ +#define USB_CMD_RUN_STOP (0x00000001) +#define USB_CMD_CTRL_RESET (0x00000002) +#define USB_CMD_PERIODIC_SCHEDULE_EN (0x00000010) +#define USB_CMD_ASYNC_SCHEDULE_EN (0x00000020) +#define USB_CMD_INT_AA_DOORBELL (0x00000040) +#define USB_CMD_ASP (0x00000300) +#define USB_CMD_ASYNC_SCH_PARK_EN (0x00000800) +#define USB_CMD_SUTW (0x00002000) +#define USB_CMD_ATDTW (0x00004000) +#define USB_CMD_ITC (0x00FF0000) + +/* bit 15,3,2 are frame list size */ +#define USB_CMD_FRAME_SIZE_1024 (0x00000000) +#define USB_CMD_FRAME_SIZE_512 (0x00000004) +#define USB_CMD_FRAME_SIZE_256 (0x00000008) +#define USB_CMD_FRAME_SIZE_128 (0x0000000C) +#define USB_CMD_FRAME_SIZE_64 (0x00008000) +#define USB_CMD_FRAME_SIZE_32 (0x00008004) +#define USB_CMD_FRAME_SIZE_16 (0x00008008) +#define USB_CMD_FRAME_SIZE_8 (0x0000800C) + +/* bit 9-8 are async schedule park mode count */ +#define USB_CMD_ASP_00 (0x00000000) +#define USB_CMD_ASP_01 (0x00000100) +#define USB_CMD_ASP_10 (0x00000200) +#define USB_CMD_ASP_11 (0x00000300) +#define USB_CMD_ASP_BIT_POS (8) + +/* bit 23-16 are interrupt threshold control */ +#define USB_CMD_ITC_NO_THRESHOLD (0x00000000) +#define USB_CMD_ITC_1_MICRO_FRM (0x00010000) +#define USB_CMD_ITC_2_MICRO_FRM (0x00020000) +#define USB_CMD_ITC_4_MICRO_FRM (0x00040000) +#define USB_CMD_ITC_8_MICRO_FRM (0x00080000) +#define USB_CMD_ITC_16_MICRO_FRM (0x00100000) +#define USB_CMD_ITC_32_MICRO_FRM (0x00200000) +#define USB_CMD_ITC_64_MICRO_FRM (0x00400000) +#define USB_CMD_ITC_BIT_POS (16) + +/* USB STS Register Bit Masks */ +#define USB_STS_INT (0x00000001) +#define USB_STS_ERR (0x00000002) +#define USB_STS_PORT_CHANGE (0x00000004) +#define USB_STS_FRM_LST_ROLL (0x00000008) +#define USB_STS_SYS_ERR (0x00000010) +#define USB_STS_IAA (0x00000020) +#define USB_STS_RESET (0x00000040) +#define USB_STS_SOF (0x00000080) +#define USB_STS_SUSPEND (0x00000100) +#define USB_STS_HC_HALTED (0x00001000) +#define USB_STS_RCL (0x00002000) +#define USB_STS_PERIODIC_SCHEDULE (0x00004000) +#define USB_STS_ASYNC_SCHEDULE (0x00008000) + +/* USB INTR Register Bit Masks */ +#define USB_INTR_INT_EN (0x00000001) +#define USB_INTR_ERR_INT_EN (0x00000002) +#define USB_INTR_PTC_DETECT_EN (0x00000004) +#define USB_INTR_FRM_LST_ROLL_EN (0x00000008) +#define USB_INTR_SYS_ERR_EN (0x00000010) +#define USB_INTR_ASYN_ADV_EN (0x00000020) +#define USB_INTR_RESET_EN (0x00000040) +#define USB_INTR_SOF_EN (0x00000080) +#define USB_INTR_DEVICE_SUSPEND (0x00000100) + +/* Device Address bit masks */ +#define USB_DEVICE_ADDRESS_MASK (0xFE000000) +#define USB_DEVICE_ADDRESS_BIT_POS (25) + +/* endpoint list address bit masks */ +#define USB_EP_LIST_ADDRESS_MASK (0xfffff800) + +/* PORTSCX Register Bit Masks */ +#define PORTSCX_CURRENT_CONNECT_STATUS (0x00000001) +#define PORTSCX_CONNECT_STATUS_CHANGE (0x00000002) +#define PORTSCX_PORT_ENABLE (0x00000004) +#define PORTSCX_PORT_EN_DIS_CHANGE (0x00000008) +#define PORTSCX_OVER_CURRENT_ACT (0x00000010) +#define PORTSCX_OVER_CURRENT_CHG (0x00000020) +#define PORTSCX_PORT_FORCE_RESUME (0x00000040) +#define PORTSCX_PORT_SUSPEND (0x00000080) +#define PORTSCX_PORT_RESET (0x00000100) +#define PORTSCX_LINE_STATUS_BITS (0x00000C00) +#define PORTSCX_PORT_POWER (0x00001000) +#define PORTSCX_PORT_INDICTOR_CTRL (0x0000C000) +#define PORTSCX_PORT_TEST_CTRL (0x000F0000) +#define PORTSCX_WAKE_ON_CONNECT_EN (0x00100000) +#define PORTSCX_WAKE_ON_CONNECT_DIS (0x00200000) +#define PORTSCX_WAKE_ON_OVER_CURRENT (0x00400000) +#define PORTSCX_PHY_LOW_POWER_SPD (0x00800000) +#define PORTSCX_PORT_FORCE_FULL_SPEED (0x01000000) +#define PORTSCX_PORT_SPEED_MASK (0x0C000000) +#define PORTSCX_PORT_WIDTH (0x10000000) +#define PORTSCX_PHY_TYPE_SEL (0xC0000000) + +/* bit 11-10 are line status */ +#define PORTSCX_LINE_STATUS_SE0 (0x00000000) +#define PORTSCX_LINE_STATUS_JSTATE (0x00000400) +#define PORTSCX_LINE_STATUS_KSTATE (0x00000800) +#define PORTSCX_LINE_STATUS_UNDEF (0x00000C00) +#define PORTSCX_LINE_STATUS_BIT_POS (10) + +/* bit 15-14 are port indicator control */ +#define PORTSCX_PIC_OFF (0x00000000) +#define PORTSCX_PIC_AMBER (0x00004000) +#define PORTSCX_PIC_GREEN (0x00008000) +#define PORTSCX_PIC_UNDEF (0x0000C000) +#define PORTSCX_PIC_BIT_POS (14) + +/* bit 19-16 are port test control */ +#define PORTSCX_PTC_DISABLE (0x00000000) +#define PORTSCX_PTC_JSTATE (0x00010000) +#define PORTSCX_PTC_KSTATE (0x00020000) +#define PORTSCX_PTC_SEQNAK (0x00030000) +#define PORTSCX_PTC_PACKET (0x00040000) +#define PORTSCX_PTC_FORCE_EN (0x00050000) +#define PORTSCX_PTC_BIT_POS (16) + +/* bit 27-26 are port speed */ +#define PORTSCX_PORT_SPEED_FULL (0x00000000) +#define PORTSCX_PORT_SPEED_LOW (0x04000000) +#define PORTSCX_PORT_SPEED_HIGH (0x08000000) +#define PORTSCX_PORT_SPEED_UNDEF (0x0C000000) +#define PORTSCX_SPEED_BIT_POS (26) + +/* bit 28 is parallel transceiver width for UTMI interface */ +#define PORTSCX_PTW (0x10000000) +#define PORTSCX_PTW_8BIT (0x00000000) +#define PORTSCX_PTW_16BIT (0x10000000) + +/* bit 31-30 are port transceiver select */ +#define PORTSCX_PTS_UTMI (0x00000000) +#define PORTSCX_PTS_ULPI (0x80000000) +#define PORTSCX_PTS_FSLS (0xC0000000) +#define PORTSCX_PTS_BIT_POS (30) + +/* USB MODE Register Bit Masks */ +#define USB_MODE_CTRL_MODE_IDLE (0x00000000) +#define USB_MODE_CTRL_MODE_DEVICE (0x00000002) +#define USB_MODE_CTRL_MODE_HOST (0x00000003) +#define USB_MODE_CTRL_MODE_RSV (0x00000001) +#define USB_MODE_SETUP_LOCK_OFF (0x00000008) +#define USB_MODE_STREAM_DISABLE (0x00000010) +/* Endpoint Flush Register */ +#define EPFLUSH_TX_OFFSET (0x00010000) +#define EPFLUSH_RX_OFFSET (0x00000000) + +/* Endpoint Setup Status bit masks */ +#define EP_SETUP_STATUS_MASK (0x0000003F) +#define EP_SETUP_STATUS_EP0 (0x00000001) + +/* ENDPOINTCTRLx Register Bit Masks */ +#define EPCTRL_TX_ENABLE (0x00800000) +#define EPCTRL_TX_DATA_TOGGLE_RST (0x00400000) /* Not EP0 */ +#define EPCTRL_TX_DATA_TOGGLE_INH (0x00200000) /* Not EP0 */ +#define EPCTRL_TX_TYPE (0x000C0000) +#define EPCTRL_TX_DATA_SOURCE (0x00020000) /* Not EP0 */ +#define EPCTRL_TX_EP_STALL (0x00010000) +#define EPCTRL_RX_ENABLE (0x00000080) +#define EPCTRL_RX_DATA_TOGGLE_RST (0x00000040) /* Not EP0 */ +#define EPCTRL_RX_DATA_TOGGLE_INH (0x00000020) /* Not EP0 */ +#define EPCTRL_RX_TYPE (0x0000000C) +#define EPCTRL_RX_DATA_SINK (0x00000002) /* Not EP0 */ +#define EPCTRL_RX_EP_STALL (0x00000001) + +/* bit 19-18 and 3-2 are endpoint type */ +#define EPCTRL_EP_TYPE_CONTROL (0) +#define EPCTRL_EP_TYPE_ISO (1) +#define EPCTRL_EP_TYPE_BULK (2) +#define EPCTRL_EP_TYPE_INTERRUPT (3) +#define EPCTRL_TX_EP_TYPE_SHIFT (18) +#define EPCTRL_RX_EP_TYPE_SHIFT (2) + +/* SNOOPn Register Bit Masks */ +#define SNOOP_ADDRESS_MASK (0xFFFFF000) +#define SNOOP_SIZE_ZERO (0x00) /* snooping disable */ +#define SNOOP_SIZE_4KB (0x0B) /* 4KB snoop size */ +#define SNOOP_SIZE_8KB (0x0C) +#define SNOOP_SIZE_16KB (0x0D) +#define SNOOP_SIZE_32KB (0x0E) +#define SNOOP_SIZE_64KB (0x0F) +#define SNOOP_SIZE_128KB (0x10) +#define SNOOP_SIZE_256KB (0x11) +#define SNOOP_SIZE_512KB (0x12) +#define SNOOP_SIZE_1MB (0x13) +#define SNOOP_SIZE_2MB (0x14) +#define SNOOP_SIZE_4MB (0x15) +#define SNOOP_SIZE_8MB (0x16) +#define SNOOP_SIZE_16MB (0x17) +#define SNOOP_SIZE_32MB (0x18) +#define SNOOP_SIZE_64MB (0x19) +#define SNOOP_SIZE_128MB (0x1A) +#define SNOOP_SIZE_256MB (0x1B) +#define SNOOP_SIZE_512MB (0x1C) +#define SNOOP_SIZE_1GB (0x1D) +#define SNOOP_SIZE_2GB (0x1E) /* 2GB snoop size */ + +/* pri_ctrl Register Bit Masks */ +#define PRI_CTRL_PRI_LVL1 (0x0000000C) +#define PRI_CTRL_PRI_LVL0 (0x00000003) + +/* si_ctrl Register Bit Masks */ +#define SI_CTRL_ERR_DISABLE (0x00000010) +#define SI_CTRL_IDRC_DISABLE (0x00000008) +#define SI_CTRL_RD_SAFE_EN (0x00000004) +#define SI_CTRL_RD_PREFETCH_DISABLE (0x00000002) +#define SI_CTRL_RD_PREFEFETCH_VAL (0x00000001) + +/* control Register Bit Masks */ +#define USB_CTRL_IOENB (0x00000004) +#define USB_CTRL_ULPI_INT0EN (0x00000001) + +/*! + * Endpoint Queue Head data struct + * Rem: all the variables of qh are LittleEndian Mode + * and NEXT_POINTER_MASK should operate on a LittleEndian, Phy Addr + */ +struct ep_queue_head { + /*! + * Mult(31-30) , Zlt(29) , Max Pkt len and IOS(15) + */ + u32 max_pkt_length; + + /*! + * Current dTD Pointer(31-5) + */ + u32 curr_dtd_ptr; + + /*! + * Next dTD Pointer(31-5), T(0) + */ + u32 next_dtd_ptr; + + /*! + * Total bytes (30-16), IOC (15), MultO(11-10), STS (7-0) + */ + u32 size_ioc_int_sts; + + /*! + * Buffer pointer Page 0 (31-12) + */ + u32 buff_ptr0; + + /*! + * Buffer pointer Page 1 (31-12) + */ + u32 buff_ptr1; + + /*! + * Buffer pointer Page 2 (31-12) + */ + u32 buff_ptr2; + + /*! + * Buffer pointer Page 3 (31-12) + */ + u32 buff_ptr3; + + /*! + * Buffer pointer Page 4 (31-12) + */ + u32 buff_ptr4; + + /*! + * reserved field 1 + */ + u32 res1; + /*! + * Setup data 8 bytes + */ + u8 setup_buffer[8]; /* Setup data 8 bytes */ + + /*! + * reserved field 2,pad out to 64 bytes + */ + u32 res2[4]; +}; + +/* Endpoint Queue Head Bit Masks */ +#define EP_QUEUE_HEAD_MULT_POS (30) +#define EP_QUEUE_HEAD_ZLT_SEL (0x20000000) +#define EP_QUEUE_HEAD_MAX_PKT_LEN_POS (16) +#define EP_QUEUE_HEAD_MAX_PKT_LEN(ep_info) (((ep_info)>>16)&0x07ff) +#define EP_QUEUE_HEAD_IOS (0x00008000) +#define EP_QUEUE_HEAD_NEXT_TERMINATE (0x00000001) +#define EP_QUEUE_HEAD_IOC (0x00008000) +#define EP_QUEUE_HEAD_MULTO (0x00000C00) +#define EP_QUEUE_HEAD_STATUS_HALT (0x00000040) +#define EP_QUEUE_HEAD_STATUS_ACTIVE (0x00000080) +#define EP_QUEUE_CURRENT_OFFSET_MASK (0x00000FFF) +#define EP_QUEUE_FRINDEX_MASK (0x000007FF) +#define EP_MAX_LENGTH_TRANSFER (0x4000) + +/*! + * Endpoint Transfer Descriptor data struct + * Rem: all the variables of td are LittleEndian Mode + * must be 32-byte aligned + */ +struct ep_td_struct { + /*! + * Next TD pointer(31-5), T(0) set indicate invalid + */ + u32 next_td_ptr; + + /*! + * Total bytes (30-16), IOC (15),MultO(11-10), STS (7-0) + */ + u32 size_ioc_sts; + + /*! + * Buffer pointer Page 0 + */ + u32 buff_ptr0; + + /*! + * Buffer pointer Page 1 + */ + u32 buff_ptr1; + + /*! + * Buffer pointer Page 2 + */ + u32 buff_ptr2; + + /*! + * Buffer pointer Page 3 + */ + u32 buff_ptr3; + + /*! + * Buffer pointer Page 4 + */ + u32 buff_ptr4; + + /*! + * dma address of this td + * */ + dma_addr_t td_dma; + + /*! + * virtual address of next td + * */ + struct ep_td_struct *next_td_virt; + + /*! + * make it an even 16 words + * */ + u32 res[7]; +}; + +/*! + * Endpoint Transfer Descriptor bit Masks + */ +#define DTD_NEXT_TERMINATE (0x00000001) +#define DTD_IOC (0x00008000) +#define DTD_STATUS_ACTIVE (0x00000080) +#define DTD_STATUS_HALTED (0x00000040) +#define DTD_STATUS_DATA_BUFF_ERR (0x00000020) +#define DTD_STATUS_TRANSACTION_ERR (0x00000008) +#define DTD_RESERVED_FIELDS (0x80007300) +#define DTD_PACKET_SIZE (0x7FFF0000) +#define DTD_LENGTH_BIT_POS (16) +#define DTD_ERROR_MASK (DTD_STATUS_HALTED | \ + DTD_STATUS_DATA_BUFF_ERR | \ + DTD_STATUS_TRANSACTION_ERR) + +/* -----------------------------------------------------------------------*/ +/* ##### enum data +*/ +typedef enum { + e_ULPI, + e_UTMI_8BIT, + e_UTMI_16BIT, + e_SERIAL +} e_PhyInterface; + +/*-------------------------------------------------------------------------*/ + +struct arcotg_req { + struct usb_request req; + struct list_head queue; + /* ep_queue() func will add + a request->queue into a udc_ep->queue 'd tail */ + struct arcotg_ep *ep; + unsigned mapped; + + struct ep_td_struct *head, *tail; /* For dTD List + this is a BigEndian Virtual addr */ + unsigned int dtd_count; +}; + +#define REQ_UNCOMPLETE (1) + +struct arcotg_ep { + struct usb_ep ep; + struct list_head queue; + struct arcotg_udc *udc; + const struct usb_endpoint_descriptor *desc; + struct usb_gadget *gadget; + + u8 already_seen; + u8 setup_stage; + u32 last_io; /* timestamp */ + + char name[14]; +#if 0 + u16 maxpacket; + u8 bEndpointAddress; + u8 bmAttributes; +#endif + unsigned double_buf:1; + unsigned stopped:1; + unsigned fnf:1; + unsigned has_dma:1; + u8 ackwait; + u8 dma_channel; + u16 dma_counter; + int lch; + + struct timer_list timer; + +}; + +#define EP_DIR_IN 1 +#define EP_DIR_OUT 0 + +struct arcotg_udc { + struct usb_gadget gadget; + struct usb_gadget_driver *driver; + struct arcotg_ep eps[USB_MAX_ENDPOINTS * 2]; + struct usb_ctrlrequest local_setup_buff; + spinlock_t lock; + struct fsl_usb2_platform_data *pdata; + u32 xcvr_type; + struct otg_transceiver *transceiver; + unsigned softconnect:1; + unsigned vbus_active:1; + unsigned stopped:1; + + struct ep_queue_head *ep_qh; /* Endpoints Queue-Head */ + int ep_qh_size; /* Endpoints Queue-Head */ + struct arcotg_req *status_req; /* ep0 status request */ + + u32 max_pipes; /* Device max pipes */ + u32 max_use_endpts; /* Max endpointes to be used */ + u32 bus_reset; /* Device is bus reseting */ + u32 resume_state; /* USB state to resume */ + u32 usb_state; /* USB current state */ + u32 usb_next_state; /* USB next state */ + u32 ep0_state; /* Enpoint zero state */ + u32 ep0_dir; /* Enpoint zero direction: can be + USB_DIR_IN or USB_DIR_OUT */ + u32 usb_sof_count; /* SOF count */ + u32 errors; /* USB ERRORs count */ + dma_addr_t ep_qh_dma; /* DMA address of ep_qh */ + struct dma_pool *dtd_pool; + u8 device_address; /* Device USB address */ + + struct completion *done; /* to make sure release() is done */ +}; + +#if 0 +static void dump_msg(const char *label, const u8 * buf, unsigned int length) +{ + unsigned int start, num, i; + char line[52], *p; + + if (length >= 512) + return; + pr_debug("udc: %s, length %u:\n", label, length); + start = 0; + while (length > 0) { + num = min(length, 16u); + p = line; + for (i = 0; i < num; ++i) { + if (i == 8) + *p++ = ' '; + sprintf(p, " %02x", buf[i]); + p += 3; + } + *p = 0; + printk(KERN_DEBUG "%6x: %s\n", start, line); + buf += num; + start += num; + length -= num; + } +} +#endif + +/*-------------------------------------------------------------------------*/ + +/* ### Add board specific defines here + */ + +/* + * ### pipe direction macro from device view + */ +#define USB_RECV (0) /* OUT EP */ +#define USB_SEND (1) /* IN EP */ + +/* + * ### internal used help routines. + */ +#define ep_index(EP) ((EP)->desc->bEndpointAddress&0xF) +#define ep_maxpacket(EP) ((EP)->ep.maxpacket) + +#define ep_is_in(EP) ( (ep_index(EP) == 0) ? (EP->udc->ep0_dir == \ + USB_DIR_IN ):((EP)->desc->bEndpointAddress \ + & USB_DIR_IN)==USB_DIR_IN) + +#define get_ep_by_pipe(udc, pipe) ((pipe == 1)? &udc->eps[0]: \ + &udc->eps[pipe]) + +/* Bulk only class request */ +#define USB_BULK_RESET_REQUEST 0xff + +#endif /* __ARCOTG_UDC_H */ diff --git a/drivers/usb/gadget/ether.c b/drivers/usb/gadget/ether.c index 9e732bff9df0..d786788b9c02 100644 --- a/drivers/usb/gadget/ether.c +++ b/drivers/usb/gadget/ether.c @@ -239,6 +239,10 @@ MODULE_PARM_DESC(host_addr, "Host Ethernet Address"); #define DEV_CONFIG_CDC #endif +#ifdef CONFIG_USB_GADGET_ARC +#define DEV_CONFIG_CDC +#endif + #ifdef CONFIG_USB_GADGET_S3C2410 #define DEV_CONFIG_CDC #endif diff --git a/drivers/usb/gadget/gadget_chips.h b/drivers/usb/gadget/gadget_chips.h index f7f159c1002b..579cd6e992c1 100644 --- a/drivers/usb/gadget/gadget_chips.h +++ b/drivers/usb/gadget/gadget_chips.h @@ -88,6 +88,12 @@ #define gadget_is_pxa27x(g) 0 #endif +#ifdef CONFIG_USB_GADGET_ARC +#define gadget_is_arcotg(g) !strcmp("arc_udc", (g)->name) +#else +#define gadget_is_arcotg(g) 0 +#endif + #ifdef CONFIG_USB_GADGET_ATMEL_USBA #define gadget_is_atmel_usba(g) !strcmp("atmel_usba_udc", (g)->name) #else @@ -212,5 +218,7 @@ static inline int usb_gadget_controller_number(struct usb_gadget *gadget) return 0x20; else if (gadget_is_m66592(gadget)) return 0x21; + else if (gadget_is_arcotg(gadget)) + return 0x22; return -ENOENT; } diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index 49a91c5ee51b..35b34bf3908c 100644 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -29,6 +29,52 @@ config USB_EHCI_HCD To compile this driver as a module, choose M here: the module will be called ehci-hcd. +config USB_EHCI_ARC + bool "Support for ARC controller" + depends on USB_EHCI_HCD && ARCH_MXC + ---help--- + Some Freescale processors have an ARC High Speed + USBOTG controller, which supports EHCI host mode. + + Say "y" here to add support for this controller + to the EHCI HCD driver. + +config USB_EHCI_ARC_H1 + bool "Support for Host1 port on ARC controller" + depends on USB_EHCI_ARC && (ARCH_MX27 || ARCH_MX3) + ---help--- + Enable support for the USB Host1 port. + +config USB_EHCI_ARC_H2 + bool "Support for Host2 port on ARC controller" + depends on USB_EHCI_ARC && (ARCH_MX27 || ARCH_MX3) + ---help--- + Enable support for the USB Host2 port. + +config USB_EHCI_ARC_OTG + bool "Support for OTG host port on ARC controller" + depends on USB_EHCI_ARC + default y + ---help--- + Enable support for the USB OTG port in HS/FS Host mode. + +choice + prompt "Select transceiver speed" + depends on USB_EHCI_ARC_OTG + default USB_EHCI_ARC_OTGHS + default USB_EHCI_ARC_OTGFS if ARCH_MX27 + +config USB_EHCI_ARC_OTGHS + bool "High Speed" + ---help--- + Enable support for the USB OTG port in HS Host mode. + +config USB_EHCI_ARC_OTGFS + bool "Full Speed" + ---help--- + Enable support for the USB OTG port in FS Host mode. +endchoice + config USB_EHCI_SPLIT_ISO bool "Full speed ISO transactions (EXPERIMENTAL)" depends on USB_EHCI_HCD && EXPERIMENTAL @@ -41,6 +87,7 @@ config USB_EHCI_SPLIT_ISO config USB_EHCI_ROOT_HUB_TT bool "Root Hub Transaction Translators (EXPERIMENTAL)" depends on USB_EHCI_HCD && EXPERIMENTAL + default y if USB_EHCI_ARC ---help--- Some EHCI chips have vendor-specific extensions to integrate transaction translators, so that no OHCI or UHCI companion diff --git a/drivers/usb/host/ehci-arc.c b/drivers/usb/host/ehci-arc.c new file mode 100644 index 000000000000..99d6772302de --- /dev/null +++ b/drivers/usb/host/ehci-arc.c @@ -0,0 +1,393 @@ +/* + * drivers/usb/host/ehci-arc.c + * + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @defgroup USB ARC OTG USB Driver + */ +/*! + * @file ehci-arc.c + * @brief platform related part of usb host driver. + * @ingroup USB + */ + +/*! + * Include files + */ + +/* Note: this file is #included by ehci-hcd.c */ + +#include <linux/platform_device.h> +#include <linux/fsl_devices.h> +#include <linux/usb/otg.h> +#include <linux/usb/fsl_xcvr.h> +#include <asm/arch/fsl_usb.h> + +#include "ehci-fsl.h" + +#undef dbg +#undef vdbg + +#if 0 +#define dbg printk +#else +#define dbg(fmt, ...) do {} while (0) +#endif + +#if 0 +#define vdbg dbg +#else +#define vdbg(fmt, ...) do {} while (0) +#endif + +extern void fsl_platform_set_vbus_power(struct fsl_usb2_platform_data *pdata, + int on); + +/* PCI-based HCs are common, but plenty of non-PCI HCs are used too */ + +/* configure so an HC device and id are always provided */ +/* always called with process context; sleeping is OK */ + +/** + * usb_hcd_fsl_probe - initialize FSL-based HCDs + * @drvier: Driver to be used for this HCD + * @pdev: USB Host Controller being probed + * Context: !in_interrupt() + * + * Allocates basic resources for this USB host controller. + * + */ +static int usb_hcd_fsl_probe(const struct hc_driver *driver, + struct platform_device *pdev) +{ + struct fsl_usb2_platform_data *pdata; + struct usb_hcd *hcd; + struct resource *res; + int irq; + int retval; + + pr_debug("initializing FSL-SOC USB Controller\n"); + + /* Need platform data for setup */ + pdata = (struct fsl_usb2_platform_data *)pdev->dev.platform_data; + if (!pdata) { + dev_err(&pdev->dev, + "No platform data for %s.\n", pdev->dev.bus_id); + return -ENODEV; + } + + retval = fsl_platform_verify(pdev); + if (retval) + return retval; + + /* + * do platform specific init: check the clock, grab/config pins, etc. + */ + if (pdata->platform_init && pdata->platform_init(pdev)) { + retval = -ENODEV; + goto err1; + } + + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!res) { + dev_err(&pdev->dev, + "Found HC with no IRQ. Check %s setup!\n", + pdev->dev.bus_id); + return -ENODEV; + } + irq = res->start; + + fsl_platform_set_vbus_power(pdata, 1); + + hcd = usb_create_hcd(driver, &pdev->dev, pdev->dev.bus_id); + if (!hcd) { + retval = -ENOMEM; + goto err1; + } + + hcd->rsrc_start = pdata->r_start; + hcd->rsrc_len = pdata->r_len; + hcd->regs = pdata->regs; + vdbg("rsrc_start=0x%llx rsrc_len=0x%llx virtual=0x%x\n", + hcd->rsrc_start, hcd->rsrc_len, hcd->regs); + + hcd->power_budget = pdata->power_budget; + + /* DDD + * the following must be done by this point, otherwise the OTG + * host port doesn't make it thru initializtion. + * ehci_halt(), called by ehci_fsl_setup() returns -ETIMEDOUT + */ + fsl_platform_set_host_mode(hcd); + + retval = usb_add_hcd(hcd, irq, IRQF_SHARED); + if (retval != 0) { + pr_debug("failed with usb_add_hcd\n"); + goto err2; + } +#if defined(CONFIG_USB_OTG) + if (pdata->does_otg) { + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + + dbg("pdev=0x%p hcd=0x%p ehci=0x%p\n", pdev, hcd, ehci); + + ehci->transceiver = otg_get_transceiver(); + dbg("ehci->transceiver=0x%p\n", ehci->transceiver); + + if (ehci->transceiver) { + retval = otg_set_host(ehci->transceiver, + &ehci_to_hcd(ehci)->self); + dev_dbg(ehci->transceiver->dev, + "init %s transceiver, retval %d\n", + ehci->transceiver->label, retval); + if (retval) { + if (ehci->transceiver) + put_device(ehci->transceiver->dev); + goto err2; + } + } else { + printk(KERN_ERR "can't find transceiver\n"); + retval = -ENODEV; + goto err2; + } + } +#endif + + return retval; + + err2: + usb_put_hcd(hcd); + err1: + dev_err(&pdev->dev, "init %s fail, %d\n", pdev->dev.bus_id, retval); + if (pdata->platform_uninit) + pdata->platform_uninit(pdata); + return retval; +} + +static void usb_hcd_fsl_remove(struct usb_hcd *hcd, + struct platform_device *pdev) +{ + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + struct fsl_usb2_platform_data *pdata; + pdata = (struct fsl_usb2_platform_data *)pdev->dev.platform_data; + + dbg("%s hcd=0x%p\n", __FUNCTION__, hcd); + + /* DDD shouldn't we turn off the power here? */ + fsl_platform_set_vbus_power(pdata, 0); + + usb_remove_hcd(hcd); + usb_put_hcd(hcd); + + if (ehci->transceiver) { + (void)otg_set_host(ehci->transceiver, 0); + put_device(ehci->transceiver->dev); + } + + /* + * do platform specific un-initialization: + * release iomux pins, etc. + */ + if (pdata->platform_uninit) + pdata->platform_uninit(pdata); +} + +/* called after powerup, by probe or system-pm "wakeup" */ +static int ehci_fsl_reinit(struct ehci_hcd *ehci) +{ + fsl_platform_usb_setup(ehci_to_hcd(ehci)); + ehci_port_power(ehci, 0); + + return 0; +} + +/* called during probe() after chip reset completes */ +static int ehci_fsl_setup(struct usb_hcd *hcd) +{ + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + int retval; + + /* EHCI registers start at offset 0x00 */ + ehci->caps = hcd->regs + 0x100; + ehci->regs = hcd->regs + 0x100 + + HC_LENGTH(readl(&ehci->caps->hc_capbase)); + + vdbg("%s(): ehci->caps=0x%p ehci->regs=0x%p\n", __FUNCTION__, + ehci->caps, ehci->regs); + + dbg_hcs_params(ehci, "reset"); + dbg_hcc_params(ehci, "reset"); + + /* cache this readonly data; minimize chip reads */ + ehci->hcs_params = readl(&ehci->caps->hcs_params); + + retval = ehci_halt(ehci); + if (retval) + return retval; + + /* data structure init */ + retval = ehci_init(hcd); + if (retval) + return retval; + + ehci->is_tdi_rh_tt = 1; + + ehci->sbrn = 0x20; + + ehci_reset(ehci); + + retval = ehci_fsl_reinit(ehci); + return retval; +} + +/* *INDENT-OFF* */ +static const struct hc_driver ehci_arc_hc_driver = { + .description = hcd_name, + .product_desc = "Freescale On-Chip EHCI Host Controller", + .hcd_priv_size = sizeof(struct ehci_hcd), + + /* + * generic hardware linkage + */ + .irq = ehci_irq, + .flags = FSL_PLATFORM_HC_FLAGS, + + /* + * basic lifecycle operations + */ + .reset = ehci_fsl_setup, + .start = ehci_run, + .stop = ehci_stop, + + /* + * managing i/o requests and associated device resources + */ + .urb_enqueue = ehci_urb_enqueue, + .urb_dequeue = ehci_urb_dequeue, + .endpoint_disable = ehci_endpoint_disable, + + /* + * scheduling support + */ + .get_frame_number = ehci_get_frame, + + /* + * root hub support + */ + .hub_status_data = ehci_hub_status_data, + .hub_control = ehci_hub_control, + .bus_suspend = ehci_bus_suspend, + .bus_resume = ehci_bus_resume, +}; +/* *INDENT-ON* */ + +#ifdef CONFIG_USB_OTG +volatile static struct ehci_regs usb_ehci_regs; + +/* suspend/resume, section 4.3 */ + +/* These routines rely on the bus (pci, platform, etc) + * to handle powerdown and wakeup, and currently also on + * transceivers that don't need any software attention to set up + * the right sort of wakeup. + * + * They're also used for turning on/off the port when doing OTG. + */ +static int ehci_arc_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + struct fsl_usb2_platform_data *pdata = + (struct fsl_usb2_platform_data *)pdev->dev.platform_data; + u32 cmd; + + dbg("%s pdev=0x%p pdata=0x%p ehci=0x%p hcd=0x%p\n", + __FUNCTION__, pdev, pdata, ehci, hcd); + dbg("%s ehci->regs=0x%p hcd->regs=0x%p hcd->state=%d\n", + __FUNCTION__, ehci->regs, hcd->regs, hcd->state); + dbg("%s pdata->usbmode=0x%x\n", __FUNCTION__, pdata->usbmode); + + hcd->state = HC_STATE_HALT; /* ignore non-host interrupts */ + + cmd = readl(&ehci->regs->command); + cmd &= ~CMD_RUN; + writel(cmd, &ehci->regs->command); + + memcpy((void *)&usb_ehci_regs, ehci->regs, sizeof(struct ehci_regs)); + usb_ehci_regs.port_status[0] &= + cpu_to_le32(~(PORT_PEC | PORT_OCC | PORT_CSC)); + + fsl_platform_set_vbus_power(pdata, 0); + + return 0; +} + +static int ehci_arc_resume(struct platform_device *pdev) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + u32 cmd; + struct fsl_usb2_platform_data *pdata = + (struct fsl_usb2_platform_data *)pdev->dev.platform_data; + + dbg("%s pdev=0x%p pdata=0x%p ehci=0x%p hcd=0x%p\n", + __FUNCTION__, pdev, pdata, ehci, hcd); + vdbg("%s ehci->regs=0x%p hcd->regs=0x%p usbmode=0x%x\n", + __FUNCTION__, ehci->regs, hcd->regs, pdata->usbmode); + + writel(USBMODE_CM_HOST, pdata->usbmode); + memcpy(ehci->regs, (void *)&usb_ehci_regs, sizeof(struct ehci_regs)); + + hcd->state = HC_STATE_RUNNING; + + cmd = readl(&ehci->regs->command); + cmd |= CMD_RUN; + writel(cmd, &ehci->regs->command); + + fsl_platform_set_vbus_power(pdata, 1); + + return 0; +} +#endif /* CONFIG_USB_OTG */ + +static int ehci_hcd_drv_probe(struct platform_device *pdev) +{ + if (usb_disabled()) + return -ENODEV; + + return usb_hcd_fsl_probe(&ehci_arc_hc_driver, pdev); +} + +static int __init_or_module ehci_hcd_drv_remove(struct platform_device *pdev) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + + usb_hcd_fsl_remove(hcd, pdev); + + return 0; +} + +/* *INDENT-OFF* */ +static struct platform_driver ehci_fsl_driver = { + .probe = ehci_hcd_drv_probe, + .remove = ehci_hcd_drv_remove, +#ifdef CONFIG_USB_OTG + .suspend = ehci_arc_suspend, + .resume = ehci_arc_resume, +#endif + .driver = { + .name = "fsl-ehci", + }, +}; +/* *INDENT-ON* */ diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c index 5f2d74ed5ad7..7bfd0bcc7bd0 100644 --- a/drivers/usb/host/ehci-hcd.c +++ b/drivers/usb/host/ehci-hcd.c @@ -954,6 +954,11 @@ MODULE_LICENSE ("GPL"); #define PLATFORM_DRIVER ehci_hcd_au1xxx_driver #endif +#ifdef CONFIG_USB_EHCI_ARC +#include "ehci-arc.c" +#define PLATFORM_DRIVER ehci_fsl_driver +#endif + #ifdef CONFIG_PPC_PS3 #include "ehci-ps3.c" #define PS3_SYSTEM_BUS_DRIVER ps3_ehci_driver diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h index 951d69fec513..8946ef4482a7 100644 --- a/drivers/usb/host/ehci.h +++ b/drivers/usb/host/ehci.h @@ -120,6 +120,12 @@ struct ehci_hcd { /* one per controller */ u8 sbrn; /* packed release number */ + /* + * OTG controllers and transceivers need software interaction; + * other external transceivers should be software-transparent + */ + struct otg_transceiver *transceiver; + /* irq statistics */ #ifdef EHCI_STATS struct ehci_stats stats; diff --git a/drivers/usb/otg/Kconfig b/drivers/usb/otg/Kconfig new file mode 100644 index 000000000000..20ccb828b740 --- /dev/null +++ b/drivers/usb/otg/Kconfig @@ -0,0 +1,5 @@ +config TRANSCEIVER_MXC_OTG + tristate "usb otg pin detect support" + depends on (MC13783_MXC || ISP1504_MXC) && USB_GADGET && USB_EHCI_HCD && USB_OTG + help + Support for USB OTG PIN detect on MXC platforms. diff --git a/drivers/usb/otg/Makefile b/drivers/usb/otg/Makefile new file mode 100644 index 000000000000..dc37de2c2360 --- /dev/null +++ b/drivers/usb/otg/Makefile @@ -0,0 +1,6 @@ +# +# Makefile for USB OTG controller driver +# +# USB transceiver +fsl_otg_arc-objs := fsl_otg.o otg_fsm.o +obj-$(CONFIG_TRANSCEIVER_MXC_OTG) += fsl_otg_arc.o diff --git a/drivers/usb/otg/fsl_otg.c b/drivers/usb/otg/fsl_otg.c new file mode 100644 index 000000000000..1436fa1c6c4b --- /dev/null +++ b/drivers/usb/otg/fsl_otg.c @@ -0,0 +1,1078 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/proc_fs.h> +#include <linux/errno.h> +#include <linux/timer.h> +#include <linux/list.h> +#include <linux/usb.h> +#include <linux/platform_device.h> +#include <linux/usb_gadget.h> +#include <linux/time.h> + +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/system.h> + +#include <linux/fsl_devices.h> +#include "fsl_otg.h" +#include <asm/arch/arc_otg.h> + +#define CONFIG_USB_OTG_DEBUG_FILES +#define DRIVER_VERSION "Revision: 1.0" +#define DRIVER_AUTHOR "Jerry Huang/Leo Li" +#define DRIVER_DESC "USB OTG Driver" +#define DRIVER_INFO DRIVER_VERSION " " DRIVER_DESC + +MODULE_DESCRIPTION("ARC USB OTG Transceiver Driver"); + +static const char otg_dr_name[] = "fsl_arc"; +static spinlock_t usb_dr_regs_lock; + +#undef HA_DATA_PULSE + +volatile static struct usb_dr_mmap *usb_dr_regs; +static struct fsl_otg *fsl_otg_dev = NULL; +static int srp_wait_done; + +/* FSM timers */ +struct fsl_otg_timer *a_wait_vrise_tmr, *a_wait_bcon_tmr, *a_aidl_bdis_tmr, + *b_ase0_brst_tmr, *b_se0_srp_tmr; + +/* Driver specific timers */ +struct fsl_otg_timer *b_data_pulse_tmr, *b_vbus_pulse_tmr, *b_srp_fail_tmr, + *b_srp_wait_tmr, *a_wait_enum_tmr; + +static struct list_head active_timers; + +static struct fsl_otg_config fsl_otg_initdata = { + .otg_port = 1, +}; + +/** + * usb_bus_start_enum - start immediate enumeration (for OTG) + * @bus: the bus (must use hcd framework) + * @port: 1-based number of port; usually bus->otg_port + * Context: in_interrupt() + * + * Starts enumeration, with an immediate reset followed later by + * khubd identifying and possibly configuring the device. + * This is needed by OTG controller drivers, where it helps meet + * HNP protocol timing requirements for starting a port reset. + */ + +#include "../../../drivers/usb/core/hcd.h" + +int usb_bus_start_enum(struct usb_bus *bus, unsigned port_num) +{ + struct usb_hcd *hcd; + int status = -EOPNOTSUPP; + + /* NOTE: since HNP can't start by grabbing the bus's address0_sem, + * boards with root hubs hooked up to internal devices (instead of + * just the OTG port) may need more attention to resetting... + */ + + hcd = container_of(bus, struct usb_hcd, self); + if (port_num && hcd->driver->start_port_reset) + status = hcd->driver->start_port_reset(hcd, port_num); + + /* run khubd shortly after (first) root port reset finishes; + * it may issue others, until at least 50 msecs have passed. + */ + if (status == 0) + mod_timer(&hcd->rh_timer, jiffies + msecs_to_jiffies(10)); + + return status; +} + +#if defined(CONFIG_ISP1504_MXC) +int write_ulpi(u8 addr, u8 data) +{ + u32 temp; + temp = 0x60000000 | (addr << 16) | data; + temp = cpu_to_le32(temp); + usb_dr_regs->ulpiview = temp; + return 0; +} +#endif + +/* prototype declaration */ +void fsl_otg_add_timer(void *timer); +void fsl_otg_del_timer(void *timer); + +/* -------------------------------------------------------------*/ +/* Operations that will be called from OTG Finite State Machine */ + +/* Charge vbus for vbus pulsing in SRP */ +void fsl_otg_chrg_vbus(int on) +{ + if (on) + usb_dr_regs->otgsc = + cpu_to_le32((le32_to_cpu(usb_dr_regs->otgsc) & + ~OTGSC_INTSTS_MASK & + ~OTGSC_CTRL_VBUS_DISCHARGE) | + OTGSC_CTRL_VBUS_CHARGE); + else + usb_dr_regs->otgsc = + cpu_to_le32((le32_to_cpu(usb_dr_regs->otgsc) & + ~OTGSC_INTSTS_MASK & ~OTGSC_CTRL_VBUS_CHARGE)); +} + +/* Discharge vbus through a resistor to ground */ +void fsl_otg_dischrg_vbus(int on) +{ + if (on) + usb_dr_regs->otgsc = + cpu_to_le32((le32_to_cpu(usb_dr_regs->otgsc) & + ~OTGSC_INTSTS_MASK) + | OTGSC_CTRL_VBUS_DISCHARGE); + else + usb_dr_regs->otgsc = + cpu_to_le32((le32_to_cpu(usb_dr_regs->otgsc) & + ~OTGSC_INTSTS_MASK & + ~OTGSC_CTRL_VBUS_DISCHARGE)); +} + +/* A-device driver vbus, controlled through PP bit in PORTSC */ +void fsl_otg_drv_vbus(int on) +{ + if (on) + usb_dr_regs->portsc = + cpu_to_le32((le32_to_cpu(usb_dr_regs->portsc) & + ~PORTSC_W1C_BITS) | PORTSC_PORT_POWER); + else + usb_dr_regs->portsc = + cpu_to_le32(le32_to_cpu(usb_dr_regs->portsc) & + ~PORTSC_W1C_BITS & ~PORTSC_PORT_POWER); + +} + +/* Pull-up D+, signalling connect by periperal. Also used in + * data-line pulsing in SRP */ +void fsl_otg_loc_conn(int on) +{ + if (on) + usb_dr_regs->otgsc = + cpu_to_le32((le32_to_cpu(usb_dr_regs->otgsc) & + ~OTGSC_INTSTS_MASK) | OTGSC_CTRL_DATA_PULSING); + else + usb_dr_regs->otgsc = + cpu_to_le32(le32_to_cpu(usb_dr_regs->otgsc) & + ~OTGSC_INTSTS_MASK & ~OTGSC_CTRL_DATA_PULSING); +} + +/* Generate SOF by host. This is controlled through suspend/resume the + * port. In host mode, controller will automatically send SOF. + * Suspend will block the data on the port. + */ +void fsl_otg_loc_sof(int on) +{ +} + +/* Start SRP pulsing by data-line pulsing, followed with v-bus pulsing. */ +void fsl_otg_start_pulse(void) +{ + srp_wait_done = 0; +#ifdef HA_DATA_PULSE + usb_dr_regs->otgsc = + cpu_to_le32((le32_to_cpu(usb_dr_regs->otgsc) & ~OTGSC_INTSTS_MASK) + | OTGSC_HA_DATA_PULSE); +#else + fsl_otg_loc_conn(1); +#endif + + fsl_otg_add_timer(b_data_pulse_tmr); +} + +void fsl_otg_pulse_vbus(void); + +void b_data_pulse_end(unsigned long foo) +{ +#ifdef HA_DATA_PULSE +#else + fsl_otg_loc_conn(0); +#endif + + /* Do VBUS pulse after data pulse */ + fsl_otg_pulse_vbus(); +} + +void fsl_otg_pulse_vbus(void) +{ + srp_wait_done = 0; + fsl_otg_chrg_vbus(1); + /* start the timer to end vbus charge */ + fsl_otg_add_timer(b_vbus_pulse_tmr); +} + +void b_vbus_pulse_end(unsigned long foo) +{ + fsl_otg_chrg_vbus(0); + + /* As USB3300 using the same a_sess_vld and b_sess_vld voltage + * we need to discharge the bus for a while to distinguish + * residual voltage of vbus pulsing and A device pull up */ + fsl_otg_dischrg_vbus(1); + fsl_otg_add_timer(b_srp_wait_tmr); +} + +void b_srp_end(unsigned long foo) +{ + fsl_otg_dischrg_vbus(0); + srp_wait_done = 1; + + if ((fsl_otg_dev->otg.state == OTG_STATE_B_SRP_INIT) && + fsl_otg_dev->fsm.b_sess_vld) + fsl_otg_dev->fsm.b_srp_done = 1; +} + +/* Workaround for a_host suspending too fast. When a_bus_req=0, + * a_host will start by SRP. It needs to set b_hnp_enable before + * actually suspending to start HNP + */ +void a_wait_enum(unsigned long foo) +{ + VDBG("a_wait_enum timeout\n"); + if (!fsl_otg_dev->otg.host->b_hnp_enable) + fsl_otg_add_timer(a_wait_enum_tmr); + else + otg_statemachine(&fsl_otg_dev->fsm); +} + +/* ------------------------------------------------------*/ + +/* The timeout callback function to set time out bit */ +void set_tmout(unsigned long indicator) +{ + *(int *)indicator = 1; +} + +/* Initialize timers */ +int fsl_otg_init_timers(struct otg_fsm *fsm) +{ + /* FSM used timers */ + a_wait_vrise_tmr = otg_timer_initializer(&set_tmout, TA_WAIT_VRISE, + (unsigned long)&fsm-> + a_wait_vrise_tmout); + if (a_wait_vrise_tmr == NULL) + return -ENOMEM; + + a_wait_bcon_tmr = + otg_timer_initializer(&set_tmout, TA_WAIT_BCON, + (unsigned long)&fsm->a_wait_bcon_tmout); + if (a_wait_bcon_tmr == NULL) + return -ENOMEM; + + a_aidl_bdis_tmr = + otg_timer_initializer(&set_tmout, TA_AIDL_BDIS, + (unsigned long)&fsm->a_aidl_bdis_tmout); + if (a_aidl_bdis_tmr == NULL) + return -ENOMEM; + + b_ase0_brst_tmr = + otg_timer_initializer(&set_tmout, TB_ASE0_BRST, + (unsigned long)&fsm->b_ase0_brst_tmout); + if (b_ase0_brst_tmr == NULL) + return -ENOMEM; + + b_se0_srp_tmr = + otg_timer_initializer(&set_tmout, TB_SE0_SRP, + (unsigned long)&fsm->b_se0_srp); + if (b_se0_srp_tmr == NULL) + return -ENOMEM; + + b_srp_fail_tmr = + otg_timer_initializer(&set_tmout, TB_SRP_FAIL, + (unsigned long)&fsm->b_srp_done); + if (b_srp_fail_tmr == NULL) + return -ENOMEM; + + a_wait_enum_tmr = + otg_timer_initializer(&a_wait_enum, 10, (unsigned long)&fsm); + if (a_wait_enum_tmr == NULL) + return -ENOMEM; + + /* device driver used timers */ + b_srp_wait_tmr = otg_timer_initializer(&b_srp_end, TB_SRP_WAIT, 0); + if (b_srp_wait_tmr == NULL) + return -ENOMEM; + + b_data_pulse_tmr = otg_timer_initializer(&b_data_pulse_end, + TB_DATA_PLS, 0); + if (b_data_pulse_tmr == NULL) + return -ENOMEM; + + b_vbus_pulse_tmr = otg_timer_initializer(&b_vbus_pulse_end, + TB_VBUS_PLS, 0); + if (b_vbus_pulse_tmr == NULL) + return -ENOMEM; + + return 0; +} + +/* Uninitialize timers */ +void fsl_otg_uninit_timers(void) +{ + /* FSM used timers */ + if (a_wait_vrise_tmr != NULL) + kfree(a_wait_vrise_tmr); + if (a_wait_bcon_tmr != NULL) + kfree(a_wait_bcon_tmr); + if (a_aidl_bdis_tmr != NULL) + kfree(a_aidl_bdis_tmr); + if (b_ase0_brst_tmr != NULL) + kfree(b_ase0_brst_tmr); + if (b_se0_srp_tmr != NULL) + kfree(b_se0_srp_tmr); + if (b_srp_fail_tmr != NULL) + kfree(b_srp_fail_tmr); + if (a_wait_enum_tmr != NULL) + kfree(a_wait_enum_tmr); + + /* device driver used timers */ + if (b_srp_wait_tmr != NULL) + kfree(b_srp_wait_tmr); + if (b_data_pulse_tmr != NULL) + kfree(b_data_pulse_tmr); + if (b_vbus_pulse_tmr != NULL) + kfree(b_vbus_pulse_tmr); +} + +/* Add timer to timer list */ +void fsl_otg_add_timer(void *gtimer) +{ + struct fsl_otg_timer *timer = (struct fsl_otg_timer *)gtimer; + struct fsl_otg_timer *tmp_timer; + + /* Check if the timer is already in the active list, + * if so update timer count + */ + list_for_each_entry(tmp_timer, &active_timers, list) + if (tmp_timer == timer) { + timer->count = timer->expires; + return; + } + timer->count = timer->expires; + list_add_tail(&timer->list, &active_timers); +} + +/* Remove timer from the timer list; clear timeout status */ +void fsl_otg_del_timer(void *gtimer) +{ + struct fsl_otg_timer *timer = (struct fsl_otg_timer *)gtimer; + struct fsl_otg_timer *tmp_timer, *del_tmp; + + list_for_each_entry_safe(tmp_timer, del_tmp, &active_timers, list) + if (tmp_timer == timer) + list_del(&timer->list); +} + +/* Reduce timer count by 1, and find timeout conditions. + * Called by fsl_otg 1ms timer interrupt + */ +int fsl_otg_tick_timer(void) +{ + struct fsl_otg_timer *tmp_timer, *del_tmp; + int expired = 0; + + list_for_each_entry_safe(tmp_timer, del_tmp, &active_timers, list) { + tmp_timer->count--; + /* check if timer expires */ + if (!tmp_timer->count) { + list_del(&tmp_timer->list); + tmp_timer->function(tmp_timer->data); + expired = 1; + } + } + + return expired; +} + +/* Reset controller, not reset the bus */ +void otg_reset_controller(void) +{ + u32 command; + unsigned long flags; + + spin_lock_irqsave(&usb_dr_regs_lock, flags); + command = readl(&usb_dr_regs->usbcmd); + command |= UCMD_RESET; + writel(command, &usb_dr_regs->usbcmd); + spin_unlock_irqrestore(&usb_dr_regs_lock, flags); + while (readl(&usb_dr_regs->usbcmd) & UCMD_RESET) + continue; +} + +/* Call suspend/resume routines in host driver */ +int fsl_otg_start_host(struct otg_fsm *fsm, int on) +{ + struct otg_transceiver *xceiv = fsm->transceiver; + struct device *dev; + struct fsl_otg *otg_dev = container_of(xceiv, struct fsl_otg, otg); + u32 retval = 0; + pm_message_t state = { 0 }; + + if (!xceiv->host) + return -ENODEV; + + dev = xceiv->host->controller; + + /* Update a_vbus_vld state as a_vbus_vld int is disabled + * in device mode + */ + fsm->a_vbus_vld = + (le32_to_cpu(usb_dr_regs->otgsc) & OTGSC_STS_A_VBUS_VALID) ? 1 : 0; + if (on) { + /* start fsl usb host controller */ + if (otg_dev->host_working) + goto end; + else { + otg_reset_controller(); + VDBG("host on......"); + if (dev->driver->resume) { + retval = dev->driver->resume(dev); + if (fsm->id) { + /* default-b */ + fsl_otg_drv_vbus(1); + /* Workaround: b_host can't driver + * vbus, but PP in PORTSC needs to + * be 1 for host to work. + * So we set drv_vbus bit in + * transceiver to 0 thru ULPI. */ +#if defined(CONFIG_ISP1504_MXC) + write_ulpi(0x0c, 0x20); +#endif + } + } + + otg_dev->host_working = 1; + } + } else { + /* stop fsl usb host controller */ + if (!otg_dev->host_working) + goto end; + else { + VDBG("host off......"); + if (dev && dev->driver) { + retval = dev->driver->suspend(dev, state); + if (fsm->id) + /* default-b */ + fsl_otg_drv_vbus(0); + } + otg_dev->host_working = 0; + } + } + end: + return retval; +} + +/* Call suspend and resume function in udc driver + * to stop and start udc driver. + */ +int fsl_otg_start_gadget(struct otg_fsm *fsm, int on) +{ + struct otg_transceiver *xceiv = fsm->transceiver; + struct device *udc_dev; + pm_message_t state = { 0 }; + + if (!xceiv->gadget || !xceiv->gadget->dev.parent) + return -ENODEV; + + VDBG("gadget %s", on ? "on" : "off"); + udc_dev = xceiv->gadget->dev.parent; + + if (on) + udc_dev->driver->resume(udc_dev); + else + udc_dev->driver->suspend(udc_dev, state); + + return 0; +} + +/* Called by initialization code of host driver. Register host controller + * to the OTG. Suspend host for OTG role detection. + */ +static int fsl_otg_set_host(struct otg_transceiver *otg_p, struct usb_bus *host) +{ + struct fsl_otg *otg_dev = container_of(otg_p, struct fsl_otg, otg); + struct device *dev; + pm_message_t state = { 0 }; + + if (!otg_p || otg_dev != fsl_otg_dev) + return -ENODEV; + + otg_p->host = host; + + otg_dev->fsm.a_bus_drop = 0; + otg_dev->fsm.a_bus_req = 1; + + if (host) { + VDBG("host off......\n"); + + otg_p->host->otg_port = fsl_otg_initdata.otg_port; + otg_p->host->is_b_host = otg_dev->fsm.id; + dev = host->controller; + + if (dev && dev->driver) + dev->driver->suspend(dev, state); + } else { /* host driver going away */ + + if (!(le32_to_cpu(otg_dev->dr_mem_map->otgsc) & + OTGSC_STS_USB_ID)) { + /* Mini-A cable connected */ + struct otg_fsm *fsm = &otg_dev->fsm; + + otg_p->state = OTG_STATE_UNDEFINED; + fsm->protocol = PROTO_UNDEF; + } + } + + otg_dev->host_working = 0; + + otg_statemachine(&otg_dev->fsm); + + return 0; +} + +/* Called by initialization code of udc. Register udc to OTG.*/ +static int fsl_otg_set_peripheral(struct otg_transceiver *otg_p, + struct usb_gadget *gadget) +{ + struct fsl_otg *otg_dev = container_of(otg_p, struct fsl_otg, otg); + + VDBG("otg_dev 0x%x", (int)otg_dev); + VDBG("fsl_otg_dev 0x%x", (int)fsl_otg_dev); + + if (!otg_p || otg_dev != fsl_otg_dev) + return -ENODEV; + + if (!gadget) { + if (!otg_dev->otg.default_a) + otg_p->gadget->ops->vbus_draw(otg_p->gadget, 0); + usb_gadget_vbus_disconnect(otg_dev->otg.gadget); + otg_dev->otg.gadget = 0; + otg_dev->fsm.b_bus_req = 0; + otg_statemachine(&otg_dev->fsm); + return 0; + } +#ifdef DEBUG + /* + * debug the initial state of the ID pin when only + * the gadget driver is loaded and no cable is connected. + * sometimes, we get an ID irq right + * after the udc driver's otg_get_transceiver() call + * that indicates that IDpin=0, which means a Mini-A + * connector is attached. not good. + */ + DBG("before: fsm.id ID pin=%d", otg_dev->fsm.id); + otg_dev->fsm.id = (otg_dev->dr_mem_map->otgsc & OTGSC_STS_USB_ID) ? + 1 : 0; + DBG("after: fsm.id ID pin=%d", otg_dev->fsm.id); + /*if (!otg_dev->fsm.id) { + printk("OTG Control = 0x%x\n", + isp1504_read(ISP1504_OTGCTL, + &otg_dev->dr_mem_map->ulpiview)); + } */ +#endif + + otg_p->gadget = gadget; + otg_p->gadget->is_a_peripheral = !otg_dev->fsm.id; + + otg_dev->fsm.b_bus_req = 1; + + /* start the gadget right away if the ID pin says Mini-B */ + DBG("ID pin=%d", otg_dev->fsm.id); + if (otg_dev->fsm.id == 1) { + fsl_otg_start_host(&otg_dev->fsm, 0); + otg_drv_vbus(&otg_dev->fsm, 0); + fsl_otg_start_gadget(&otg_dev->fsm, 1); + } + + return 0; +} + +/* Set OTG port power, only for B-device */ +static int fsl_otg_set_power(struct otg_transceiver *otg_p, unsigned mA) +{ + if (!fsl_otg_dev) + return -ENODEV; + if (otg_p->state == OTG_STATE_B_PERIPHERAL) + printk("FSL OTG:Draw %d mA\n", mA); + + return 0; +} + +/* Delayed pin detect interrupt processing. + * + * When the Mini-A cable is disconnected from the board, + * the pin-detect interrupt happens before the disconnnect + * interrupts for the connected device(s). In order to + * process the disconnect interrupt(s) prior to switching + * roles, the pin-detect interrupts are delayed, and handled + * by this routine. + */ +static void fsl_otg_event(struct work_struct *work) +{ + struct delayed_work *dwork = + container_of(work, struct delayed_work, work); + struct fsl_otg *otg = container_of(dwork, struct fsl_otg, otg_event); + struct otg_fsm *fsm = &otg->fsm; + + if (fsm->id) { /* switch to gadget */ + fsl_otg_start_host(fsm, 0); + otg_drv_vbus(fsm, 0); + fsl_otg_start_gadget(fsm, 1); + } +} + +/* Interrupt handler. OTG/host/peripheral share the same int line. + * OTG driver clears OTGSC interrupts and leaves USB interrupts + * intact. It needs to have knowledge of some USB interrupts + * such as port change. + */ +irqreturn_t fsl_otg_isr(int irq, void *dev_id) +{ + struct otg_fsm *fsm = &((struct fsl_otg *)dev_id)->fsm; + struct otg_transceiver *otg = &((struct fsl_otg *)dev_id)->otg; + u32 otg_int_src, otg_sc; + + otg_sc = le32_to_cpu(usb_dr_regs->otgsc); + otg_int_src = otg_sc & OTGSC_INTSTS_MASK & (otg_sc >> 8); + + /* Only clear otg interrupts */ + usb_dr_regs->otgsc |= cpu_to_le32(otg_sc & OTGSC_INTSTS_MASK); + + /*FIXME: ID change not generate when init to 0 */ + fsm->id = (otg_sc & OTGSC_STS_USB_ID) ? 1 : 0; + otg->default_a = (fsm->id == 0); + + /* process OTG interrupts */ + if (otg_int_src) { + if (otg_int_src & OTGSC_IS_USB_ID) { + fsm->id = (otg_sc & OTGSC_STS_USB_ID) ? 1 : 0; + otg->default_a = (fsm->id == 0); + if (otg->host) + otg->host->is_b_host = fsm->id; + if (otg->gadget) + otg->gadget->is_a_peripheral = !fsm->id; + VDBG("IRQ=ID now=%d", fsm->id); + + if (fsm->id) { /* switch to gadget */ + schedule_delayed_work((struct delayed_work *) + &((struct fsl_otg *) + dev_id)->otg_event, 25); + } else { /* switch to host */ + cancel_delayed_work((struct delayed_work *) + &((struct fsl_otg *) + dev_id)->otg_event); + fsl_otg_start_gadget(fsm, 0); + otg_drv_vbus(fsm, 1); + fsl_otg_start_host(fsm, 1); + } + + return IRQ_HANDLED; + } + } + + return IRQ_NONE; +} + +static struct otg_fsm_ops fsl_otg_ops = { + .chrg_vbus = fsl_otg_chrg_vbus, + .drv_vbus = fsl_otg_drv_vbus, + .loc_conn = fsl_otg_loc_conn, + .loc_sof = fsl_otg_loc_sof, + .start_pulse = fsl_otg_start_pulse, + + .add_timer = fsl_otg_add_timer, + .del_timer = fsl_otg_del_timer, + + .start_host = fsl_otg_start_host, + .start_gadget = fsl_otg_start_gadget, +}; + +/* Initialize the global variable fsl_otg_dev and request IRQ for OTG */ +static int fsl_otg_conf(struct platform_device *pdev) +{ + int status; + struct fsl_otg *fsl_otg_tc; + struct fsl_usb2_platform_data *pdata; + + pdata = (struct fsl_usb2_platform_data *)pdev->dev.platform_data; + + DBG(); + + if (fsl_otg_dev) + return 0; + + /* allocate space to fsl otg device */ + fsl_otg_tc = kmalloc(sizeof(struct fsl_otg), GFP_KERNEL); + if (!fsl_otg_tc) + return -ENODEV; + + memset(fsl_otg_tc, 0, sizeof(struct fsl_otg)); + + fsl_otg_tc->dr_mem_map = pdata->regs; + + DBG("set dr_mem_map to 0x%p", pdata->regs); + spin_lock_init(&usb_dr_regs_lock); + + INIT_DELAYED_WORK(&fsl_otg_tc->otg_event, fsl_otg_event); + + INIT_LIST_HEAD(&active_timers); + status = fsl_otg_init_timers(&fsl_otg_tc->fsm); + if (status) { + printk(KERN_INFO "Couldn't init OTG timers\n"); + fsl_otg_uninit_timers(); + kfree(fsl_otg_tc); + return status; + } + + /* Set OTG state machine operations */ + fsl_otg_tc->fsm.ops = &fsl_otg_ops; + + /* record initial state of ID pin */ + fsl_otg_tc->fsm.id = (fsl_otg_tc->dr_mem_map->otgsc & OTGSC_STS_USB_ID) + ? 1 : 0; + DBG("initial ID pin=%d", fsl_otg_tc->fsm.id); + + /* initialize the otg structure */ + fsl_otg_tc->otg.label = DRIVER_DESC; + fsl_otg_tc->otg.set_host = fsl_otg_set_host; + fsl_otg_tc->otg.set_peripheral = fsl_otg_set_peripheral; + fsl_otg_tc->otg.set_power = fsl_otg_set_power; + + fsl_otg_dev = fsl_otg_tc; + + /* Store the otg transceiver */ + status = otg_set_transceiver(&fsl_otg_tc->otg); + if (status) { + printk(KERN_WARNING ": unable to register OTG transceiver.\n"); + return status; + } + + return 0; +} + +/* OTG Initialization*/ +int usb_otg_start(struct platform_device *pdev) +{ + struct fsl_otg *p_otg; + struct otg_transceiver *otg_trans = otg_get_transceiver(); + struct otg_fsm *fsm; + int status; + u32 temp; + struct resource *res; + unsigned long flags; + + DBG(); + + p_otg = container_of(otg_trans, struct fsl_otg, otg); + fsm = &p_otg->fsm; + + /* Initialize the state machine structure with default values */ + SET_OTG_STATE(otg_trans, OTG_STATE_UNDEFINED); + fsm->transceiver = &p_otg->otg; + + usb_dr_regs = p_otg->dr_mem_map; + DBG("set usb_dr_regs to 0x%p", usb_dr_regs); + + /* request irq */ + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!res) { + dev_err(&pdev->dev, "Can't find irq resource.\n"); + return -ENODEV; + } + p_otg->irq = res->start; + DBG("requesting irq %d", p_otg->irq); + status = + request_irq(p_otg->irq, fsl_otg_isr, IRQF_SHARED, "fsl_arc", p_otg); + if (status) { + dev_dbg(p_otg->otg.dev, "can't get IRQ %d, error %d\n", + p_otg->irq, status); + kfree(p_otg); + return status; + } + + /* + * The ID input is FALSE when a Mini-A plug is inserted + * in the Mini-AB receptacle. Otherwise, this input is TRUE. + */ + if (le32_to_cpu(p_otg->dr_mem_map->otgsc) & OTGSC_STS_USB_ID) + p_otg->otg.state = OTG_STATE_UNDEFINED; /* not Mini-A */ + else + p_otg->otg.state = OTG_STATE_A_IDLE; /* Mini-A */ + + /* enable OTG interrupt */ + spin_lock_irqsave(&usb_dr_regs_lock, flags); + temp = readl(&p_otg->dr_mem_map->otgsc); + + temp &= ~OTGSC_INTERRUPT_ENABLE_BITS_MASK; + temp |= OTGSC_IE_USB_ID; + writel(temp, &p_otg->dr_mem_map->otgsc); + spin_unlock_irqrestore(&usb_dr_regs_lock, flags); + + return 0; +} + +static int board_init(struct platform_device *pdev) +{ + struct fsl_usb2_platform_data *pdata; + pdata = (struct fsl_usb2_platform_data *)pdev->dev.platform_data; + + /* + * do platform specific init: check the clock, grab/config pins, etc. + */ + if (pdata->platform_init(pdev) != 0) + return -EINVAL; + + return 0; +} + +/*------------------------------------------------------------------------- + PROC File System Support +-------------------------------------------------------------------------*/ +#ifdef CONFIG_USB_OTG_DEBUG_FILES + +#include <linux/seq_file.h> + +static const char proc_filename[] = "driver/isp1504_otg"; + +static int otg_proc_read(char *page, char **start, off_t off, int count, + int *eof, void *_dev) +{ + struct otg_fsm *fsm = &fsl_otg_dev->fsm; + char *buf = page; + char *next = buf; + unsigned size = count; + unsigned long flags; + int t; + u32 tmp_reg; + + if (off != 0) + return 0; + + spin_lock_irqsave(&fsm->lock, flags); + + /* ------basic driver infomation ---- */ + t = scnprintf(next, size, + DRIVER_DESC "\n" "isp1504_otg version: %s\n\n", + DRIVER_VERSION); + size -= t; + next += t; + + /* ------ Registers ----- */ + tmp_reg = le32_to_cpu(usb_dr_regs->otgsc); + t = scnprintf(next, size, "OTGSC reg: %x\n", tmp_reg); + size -= t; + next += t; + + tmp_reg = le32_to_cpu(usb_dr_regs->portsc); + t = scnprintf(next, size, "PORTSC reg: %x\n", tmp_reg); + size -= t; + next += t; + + tmp_reg = le32_to_cpu(usb_dr_regs->usbmode); + t = scnprintf(next, size, "USBMODE reg: %x\n", tmp_reg); + size -= t; + next += t; + + tmp_reg = le32_to_cpu(usb_dr_regs->usbcmd); + t = scnprintf(next, size, "USBCMD reg: %x\n", tmp_reg); + size -= t; + next += t; + + tmp_reg = le32_to_cpu(usb_dr_regs->usbsts); + t = scnprintf(next, size, "USBSTS reg: %x\n", tmp_reg); + size -= t; + next += t; + + /* ------ State ----- */ + t = scnprintf(next, size, + "OTG state: %s\n\n", + state_string(fsl_otg_dev->otg.state)); + size -= t; + next += t; + +#ifdef DEBUG + /* ------ State Machine Variables ----- */ + t = scnprintf(next, size, "a_bus_req: %d\n", fsm->a_bus_req); + size -= t; + next += t; + + t = scnprintf(next, size, "b_bus_req: %d\n", fsm->b_bus_req); + size -= t; + next += t; + + t = scnprintf(next, size, "a_bus_resume: %d\n", fsm->a_bus_resume); + size -= t; + next += t; + + t = scnprintf(next, size, "a_bus_suspend: %d\n", fsm->a_bus_suspend); + size -= t; + next += t; + + t = scnprintf(next, size, "a_conn: %d\n", fsm->a_conn); + size -= t; + next += t; + + t = scnprintf(next, size, "a_sess_vld: %d\n", fsm->a_sess_vld); + size -= t; + next += t; + + t = scnprintf(next, size, "a_srp_det: %d\n", fsm->a_srp_det); + size -= t; + next += t; + + t = scnprintf(next, size, "a_vbus_vld: %d\n", fsm->a_vbus_vld); + size -= t; + next += t; + + t = scnprintf(next, size, "b_bus_resume: %d\n", fsm->b_bus_resume); + size -= t; + next += t; + + t = scnprintf(next, size, "b_bus_suspend: %d\n", fsm->b_bus_suspend); + size -= t; + next += t; + + t = scnprintf(next, size, "b_conn: %d\n", fsm->b_conn); + size -= t; + next += t; + + t = scnprintf(next, size, "b_se0_srp: %d\n", fsm->b_se0_srp); + size -= t; + next += t; + + t = scnprintf(next, size, "b_sess_end: %d\n", fsm->b_sess_end); + size -= t; + next += t; + + t = scnprintf(next, size, "b_sess_vld: %d\n", fsm->b_sess_vld); + size -= t; + next += t; + + t = scnprintf(next, size, "id: %d\n", fsm->id); + size -= t; + next += t; +#endif + + spin_unlock_irqrestore(&fsm->lock, flags); + + *eof = 1; + return count - size; +} + +#define create_proc_file() create_proc_read_entry(proc_filename, \ + 0, NULL, otg_proc_read, NULL) + +#define remove_proc_file() remove_proc_entry(proc_filename, NULL) + +#else /* !CONFIG_USB_OTG_DEBUG_FILES */ + +#define create_proc_file() do {} while (0) +#define remove_proc_file() do {} while (0) + +#endif /*CONFIG_USB_OTG_DEBUG_FILES */ + +static int __init fsl_otg_probe(struct platform_device *pdev) +{ + int status; + + DBG("pdev=0x%p", pdev); + + if (!pdev) + return -ENODEV; + + /* Initialize the clock, multiplexing pin and PHY interface */ + board_init(pdev); + + /* configure the OTG */ + status = fsl_otg_conf(pdev); + if (status) { + printk(KERN_INFO "Couldn't init OTG module\n"); + return -status; + } + + /* start OTG */ + status = usb_otg_start(pdev); + + create_proc_file(); + return status; +} + +static int __exit fsl_otg_remove(struct platform_device *pdev) +{ + u32 ie; + struct fsl_usb2_platform_data *pdata; + unsigned long flags; + + pdata = (struct fsl_usb2_platform_data *)pdev->dev.platform_data; + + DBG("pdev=0x%p pdata=0x%p", pdev, pdata); + + otg_set_transceiver(NULL); + + /* disable and clear OTGSC interrupts */ + spin_lock_irqsave(&usb_dr_regs_lock, flags); + ie = readl(&usb_dr_regs->otgsc); + ie &= ~OTGSC_INTERRUPT_ENABLE_BITS_MASK; + ie |= OTGSC_INTERRUPT_STATUS_BITS_MASK; + writel(ie, &usb_dr_regs->otgsc); + spin_unlock_irqrestore(&usb_dr_regs_lock, flags); + + free_irq(fsl_otg_dev->irq, fsl_otg_dev); + + kfree(fsl_otg_dev); + + remove_proc_file(); + + if (pdata->platform_uninit) + pdata->platform_uninit(pdata); + + fsl_otg_dev = NULL; + return 0; +} + +struct platform_driver fsl_otg_driver = { + .probe = fsl_otg_probe, + .remove = fsl_otg_remove, + .driver = { + .name = "fsl_arc", + .owner = THIS_MODULE, + }, +}; + +/*-------------------------------------------------------------------------*/ + +static int __init fsl_usb_otg_init(void) +{ + printk(KERN_INFO "driver %s, %s\n", otg_dr_name, DRIVER_VERSION); + return platform_driver_register(&fsl_otg_driver); +} + +static void __exit fsl_usb_otg_exit(void) +{ + platform_driver_unregister(&fsl_otg_driver); +} + +module_init(fsl_usb_otg_init); +module_exit(fsl_usb_otg_exit); + +MODULE_DESCRIPTION(DRIVER_INFO); +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/otg/fsl_otg.h b/drivers/usb/otg/fsl_otg.h new file mode 100644 index 000000000000..b5a8a4b0afa5 --- /dev/null +++ b/drivers/usb/otg/fsl_otg.h @@ -0,0 +1,239 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include "otg_fsm.h" +#include <linux/usb/otg.h> + + /* USB Command Register Bit Masks */ +#define USB_CMD_RUN_STOP (0x1<<0 ) +#define USB_CMD_CTRL_RESET (0x1<<1 ) +#define USB_CMD_PERIODIC_SCHEDULE_EN (0x1<<4 ) +#define USB_CMD_ASYNC_SCHEDULE_EN (0x1<<5 ) +#define USB_CMD_INT_AA_DOORBELL (0x1<<6 ) +#define USB_CMD_ASP (0x3<<8 ) +#define USB_CMD_ASYNC_SCH_PARK_EN (0x1<<11 ) +#define USB_CMD_SUTW (0x1<<13 ) +#define USB_CMD_ATDTW (0x1<<14 ) +#define USB_CMD_ITC (0xFF<<16) + +/* bit 15,3,2 are frame list size */ +#define USB_CMD_FRAME_SIZE_1024 (0x0<<15 | 0x0<<2) +#define USB_CMD_FRAME_SIZE_512 (0x0<<15 | 0x1<<2) +#define USB_CMD_FRAME_SIZE_256 (0x0<<15 | 0x2<<2) +#define USB_CMD_FRAME_SIZE_128 (0x0<<15 | 0x3<<2) +#define USB_CMD_FRAME_SIZE_64 (0x1<<15 | 0x0<<2) +#define USB_CMD_FRAME_SIZE_32 (0x1<<15 | 0x1<<2) +#define USB_CMD_FRAME_SIZE_16 (0x1<<15 | 0x2<<2) +#define USB_CMD_FRAME_SIZE_8 (0x1<<15 | 0x3<<2) + +/* bit 9-8 are async schedule park mode count */ +#define USB_CMD_ASP_00 (0x0<<8) +#define USB_CMD_ASP_01 (0x1<<8) +#define USB_CMD_ASP_10 (0x2<<8) +#define USB_CMD_ASP_11 (0x3<<8) +#define USB_CMD_ASP_BIT_POS (8) + +/* bit 23-16 are interrupt threshold control */ +#define USB_CMD_ITC_NO_THRESHOLD (0x00<<16) +#define USB_CMD_ITC_1_MICRO_FRM (0x01<<16) +#define USB_CMD_ITC_2_MICRO_FRM (0x02<<16) +#define USB_CMD_ITC_4_MICRO_FRM (0x04<<16) +#define USB_CMD_ITC_8_MICRO_FRM (0x08<<16) +#define USB_CMD_ITC_16_MICRO_FRM (0x10<<16) +#define USB_CMD_ITC_32_MICRO_FRM (0x20<<16) +#define USB_CMD_ITC_64_MICRO_FRM (0x40<<16) +#define USB_CMD_ITC_BIT_POS (16) + +/* USB Status Register Bit Masks */ +#define USB_STS_INT (0x1<<0 ) +#define USB_STS_ERR (0x1<<1 ) +#define USB_STS_PORT_CHANGE (0x1<<2 ) +#define USB_STS_FRM_LST_ROLL (0x1<<3 ) +#define USB_STS_SYS_ERR (0x1<<4 ) +#define USB_STS_IAA (0x1<<5 ) +#define USB_STS_RESET_RECEIVED (0x1<<6 ) +#define USB_STS_SOF (0x1<<7 ) +#define USB_STS_DCSUSPEND (0x1<<8 ) +#define USB_STS_HC_HALTED (0x1<<12) +#define USB_STS_RCL (0x1<<13) +#define USB_STS_PERIODIC_SCHEDULE (0x1<<14) +#define USB_STS_ASYNC_SCHEDULE (0x1<<15) + +/* USB Interrupt Enable Register Bit Masks */ +#define USB_INTR_INT_EN (0x1<<0 ) +#define USB_INTR_ERR_INT_EN (0x1<<1 ) +#define USB_INTR_PC_DETECT_EN (0x1<<2 ) +#define USB_INTR_FRM_LST_ROLL_EN (0x1<<3 ) +#define USB_INTR_SYS_ERR_EN (0x1<<4 ) +#define USB_INTR_ASYN_ADV_EN (0x1<<5 ) +#define USB_INTR_RESET_EN (0x1<<6 ) +#define USB_INTR_SOF_EN (0x1<<7 ) +#define USB_INTR_DEVICE_SUSPEND (0x1<<8 ) + +/* Device Address bit masks */ +#define USB_DEVICE_ADDRESS_MASK (0x7F<<25) +#define USB_DEVICE_ADDRESS_BIT_POS (25) + +/* USB MODE Register Bit Masks */ +#define USB_MODE_CTRL_MODE_IDLE (0x0<<0) +#define USB_MODE_CTRL_MODE_DEVICE (0x2<<0) +#define USB_MODE_CTRL_MODE_HOST (0x3<<0) +#define USB_MODE_CTRL_MODE_RSV (0x1<<0) +#define USB_MODE_SETUP_LOCK_OFF (0x1<<3) +#define USB_MODE_STREAM_DISABLE (0x1<<4) + +/* + * A-DEVICE timing constants + */ + +/* Wait for VBUS Rise */ +#define TA_WAIT_VRISE (100) /* a_wait_vrise 100 ms, section: 6.6.5.1 */ + +/* Wait for B-Connect */ +#define TA_WAIT_BCON (10000) /* a_wait_bcon > 1 sec, section: 6.6.5.2 + * This is only used to get out of + * OTG_STATE_A_WAIT_BCON state if there was + * no connection for these many milliseconds + */ + +/* A-Idle to B-Disconnect */ +/* It is necessary for this timer to be more than 750 ms because of a bug in OPT + * test 5.4 in which B OPT disconnects after 750 ms instead of 75ms as stated + * in the test description + */ +#define TA_AIDL_BDIS (5000) /* a_suspend minimum 200 ms, section: 6.6.5.3 */ + +/* B-Idle to A-Disconnect */ +#define TA_BIDL_ADIS (12) /* 3 to 200 ms */ + +/* B-device timing constants */ + +/* Data-Line Pulse Time*/ +#define TB_DATA_PLS (10) /* b_srp_init,continue 5~10ms, section:5.3.3 */ +#define TB_DATA_PLS_MIN (5) /* minimum 5 ms */ +#define TB_DATA_PLS_MAX (10) /* maximum 10 ms */ + +/* SRP Initiate Time */ +#define TB_SRP_INIT (100) /* b_srp_init,maximum 100 ms, section:5.3.8 */ + +/* SRP Fail Time */ +#define TB_SRP_FAIL (7000) /* b_srp_init,Fail time 5~30s, section:6.8.2.2 */ + +/* SRP result wait time */ +#define TB_SRP_WAIT (60) + +/* VBus time */ +#define TB_VBUS_PLS (30) /* time to keep vbus pulsing asserted */ + +/* Discharge time */ +/* This time should be less than 10ms. It varies from system to system. */ +#define TB_VBUS_DSCHRG (8) + +/* A-SE0 to B-Reset */ +#define TB_ASE0_BRST (20) /* b_wait_acon, mini 3.125 ms,section:6.8.2.4 */ + +/* A bus suspend timer before we can switch to b_wait_aconn */ +#define TB_A_SUSPEND (7) +#define TB_BUS_RESUME (12) + +/* SE0 Time Before SRP */ +#define TB_SE0_SRP (2) /* b_idle,minimum 2 ms, section:5.3.2 */ + +#define SET_OTG_STATE(otg_ptr, newstate) ((otg_ptr)->state=newstate) + +struct usb_dr_mmap { + /* Capability register */ + u8 res1[256]; + u16 caplength; /* Capability Register Length */ + u16 hciversion; /* Host Controller Interface Version */ + u32 hcsparams; /* Host Controller Structual Parameters */ + u32 hccparams; /* Host Controller Capability Parameters */ + u8 res2[20]; + u32 dciversion; /* Device Controller Interface Version */ + u32 dccparams; /* Device Controller Capability Parameters */ + u8 res3[24]; + /* Operation register */ + u32 usbcmd; /* USB Command Register */ + u32 usbsts; /* USB Status Register */ + u32 usbintr; /* USB Interrupt Enable Register */ + u32 frindex; /* Frame Index Register */ + u8 res4[4]; + u32 deviceaddr; /* Device Address */ + u32 endpointlistaddr; /* Endpoint List Address Register */ + u8 res5[4]; + u32 burstsize; /* Master Interface Data Burst Size Register */ + u32 txttfilltuning; /* Transmit FIFO Tuning Controls Register */ + u8 res6[8]; + u32 ulpiview; /* ULPI register access */ + u8 res7[12]; + u32 configflag; /* Configure Flag Register */ + u32 portsc; /* Port 1 Status and Control Register */ + u8 res8[28]; + u32 otgsc; /* On-The-Go Status and Control */ + u32 usbmode; /* USB Mode Register */ + u32 endptsetupstat; /* Endpoint Setup Status Register */ + u32 endpointprime; /* Endpoint Initialization Register */ + u32 endptflush; /* Endpoint Flush Register */ + u32 endptstatus; /* Endpoint Status Register */ + u32 endptcomplete; /* Endpoint Complete Register */ + u32 endptctrl[6]; /* Endpoint Control Registers */ + u8 res9[552]; + u32 snoop1; + u32 snoop2; + u32 age_cnt_thresh; /* Age Count Threshold Register */ + u32 pri_ctrl; /* Priority Control Register */ + u32 si_ctrl; /* System Interface Control Register */ + u8 res10[236]; + u32 control; /* General Purpose Control Register */ +}; + +struct fsl_otg_timer { + unsigned long expires; /* Number of count increase to timeout */ + unsigned long count; /* Tick counter */ + void (*function) (unsigned long); /* Timeout function */ + unsigned long data; /* Data passed to function */ + struct list_head list; +}; + +struct fsl_otg_timer inline *otg_timer_initializer + (void (*function) (unsigned long), unsigned long expires, + unsigned long data) { + struct fsl_otg_timer *timer; + timer = kmalloc(sizeof(struct fsl_otg_timer), GFP_KERNEL); + if (timer == NULL) + return NULL; + timer->function = function; + timer->expires = expires; + timer->data = data; + return timer; +} + +struct fsl_otg { + struct otg_transceiver otg; + struct otg_fsm fsm; + struct usb_dr_mmap *dr_mem_map; + struct delayed_work otg_event; + + /*used for usb host */ + u8 host_working; + u8 on_off; + + int irq; +}; + +struct fsl_otg_config { + u8 otg_port; +}; + +extern const char *state_string(enum usb_otg_state state); +extern int otg_set_resources(struct resource *resources, int num); diff --git a/drivers/usb/otg/otg_fsm.c b/drivers/usb/otg/otg_fsm.c new file mode 100644 index 000000000000..43e5af105441 --- /dev/null +++ b/drivers/usb/otg/otg_fsm.c @@ -0,0 +1,394 @@ +/* + * Copyright 2006-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <asm/types.h> +#include <linux/kernel.h> +#include <linux/usb/otg.h> +#include <linux/spinlock.h> +#include <linux/delay.h> +#include <linux/usb.h> +#include <linux/usb_gadget.h> + +#include "otg_fsm.h" + +/* Defined by device specific driver, for different timer implementation */ +extern void *a_wait_vrise_tmr, *a_wait_bcon_tmr, *a_aidl_bdis_tmr, + *b_ase0_brst_tmr, *b_se0_srp_tmr, *b_srp_fail_tmr, *a_wait_enum_tmr; + +const char *state_string(enum usb_otg_state state) +{ + switch (state) { + case OTG_STATE_A_IDLE: + return "a_idle"; + case OTG_STATE_A_WAIT_VRISE: + return "a_wait_vrise"; + case OTG_STATE_A_WAIT_BCON: + return "a_wait_bcon"; + case OTG_STATE_A_HOST: + return "a_host"; + case OTG_STATE_A_SUSPEND: + return "a_suspend"; + case OTG_STATE_A_PERIPHERAL: + return "a_peripheral"; + case OTG_STATE_A_WAIT_VFALL: + return "a_wait_vfall"; + case OTG_STATE_A_VBUS_ERR: + return "a_vbus_err"; + case OTG_STATE_B_IDLE: + return "b_idle"; + case OTG_STATE_B_SRP_INIT: + return "b_srp_init"; + case OTG_STATE_B_PERIPHERAL: + return "b_peripheral"; + case OTG_STATE_B_WAIT_ACON: + return "b_wait_acon"; + case OTG_STATE_B_HOST: + return "b_host"; + default: + return "UNDEFINED"; + } +} + +const char *protocol_string(int p) +{ + switch (p) { + case PROTO_HOST: + return "Host"; + case PROTO_GADGET: + return "Peripheral"; + default: + return "undef"; + } +} + +/* Change USB protocol when there is a protocol change */ +static int otg_set_protocol(struct otg_fsm *fsm, int protocol) +{ + int ret = 0; + + if (fsm->protocol != protocol) { + VDBG("Change role from %s to %s", + protocol_string(fsm->protocol), protocol_string(protocol)); + + /* stop old protocol */ + if (fsm->protocol == PROTO_HOST) + ret = fsm->ops->start_host(fsm, 0); + else if (fsm->protocol == PROTO_GADGET) + ret = fsm->ops->start_gadget(fsm, 0); + if (ret) + return ret; + + /* start new protocol */ + if (protocol == PROTO_HOST) + ret = fsm->ops->start_host(fsm, 1); + else if (protocol == PROTO_GADGET) + ret = fsm->ops->start_gadget(fsm, 1); + if (ret) + return ret; + + fsm->protocol = protocol; + return 0; + } + + return 0; +} + +static int state_changed = 0; + +/* Called when leaving a state. Do state clean up jobs here */ +void otg_leave_state(struct otg_fsm *fsm, enum usb_otg_state old_state) +{ + switch (old_state) { + case OTG_STATE_B_IDLE: + otg_del_timer(fsm, b_se0_srp_tmr); + fsm->b_se0_srp = 0; + break; + case OTG_STATE_B_SRP_INIT: + fsm->b_srp_done = 0; + break; + case OTG_STATE_B_PERIPHERAL: + break; + case OTG_STATE_B_WAIT_ACON: + otg_del_timer(fsm, b_ase0_brst_tmr); + fsm->b_ase0_brst_tmout = 0; + break; + case OTG_STATE_B_HOST: + break; + case OTG_STATE_A_IDLE: + break; + case OTG_STATE_A_WAIT_VRISE: + otg_del_timer(fsm, a_wait_vrise_tmr); + fsm->a_wait_vrise_tmout = 0; + break; + case OTG_STATE_A_WAIT_BCON: + otg_del_timer(fsm, a_wait_bcon_tmr); + fsm->a_wait_bcon_tmout = 0; + break; + case OTG_STATE_A_HOST: + otg_del_timer(fsm, a_wait_enum_tmr); + break; + case OTG_STATE_A_SUSPEND: + otg_del_timer(fsm, a_aidl_bdis_tmr); + fsm->a_aidl_bdis_tmout = 0; + fsm->a_suspend_req = 0; + break; + case OTG_STATE_A_PERIPHERAL: + break; + case OTG_STATE_A_WAIT_VFALL: + otg_del_timer(fsm, a_wait_vrise_tmr); + break; + case OTG_STATE_A_VBUS_ERR: + break; + default: + break; + } +} + +/* Called when entering a state */ +int otg_set_state(struct otg_fsm *fsm, enum usb_otg_state new_state) +{ + state_changed = 1; + if (fsm->transceiver->state == new_state) + return 0; + + VDBG("chg state to %s", state_string(new_state)); + + otg_leave_state(fsm, fsm->transceiver->state); + + switch (new_state) { + case OTG_STATE_B_IDLE: + otg_drv_vbus(fsm, 0); + otg_chrg_vbus(fsm, 0); + otg_loc_conn(fsm, 0); + otg_loc_sof(fsm, 0); + otg_set_protocol(fsm, PROTO_UNDEF); + otg_add_timer(fsm, b_se0_srp_tmr); + break; + case OTG_STATE_B_SRP_INIT: + otg_start_pulse(fsm); + otg_loc_sof(fsm, 0); + otg_set_protocol(fsm, PROTO_UNDEF); + otg_add_timer(fsm, b_srp_fail_tmr); + break; + case OTG_STATE_B_PERIPHERAL: + otg_chrg_vbus(fsm, 0); + otg_loc_conn(fsm, 1); + otg_loc_sof(fsm, 0); + otg_set_protocol(fsm, PROTO_GADGET); + break; + case OTG_STATE_B_WAIT_ACON: + otg_chrg_vbus(fsm, 0); + otg_loc_conn(fsm, 0); + otg_loc_sof(fsm, 0); + otg_set_protocol(fsm, PROTO_HOST); + otg_add_timer(fsm, b_ase0_brst_tmr); + fsm->a_bus_suspend = 0; + break; + case OTG_STATE_B_HOST: + otg_chrg_vbus(fsm, 0); + otg_loc_conn(fsm, 0); + otg_loc_sof(fsm, 1); + otg_set_protocol(fsm, PROTO_HOST); + usb_bus_start_enum(fsm->transceiver->host, + fsm->transceiver->host->otg_port); + break; + case OTG_STATE_A_IDLE: + otg_drv_vbus(fsm, 0); + otg_chrg_vbus(fsm, 0); + otg_loc_conn(fsm, 0); + otg_loc_sof(fsm, 0); + otg_set_protocol(fsm, PROTO_HOST); + break; + case OTG_STATE_A_WAIT_VRISE: + otg_drv_vbus(fsm, 1); + otg_loc_conn(fsm, 0); + otg_loc_sof(fsm, 0); + otg_set_protocol(fsm, PROTO_HOST); + otg_add_timer(fsm, a_wait_vrise_tmr); + break; + case OTG_STATE_A_WAIT_BCON: + otg_drv_vbus(fsm, 1); + otg_loc_conn(fsm, 0); + otg_loc_sof(fsm, 0); + otg_set_protocol(fsm, PROTO_HOST); + otg_add_timer(fsm, a_wait_bcon_tmr); + break; + case OTG_STATE_A_HOST: + otg_drv_vbus(fsm, 1); + otg_loc_conn(fsm, 0); + otg_loc_sof(fsm, 1); + otg_set_protocol(fsm, PROTO_HOST); + /* When HNP is triggered while a_bus_req = 0, a_host will + * suspend too fast to complete a_set_b_hnp_en + */ + if (!fsm->a_bus_req || fsm->a_suspend_req) + otg_add_timer(fsm, a_wait_enum_tmr); + break; + case OTG_STATE_A_SUSPEND: + otg_drv_vbus(fsm, 1); + otg_loc_conn(fsm, 0); + otg_loc_sof(fsm, 0); + otg_set_protocol(fsm, PROTO_HOST); + otg_add_timer(fsm, a_aidl_bdis_tmr); + + break; + case OTG_STATE_A_PERIPHERAL: + otg_loc_conn(fsm, 1); + otg_loc_sof(fsm, 0); + otg_set_protocol(fsm, PROTO_GADGET); + otg_drv_vbus(fsm, 1); + break; + case OTG_STATE_A_WAIT_VFALL: + otg_drv_vbus(fsm, 0); + otg_loc_conn(fsm, 0); + otg_loc_sof(fsm, 0); + otg_set_protocol(fsm, PROTO_HOST); + break; + case OTG_STATE_A_VBUS_ERR: + otg_drv_vbus(fsm, 0); + otg_loc_conn(fsm, 0); + otg_loc_sof(fsm, 0); + otg_set_protocol(fsm, PROTO_UNDEF); + break; + default: + break; + } + + fsm->transceiver->state = new_state; + return 0; +} + +/* State change judgement */ +int otg_statemachine(struct otg_fsm *fsm) +{ + enum usb_otg_state state; + unsigned long flags; + + spin_lock_irqsave(&fsm->lock, flags); + + state = fsm->transceiver->state; + state_changed = 0; + /* State machine state change judgement */ + + VDBG("top: curr state=%s", state_string(state)); + + switch (state) { + case OTG_STATE_UNDEFINED: + VDBG("fsm->id = %d", fsm->id); + if (fsm->id) + otg_set_state(fsm, OTG_STATE_B_IDLE); + else + otg_set_state(fsm, OTG_STATE_A_IDLE); + break; + case OTG_STATE_B_IDLE: + VDBG("gadget: %p", fsm->transceiver->gadget); + if (!fsm->id) + otg_set_state(fsm, OTG_STATE_A_IDLE); + else if (fsm->b_sess_vld && fsm->transceiver->gadget) + otg_set_state(fsm, OTG_STATE_B_PERIPHERAL); + else if (fsm->b_bus_req && fsm->b_sess_end && fsm->b_se0_srp) + otg_set_state(fsm, OTG_STATE_B_SRP_INIT); + break; + case OTG_STATE_B_SRP_INIT: + if (!fsm->id || fsm->b_srp_done) + otg_set_state(fsm, OTG_STATE_B_IDLE); + break; + case OTG_STATE_B_PERIPHERAL: + if (!fsm->id || !fsm->b_sess_vld) + otg_set_state(fsm, OTG_STATE_B_IDLE); + else if (fsm->b_bus_req && + fsm->transceiver->gadget->b_hnp_enable && + fsm->a_bus_suspend) + otg_set_state(fsm, OTG_STATE_B_WAIT_ACON); + break; + case OTG_STATE_B_WAIT_ACON: + if (fsm->a_conn) + otg_set_state(fsm, OTG_STATE_B_HOST); + else if (!fsm->id || !fsm->b_sess_vld) + otg_set_state(fsm, OTG_STATE_B_IDLE); + else if (fsm->a_bus_resume || fsm->b_ase0_brst_tmout) { + fsm->b_ase0_brst_tmout = 0; + otg_set_state(fsm, OTG_STATE_B_PERIPHERAL); + } + break; + case OTG_STATE_B_HOST: + if (!fsm->id || !fsm->b_sess_vld) + otg_set_state(fsm, OTG_STATE_B_IDLE); + else if (!fsm->b_bus_req || !fsm->a_conn) + otg_set_state(fsm, OTG_STATE_B_PERIPHERAL); + break; + case OTG_STATE_A_IDLE: + if (fsm->id) + otg_set_state(fsm, OTG_STATE_B_IDLE); + else if (!fsm->a_bus_drop && (fsm->a_bus_req || fsm->a_srp_det)) + otg_set_state(fsm, OTG_STATE_A_WAIT_VRISE); + break; + case OTG_STATE_A_WAIT_VRISE: + if (fsm->id || fsm->a_bus_drop || fsm->a_vbus_vld || + fsm->a_wait_vrise_tmout) { + otg_set_state(fsm, OTG_STATE_A_WAIT_BCON); + } + break; + case OTG_STATE_A_WAIT_BCON: + if (!fsm->a_vbus_vld) + otg_set_state(fsm, OTG_STATE_A_VBUS_ERR); + else if (fsm->b_conn) + otg_set_state(fsm, OTG_STATE_A_HOST); + else if (fsm->id | fsm->a_bus_drop | fsm->a_wait_bcon_tmout) + otg_set_state(fsm, OTG_STATE_A_WAIT_VFALL); + break; + case OTG_STATE_A_HOST: + if ((!fsm->a_bus_req || fsm->a_suspend_req) && + fsm->transceiver->host->b_hnp_enable) + otg_set_state(fsm, OTG_STATE_A_SUSPEND); + else if (fsm->id || !fsm->b_conn || fsm->a_bus_drop) + otg_set_state(fsm, OTG_STATE_A_WAIT_BCON); + else if (!fsm->a_vbus_vld) + otg_set_state(fsm, OTG_STATE_A_VBUS_ERR); + break; + case OTG_STATE_A_SUSPEND: + if (!fsm->b_conn && fsm->transceiver->host->b_hnp_enable) + otg_set_state(fsm, OTG_STATE_A_PERIPHERAL); + else if (!fsm->b_conn && !fsm->transceiver->host->b_hnp_enable) + otg_set_state(fsm, OTG_STATE_A_WAIT_BCON); + else if (fsm->a_bus_req || fsm->b_bus_resume) + otg_set_state(fsm, OTG_STATE_A_HOST); + else if (fsm->id || fsm->a_bus_drop || fsm->a_aidl_bdis_tmout) + otg_set_state(fsm, OTG_STATE_A_WAIT_VFALL); + else if (!fsm->a_vbus_vld) + otg_set_state(fsm, OTG_STATE_A_VBUS_ERR); + break; + case OTG_STATE_A_PERIPHERAL: + if (fsm->id || fsm->a_bus_drop) + otg_set_state(fsm, OTG_STATE_A_WAIT_VFALL); + else if (fsm->b_bus_suspend) + otg_set_state(fsm, OTG_STATE_A_WAIT_BCON); + else if (!fsm->a_vbus_vld) + otg_set_state(fsm, OTG_STATE_A_VBUS_ERR); + break; + case OTG_STATE_A_WAIT_VFALL: + if (fsm->id || fsm->a_bus_req || + (!fsm->a_sess_vld && !fsm->b_conn)) + otg_set_state(fsm, OTG_STATE_A_IDLE); + break; + case OTG_STATE_A_VBUS_ERR: + if (fsm->id || fsm->a_bus_drop || fsm->a_clr_err) + otg_set_state(fsm, OTG_STATE_A_WAIT_VFALL); + break; + default: + break; + } + spin_unlock_irqrestore(&fsm->lock, flags); + + return state_changed; +} diff --git a/drivers/usb/otg/otg_fsm.h b/drivers/usb/otg/otg_fsm.h new file mode 100644 index 000000000000..5b1c98f16be9 --- /dev/null +++ b/drivers/usb/otg/otg_fsm.h @@ -0,0 +1,152 @@ +/* + * Copyright 2006-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#if 0 +#define DEBUG 1 +#define VERBOSE 1 +#endif + +#ifdef DEBUG + +/* +#define DBG(fmt, args...) printk("[%s] " fmt "\n", \ + __FUNCTION__, ## args) +*/ +#define DBG(fmt, args...) printk("j=%lu [%s] " fmt "\n", \ + jiffies, __FUNCTION__, ## args) + +#else +#define DBG(fmt, args...) do{}while(0) +#endif + +#ifdef VERBOSE +#define VDBG DBG +#else +#define VDBG(stuff...) do{}while(0) +#endif + +#ifdef VERBOSE +#define MPC_LOC printk("Current Location [%s]:[%d]\n", __FILE__, __LINE__) +#else +#define MPC_LOC do{}while(0) +#endif + +#define PROTO_UNDEF (0) +#define PROTO_HOST (1) +#define PROTO_GADGET (2) + +/* OTG state machine according to the OTG spec */ +struct otg_fsm { + /* Input */ + int a_bus_resume; + int a_bus_suspend; + int a_conn; + int a_sess_vld; + int a_srp_det; + int a_vbus_vld; + int b_bus_resume; + int b_bus_suspend; + int b_conn; + int b_se0_srp; + int b_sess_end; + int b_sess_vld; + int id; + + /* Internal variables */ + int a_set_b_hnp_en; + int b_srp_done; + int b_hnp_enable; + + /* Timeout indicator for timers */ + int a_wait_vrise_tmout; + int a_wait_bcon_tmout; + int a_aidl_bdis_tmout; + int b_ase0_brst_tmout; + + /* Informative variables */ + int a_bus_drop; + int a_bus_req; + int a_clr_err; + int a_suspend_req; + int b_bus_req; + + /* Output */ + int drv_vbus; + int loc_conn; + int loc_sof; + + struct otg_fsm_ops *ops; + struct otg_transceiver *transceiver; + + /* Current usb protocol used: 0:undefine; 1:host; 2:client */ + int protocol; + spinlock_t lock; +}; + +struct otg_fsm_ops { + void (*chrg_vbus) (int on); + void (*drv_vbus) (int on); + void (*loc_conn) (int on); + void (*loc_sof) (int on); + void (*start_pulse) (void); + void (*add_timer) (void *timer); + void (*del_timer) (void *timer); + int (*start_host) (struct otg_fsm * fsm, int on); + int (*start_gadget) (struct otg_fsm * fsm, int on); +}; + +static inline void otg_chrg_vbus(struct otg_fsm *fsm, int on) +{ + fsm->ops->chrg_vbus(on); +} + +static inline void otg_drv_vbus(struct otg_fsm *fsm, int on) +{ + if (fsm->drv_vbus != on) { + fsm->drv_vbus = on; + fsm->ops->drv_vbus(on); + } +} + +static inline void otg_loc_conn(struct otg_fsm *fsm, int on) +{ + if (fsm->loc_conn != on) { + fsm->loc_conn = on; + fsm->ops->loc_conn(on); + } +} + +static inline void otg_loc_sof(struct otg_fsm *fsm, int on) +{ + if (fsm->loc_sof != on) { + fsm->loc_sof = on; + fsm->ops->loc_sof(on); + } +} + +static inline void otg_start_pulse(struct otg_fsm *fsm) +{ + fsm->ops->start_pulse(); +} + +static inline void otg_add_timer(struct otg_fsm *fsm, void *timer) +{ + fsm->ops->add_timer(timer); +} + +static inline void otg_del_timer(struct otg_fsm *fsm, void *timer) +{ + fsm->ops->del_timer(timer); +} + +int otg_statemachine(struct otg_fsm *fsm); diff --git a/drivers/usb/usblan/Kconfig b/drivers/usb/usblan/Kconfig new file mode 100644 index 000000000000..cdf4d1c45d63 --- /dev/null +++ b/drivers/usb/usblan/Kconfig @@ -0,0 +1,24 @@ +# +# USB Network devices configuration +# +comment "Belcarra USBLAN Networking for USB" + depends on USB && NET + +config USB_USBLAN + tristate "Support for Belcarra USBLAN Network Devices" + depends on USB && NET + +config USB_USBLAN_IDS + bool "Override built-in Vendor and Product ID's" + depends on USB && NET && USB_USBLAN + default "n" + +config USB_USBLAN_VENDORID + hex "USB Vendor ID for the USBLAN network device" + depends on USB_USBLAN && USB_USBLAN_IDS + default "0" + +config USB_USBLAN_PRODUCTID + hex "USB Product ID for the USBLAN network device" + depends on USB_USBLAN && USB_USBLAN_IDS + default "0" diff --git a/drivers/usb/usblan/Makefile b/drivers/usb/usblan/Makefile new file mode 100644 index 000000000000..969d96804c50 --- /dev/null +++ b/drivers/usb/usblan/Makefile @@ -0,0 +1,8 @@ +# +# Makefile for USB Network drivers +# + +OTGDIR=$(srctree)/drivers/otg +EXTRA_CFLAGS += -I$(OTGDIR) +EXTRA_CFLAGS_nostdinc += -I$(OTGDIR) +obj-$(CONFIG_USB_USBLAN) += usblan.o diff --git a/drivers/usb/usblan/usblan-compat.h b/drivers/usb/usblan/usblan-compat.h new file mode 100644 index 000000000000..b02eba4056d9 --- /dev/null +++ b/drivers/usb/usblan/usblan-compat.h @@ -0,0 +1,286 @@ +/********************************************************************* + * A set of macros to cross-navigate Linux 2.4 and Linux 2.6 kernel + * facilities. + * Copyright (c) 2004, 2005 Belcarra Technologies Corp + * + * 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. + * + * By: + * Stuart Lynne <sl@belcarra.com>, Bruce Balden <balden@belcarra.com> + * + ***********************************************************************/ +#ifndef _USB_NET_USBLAN_COMPAT_H +#define _USB_NET_USBLAN_COMPAT_H 1 +#if LINUX_VERSION_CODE > KERNEL_VERSION(2,5,2) +#define LINUX26 +#elif LINUX_VERSION_CODE > KERNEL_VERSION(2,4,5) +#define LINUX24 +#else /* LINUX_VERSION_CODE > KERNEL_VERSION(2,4,5) */ +#define LINUX24 +#define LINUX_OLD +#warning "Early unsupported release of Linux kernel" +#endif /* LINUX_VERSION_CODE > KERNEL_VERSION(2,4,5) */ + + +/*! @{ */ +#undef PRAGMAPACK +#define PACKED __attribute__((packed)) +#define INLINE __inline__ + +/*! @} */ + +/*! @name Memory Allocation Primitives + * + * CKMALLOC() + * LSTRDUP() + * LKFREE() + * LIST_ENTRY() + * LIST_FOR_EACH() + */ + + /*! @{ */ + +#if defined(LINUX26) + #include <linux/gfp.h> +#define GET_KERNEL_PAGE() __get_free_page(GFP_KERNEL) + +#else /* LINUX26 */ + + #include <linux/mm.h> + #define GET_KERNEL_PAGE() get_free_page(GFP_KERNEL) +#endif /* LINUX26 */ + + + +// Common to all supported versions of Linux ?? + +#define CKMALLOC(n,f) _ckmalloc(__FUNCTION__, __LINE__, n, f) +#define LSTRDUP(str) _lstrdup(__FUNCTION__, __LINE__, str) +#define LKFREE(p) _lkfree(__FUNCTION__, __LINE__, p) + +#define ckmalloc(n,f) _ckmalloc(__FUNCTION__, __LINE__, n, f) +#define lstrdup(str) _lstrdup(__FUNCTION__, __LINE__, str) +#define lkfree(p) _lkfree(__FUNCTION__, __LINE__, p) + +#define OTG_MALLOC_TEST +#undef OTG_MALLOC_DEBUG + +#ifdef OTG_MALLOC_TEST + extern int otg_mallocs; +#endif + + +static INLINE void *_ckmalloc (const char *func, int line, int n, int f) +{ + void *p; + if ((p = kmalloc (n, f)) == NULL) { + return NULL; + } + memset (p, 0, n); + #ifdef OTG_MALLOC_TEST + ++otg_mallocs; + #endif + #ifdef OTG_MALLOC_DEBUG + printk(KERN_INFO"%s: %p %s %d %d\n", __FUNCTION__, p, func, line, otg_mallocs); + #endif + return p; +} + +static INLINE char *_lstrdup (const char *func, int line, char *str) +{ + int n; + char *s; + if (str && (n = strlen (str) + 1) && (s = kmalloc (n, GFP_ATOMIC))) { +#ifdef OTG_MALLOC_TEST + ++otg_mallocs; +#endif +#ifdef OTG_MALLOC_DEBUG + printk(KERN_INFO"%s: %p %s %d %d\n", __FUNCTION__, s, func, line, otg_mallocs); +#endif + return strcpy (s, str); + } + return NULL; +} + +static INLINE void _lkfree (const char *func, int line, void *p) +{ + if (p) { +#ifdef OTG_MALLOC_TEST + --otg_mallocs; +#endif +#ifdef OTG_MALLOC_DEBUG + printk(KERN_INFO"%s: %p %s %d %d\n", __FUNCTION__, p, func, line, otg_mallocs); +#endif + kfree (p); +#ifdef MALLOC_TEST + if (otg_mallocs < 0) { + printk(KERN_INFO"%s: %p %s %d %d otg_mallocs less zero!\n", __FUNCTION__, p, func, line, otg_mallocs); + } +#endif +#ifdef OTG_MALLOC_DEBUG + else { + printk(KERN_INFO"%s: %s %d NULL\n", __FUNCTION__, func, line); + } +#endif + } +} + +#if 1 +#include <linux/list.h> +#define LIST_NODE struct list_head +#define LIST_NODE_INIT(name) struct list_head name = {&name, &name} +#define INIT_LIST_NODE(ptr) INIT_LIST_HEAD(ptr) +#define LIST_ENTRY(pointer, type, member) list_entry(pointer, type, member) +#define LIST_FOR_EACH(cursor, head) list_for_each(cursor, head) +#define LIST_ADD_TAIL(n,h) list_add_tail(n,h) +#define LIST_DEL(h) list_del(&(h)) +#else +#include <otg/otg-list.h> +#endif + +/*! @} */ + + +/*! @name Atomic Operations + * + * atomic_post_inc() + * atomic_pre_dec() + */ +/*! @{ */ +static __inline__ int atomic_post_inc(volatile atomic_t *v) +{ + unsigned long flags; + int result; + local_irq_save(flags); + result = (v->counter)++; + local_irq_restore(flags); + return(result); +} + +static __inline__ int atomic_pre_dec(volatile atomic_t *v) +{ + unsigned long flags; + int result; + local_irq_save(flags); + result = --(v->counter); + local_irq_restore(flags); + return(result); +} +/*! @} */ + + + +/*!@name Scheduling Primitives + * + * WORK_STRUCT + * WORK_ITEM + * + * SCHEDULE_TIMEOUT()\n + * SET_WORK_ARG()\n + * SCHEDULE_WORK()\n + * SCHEDULE_IMMEDIATE_WORK()\n + * NO_WORK_DATA()\n + * MOD_DEC_USE_COUNT\n + * MOD_INC_USE_COUNT\n + */ + +/*! @{ */ + +static void inline SCHEDULE_TIMEOUT(int seconds){ + schedule_timeout( seconds * HZ ); +} + + +/* Separate Linux 2.4 and 2.6 versions of scheduling primitives */ +#if defined(LINUX26) + #include <linux/workqueue.h> + + #define WORK_STRUCT work_struct + #define WORK_ITEM work_struct + typedef struct WORK_ITEM WORK_ITEM; + #if 0 + #define PREPARE_WORK_ITEM(__item,__routine,__data) INIT_WORK((__item),(__routine),(__data)) + #else + #include <linux/interrupt.h> + #define PREPARE_WORK_ITEM(__item,__routine,__data) __prepare_work(&(__item),(__routine),(__data)) + static inline void __prepare_work(struct work_struct *_work, + void (*_routine), + void * _data){ + INIT_LIST_HEAD(&_work->entry); + _work->pending = 0; + _work->func = _routine; + _work->data = _data; + init_timer(&_work->timer); + } + #endif + #undef PREPARE_WORK + typedef void (* WORK_PROC)(void *); + + #define SET_WORK_ARG(__item, __data) (__item).data = __data + + #define SCHEDULE_WORK(item) schedule_work(&(item)) + #define SCHEDULE_IMMEDIATE_WORK(item) SCHEDULE_WORK((item)) + #define PENDING_WORK_ITEM(item) ((item).pending != 0) + #define NO_WORK_DATA(item) (!(item).data) + #define _MOD_DEC_USE_COUNT //Not used in 2.6 + #define _MOD_INC_USE_COUNT //Not used in 2.6 + +#else /* LINUX26 */ + + #define WORK_STRUCT tq_struct + #define WORK_ITEM tq_struct + typedef struct WORK_ITEM WORK_ITEM; + #define PREPARE_WORK_ITEM(item,work_routine,work_data) { item.routine = work_routine; item.data = work_data; } + #define SET_WORK_ARG(__item, __data) (__item).data = __data + #define NO_WORK_DATA(item) (!(item).data) + #define SCHEDULE_WORK(item) schedule_task(&(item)) + #define PENDING_WORK_ITEM(item) ((item).sync != 0) + #define _MOD_DEC_USE_COUNT MOD_DEC_USE_COUNT + #define _MOD_INC_USE_COUNT MOD_INC_USE_COUNT + + typedef void (* WORK_PROC)(void *); + + #if !defined(IRQ_HANDLED) + // Irq's + typedef void irqreturn_t; + #define IRQ_NONE + #define IRQ_HANDLED + #define IRQ_RETVAL(x) + #endif +#endif /* LINUX26 */ + +/*! @} */ + +/*!@name Semaphores + * + * up() + * down() + */ +/*! @{ */ +#define UP(s) up(s) +#define DOWN(s) down(s) +/*! @} */ + +/*! @name Printk + * + * PRINTK() + */ +/*! @{ */ +#define PRINTK(s) printk(s) +/*! @} */ + +/*! + * Cache + * @{ + */ +#define CACHE_SYNC_RCV(buf, len) pci_map_single (NULL, (void *) buf, len, PCI_DMA_FROMDEVICE) +#define CACHE_SYNC_TX(buf, len) consistent_sync (buf, len, PCI_DMA_TODEVICE) + +/* @} */ + + + +#endif diff --git a/drivers/usb/usblan/usblan.c b/drivers/usb/usblan/usblan.c new file mode 100644 index 000000000000..f59c094ac015 --- /dev/null +++ b/drivers/usb/usblan/usblan.c @@ -0,0 +1,3045 @@ +/* + * USB Host to USB Device Network Function Driver + * + * Copyright (c) 2002, 2003 Belcarra + * Copyright (c) 2001 Lineo + * + * + * 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. + * + * By: + * Stuart Lynne <sl@belcarra.com>, Bruce Balden <balden@belcarra.com> + * + * Some algorithms adopted from usbnet.c: + * + * Copyright (C) 2000-2001 by David Brownell <dbrownell@users.sourceforge.net> + * + */ + +#ifdef MODULE +#include <linux/module.h> +#endif +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +//#include <net/arp.h> +#include <linux/rtnetlink.h> +#include <linux/smp_lock.h> +#include <linux/ctype.h> +#include <linux/timer.h> +#include <linux/string.h> +#include <linux/pkt_sched.h> +#include <linux/random.h> +#include <linux/version.h> + +#include <linux/skbuff.h> +#include <net/arp.h> +#include <linux/atmdev.h> +#include <linux/ip.h> +#include <linux/if_ether.h> +#include <linux/in.h> +#include <linux/inetdevice.h> +#include <linux/workqueue.h> + + + +//#include "linux-pch.h" + +#include <linux/usb.h> + +#include <asm/uaccess.h> +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/system.h> +#include "usblan-compat.h" +#include "usblan.h" + +#define PACKED_ENUM enum +#define PACKED_ENUM_EXTRA +#define PACKED1 __attribute__((packed)) +#define PACKED2 +#define PACKED0 + + +#if defined(USBLAN_LOCAL_CONFIG) +#include "./usblan-config.h" +#endif + +#define MIN(a,b) (((a) < (b))?(a):(b)) +#define MAX(a,b) (((a) > (b))?(a):(b)) + +#define UNLESS(x) if (!(x)) +#define THROW(x) goto x +#define CATCH(x) while(0) x: +#define THROW_IF(e, x) if (e) { goto x; } +#define THROW_UNLESS(e, x) UNLESS (e) { goto x; } +#define BREAK_IF(x) if (x) { break; } +#define CONTINUE_IF(x) if (x) { continue; } +#define RETURN_IF(x,y) if (y) { return x; } + + +#define mutex_lock(x) down(x) +#define mutex_unlock(x) up(x) + + +#define DRIVER_VERSION "2.0.0" +#define DRIVER_AUTHOR "sl@belcarra.com" +#define DRIVER_DESC "Linux USBLAN driver" + +#ifdef MODULE +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); + +// XXX This needs to be corrected down to the last version of the +// kernel that did NOT have MODULE_LICENSE +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,15) +MODULE_LICENSE("GPL"); +#endif +#endif + +#define STATIC + +/* Module Parameters ************************************************************************* */ + +#define MAX_INTERFACES 1 + +#define MAX_RCV_SKBS 10 +#define TIMEOUT_JIFFIES (4*HZ) +#define MAX_PACKET 32768 +#define MIN_PACKET sizeof(struct ethhdr) + + +#define TX_QLEN 2 +#define RX_QLEN 2 + +static DECLARE_MUTEX(usbd_mutex); // lock for global changes +static LIST_HEAD(usbd_list); // a list for all active devices + + +typedef enum usbdnet_device_type { + usbdnet_unknown, usbdnet_basic, usbdnet_cdc, usbdnet_safe, usbdnet_blan, usbdnet_rndis +} usbdnet_device_type_t; + +char * usbdnet_device_names[] = { + "UNKNOWN", "BASIC", "CDC", "SAFE", "BLAN", "RNDIS", +}; + + + +__u8 SAFE_VERSION[2] = { + 0x00, 0x01, /* BCD Version */ +}; +__u8 SAFE_GUID[16] = { + 0x5d, 0x34, 0xcf, 0x66, 0x11, 0x18, 0x11, 0xd6, /* bGUID */ + 0xa2, 0x1a, 0x00, 0x01, 0x02, 0xca, 0x9a, 0x7f, /* bGUID */ +}; + +__u8 BLAN_VERSION[2] = { + 0x00, 0x01, /* BCD Version */ +}; +__u8 BLAN_GUID[16] = { + 0x74, 0xf0, 0x3d, 0xbd, 0x1e, 0xc1, 0x44, 0x70, /* bGUID */ + 0xa3, 0x67, 0x71, 0x34, 0xc9, 0xf5, 0x54, 0x37, /* bGUID */ +}; + +// data detail +#define DATA_CRC 0x01 +#define DATA_PADBEFORE 0x02 +#define DATA_PADAFTER 0x04 +#define DATA_FERMAT 0x08 + +// cdc notifications +#define CDC_NOTIFICATION 0xa1 +#define CDC_NOTIFICATION_NETWORK 0x00 +#define CDC_NOTIFICATION_SPEEDCHANGE 0x2a + +#define USB_DT_CS_INTERFACE 0x24 + +#define MDLM_FUNCTIONAL 0x12 +#define MDLM_DETAIL 0x13 + +#define MDLM_SAFE_GUID 0x00 +#define MDLM_BLAN_GUID 0x01 + + +#if 0 +STATIC void usblan_test_kalloc(char *msg) +{ + struct urb *foo; + printk(KERN_INFO "%s: %s\n",__FUNCTION__,msg); + if (NULL != (foo = USB_ALLOC_URB(0,GFP_ATOMIC))) { + usb_free_urb(foo); + } + printk(KERN_INFO "%s: #%08x\n",__FUNCTION__,(u32)(void*)foo); +} +#endif + + + +/* struct private + * + * This structure contains the network interface and additional per USB device information. + * + * A pointer to this structure is used in two three places: + * + * net->priv + * urb->context + * skb->cb.priv + */ +struct private { + + // general + struct usb_device *usbdev; // usb core layer provides this for the probed device + struct semaphore mutex; // lock for changes to this structure + struct list_head list; // to maintain a list of these devices () + wait_queue_head_t *wait; + wait_queue_head_t *ctrl_wait; + + struct tasklet_struct bh; + struct WORK_STRUCT crc_task; + struct WORK_STRUCT ctrl_task; + struct WORK_STRUCT reset_task; + struct WORK_STRUCT unlink_task; + + int intf_count; + int intf_max; + + // network + struct net_device net; + struct net_device_stats stats; + unsigned char dev_addr[ETH_ALEN]; + + // queues + struct sk_buff_head rxq; + struct sk_buff_head txq; + struct sk_buff_head unlink; + struct sk_buff_head done; + + // + int crc32; // append and check for appended 32bit CRC + int padded; // pad bulk transfers such that (urb->transfer_buffer_length % data_ep_out_size) == 1 + int addr_set; // set_address + int sawCRC; + + int timeouts; + + usbdnet_device_type_t usbdnet_device_type; + + //struct usb_interface *data_interface; + //struct usb_interface *comm_interface; + + struct usb_ctrlrequest ctrl_request; + struct urb *ctrl_urb; + int configuration_number; + int bConfigurationValue; + + int comm_interface; + int comm_bInterfaceNumber; + int comm_bAlternateSetting; + int comm_ep_in; + int comm_ep_in_size; + + int data_interface; + + int data_bInterfaceNumber; + int data_bAlternateSetting; + + int nodata_bInterfaceNumber; + int nodata_bAlternateSetting; + + int data_ep_in; + int data_ep_out; + int data_ep_in_size; + int data_ep_out_size; + + u8 CRCInUse; + u8 CDC; + u8 bmNetworkCapabilities; + u8 bmDataCapabilities; + u8 bPad; +}; + +/* struct skb_cb + * + * This defines how we use the skb->cb data area. It allows us to get back to the private + * data structure and track the current state of skb in our done queue. There is a pointer + * to the active urb so that it can be cancelled (e.g. tx_timeout). + * + * skb->cb + */ +typedef enum skb_state { + unknown = 0, + tx_start, // an skb in priv->txq + tx_done, // a transmitted skb in priv->done + rx_start, // an skb in priv->rxq + rx_done, // a received skb in priv->done + rx_cleanup, // a received skb being thrown out due to an error condition +} skb_state_t; + +struct skb_cb { + struct private *priv; + struct urb *urb; + skb_state_t state; + unsigned long int jiffies; +}; + + +static struct usb_driver usblan_driver; + + +struct usb_mdlm_detail_descriptor { + u8 bLength; + u8 bDescriptorType; + u8 bDescriptorSubType; + u8 bGuidDescriptorType; +} __attribute__ ((packed)); + +struct usb_safe_detail_descriptor { + u8 bLength; + u8 bDescriptorType; + u8 bDescriptorSubType; + u8 bGuidDescriptorType; + u8 bmNetworkCapabilities; + u8 bmDataCapabilities; +} __attribute__ ((packed)); + +struct usb_blan_detail_descriptor { + u8 bLength; + u8 bDescriptorType; + u8 bDescriptorSubType; + u8 bGuidDescriptorType; + u8 bmNetworkCapabilities; + u8 bmDataCapabilities; + u8 bPad; +} __attribute__ ((packed)); + +struct usb_class_descriptor { + u8 bLength; + u8 bDescriptorType; + u8 bDescriptorSubType; + u8 data[0]; +} __attribute__ ((packed)); + +struct usb_guid_descriptor { + u8 bLength; + u8 bDescriptorType; + u8 bDescriptorSubType; + u8 bGuidDescriptorType; + u8 data[0]; +} __attribute__ ((packed)); + +struct usb_notification_descriptor { + u8 bmRequestType; + u8 bNotification; + u16 wValue; + u16 wIndex; + u16 wLength; + u8 data[2]; +} __attribute__ ((packed)); + +struct usb_speedchange_descriptor { + u8 bmRequestType; + u8 bNotification; + u16 wValue; + u16 wIndex; + u16 wLength; + u32 data[2]; +} __attribute__ ((packed)); + +struct usb_mdlm_functional_descriptor { + u8 bLength; + u8 bDescriptorType; + u8 bDescriptorSubType; + u16 bcdVersion; + u8 bGuid[16]; +} __attribute__ ((packed)); + + + +/* + * default MAC address to use + */ +static unsigned char default_addr[ETH_ALEN]; + +/* Module Parameters ************************************************************************* */ + +#define VENDOR_SPECIFIC_CLASS 0xff +#define VENDOR_SPECIFIC_SUBCLASS 0xff +#define VENDOR_SPECIFIC_PROTOCOL 0xff + +#define MTU 1500+100 + + +#if defined(CONFIG_USBD_USBLAN_VENDOR) && !defined(CONFIG_USBD_USBLAN_PRODUCT) +#abort "USBLAN_VENDOR defined without USBLAN_PRODUCT" +#endif + +#define CDC_DEVICE_CLASS 0x02 // Device descriptor Class + +#define CDC_INTERFACE_CLASS 0x02 // CDC interface descriptor Class +#define CDC_INTERFACE_SUBCLASS 0x06 // CDC interface descriptor SubClass +#define RNDIS_INTERFACE_SUBCLASS 0x02 // CDC interface descriptor SubClass +#define MDLM_INTERFACE_SUBCLASS 0x0a // CDC interface descriptor SubClass + +#define DATA_INTERFACE_CLASS 0x0a // Data interface descriptor Class + +#define DEFAULT_PROTOCOL 0x00 +#define VENDOR_SPECIFIC_PROTOCOL 0xff + +#define LINEO_INTERFACE_CLASS 0xff // Lineo private interface descriptor Class + +#define LINEO_INTERFACE_SUBCLASS_SAFENET 0x01 // Lineo private interface descriptor SubClass +#define LINEO_INTERFACE_SUBCLASS_SAFESERIAL 0x02 + +#define LINEO_SAFENET_CRC 0x01 // Lineo private interface descriptor Protocol +#define LINEO_SAFENET_CRC_PADDED 0x02 // Lineo private interface descriptor Protocol + +#define LINEO_SAFESERIAL_CRC 0x01 +#define LINEO_SAFESERIAL_CRC_PADDED 0x02 + + +#define NETWORK_ADDR_HOST 0xac100005 /* 172.16.0.0 */ +#define NETWORK_ADDR_CLIENT 0xac100006 /* 172.16.0.0 */ +#define NETWORK_MASK 0xfffffffc + + +static __u32 vendor_id; // no default +static __u32 product_id; // no default +static __u32 class = LINEO_INTERFACE_CLASS; +static __u32 subclass = LINEO_INTERFACE_SUBCLASS_SAFENET; + +static __u32 echo_fcs; // no default +static __u32 noisy_fcs; // no default +static __u32 echo_rx; // no default +static __u32 echo_tx; // no default + +#ifdef MODULE +MODULE_PARM_DESC(vendor_id, "User specified USB idVendor"); +MODULE_PARM_DESC(product_id, "User specified USB idProduct"); +MODULE_PARM_DESC(class, "User specified USB Class"); +MODULE_PARM_DESC(subclass, "User specified USB SubClass"); +MODULE_PARM(vendor_id, "i"); +MODULE_PARM(product_id, "i"); +MODULE_PARM(class, "i"); +MODULE_PARM(subclass, "i"); + +MODULE_PARM_DESC(echo_tx, "echo TX urbs"); +MODULE_PARM_DESC(echo_rx, "echo RCV urbs"); +MODULE_PARM_DESC(echo_fcs, "BAD FCS"); +MODULE_PARM_DESC(noisy_fcs, "BAD FCS info"); +MODULE_PARM(echo_tx, "i"); +MODULE_PARM(echo_rx, "i"); +MODULE_PARM(echo_fcs, "i"); +MODULE_PARM(noisy_fcs, "i"); +#endif + +//match_flags: DEVICE_ID_MATCH_DEVICE | USB_DEVICE_ID_MATCH_DEV_CLASS , + +#define MY_USB_DEVICE(vend,prod) \ +match_flags: USB_DEVICE_ID_MATCH_DEVICE , \ +idVendor: (vend), \ +idProduct: (prod),\ +bDeviceClass: (CDC_INTERFACE_CLASS), + +static __devinitdata struct usb_device_id id_table[] = { + + // RNDIS devices Vend Prod bDeviceClass bInterfaceClass bInterfaceSubClass + + {MY_USB_DEVICE(0x15ec, 0xf001)}, // Belcarra Network Demo + {MY_USB_DEVICE(0x12b9, 0xf001)}, // Belcarra Network Demo + + +#if 1 +#if defined(CONFIG_USB_USBLAN_VENDORID) && defined(CONFIG_USB_USBLAN_PRODUCTID) + // A configured driver + {MY_USB_DEVICE(CONFIG_USB_USBLAN_VENDORID, CONFIG_USB_USBLAN_PRODUCTID)}, +#endif +#endif + + // extra null entry for module vendor_id/produc parameters and terminating entry + {}, {}, +}; + + +#ifdef MODULE +MODULE_DEVICE_TABLE(usb, id_table); +#endif + +#define ECHO_FCS +#define ECHO_RCV + +#undef ECHO_TX_SKB +#define ECHO_TX_URB + +__u32 crc32_table[256] = { + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, + 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, + 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, + 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, + 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, + 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, + 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, + 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, + 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, + 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, + 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, + 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, + 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, + 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, + 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, + 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, + 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, + 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, + 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, + 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, + 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, + 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, + 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, + 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, + 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, + 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, + 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, + 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, + 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, + 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, + 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, + 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d +}; + +#define CRC32_INITFCS 0xffffffff // Initial FCS value +#define CRC32_GOODFCS 0xdebb20e3 // Good final FCS value + +#define CRC32_FCS(fcs, c) (((fcs) >> 8) ^ crc32_table[((fcs) ^ (c)) & 0xff]) + +/* fcs_memcpy32 - memcpy and calculate fcs + * Perform a memcpy and calculate fcs using ppp 32bit CRC algorithm. + */ +static __u32 __inline__ +fcs_memcpy32(unsigned char *dp, unsigned char *sp, int len, __u32 fcs) +{ + for (; len-- > 0; fcs = CRC32_FCS(fcs, *dp++ = *sp++)); + return fcs; +} + +/* fcs_pad32 - pad and calculate fcs + * Pad and calculate fcs using ppp 32bit CRC algorithm. + */ +static __u32 __inline__ +fcs_pad32(unsigned char *dp, int len, __u32 fcs) +{ + for (; len-- > 0; fcs = CRC32_FCS(fcs, *dp++ = '\0')); + return fcs; +} + +/* fcs_compute32 - memcpy and calculate fcs + * Perform a memcpy and calculate fcs using ppp 32bit CRC algorithm. + */ +static __u32 __inline__ +fcs_compute32(unsigned char *sp, int len, __u32 fcs) +{ + for (; len-- > 0; fcs = CRC32_FCS(fcs, *sp++)); + return fcs; +} + +void +wait_for_sync(struct WORK_STRUCT *tq) +{ + + // wait for pending bottom halfs to exit + while (PENDING_WORK_ITEM((*tq))){ + SCHEDULE_TIMEOUT(1); + } + + tq->data = 0; + + while (PENDING_WORK_ITEM((*tq))) { + SCHEDULE_TIMEOUT(1); + } +} + + +#define RETRYTIME 2 + +void +skb_bad_crc(struct sk_buff *skb, __u32 fcs) +{ +#ifdef ECHO_FCS + if (noisy_fcs) { + printk(KERN_INFO"%s: BAD FCS len: %4d crc: %08x last: %02x %02x %02x %02x\n", __FUNCTION__, + skb->len, fcs, skb->data[skb->len - 4], + skb->data[skb->len - 3], skb->data[skb->len - 2], skb->data[skb->len - 1] + ); + } + if (echo_fcs) { + int i; + unsigned char *cp = skb->data; + printk(KERN_INFO "%s: FAILED skb: %p head: %p data: %p tail: %p len: %d", __FUNCTION__, + skb, skb->head, skb->data, skb->tail, skb->len); + for (i = 0; i < skb->len; i++) { + if ((i % 32) == 0) { + printk("\nrcv[%02x]: ", i); + } + printk("%02x ", cp[i]); + } + printk("\n"); + } +#endif +} + +#if 0 +static void +dump_skb(struct sk_buff *skb, char *msg) +{ + int i; + unsigned char *cp = skb->data; + + printk(KERN_INFO "\n%s", msg); + for (i = 0; i < skb->len; i++) { + if (!(i % 32)) { + printk("\n[%02x] ", i); + } + printk("%02x ", cp[i]); + } + printk("\n"); +} +#endif + +/* ********************************************************************************************* */ +#if defined(LONG_STRING_OF_ZEROES_HACK) + +/* fermat + * + * This is a hack designed to help some broken hardware that cannot successfully + * transmit long strings of zero's without causing the host port to signal a + * status change and drop the connection. + * + */ + +typedef unsigned char BYTE; +typedef struct fermat { + int length; + BYTE power[256]; +} FERMAT; + +STATIC void fermat_init(void); +STATIC void fermat_encode(BYTE *data, int length); +STATIC void fermat_decode(BYTE *data, int length); + +STATIC int fermat_setup(FERMAT *p, int seed){ + int i = 0; + unsigned long x,y; + y = 1; + do{ + x = y; + p->power[i] = ( x == 256 ? 0 : x); + y = ( seed * x ) % 257; + i += 1; + }while( y != 1); + p->length = i; + return i; +} + +STATIC void fermat_xform(FERMAT *p, BYTE *data, int length){ + BYTE *pw = p->power; + int i, j; + BYTE * q ; + for(i = 0, j=0, q = data; i < length; i++, j++, q++){ + if(j>=p->length){ + j = 0; + } + *q ^= pw[j]; + } +} + +static FERMAT default_fermat; +static const int primitive_root = 5; +STATIC void fermat_init(){ + (void) fermat_setup(&default_fermat, primitive_root); +} + +// Here are the public official versions. +// Change the primitive_root above to another primitive root +// if you need better scatter. Possible values are 3 and 7 + + +STATIC void fermat_encode(BYTE *data, int length){ + fermat_xform(&default_fermat, data, length); +} + +STATIC void fermat_decode(BYTE *data, int length){ + fermat_xform(&default_fermat, data, length); +} +#endif + + +/* ********************************************************************************************* */ + +STATIC void defer_skb(struct private *priv, struct sk_buff *skb, struct skb_cb *cb, + skb_state_t state, struct sk_buff_head *list); +STATIC int unlink_urbs(struct sk_buff_head *q); +STATIC void urb_tx_complete(struct urb *urb); +STATIC void urb_rx_complete(struct urb *urb); +#if 0 +STATIC void urb_dead_complete(struct urb *urb); +#endif + + +/* Network Configuration *********************************************************************** */ + +/* sock_ioctl - perform an ioctl call to inet device + */ +static int sock_ioctl(u32 cmd, struct ifreq *ifreq) +{ + int rc = 0; + mm_segment_t save_get_fs = get_fs(); + //printk(KERN_INFO"%s: cmd: %x\n", __FUNCTION__, cmd); + set_fs(get_ds()); + rc = devinet_ioctl(cmd, ifreq); + set_fs(save_get_fs); + return rc; +} + +/* sock_addr - setup a socket address for specified interface + */ +static int sock_addr(char * ifname, u32 cmd, u32 s_addr) +{ + struct ifreq ifreq; + struct sockaddr_in *sin = (void *) &(ifreq.ifr_ifru.ifru_addr); + + //printk(KERN_INFO"%s: ifname: %s addr: %x\n", __FUNCTION__, ifname, ntohl(s_addr)); + + memset(&ifreq, 0, sizeof(ifreq)); + strcpy(ifreq.ifr_ifrn.ifrn_name, ifname); + + sin->sin_family = AF_INET; + sin->sin_addr.s_addr = s_addr; + + return sock_ioctl(cmd, &ifreq); +} + + +/* sock_flags - set flags for specified interface + */ +static int sock_flags(char * ifname, u16 oflags, u16 sflags, u16 rflags) +{ + int rc = 0; + struct ifreq ifreq; + + //printk(KERN_INFO"%s: ifname: %s oflags: %x s_flags: %x r_flags: %x\n", __FUNCTION__, ifname, oflags, sflags, rflags); + + memset(&ifreq, 0, sizeof(ifreq)); + strcpy(ifreq.ifr_ifrn.ifrn_name, ifname); + + oflags |= sflags; + oflags &= ~rflags; + ifreq.ifr_flags = oflags; + + //printk(KERN_INFO"%s: -> ifr_flags: %x \n", __FUNCTION__, ifreq.ifr_flags); + + THROW_IF ((rc = sock_ioctl(SIOCSIFFLAGS, &ifreq)), error); + + //printk(KERN_INFO"%s: <- ifr_flags: %x \n", __FUNCTION__, ifreq.ifr_flags); + + CATCH(error) { + printk(KERN_INFO"%s: ifconfig: cannot get/set interface flags (%d)\n", __FUNCTION__, rc); + return rc; + } + return rc; +} + +/* network_attach - configure interface + * + * This will use socket calls to configure the interface to the supplied + * ip address and make it active. + */ +STATIC int network_attach(struct net_device *net, u32 host_ip, u32 mask, int attach) +{ + int err = 0; + + //printk(KERN_INFO"%s: net: %p host_ip: %08x mask: %08x attach: %d\n", __FUNCTION__, net, host_ip, mask, attach); + if (attach) { + u16 oflags = net ? net->flags : 0; + + /* setup host_ip address, netwask, and broadcast address */ + if (host_ip) { + THROW_IF ((err = sock_addr(net->name, SIOCSIFADDR, htonl(host_ip))), error); + if (mask) { + THROW_IF ((err = sock_addr(net->name, SIOCSIFNETMASK, htonl(mask))), error); + THROW_IF ((err = sock_addr(net->name, SIOCSIFBRDADDR, htonl(host_ip | ~mask))), error); + } + /* bring the interface up */ + THROW_IF ((err = sock_flags(net->name, oflags, IFF_UP, 0)), error); + } + + + } + else { + u16 oflags = net ? net->flags : 0; + /* bring the interface down */ + THROW_IF ((err = sock_flags(net->name, oflags, 0, IFF_UP)), error); + } + + CATCH(error) { + printk(KERN_INFO"%s: ifconfig: cannot configure interface (%d)\n", __FUNCTION__, err); + return err; + } + return 0; +} + + +/* ********************************************************************************************* */ + +/* Network Support Functions - these are called by the network layer *************************** */ + +/* net_get_stats - network device get stats function + * Retreive network device stats structure. + */ +STATIC struct net_device_stats * +net_get_stats(struct net_device *net) +{ + struct private *priv = (struct private *) net->priv; + //printk(KERN_INFO "%s:\n", __FUNCTION__); + return &priv->stats; +} + +/* net_set_mac_addr - network device set mac address function + */ +STATIC int +net_set_mac_address(struct net_device *net, void *p) +{ + struct private *priv = (struct private *) net->priv; + struct sockaddr *addr = p; + + //printk(KERN_INFO "%s:\n", __FUNCTION__); + if (netif_running(net)) { + return -EBUSY; + } + memcpy(net->dev_addr, addr->sa_data, net->addr_len); + priv->addr_set = 1; + return 0; +} + +/* net_change_mtu - network device set config function + * Set MTU, if running we can only change it to something less + * than or equal to MTU when PVC opened. + */ +STATIC int +net_change_mtu(struct net_device *net, int mtu) +{ + //printk(KERN_INFO "%s:\n", __FUNCTION__); + if ((mtu < sizeof(struct ethhdr)) || (mtu > (MAX_PACKET - 4))) { + return -EINVAL; + } + if (netif_running(net)) { + if (mtu > net->mtu) { + return -EBUSY; + } + } + net->mtu = mtu; + return 0; +} + +/* net_open - called by network layer to open network interface + */ +STATIC int +net_open(struct net_device *net) +{ + struct private *priv = (struct private *) net->priv; + + //printk(KERN_INFO "%s: priv#%08x net#%08x\n",__FUNCTION__,(u32)(void*)priv,(u32)(void*)net); + mutex_lock(&priv->mutex); + //printk(KERN_INFO "%s: AAA usbdev#%08x dataIF=%d alt=%d\n",__FUNCTION__,(u32)(void*)priv->usbdev, priv->data_bInterfaceNumber, priv->data_bAlternateSetting); + + // enable traffic + //printk(KERN_INFO"%s: setting data interface bInterfaceNumber: %d bAlternateSetting: %d\n", __FUNCTION__, + // priv->data_bInterfaceNumber, priv->data_bAlternateSetting); + +#if defined(LINUX24) + if (usb_set_interface( priv->usbdev, priv->data_bInterfaceNumber, priv->data_bAlternateSetting)) { + err("usb_set_interface() failed"); + } +#endif + + // XXX find interface ip / netmask, if netmask is 255.255.255.252 + // then send ip and ip+1 to device as suggested addresses + + + + + //printk(KERN_INFO "%s: BBB\n",__FUNCTION__); + + // tell the network layer to enable transmit queue + netif_start_queue(net); + //printk(KERN_INFO "%s: BBB\n",__FUNCTION__); + + // call the bottom half to schedule some receive urbs + tasklet_schedule(&priv->bh); + //printk(KERN_INFO "%s: CCC\n",__FUNCTION__); + + mutex_unlock(&priv->mutex); + //printk(KERN_INFO "%s: DDD\n",__FUNCTION__); + return 0; +} + +/* net_stop - called by network layer to stop network interface + */ +STATIC int +net_stop(struct net_device *net) +{ + struct private *priv = (struct private *) net->priv; + + DECLARE_WAIT_QUEUE_HEAD(unlink_wakeup); + DECLARE_WAITQUEUE(wait, current); + + //printk(KERN_INFO "%s:\n",__FUNCTION__); + + mutex_lock(&priv->mutex); + + // tell the network layer to disable the transmit queue + netif_stop_queue(net); + + + // disable (if possible) network traffic + if (priv->nodata_bInterfaceNumber >= 0) { + + //printk(KERN_INFO"%s: setting nodata interface bInterfaceNumber: %d bAlternateSetting: %d\n", __FUNCTION__, + // priv->nodata_bInterfaceNumber, priv->nodata_bAlternateSetting); + + if (usb_set_interface( priv->usbdev, priv->nodata_bInterfaceNumber, priv->nodata_bAlternateSetting)) { + err("usb_set_interface() failed"); + } + } + + + // setup a wait queue - this also acts as a flag to prevent bottom half from allocating more urbs + add_wait_queue(&unlink_wakeup, &wait); + priv->wait = &unlink_wakeup; + + // move the tx and rx urbs into the done queue + unlink_urbs(&priv->txq); + unlink_urbs(&priv->rxq); + + // wait for done queue to empty + while (skb_queue_len(&priv->rxq) || skb_queue_len(&priv->txq) || skb_queue_len(&priv->done)) { + current->state = TASK_UNINTERRUPTIBLE; + schedule_timeout(TIMEOUT_JIFFIES * 1000); + } + priv->wait = 0; + + // cleanup + remove_wait_queue(&unlink_wakeup, &wait); + + mutex_unlock(&priv->mutex); + return 0; +} + + +/* net_tx_timeout - called by network layer to cancel outstanding skbs + */ +STATIC void +net_tx_timeout(struct net_device *net) +{ + //struct private *priv = (struct private *) net->priv; + //unsigned char *cp; + //struct urb *urb; + + //unsigned char deadbeef[] = "DEADBEEF"; + + //printk(KERN_INFO"%s:\n", __FUNCTION__); + //unlink_urbs(&priv->txq); + //tasklet_schedule(&priv->bh); + + //if (priv->unlink_task.sync == 0) { + // schedule_task(&priv->unlink_task); + //} + + return; +#if 0 + // Attempt to send a short usb packet to the device, this will ensure that + // any partially completed bulk transfer will be terminated. + + if (!(cp = kmalloc(sizeof(deadbeef), GFP_KERNEL))) { + return; + } + if (!(urb = USB_ALLOC_URB(0,GFP_ATOMIC))) { + kfree(cp); + return; + } + memcpy(cp, deadbeef, sizeof(deadbeef)); + + FILL_BULK_URB(urb, priv->usbdev, usb_sndbulkpipe(priv->usbdev, priv->data_ep_out), + cp, sizeof(deadbeef), urb_dead_complete, NULL); + + urb->transfer_flags = USB_QUEUE_BULK | USB_ASYNC_UNLINK | USB_NO_FSBR; + + //usb_endpoint_running(priv->usbdev, usb_pipeendpoint(priv->data_ep_out), usb_pipeout(priv->data_ep_out)); + //usb_settoggle(priv->usbdev, usb_pipeendpoint(priv->data_ep_out), usb_pipeout(priv->data_ep_out), 0); + + if (usb_submit_urb(urb)) { + kfree(cp); + urb->transfer_buffer = NULL; + usb_free_urb(urb); + } +#endif +} + +/* net_hard_start_xmit - called by network layer to transmit skb + */ +STATIC int +net_hard_start_xmit(struct sk_buff *skb, struct net_device *net) +{ + struct private *priv = (struct private *) net->priv; + struct urb *urb; + struct skb_cb *cb; + struct sk_buff *skb2; + int flags = in_interrupt()? GFP_ATOMIC : GFP_KERNEL; + __u32 fcs; + int length; + int pad; + + //printk(KERN_INFO "%s:\n",__FUNCTION__); + + echo_tx = 0; + // debug + if (echo_tx) { + int i; + unsigned char *cp = skb->data; + printk(KERN_INFO"%s: skb: %p %d\n", __FUNCTION__, skb, skb->len); + for (i = 0; i < skb->len; i++) { + if ((i % 32) == 0) { + printk("\ntx[%3x]: ", i); + } + printk("%02x ", cp[i]); + } + printk("\n"); + } + + // allocate urb + THROW_IF (priv->wait || !(urb = USB_ALLOC_URB(0,flags)), free_skb_only); + + // calculate length required + if (priv->bmDataCapabilities & (DATA_PADBEFORE | DATA_PADAFTER)) { + // we need to pad so that after appending the CRC we have a multiple of packetsize + length = priv->data_ep_out_size * (((skb->len + 4 + 1) / priv->data_ep_out_size) + 1); + } + else { + // require a minimum of one full packet + length = MAX(priv->data_ep_out_size, skb->len + 4 + 1); + } + + // allocate a new skb, copy data to it computing FCS, + // the extra bytes are for the CRC and optional pad byte + THROW_IF (!(skb2 = alloc_skb(length + 4, flags)), free_urb_and_skb); // XXX +4 ? + + if (priv->bmDataCapabilities & DATA_CRC) { + + fcs = fcs_memcpy32(skb_put(skb2, skb->len), skb->data, skb->len, CRC32_INITFCS); + + //dump_skb(skb, "skb"); + dev_kfree_skb_any(skb); + skb = skb2; + + if (priv->bmDataCapabilities & DATA_PADBEFORE) { + if ((pad = (length - skb->len - 4)) > 0) { + // pad to required length less four (CRC), copy fcs and append pad byte if required + fcs = fcs_pad32(skb_put(skb, pad), pad, fcs); + } + } + + fcs = ~fcs; + *skb_put(skb, 1) = fcs & 0xff; + *skb_put(skb, 1) = (fcs >> 8) & 0xff; + *skb_put(skb, 1) = (fcs >> 16) & 0xff; + *skb_put(skb, 1) = (fcs >> 24) & 0xff; + + if (priv->bmDataCapabilities & DATA_PADAFTER) { + while ((skb->len % priv->bPad) || !(skb->len % priv->data_ep_out_size)) skb_put(skb, 1); + } + + // append a byte if required, we overallocated by one to allow for this + else if (!(skb->len % priv->data_ep_out_size)) { + *skb_put(skb, 1) = 0; + } + + //dump_skb(skb, "skb with CRC"); + + } + else { + memcpy(skb_put(skb2, skb->len), skb->data, skb->len); + + //dump_skb(skb, "skb"); + dev_kfree_skb_any(skb); + skb = skb2; + + if (!(skb->len % priv->data_ep_out_size)) { + *skb_put(skb, 1) = 0; + } + } + + // hand urb off to usb layer + + cb = (struct skb_cb *) skb->cb; + cb->urb = urb; + cb->priv = priv; + cb->state = tx_start; + cb->jiffies = jiffies; + + // urb->context ends up with pointer to skb + FILL_BULK_URB(urb, priv->usbdev, usb_sndbulkpipe(priv->usbdev, priv->data_ep_out), skb->data, skb->len, + urb_tx_complete, skb); + + urb->transfer_flags = USB_QUEUE_BULK | USB_ASYNC_UNLINK | USB_NO_FSBR; +#if defined(LINUX24) + urb->timeout = 2; +#endif /* XXX FIX ME */ + + + // submit the urb and restart (or not) the network device queue + //printk(KERN_INFO"%s: skb: %p %d urb %p len: %d\n", __FUNCTION__, skb, skb->len, urb, urb->transfer_buffer_length); + + if (USB_SUBMIT_URB(urb)) { + netif_start_queue(net); + priv->stats.tx_dropped++; + usb_free_urb(urb); + THROW(free_urb_and_skb); + } + skb_queue_tail(&priv->txq, skb); + if (priv->txq.qlen < TX_QLEN) { + netif_start_queue(net); + } + else { + net->trans_start = jiffies; + } + CATCH(free_skb_only) { + CATCH(free_urb_and_skb) { + usb_free_urb(urb); + } + dev_kfree_skb_any(skb); + return NET_XMIT_DROP; + } + return NET_XMIT_SUCCESS; +} + + +/* Receive Related ***************************************************************************** */ + +/* rx_submit - queue an urb to receive data + */ +STATIC void +rx_submit(struct private *priv, struct urb *urb, int gpf) +{ + struct sk_buff *skb; + struct skb_cb *cb; + unsigned long flags; + int size; + + //printk(KERN_INFO "%s:\n",__FUNCTION__); + + //usblan_test_kalloc("AAA"); + size = ((priv->net.mtu + 14 + 4 + priv->data_ep_in_size) / priv->data_ep_in_size) * priv->data_ep_in_size; + + //printk(KERN_INFO"%s: ep_in: %d ep_in_size: %d size: %d urb#%08x\n", __FUNCTION__, + // priv->data_ep_in, priv->data_ep_in_size, size, (u32)(void*)urb); + + //TBR-32-bit if (!(skb = alloc_skb(size + 2, gpf))) { + if (!(skb = alloc_skb(size + 4, gpf))) { + //printk(KERN_INFO "%s: alloc_skb() failed.\n",__FUNCTION__); + dbg("no rx skb"); + tasklet_schedule(&priv->bh); + THROW(free_urb_only); + return; + } + //TBR-32-bit skb_reserve(skb, 2); + skb_reserve(skb, 4); + //usblan_test_kalloc("BBB"); + + cb = (struct skb_cb *) skb->cb; + cb->priv = priv; + cb->urb = urb; + cb->state = rx_start; + //usblan_test_kalloc("CCC"); + + //printk(KERN_INFO"%s: AAA\n", __FUNCTION__); + // urb->context ends up with pointer to skb + FILL_BULK_URB(urb, priv->usbdev, usb_rcvbulkpipe(priv->usbdev, priv->data_ep_in), skb->data, size, urb_rx_complete, skb); + //usblan_test_kalloc("DDD"); + + urb->transfer_flags |= USB_QUEUE_BULK; + + //printk(KERN_INFO"%s: BBB\n", __FUNCTION__); + spin_lock_irqsave(&priv->rxq.lock, flags); + //printk(KERN_INFO"%s: CCC\n", __FUNCTION__); + if (netif_running(&priv->net)) { + + //printk(KERN_INFO"%s: DDD urb %p\n", __FUNCTION__, urb); + //usblan_test_kalloc("EEE"); + + //printk(KERN_INFO "%s: starting URB #%08x\n",__FUNCTION__,(u32)(void*)urb); + + spin_unlock_irqrestore(&priv->rxq.lock, flags); + if (USB_SUBMIT_URB(urb)) { + //printk(KERN_INFO "%s: submit failed freeing URB: %p\n", __FUNCTION__, urb); + //usblan_test_kalloc("FFF"); + tasklet_schedule(&priv->bh); + THROW(free_urb_and_skb); + } + else { + //printk(KERN_INFO"%s: FFF skb %p\n", __FUNCTION__, skb); + __skb_queue_tail(&priv->rxq, skb); + } + //printk(KERN_INFO"%s: GGG urb %p\n", __FUNCTION__, urb); + //usblan_test_kalloc("GGG"); + } + else { + spin_unlock_irqrestore(&priv->rxq.lock, flags); + //printk(KERN_INFO "%s: network stopped, freeing urb: %p\n", __FUNCTION__, urb); + THROW(free_urb_and_skb); + } + + CATCH(free_urb_only) { + + CATCH(free_urb_and_skb) { + printk(KERN_INFO"%s: error freeing skb %p\n", __FUNCTION__, skb); + dev_kfree_skb_any(skb); + } + printk(KERN_INFO"%s: error freeing urb %p\n", __FUNCTION__, urb); + usb_free_urb(urb); + } +} + +/* urb_rx_complete - called by usb core layer when urb has been received + */ +STATIC void +urb_rx_complete(struct urb *urb) +{ + struct sk_buff *skb; + struct skb_cb *cb; + struct private *priv; + + //printk(KERN_INFO "%s: urb %p len: %d status=%d\n", __FUNCTION__, urb, urb->actual_length,urb->status); + + if (!(skb = (struct sk_buff *) urb->context)) { + printk(KERN_ERR "%s: skb NULL\n", __FUNCTION__); + } + if (!(cb = (struct skb_cb *) skb->cb)) { + printk(KERN_ERR "%s: cb NULL\n", __FUNCTION__); + } + if (!(priv = cb->priv)) { + printk(KERN_ERR "%s: priv NULL\n", __FUNCTION__); + } + + + switch (urb->status) { + case 0: + priv->timeouts = 0; + + if ((MIN_PACKET < urb->actual_length) && (urb->actual_length < MAX_PACKET)) { + cb->urb = NULL; + skb_put(skb, urb->actual_length); + defer_skb(priv, skb, cb, rx_done, &priv->rxq); + if (netif_running(&priv->net)) { + rx_submit(priv, urb, GFP_ATOMIC); + } + break; + } + + /* FALLTHROUGH */ + + case -EOVERFLOW: + priv->stats.rx_over_errors++; + case -EILSEQ: + case -ECONNABORTED: + case -ETIMEDOUT: + priv->timeouts++; + priv->stats.rx_dropped++; + //printk(KERN_INFO"%s: RX_CLEANUP urb->status: %d timeout: %d\n", __FUNCTION__, urb->status, priv->timeouts); + defer_skb(priv, skb, cb, rx_cleanup, &priv->rxq); + + // XXX provisional, this will attempt to force a reset for a device that + // there have been multiple receive timeouts. This is a host + // that is no longer responding to IN with a NAK. Typically this is + // due to a device that has stopped operation without dropping the + // usb control resistor to tell us. + + if (priv->timeouts > 20) { + priv->timeouts = 0; + //printk(KERN_INFO "%s: scheduling reset task\n", __FUNCTION__); + +#if 0 + if (priv->reset_task.sync == 0) { + schedule_task(&priv->reset_task); +#else + if(!PENDING_WORK_ITEM(priv->reset_task)){ + SCHEDULE_WORK(priv->reset_task); +#endif + } + } + break; + } +} + + +/* bh_rx_process - called by bottom half to process received skb + */ +STATIC inline void +bh_rx_process(struct private *priv, struct sk_buff *skb) +{ + __u32 fcs; + +#if 0 + if (skb->len > (priv->net.mtu + 16 + 4 + 1 + 100)) { + printk(KERN_INFO "%s: URB too large\n", __FUNCTION__); + priv->stats.rx_length_errors++; + THROW(crc_error); + dev_kfree_skb(skb); + priv->stats.rx_errors++; + return; + } +#endif +#if 0 + if (priv->bmDataCapabilities & DATA_FERMAT) { + fermat_decode(skb->data, skb->len); + } +#endif + //printk(KERN_INFO "%s:\n",__FUNCTION__); + if (priv->bmDataCapabilities & DATA_CRC) { + + // check if we need to check for extra byte + if ((skb->len % priv->data_ep_in_size) == 1) { + + // check fcs across length minus one bytes + if ((fcs = fcs_compute32(skb->data, skb->len - 1, CRC32_INITFCS)) == CRC32_GOODFCS) { + // success, trim extra byte and fall through + skb_trim(skb, skb->len - 1); + + priv->sawCRC = 1; + } + // failed, check additional byte + else if ((fcs = fcs_compute32(skb->data + skb->len - 1, 1, fcs)) != CRC32_GOODFCS) { + // failed + printk(KERN_INFO "%s: CRC fail on extra byte\n", __FUNCTION__); + THROW_IF(priv->sawCRC, crc_error); + return; + } + // success fall through, possibly with corrected length + } + // normal check across full frame + else if ((fcs = fcs_compute32(skb->data, skb->len, CRC32_INITFCS)) != CRC32_GOODFCS) { + printk(KERN_INFO "%s: CRC fail len: %d\n", __FUNCTION__, skb->len); + THROW_IF(priv->sawCRC, crc_error); + return; + } + + // trim fcs + skb_trim(skb, skb->len - 4); + } + + // debug + echo_rx = 0; + if (echo_rx) { + int i; + unsigned char *cp = skb->data; + + for (i = 0; i < skb->len; i++) { + if ((i % 32) == 0) { + printk("\nrx[%3x]: ", i); + } + printk("%02x ", cp[i]); + } + printk("\n"); + } + + // push the skb up + memset(skb->cb, 0, sizeof(struct skb_cb)); + skb->dev = &priv->net; + skb->protocol = eth_type_trans(skb, &priv->net); + skb->pkt_type = PACKET_HOST; + skb->ip_summed = CHECKSUM_UNNECESSARY; + priv->stats.rx_packets++; + priv->stats.rx_bytes += skb->len; + if (netif_rx(skb)) { + printk(KERN_INFO "%s: submitting skb failed\n", __FUNCTION__); + } + + CATCH(crc_error) { + dev_kfree_skb_any(skb); + priv->stats.rx_errors++; + } +} + + +/* Transmit Related **************************************************************************** */ + +/* This is a version of usb_clear_halt() that doesn't read the status from + * the device -- this is because some devices crash their internal firmware + * when the status is requested after a halt + */ +STATIC int +local_clear_halt(struct usb_device *dev, int pipe) +{ + int result; + int endp = usb_pipeendpoint(pipe) | (usb_pipein(pipe) << 7); + + if ((result = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + USB_REQ_CLEAR_FEATURE, USB_RECIP_ENDPOINT, 0, endp, NULL, 0, HZ * 3))) + { + return result; + } + + // reset the toggles and endpoint flags + // usb_endpoint_running(dev, usb_pipeendpoint(pipe), usb_pipeout(pipe)); + // usb_settoggle(dev, usb_pipeendpoint(pipe), usb_pipeout(pipe), 0); + + return 0; +} + +/* ctrl_task - called as kernel task to send clear halt message + */ +STATIC void +ctrl_task(void *data) +{ + struct private *priv = (struct private *) data; + + local_clear_halt(priv->usbdev, usb_sndbulkpipe(priv->usbdev, priv->data_ep_out)); + netif_wake_queue(&priv->net); +} + +/* reset_task - called as kernel task to send reset the device + */ +STATIC void +reset_task(void *data) +{ + struct private *priv = (struct private *) data; + + if (usb_reset_device(priv->usbdev)) { + printk(KERN_INFO "%s: reset failed\n", __FUNCTION__); + } +} + +/* urb_tx_complete - called by usb core layer when network skb urb has been transmitted + */ +STATIC void +urb_tx_complete(struct urb *urb) +{ + struct sk_buff *skb; + + //printk(KERN_INFO"%s: urb: %p\n", __FUNCTION__, urb); + + if (urb->status) { + printk(KERN_INFO "%s: urb: %p status: %d\n", __FUNCTION__, urb, urb->status); + } + + urb->dev = 0; + + if ((skb = urb->context)) { + struct skb_cb *cb; + struct private *priv; + + if (!(cb = (struct skb_cb *) skb->cb)) { + printk(KERN_ERR "%s: cb NULL skb: %p\n", __FUNCTION__, skb); + } + if (!(priv = cb->priv)) { + printk(KERN_ERR "%s: priv NULL skb: %p cb: %p\n", __FUNCTION__, skb, cb); + } + + if (!cb->urb) { + printk(KERN_ERR "%s: urb NULL skb: %p cb: %p\n", __FUNCTION__, skb, cb); + } + + if (cb->urb != urb) { + printk(KERN_ERR "%s: urb not urb skb: %p cb: %p cb->urb: %p urb: %p\n", __FUNCTION__, + skb, cb, cb->urb, urb); + } + + if (urb->status == USB_ST_STALL) { + printk(KERN_ERR "%s: USB_ST_STALL\n", __FUNCTION__); +#if defined(LINUX24) + if (priv->ctrl_task.sync == 0) { + schedule_task(&priv->ctrl_task); + } +#else + if (!PENDING_WORK_ITEM(priv->ctrl_task)){ + SCHEDULE_WORK(priv->ctrl_task); + } +#endif + + } + defer_skb(priv, skb, cb, tx_done, &priv->txq); + } +} + +#if 0 +/* urb_dead_complete - called by usb core layer when deadbeef urb has been transmitted + */ +STATIC void +urb_dead_complete(struct urb *urb) +{ + urb->dev = 0; + + if (urb->transfer_buffer) { + kfree(urb->transfer_buffer); + urb->transfer_buffer = NULL; + } + usb_free_urb(urb); +} +#endif + + +/* Bottom Half ********************************************************************************* */ + +/* defer_skb - put an skb on done list and schedule bottom half if necessary + */ +STATIC void +defer_skb(struct private *priv, struct sk_buff *skb, struct skb_cb *cb, + skb_state_t state, struct sk_buff_head *list) +{ + unsigned long flags; + + if (!cb) { + printk(KERN_ERR "%s: cb is NULL!\n", __FUNCTION__); + } + + //if !(cb->urb) { + // printk(KERN_ERR"%s: urb is NULL!\n", __FUNCTION__); + //} + + cb->state = state; + + if (!skb) { + printk(KERN_ERR "%s: skb is NULL!\n", __FUNCTION__); + return; + } + + spin_lock_irqsave(&list->lock, flags); + __skb_unlink(skb, list); + spin_unlock(&list->lock); + + + // link to done queue + spin_lock(&priv->done.lock); + __skb_queue_tail(&priv->done, skb); + + if (priv->done.qlen == 1) { + tasklet_schedule(&priv->bh); + } + spin_unlock_irqrestore(&priv->done.lock, flags); +} + + +/* unlink_urbs - tell usb core layer that we want it to abandon attempts to send/receive urbs + */ +STATIC int +unlink_urbs(struct sk_buff_head *q) +{ + struct sk_buff *skb; + int count = 0; + + // move from the current queue to the unlink queue + while ((skb = skb_dequeue(q))) { + struct skb_cb *cb; + struct urb *urb; + struct private *priv; + int retval; + + if (!(cb = (struct skb_cb *) skb->cb)) { + printk(KERN_ERR "%s: cb NULL\n", __FUNCTION__); + continue; + } + + if (!(urb = cb->urb)) { + printk(KERN_ERR "%s: urb NULL\n", __FUNCTION__); + continue; + } + + if (!(priv = cb->priv)) { + printk(KERN_ERR "%s: priv NULL\n", __FUNCTION__); + continue; + } + + // place them here until they can be processed after unlinking + skb_queue_tail(&priv->unlink, skb); + + //printk(KERN_INFO "%s: unlinking skb: %p len: %d jiffs: %ld\n", __FUNCTION__, + // skb, skb->len, jiffies - cb->jiffies); + + // usb core layer will call rx_complete() with appropriate status so that we can remove + urb->transfer_flags |= USB_ASYNC_UNLINK; + if ((retval = usb_unlink_urb(urb)) < 0) { + dbg("unlink urb err, %d", retval); + } + else { + count++; + } + } + return count; +} + +/* unlink_task - called as kernel task to send crc message + */ +STATIC void +unlink_task(void *data) +{ + struct private *priv = (struct private *) data; + //printk(KERN_INFO"%s:\n", __FUNCTION__); + unlink_urbs(&priv->txq); + tasklet_schedule(&priv->bh); +} + + +/* bh - bottom half + */ +STATIC void +bh(unsigned long data) +{ + struct private *priv = (struct private *) data; + struct sk_buff *skb; + + //printk(KERN_INFO "%s: priv#%08x\n", __FUNCTION__, (u32)(void*)priv); + + if (!priv) { + printk(KERN_INFO "%s: priv NULL\n", __FUNCTION__); + return; + } + + // process all skb's on the done queue + while ((skb = skb_dequeue(&priv->done))) { + + struct skb_cb *cb; + + //printk(KERN_INFO "%s: skb#%08x\n", __FUNCTION__, (u32)(void*)skb); + + if (!(cb = (struct skb_cb *) skb->cb)) { + printk(KERN_INFO "%s: cb NULL skb: %p\n", __FUNCTION__, skb); + continue; + } + + switch (cb->state) { + case rx_done: + // printk(KERN_INFO"%s: rx_done skb: %p\n", __FUNCTION__, skb); + bh_rx_process(priv, skb); + break; + + case tx_done: + // printk(KERN_INFO"%s: tx_done skb: %p\n", __FUNCTION__, skb); + { + struct urb *urb; + if (!(urb = cb->urb)) { + printk(KERN_INFO "%s: urb NULL skb: %p cb: %p\n", __FUNCTION__, skb, cb); + dev_kfree_skb_any(skb); + continue; + } + + if (urb->status) { + priv->stats.tx_errors++; + //printk(KERN_INFO "%s: urb: %p skb: %p status: %d\n", __FUNCTION__, + // urb, skb, cb->urb->status); + urb->transfer_buffer_length = 0; + urb->transfer_buffer = NULL; + //usb_free_urb(urb); + //dev_kfree_skb_any(skb); + } + else { + priv->stats.tx_packets++; + priv->stats.tx_bytes += skb->len; + usb_free_urb(urb); + dev_kfree_skb_any(skb); + } + //usb_free_urb(urb); + //dev_kfree_skb_any(skb); + break; + } + case rx_cleanup: + //printk(KERN_INFO"%s: rx_cleanup skb: %p\n", __FUNCTION__, skb); + { + struct urb *urb; + if (!(urb = cb->urb)) { + printk(KERN_INFO "%s: urb NULL skb: %p cb: %p\n", __FUNCTION__, skb, cb); + dev_kfree_skb_any(skb); + continue; + } + + if (urb) { + usb_free_urb(urb); + } + dev_kfree_skb_any(skb); + break; + } + case unknown: + case tx_start: + case rx_start: + printk(KERN_INFO "%s: UNKNOWN\n", __FUNCTION__); + printk(KERN_INFO "%s: inconsistant cb state: %d\n", __FUNCTION__, cb->state); + break; + } + } + + //printk(KERN_INFO "%s: priv->wait#%08x\n", __FUNCTION__, (u32)(void*)priv->wait); + + // are we waiting for pending urbs to complete? + if (priv->wait) { + if (!(priv->txq.qlen + priv->rxq.qlen + priv->done.qlen + priv->unlink.qlen)) { + //printk(KERN_INFO "%s: wakeup\n", __FUNCTION__); + wake_up(priv->wait); + } + } + + // do we need to queue up receive urbs? + else if (netif_running(&priv->net)) { + + while ((priv->rxq.qlen < RX_QLEN) && (priv->timeouts < 4)) { + struct urb *urb; + + //usblan_test_kalloc("bhR0"); + //printk(KERN_INFO "%s: allocating receive urb ATOMIC#%08x KERNEL#%08x\n", __FUNCTION__, + // GFP_ATOMIC,GFP_KERNEL); + // allocate an urb and use rx_submit to prepare and add it to the rxq + if ((urb = USB_ALLOC_URB(0,GFP_ATOMIC))) { + //usblan_test_kalloc("bhR1"); + //printk(KERN_INFO "%s: allocated receive urb#%08x\n", __FUNCTION__,(u32)(void*)urb); + rx_submit(priv, urb, GFP_ATOMIC); + //usblan_test_kalloc("bhR2"); + } + else { + // we failed, schedule another run to try again + //printk(KERN_INFO "%s: allocating receive urb failed\n", __FUNCTION__); + tasklet_schedule(&priv->bh); + } + } + + if (priv->txq.qlen < TX_QLEN) { + //printk(KERN_INFO"%s: wake queue\n", __FUNCTION__); + netif_wake_queue(&priv->net); + } + } +} + + + + +/* USB Functions - Probe and Disconnect ******************************************************** */ + +#define BELCARRA_SETTIME 0x04 +#define BELCARRA_SETIP 0x05 +#define BELCARRA_SETMSK 0x06 +#define BELCARRA_SETROUTER 0x07 + + +static void vendor_callback(struct urb *urb) +{ + //printk(KERN_INFO"%s:\n", __FUNCTION__); + //wake_up(priv->ctrl_wait); + + usb_free_urb(urb); + //printk(KERN_INFO"%s: finish\n", __FUNCTION__); +} + + +int VendorOut(struct private *priv, u8 bRequest, u16 wValue, u16 wIndex) +{ + struct usb_ctrlrequest *ctrl_request = &priv->ctrl_request; + int rc = 0; + + //DECLARE_WAIT_QUEUE_HEAD(vendor_wakeup); + //DECLARE_WAITQUEUE(wait, current); + + //printk(KERN_INFO"%s: bRequest: %02x wValue: %04x wIndex: %04x\n", __FUNCTION__, bRequest, wValue, wIndex); + + // setup a wait queue - this also acts as a flag to prevent bottom half from allocating more urbs + //add_wait_queue(&vendor_wakeup, &wait); + //priv->ctrl_wait = &vendor_wakeup; + + mutex_lock(&priv->mutex); + + THROW_UNLESS((priv->ctrl_urb = USB_ALLOC_URB(0, GFP_ATOMIC)), error); + + // create urb + ctrl_request->bRequestType = USB_TYPE_VENDOR | USB_RECIP_DEVICE; + ctrl_request->bRequest = bRequest; + ctrl_request->wValue = wValue; + ctrl_request->wIndex = wIndex; + ctrl_request->wLength = 0; + + FILL_CONTROL_URB(priv->ctrl_urb, priv->usbdev, + usb_sndctrlpipe(priv->usbdev, 0), + (char *)&priv->ctrl_request, + NULL, 0, vendor_callback, priv + ); + + // submit urb + + THROW_IF(USB_SUBMIT_URB(priv->ctrl_urb), error); + + // sleep + //current->state = TASK_UNINTERRUPTIBLE; + //schedule_timeout(TIMEOUT_JIFFIES * 1000); + //priv->ctrl_wait = NULL; + + + CATCH(error) { + rc = -EINVAL; + } + + //if (priv->ctrl_urb) + // usb_free_urb(priv->ctrl_urb); + //priv->ctrl_urb = NULL; + + // cleanup + //remove_wait_queue(&vendor_wakeup, &wait); + mutex_unlock(&priv->mutex); + return rc; +} + +int VendorOutLong(struct private *priv, u16 bRequest, u32 data) +{ + //printk(KERN_INFO"%s: bRequest: %02x data: %04x\n", __FUNCTION__, bRequest, data); + data = ntohl(data); + return VendorOut(priv, bRequest, (u16)(data>>16), (u16)(data&0xffff)); +} + +void network_notify(struct private *priv, u32 host_ip, u32 mask, u32 client_ip) +{ + + + //printk(KERN_INFO"%s: client_ip: %08x mask: %08x host_ip: %x\n", __FUNCTION__, client_ip, mask, host_ip); + THROW_IF(VendorOutLong(priv, BELCARRA_SETIP, client_ip), error ); + THROW_IF(VendorOutLong(priv, BELCARRA_SETMSK, mask), error ); + THROW_IF(VendorOutLong(priv, BELCARRA_SETROUTER, host_ip), error ); + + //printk(KERN_INFO"%s: host_ip\n", __FUNCTION__); + //printk(KERN_INFO"%s: finished\n", __FUNCTION__); + + CATCH(error) { + } +} + + +#if 0 +/* This is a version of usb_clear_halt() that doesn't read the status from + * the device -- this is because some devices crash their internal firmware + * when the status is requested after a halt + */ +STATIC int +set_crc(struct usb_device *dev, int pipe) +{ + return usb_control_msg(dev, + usb_sndctrlpipe(dev, 0), + 0x3, + USB_TYPE_VENDOR | USB_RECIP_INTERFACE | USB_DIR_OUT, + 0, + usb_pipeendpoint(pipe) | (usb_pipein(pipe) << 7), + NULL, 0, HZ * 3); +} +#endif + +#if 0 +/* crc_task - called as kernel task to send crc message + */ +STATIC void +crc_task(void *data) +{ + struct private *priv = (struct private *) data; + set_crc(priv->usbdev, usb_sndctrlpipe(priv->usbdev, 0)); +} +#endif + + +/* create_private - create private data structure and initialize network interface + */ +STATIC struct private * +create_private(int devnum) +{ + struct private *priv; + struct net_device *net; + + if (!(priv = kmalloc(sizeof(struct private), GFP_KERNEL))) { + return NULL; + } + + memset(priv, 0, sizeof(struct private)); + + net = &priv->net; +#ifdef MODULE + SET_MODULE_OWNER(net); +#endif + net->priv = priv; + //printk(KERN_INFO "%s: priv#%08x net#%08x\n",__FUNCTION__,(u32)(void*)priv,(u32)(void*)net); + // XXX usbb vs usbl + + switch(priv->usbdnet_device_type) { + + case usbdnet_blan: + strcpy(net->name, "usbl%d"); + break; + default: + strcpy(net->name, "usb%d"); + break; + } + memcpy(priv->dev_addr, default_addr, ETH_ALEN); + + priv->dev_addr[ETH_ALEN - 1] = (unsigned char) devnum; + + memcpy(net->dev_addr, default_addr, sizeof(default_addr)); + net->dev_addr[ETH_ALEN - 1] = devnum; + + ether_setup(net); + + net->set_mac_address = net_set_mac_address; + net->hard_start_xmit = net_hard_start_xmit; + net->get_stats = net_get_stats; + net->change_mtu = net_change_mtu; + net->open = net_open; + net->stop = net_stop; + net->tx_timeout = net_tx_timeout; + net->watchdog_timeo = TIMEOUT_JIFFIES; + + //printk(KERN_INFO"%s: finis\n", __FUNCTION__); + return priv; +} + +STATIC struct usb_device_id * +idp_search(int idVendor, int idProduct) +{ + struct usb_device_id *idp; + + //printk(KERN_INFO "%s: look for idVendor: %04x idProduct: %04x\n", __FUNCTION__, idVendor, idProduct); + + // search id_table for a match + for (idp = id_table;; idp++) { + + //printk(KERN_INFO "%s: looking at idVendor: %04x idProduct: %04x\n", __FUNCTION__, + // idp->idVendor, idp->idProduct); + + // end of table + BREAK_IF (!idp->idVendor && !idp->idProduct); + + // check for match + if ((idp->idVendor == idVendor) && (idp->idProduct == idProduct)) { + //printk(KERN_INFO "%s: MATCH\n", __FUNCTION__); + return idp; + } + } + return NULL; +} + + + +/* + * See if we have a CDC style communications interface: + * + * 1. Must have specified class, subclass and protocol + * 2. May have an optional INTERRUPT endpoint. + * + */ +STATIC int find_interface_comm( + struct private *priv, + struct usb_interface *comm_interface, + int class, int subclass, int protocol) +{ + int alternate_number; + struct usb_interface_descriptor *interface_descriptor; + + //printk(KERN_INFO"%s: class: %x subclass: %x protocol: %x\n", __FUNCTION__, class, subclass, protocol ); + + // iterate across interface alternate descriptors looking for suitable one + for (alternate_number = 0; alternate_number < comm_interface->num_altsetting; alternate_number++) { + +// interface_descriptor = comm_interface->altsetting + alternate_number; + interface_descriptor = USB_ALTSETTING(comm_interface,alternate_number); + priv->comm_ep_in = 0; + + //printk(KERN_INFO"%s: alt: %x class: %x subclass: %x protocol: %x\n", __FUNCTION__, + // alternate_number, + // interface_descriptor->bInterfaceClass, + // interface_descriptor->bInterfaceSubClass, + // interface_descriptor->bInterfaceProtocol); + + // check for class, sub-class and too many endpoints (zero or one ok) + CONTINUE_IF ((interface_descriptor->bInterfaceClass != class) || + (interface_descriptor->bInterfaceSubClass != subclass) || + (interface_descriptor->bInterfaceProtocol != protocol) || + (1 < interface_descriptor->bNumEndpoints) ); + + // if we have an endpoint check for validity + if (interface_descriptor->bNumEndpoints) { + //struct usb_endpoint_descriptor *endpoint = interface_descriptor->endpoint; + struct usb_endpoint_descriptor *endpoint = USB_IFC2EP(interface_descriptor,0); + CONTINUE_IF (!(endpoint->bEndpointAddress & USB_DIR_IN) || + (endpoint->bmAttributes != USB_ENDPOINT_XFER_INT)); + priv->comm_ep_in = endpoint->bEndpointAddress & 0x7f; + priv->comm_ep_in_size = endpoint->wMaxPacketSize; + //printk(KERN_INFO"%s: ep found %d\n", __FUNCTION__, priv->comm_ep_in); + } + + priv->comm_bInterfaceNumber = interface_descriptor->bInterfaceNumber; + priv->comm_bAlternateSetting = interface_descriptor->bAlternateSetting; + + //printk(KERN_INFO"%s: found %d %d\n", __FUNCTION__, + // priv->comm_bInterfaceNumber, priv->comm_bAlternateSetting); + return 0; + } + return -1; +} + + +/* + * See if we can have a CDC style data interface: + * + * 1. Must have specified class, subclass and protocol + * 2. Must have (only) a BULK-IN and BULK-OUT endpoint. + * 3. Optionally May have INTERRUPT endpoint. + * + */ +STATIC int find_interface_data( + struct private *priv, + struct usb_interface *data_interface, + int class, int subclass, int protocol, + int allow_interrupt) +{ + int alternate_number; + struct usb_interface_descriptor *interface_descriptor; + + //printk(KERN_INFO"%s: class: %x subclass: %x protocol: %x\n", __FUNCTION__, class, subclass, protocol ); + + for (alternate_number = 0; alternate_number < data_interface->num_altsetting; alternate_number++) { + + //interface_descriptor = data_interface->altsetting + alternate_number; + interface_descriptor = USB_ALTSETTING(data_interface,alternate_number); + priv->data_ep_in = priv->data_ep_out = 0; + + //printk(KERN_INFO"%s: alt: %d class: %x subclass: %x protocol: %x endpoints: %d\n", __FUNCTION__, + // alternate_number, + // interface_descriptor->bInterfaceClass, + // interface_descriptor->bInterfaceSubClass, + // interface_descriptor->bInterfaceProtocol, + // interface_descriptor->bNumEndpoints); + + // check for class, sub-class and wrong number of endpoints (must be two) + CONTINUE_IF ((interface_descriptor->bInterfaceClass != class) || + (interface_descriptor->bInterfaceSubClass != subclass) || + (interface_descriptor->bInterfaceProtocol != protocol) ); + + CONTINUE_IF (allow_interrupt && !((1 < interface_descriptor->bNumEndpoints) + && (3 >= interface_descriptor->bNumEndpoints))); + + CONTINUE_IF (!allow_interrupt && (2 != interface_descriptor->bNumEndpoints)); + + // check endpoints for validity, must be BULK and we want one in each direction, no more, no less + if (interface_descriptor->bNumEndpoints) { + int endpoint_number; + for (endpoint_number = 0; endpoint_number < interface_descriptor->bNumEndpoints; endpoint_number++) { + //struct usb_endpoint_descriptor *endpoint = interface_descriptor->endpoint + endpoint_number; + struct usb_endpoint_descriptor *endpoint = USB_IFC2EP(interface_descriptor,endpoint_number); + + BREAK_IF (USB_ENDPOINT_XFER_BULK != endpoint->bmAttributes); + + if (endpoint->bEndpointAddress & USB_DIR_IN) { + priv->data_ep_in = endpoint->bEndpointAddress & 0x7f; + priv->data_ep_in_size = endpoint->wMaxPacketSize; + } + else { + priv->data_ep_out = endpoint->bEndpointAddress & 0x7f; + priv->data_ep_out_size = endpoint->wMaxPacketSize; + } + } + } + + CONTINUE_IF (!priv->data_ep_in || !priv->data_ep_out); + + //printk(KERN_INFO"%s: ep found %d %d\n", __FUNCTION__, priv->data_ep_in, priv->data_ep_out); + + priv->data_bInterfaceNumber = interface_descriptor->bInterfaceNumber; + priv->data_bAlternateSetting = interface_descriptor->bAlternateSetting; + + //printk(KERN_INFO"%s: found %d %d\n", __FUNCTION__, + // priv->data_bInterfaceNumber, priv->data_bAlternateSetting); + return 0; + } + //printk(KERN_INFO"%s: not found\n", __FUNCTION__); + return -1; +} + +/* + * See if we can have a CDC style no-data interface: + * + * 1. Must have specified class, subclass and protocol + * 2. Must have a zero endpoints. + */ +STATIC int find_interface_nodata( + struct private *priv, + struct usb_interface *data_interface, + int class, int subclass, int protocol) +{ + int alternate_number; + struct usb_interface_descriptor *interface_descriptor; + + for (alternate_number = 0; alternate_number < data_interface->num_altsetting; alternate_number++) { + + //interface_descriptor = data_interface->altsetting + alternate_number; + interface_descriptor = USB_ALTSETTING(data_interface,alternate_number); + + //printk(KERN_INFO"%s: alt: %d class: %d subclass: %d endpoints: %d\n", __FUNCTION__, alternate_number, + // interface_descriptor->bInterfaceClass, + // interface_descriptor->bInterfaceSubClass, + // interface_descriptor->bNumEndpoints); + + // check for class, sub-class, protocol and verify no endpoints + CONTINUE_IF ((interface_descriptor->bInterfaceClass != class) || + (interface_descriptor->bInterfaceSubClass != subclass) || + (interface_descriptor->bInterfaceProtocol != protocol) || + (interface_descriptor->bNumEndpoints)); + + priv->nodata_bInterfaceNumber = interface_descriptor->bInterfaceNumber; + priv->nodata_bAlternateSetting = interface_descriptor->bAlternateSetting; + + //printk(KERN_INFO"%s: found %d %d\n", __FUNCTION__, + // priv->nodata_bInterfaceNumber, priv->nodata_bAlternateSetting); + + return 0; + } + //printk(KERN_INFO"%s: not found\n", __FUNCTION__); + return -1; +} + +/* + * See if we can have a MDLM style no-data interface: + * + * 1. Must have specified class, subclass and protocol + * 2. Must have have BULK-IN and BULK-OUT endpoints + * 3. May have INTERRUPT endpoint + * + */ +STATIC int find_interface_mdlm( + struct private *priv, + struct usb_interface *mdlm_interface, + int class, + int subclass, + int protocol, + char *guid + ) +{ + struct usb_interface_descriptor *interface_descriptor; + struct usb_class_descriptor *extra; + int extralen; + + if (mdlm_interface->num_altsetting > 1) { + printk(KERN_INFO"%s: too many intefaces num_altsetting: %d\n", __FUNCTION__, mdlm_interface->num_altsetting); + return -1; + } + + //interface_descriptor = mdlm_interface->altsetting; + interface_descriptor = USB_ALTSETTING(mdlm_interface,0); + + //printk(KERN_INFO"%s: class: %d subclass: %d endpoints: %d\n", __FUNCTION__, + // interface_descriptor->bInterfaceClass, + // interface_descriptor->bInterfaceSubClass, + // interface_descriptor->bNumEndpoints + // ); + + // check for class, sub-class, protocol and correct number of endpoints (two or three ok) + if ((interface_descriptor->bInterfaceClass != class) || + (interface_descriptor->bInterfaceSubClass != subclass) || + (interface_descriptor->bInterfaceProtocol != protocol) || + !((1 < interface_descriptor->bNumEndpoints) && (3 >= interface_descriptor->bNumEndpoints)) + ) + { + return -1; + } + + if (interface_descriptor->bNumEndpoints) { + int endpoint_number; + priv->comm_ep_in = priv->data_ep_in = priv->data_ep_out = 0; + for (endpoint_number = 0; endpoint_number < interface_descriptor->bNumEndpoints; endpoint_number++) { + //struct usb_endpoint_descriptor *endpoint = interface_descriptor->endpoint + endpoint_number; + struct usb_endpoint_descriptor *endpoint = USB_IFC2EP(interface_descriptor,endpoint_number); + + if (USB_ENDPOINT_XFER_BULK == endpoint->bmAttributes) { + if (endpoint->bEndpointAddress & USB_DIR_IN) { + priv->data_ep_in = endpoint->bEndpointAddress & 0x7f; + priv->data_ep_in_size = endpoint->wMaxPacketSize; + } + else { + priv->data_ep_out = endpoint->bEndpointAddress & 0x7f; + priv->data_ep_out_size = endpoint->wMaxPacketSize; + } + } + else if ((USB_ENDPOINT_XFER_INT == endpoint->bmAttributes) && + (endpoint->bEndpointAddress & USB_DIR_IN)) + { + priv->comm_ep_in = endpoint->bEndpointAddress & 0x7f; + priv->comm_ep_in_size = endpoint->wMaxPacketSize; + } + else { + return -1; + } + } + } + + if (!priv->data_ep_in || !priv->data_ep_out) { + return -1; + } + + extra = (struct usb_class_descriptor *)USB_IFC2HOST(interface_descriptor)->extra; + extralen = USB_IFC2HOST(interface_descriptor)->extralen; + while (extra && (extralen > 0)) { + + u8 *cp = (u8 *) extra; + + //printk(KERN_INFO"%s: %p extralen: %02x bLength: %02x bDescriptorType: %02x bDescriptorSubType: %02x\n", + // __FUNCTION__, extra, extralen, + // extra->bLength, extra->bDescriptorType, extra->bDescriptorSubType); + + BREAK_IF(!extra->bLength); + + // check for CS descriptors (0x24) + switch(extra->bDescriptorType) { + + + case USB_DT_CS_INTERFACE: + + //printk(KERN_INFO"%s: CS bDescriptorSubType: %02x\n", __FUNCTION__, extra->bDescriptorSubType); + + // then check for MDLM Functional or Detail descriptors + switch (extra->bDescriptorSubType) { + + // check for bGuid match + case MDLM_FUNCTIONAL: + { + struct usb_mdlm_functional_descriptor *functional = + (struct usb_mdlm_functional_descriptor *) extra; + //u8 *bGuid = (u8 *)&functional->bGuid; + //printk(KERN_INFO"%s: FUNCTIONAL bGUID %02x %02x %02x %02x %02x %02x %02x %02x\n", + // __FUNCTION__, + // bGuid[0],bGuid[1],bGuid[2],bGuid[3], + // bGuid[4],bGuid[5],bGuid[6],bGuid[7] + // ); + + //printk(KERN_INFO"%s: FUNCTIONAL bGUID %02x %02x %02x %02x %02x %02x %02x %02x\n", + // __FUNCTION__, + // bGuid[8],bGuid[9],bGuid[10],bGuid[11], + // bGuid[12],bGuid[13],bGuid[14],bGuid[15] + // ); + + RETURN_IF( -1, memcmp(guid, functional->bGuid, sizeof(functional->bGuid) != 0 )); + break; + } + + // find details + case MDLM_DETAIL: + { + struct usb_mdlm_detail_descriptor *mdlm = (struct usb_mdlm_detail_descriptor *) extra; + + //printk(KERN_INFO"%s: DETAIL bGuidDescriptorType\n", __FUNCTION__); + + // figure out what type of detail record it is + switch (mdlm->bGuidDescriptorType) { + case MDLM_SAFE_GUID: + { + struct usb_safe_detail_descriptor *safe = + (struct usb_safe_detail_descriptor *) extra; + //printk(KERN_INFO"%s: SAFE bmDataCapabilities: %02x\n", + // __FUNCTION__, safe->bmDataCapabilities); + priv->bmNetworkCapabilities = safe->bmNetworkCapabilities; + priv->bmDataCapabilities = safe->bmDataCapabilities; + break; + } + case MDLM_BLAN_GUID: + { + struct usb_blan_detail_descriptor *blan = + (struct usb_blan_detail_descriptor *) extra; + + //printk(KERN_INFO"%s: BLAN bmDataCapabilities: %02x\n", + // __FUNCTION__, blan->bmDataCapabilities); + priv->bmNetworkCapabilities = blan->bmNetworkCapabilities; + priv->bmDataCapabilities = blan->bmDataCapabilities; + priv->bPad = blan->bPad; + break; + } + default: + break; + } + } + + default: + break; + } + + default: + break; + } + + extralen -= extra->bLength; + cp = cp + extra->bLength; + extra = (struct usb_class_descriptor *) cp; + } + + priv->data_bInterfaceNumber = interface_descriptor->bInterfaceNumber; + priv->data_bAlternateSetting = interface_descriptor->bAlternateSetting; + + //printk(KERN_INFO"%s: found %d %d\n", __FUNCTION__, priv->data_bInterfaceNumber, priv->data_bAlternateSetting); + return 0; +} + +/* + * See if we can have a CDC interface: + * + * 1. class CDC_INTERFACE_CLASS + * 2. subclass CDC_INTERFACE_SUBCLASS + * 3. protocol DEFAULT_PROTOCOL + * + */ +STATIC int verify_ethernet_comm_interface( + struct usb_device *device, + struct private *priv, + int comm_number, struct usb_interface *comm_interface, + int data_number, struct usb_interface *data_interface + ) +{ + //printk(KERN_INFO"verify_ethernet_comm_interface:\n"); + + if (find_interface_comm(priv, comm_interface, CDC_INTERFACE_CLASS, CDC_INTERFACE_SUBCLASS, DEFAULT_PROTOCOL)) { + //printk(KERN_INFO"verify_ethernet_comm_interface: no comm\n"); + return -1; + } + + if (find_interface_data(priv, data_interface, DATA_INTERFACE_CLASS, 0, DEFAULT_PROTOCOL, 0)) { + //printk(KERN_INFO"verify_ethernet_comm_interface: no data\n"); + return -1; + } + + if (find_interface_nodata(priv, data_interface, DATA_INTERFACE_CLASS, 0, DEFAULT_PROTOCOL)) { + //printk(KERN_INFO"verify_ethernet_comm_interface: no nodata\n"); + } + + //printk(KERN_INFO"verify_ethernet_comm_interface: found\n"); + + priv->comm_interface = comm_number; + priv->data_interface = data_number; + priv->usbdnet_device_type = usbdnet_cdc; + + return 0; +} + +/* + * See if we can have an RNDIS interface: + * + * 1. class CDC_INTERFACE_CLASS + * 2. subclass RNDIS_INTERFACE_SUBCLASS + * 3. protocol VENDOR_SPECIFIC_PROTOCOL + * + */ +STATIC int verify_rndis_comm_interface( + struct usb_device *device, + struct private *priv, + int comm_number, struct usb_interface *comm_interface, + int data_number, struct usb_interface *data_interface + ) +{ + + //printk(KERN_INFO"verify_rndis_comm_interface:\n"); + + if (find_interface_comm(priv, comm_interface, CDC_INTERFACE_CLASS, RNDIS_INTERFACE_SUBCLASS, VENDOR_SPECIFIC_PROTOCOL)) { + //printk(KERN_INFO"verify_rndis_comm_interface: no comm\n"); + return -1; + } + + if (find_interface_data(priv, data_interface, DATA_INTERFACE_CLASS, 0, DEFAULT_PROTOCOL, 0)) { + //printk(KERN_INFO"verify_rndis_comm_interface: no data\n"); + return -1; + } + + //if (find_interface_nodata(priv, data_interface, DATA_INTERFACE_CLASS, 0, DEFAULT_PROTOCOL)) { + // printk(KERN_INFO"verify_rndis_comm_interface: no nodata\n"); + //} + + //printk(KERN_INFO"verify_rndis_comm_interface: found\n"); + + priv->comm_interface = comm_number; + priv->data_interface = data_number; + priv->usbdnet_device_type = usbdnet_rndis; + + return 0; +} + + +/* + * See if we can have an MDLM-BLAN interface: + * + * 1. class CDC_INTERFACE_CLASS + * 2. subclass MDLM_INTERFACE_SUBCLASS + * 3. protocol DEFAULT_PROTOCOL + * + */ +STATIC int verify_blan_interface( + struct usb_device *device, + struct private *priv, + struct usb_interface *data_interface + ) +{ + if (find_interface_mdlm(priv, data_interface, + CDC_INTERFACE_CLASS, + MDLM_INTERFACE_SUBCLASS, + DEFAULT_PROTOCOL, + BLAN_GUID)) + { + printk(KERN_INFO"%s: not found\n", __FUNCTION__); + return -1; + } + + //printk(KERN_INFO"%s: found bmDataCapabilities: %02x bPad %02x\n", + // __FUNCTION__, priv->bmDataCapabilities, priv->bPad); + + priv->comm_interface = priv->data_interface = 0; + priv->usbdnet_device_type = usbdnet_blan; + return 0; +} + +/* + * See if we can have an MDLM-SAFE interface: + * + * 1. class CDC_INTERFACE_CLASS + * 2. subclass MDLM_INTERFACE_SUBCLASS + * 3. protocol DEFAULT_PROTOCOL + * + */ +STATIC int verify_safe_interface( + struct usb_device *device, + struct private *priv, + struct usb_interface *data_interface + ) +{ + if (find_interface_mdlm(priv, data_interface, + CDC_INTERFACE_CLASS, + MDLM_INTERFACE_SUBCLASS, + DEFAULT_PROTOCOL, + SAFE_GUID)) + { + printk(KERN_INFO"%s: not found\n", __FUNCTION__); + return -1; + } + + //printk(KERN_INFO"%s: found bmDataCapabilities: %02x\n", __FUNCTION__, priv->bmDataCapabilities); + + priv->comm_interface = priv->data_interface = 0; + priv->usbdnet_device_type = usbdnet_safe; + return 0; +} + +/* + * See if we can have a BASIC interface: + * + * 1. class VENDOR_SPECIFIC_CLASS + * 2. subclass LINEO_INTERFACE_SUBCLASS_SAFENET + * 3. protocol LINEO_SAFENET_CRC + * + */ +STATIC int verify_basic_interface( + struct usb_device *device, + struct private *priv, + struct usb_interface *data_interface + ) +{ + if (find_interface_data(priv, data_interface, + VENDOR_SPECIFIC_CLASS, + LINEO_INTERFACE_SUBCLASS_SAFENET, + LINEO_SAFENET_CRC, + 1)) + { + printk(KERN_INFO"%s: not found\n", __FUNCTION__); + return -1; + } + + //printk(KERN_INFO"%s: found\n", __FUNCTION__); + + priv->comm_interface = priv->data_interface = 0; + priv->usbdnet_device_type = usbdnet_basic; + return 0; +} + + +/* + * Iterate across configurations looking for one that we like. + */ +STATIC int find_valid_configuration(struct usb_device *usbdev, struct private *priv) +{ + int configuration_number; + + // We will try each and every possible configuration + for ( configuration_number = 0; + configuration_number < usbdev->descriptor.bNumConfigurations; + configuration_number++ ) + { + + //struct usb_config_descriptor *configuration = usbdev->config + configuration_number; + struct usb_config_descriptor *configuration = USB_DEV2CONFIG(usbdev,configuration_number); + + //printk(KERN_INFO"%s[%d] bConfigurationValue: %d bNumInterfaces: %d\n", __FUNCTION__, + // configuration_number, configuration->bConfigurationValue, configuration->bNumInterfaces); + + priv->configuration_number = configuration_number; + priv->bConfigurationValue = configuration->bConfigurationValue; + + priv->comm_bInterfaceNumber = priv->comm_bAlternateSetting = -1; + priv->data_bInterfaceNumber = priv->data_bAlternateSetting = -1; + priv->nodata_bInterfaceNumber = priv->nodata_bAlternateSetting = -1; + + /* + * CDC and RNDIS devices have two interfaces, + * MDLM and simple devices have a single interface, + * we don't handle any devices with more or less than one or two intefaces. + */ + //printk(KERN_INFO"%s: interface(s) %d\n", __FUNCTION__, configuration->bNumInterfaces); + switch(configuration->bNumInterfaces) { + + /* + * Tests for devices with two interfaces, e.g. CDC and RNDIS. + * + * We cannot guarantee which order the comm and data interfaces will be so we have to check for + * comm/data or data/comm. + */ + case 2: { + //struct usb_interface *int0 = configuration->interface + 0; + //struct usb_interface *int1 = configuration->interface + 1; + struct usb_interface *int0 = USB_CONFIG2IFC(configuration,0); + struct usb_interface *int1 = USB_CONFIG2IFC(configuration,1); + + if ( !verify_ethernet_comm_interface(usbdev, priv, 0, int0, 1, int1)) { + //printk(KERN_INFO"%s: cdc 0 1\n", __FUNCTION__); + return 0; + } + else if ( !verify_ethernet_comm_interface(usbdev, priv, 1, int1, 0, int0)) { + //printk(KERN_INFO"%s: cdc 1 0\n", __FUNCTION__); + return 0; + } + else if ( !verify_rndis_comm_interface(usbdev, priv, 0, int0, 1, int1)) { + //printk(KERN_INFO"%s: rndis 0 1\n", __FUNCTION__); + return 0; + } + else if ( !verify_rndis_comm_interface(usbdev, priv, 1, int1, 0, int0)) { + //printk(KERN_INFO"%s: rndis 1 0\n", __FUNCTION__); + return 0; + } + } + break; + + /* + * tests for devices with a single interface, e.g. MDLM-SAFE MDLM-BLAN or BASIC + */ + case 1: + + if ( !verify_blan_interface(usbdev, priv, USB_CONFIG2IFC(configuration,0))) { + //printk(KERN_INFO"%s[%d] bConfigurationValue: %d bNumInterfaces: %d\n", __FUNCTION__, + // configuration_number, priv->bConfigurationValue, configuration->bNumInterfaces); + //printk(KERN_INFO"%s: mdlm-blan\n", __FUNCTION__); + return 0; + } + else if ( !verify_safe_interface(usbdev, priv, USB_CONFIG2IFC(configuration,0))) { + //printk(KERN_INFO"%s[%d] bConfigurationValue: %d bNumInterfaces: %d\n", __FUNCTION__, + // configuration_number, priv->bConfigurationValue, configuration->bNumInterfaces); + //printk(KERN_INFO"%s: mdlm-safe\n", __FUNCTION__); + return 0; + } + else if ( !verify_basic_interface(usbdev, priv, USB_CONFIG2IFC(configuration,0))) { + //printk(KERN_INFO"%s: basic\n", __FUNCTION__); + return 0; + } + break; + + /* + * currently we have no support for devices with zero or more than + * two interfaces.... + */ + default: + break; + } + priv->configuration_number = priv->bConfigurationValue = 0; + } + + // None of the configurations suited us. + //printk(KERN_INFO"%s: nothing found\n", __FUNCTION__); + return -1; +} + +/* + * check the active configuration to see if there are any claimed interfaces + */ +STATIC int verify_no_claimed_interfaces(struct usb_config_descriptor *act_config) +{ + struct usb_interface *interface; + int interface_number; + + // Go through all the interfaces and make sure none are + // claimed by anybody else. + // + + //printk(KERN_INFO"%s:\n", __FUNCTION__); + if (!act_config) { + printk(KERN_INFO"%s: NULL\n", __FUNCTION__); + return 0; + } + + //printk(KERN_INFO"%s: bNumInterfaces: %d\n", __FUNCTION__, act_config->bNumInterfaces); + for (interface_number = 0; interface_number < act_config->bNumInterfaces; interface_number++) { + + + interface = USB_CONFIG2IFC(act_config,interface_number); + +#if 0 + if (usb_interface_claimed(interface)) { + printk(KERN_INFO"%s: failed\n", __FUNCTION__); + return -1; + } +#endif + } + //printk(KERN_INFO"%s: ok\n", __FUNCTION__); + return 0; +} + +#if defined(CONFIG_USB_USBLAN) || defined(CONFIG_USB_USBLAN_MODULE) +/******************************************************** + * In the 2.6 host system, the result of "probe" is a + * ERROR value, (0 for success, negative for failure) + * The actual value of priv is not passed to the caller + * of probe (usb_probe_interface) + *****************************************************/ +STATIC int usblan_probe_return(struct private *priv){ + return ((priv == 0) ? -1 : 0); +} + +#else + +#error +/******************************************************** + * In the 2.4 host system, the probe routine is called + * by usb_find_interface_driver. If probe() returns a + * non zero pointer, that value is passed along to + * usb_claim_interface() + *******************************************************/ +STATIC void * usblan_probe_return(struct private *priv){ + return priv; +} + +#error + +#endif + +#if defined(CONFIG_USB_USBLAN) || defined(CONFIG_USB_USBLAN_MODULE) +STATIC int usblan_probe(struct usb_interface * udev, const struct usb_device_id *id){ + struct private *priv = NULL; + struct usb_device_descriptor *device = NULL; + struct usb_device *usbdev; + + /* find the usb device for the usb_interface */ + usbdev = interface_to_usbdev(udev); + /* find the usb device descriptor */ + device = &usbdev->descriptor; + //We can now resume the old probe routine except when returning a value +#else +#error +STATIC void * +usblan_probe(struct usb_device *usbdev, unsigned int ifnum, const struct usb_device_id *id) +{ + struct private *priv = NULL; + struct usb_device_descriptor *device = &usbdev->descriptor; + +#endif + + /* Begin the common portion of the probe routine */ + //printk(KERN_INFO "%s: probe\n", __FUNCTION__); + + /* do we have a valid device structure, is the device a CDC device, is the product_id and + * vendor_id one that we are supposed to handle, has anyone else claimed any interfaces? + */ + THROW_IF (device->bDeviceClass != CDC_DEVICE_CLASS, error); + THROW_IF (!idp_search(device->idVendor, device->idProduct), error); +#if defined(LINUX24) + THROW_IF (verify_no_claimed_interfaces(USB_CONFIG2DESC(usbdev->actconfig)), error); +#endif + + /* create a private structure to save state information about this usb device. + */ + THROW_IF (!(priv = create_private(usbdev->devnum)), error); + THROW_IF (find_valid_configuration(usbdev, priv), error); + + /* + * There is a valid configuration. + */ + //printk(KERN_INFO"%s: configuration_number: %d bConfigurationValue: %d\n", __FUNCTION__, + // priv->configuration_number, priv->bConfigurationValue); + + THROW_IF (register_netdev(&priv->net), free_priv); + + +#if !defined(CONFIG_USB_USBLAN) && !defined(CONFIG_USB_USBLAN_MODULE) + // usb_set_config() is not available inside probe() for 2.6. + //printk(KERN_INFO"%s: setting configuration bConfigurationValue: %d\n", __FUNCTION__, priv->bConfigurationValue); + + THROW_IF ( usb_set_configuration( usbdev, priv->bConfigurationValue ), unregister ); +#else + // Claim the interfaces we want before starting to operate on them... + // manually claim the interfaces we want. + switch (priv->usbdnet_device_type ) { + case usbdnet_cdc: + case usbdnet_rndis: + //printk(KERN_INFO"%s: claiming comm interface: configuration: %d interface: %d\n", __FUNCTION__, + // priv->configuration_number, priv->comm_interface); + + usb_driver_claim_interface( &usblan_driver, + USB_CONFIG2IFC(USB_DEV2CONFIG(usbdev,priv->configuration_number),priv->comm_interface), priv); + priv->intf_count += 1; + + /* FALL THROUGH */ // In order to claim the data interface too. + case usbdnet_safe: + case usbdnet_basic: + case usbdnet_blan: + //printk(KERN_INFO"%s: claiming data interface: configuration: %d interface: %d\n", __FUNCTION__, + // priv->configuration_number, priv->data_interface); + + usb_driver_claim_interface( &usblan_driver, + USB_CONFIG2IFC(USB_DEV2CONFIG(usbdev,priv->configuration_number),priv->data_interface), + priv ); + priv->intf_count += 1; + + break; + case usbdnet_unknown: + THROW(unregister); + } + priv->intf_max = priv->intf_count; +#endif + + //printk(KERN_INFO"%s: %s\n", __FUNCTION__, usbdnet_device_names[priv->usbdnet_device_type]); + + /* + * If we have a nodata interface interface use it, otherwise use the normal data interface. + * MDLM and Safe devices never have a nodata interface. + */ + switch (priv->usbdnet_device_type ) { + case usbdnet_cdc: + case usbdnet_rndis: + + //printk(KERN_INFO"%s: setting comm interface bInterfaceNumber: %d bAlternateSetting: %d\n", __FUNCTION__, + // priv->comm_bInterfaceNumber, priv->comm_bAlternateSetting); + THROW_IF (usb_set_interface(usbdev, priv->comm_bInterfaceNumber, priv->comm_bAlternateSetting), unregister); + + if (priv->nodata_bInterfaceNumber >= 0) { + //printk(KERN_INFO"%s: setting nodata interface bInterfaceNumber: %d bAlternateSetting: %d\n", + // __FUNCTION__, priv->nodata_bInterfaceNumber, priv->nodata_bAlternateSetting); + THROW_IF (usb_set_interface( usbdev, priv->nodata_bInterfaceNumber, priv->nodata_bAlternateSetting), + unregister); + } + else { + /* FALL THROUGH */ + + //printk(KERN_INFO"%s: setting data interface bInterfaceNumber: %d bAlternateSetting: %d\n", + // __FUNCTION__, priv->data_bInterfaceNumber, priv->data_bAlternateSetting); + THROW_IF (usb_set_interface( usbdev, priv->data_bInterfaceNumber, priv->data_bAlternateSetting), + unregister); + } + break; + + case usbdnet_safe: + case usbdnet_blan: + case usbdnet_basic: + + break; + + case usbdnet_unknown: + THROW(unregister); + } + + +#if !defined(CONFIG_USB_USBLAN) && !defined(CONFIG_USB_USBLAN_MODULE) + // manually claim the interfaces we want. + switch (priv->usbdnet_device_type ) { + case usbdnet_cdc: + case usbdnet_rndis: + //printk(KERN_INFO"%s: claiming comm interface: configuration: %d interface: %d\n", __FUNCTION__, + // priv->configuration_number, priv->comm_interface); + + usb_driver_claim_interface( &usblan_driver, + USB_CONFIG2IFC(USB_DEV2CONFIG(usbdev,priv->configuration_number),priv->comm_interface), priv); + //&(usbdev->config[priv->configuration_number].interface[priv->comm_interface]), priv ); + priv->intf_count += 1; + + /* FALL THROUGH */ // In order to claim the data interface too. + case usbdnet_safe: + case usbdnet_basic: + case usbdnet_blan: + //printk(KERN_INFO"%s: claiming data interface: configuration: %d interface: %d\n", __FUNCTION__, + // priv->configuration_number, priv->data_interface); + + usb_driver_claim_interface( &usblan_driver, + //&(usbdev->config[priv->configuration_number].interface[priv->data_interface]), + USB_CONFIG2IFC(USB_DEV2CONFIG(usbdev,priv->configuration_number),priv->data_interface), + priv ); + priv->intf_count += 1; + + break; + case usbdnet_unknown: + THROW(unregister); + } + priv->intf_max = priv->intf_count; +#endif + + + skb_queue_head_init(&priv->rxq); + skb_queue_head_init(&priv->txq); + skb_queue_head_init(&priv->unlink); + skb_queue_head_init(&priv->done); + + //printk(KERN_INFO "%s: tx_size: %3d rx_size: %3d\n", __FUNCTION__, priv->data_ep_out_size, priv->data_ep_in_size); + //printk(KERN_INFO "%s: tx_ep : %3x rx_ep : %3x\n", __FUNCTION__, priv->data_ep_out, priv->data_ep_in); + + priv->usbdev = usbdev; + +#if defined(LINUX24) + // ctrl task for clear halt operation + priv->ctrl_task.routine = ctrl_task; + priv->ctrl_task.data = priv; + priv->ctrl_task.sync = 0; +#else + PREPARE_WORK_ITEM(priv->ctrl_task, ctrl_task, priv); +#endif + + // reset task for reseting device +#if defined(LINUX26) + PREPARE_WORK_ITEM(priv->reset_task, reset_task, priv); +#else + priv->reset_task.routine = reset_task; + priv->reset_task.data = priv; + priv->reset_task.sync = 0; +#endif + + // unlink task for reseting device +#if defined(LINUX26) + PREPARE_WORK_ITEM(priv->unlink_task, unlink_task, priv); +#else + priv->unlink_task.routine = unlink_task; + priv->unlink_task.data = priv; + priv->unlink_task.sync = 0; +#endif + + // bottom half processing tasklet + priv->bh.func = bh; + priv->bh.data = (unsigned long) priv; + + // init mutex and list, add to device list + init_MUTEX(&priv->mutex); + INIT_LIST_HEAD(&priv->list); + + mutex_lock(&usbd_mutex); + list_add(&priv->list, &usbd_list); + mutex_unlock(&usbd_mutex); + + //printk(KERN_INFO "%s: success %s\n", __FUNCTION__, DRIVER_VERSION); + + switch (priv->usbdnet_device_type ) { + case usbdnet_blan: + network_notify(priv, NETWORK_ADDR_HOST, NETWORK_MASK, NETWORK_ADDR_CLIENT); + network_attach(&priv->net, NETWORK_ADDR_HOST, NETWORK_MASK, 1); + break; + default: + break; + } + + + // start as if the link is up + netif_device_attach(&priv->net); + +//#define NETWORK_ADDR_HOST 0xac100005 /* 172.16.0.0 */ +//#define NETWORK_ADDR_CLIENT 0xac100006 /* 172.16.0.0 */ +//#define NETWORK_MASK 0xfffffffc + +#if defined(CONFIG_USB_USBLAN) || defined(CONFIG_USB_USBLAN_MODULE) + usb_get_dev(usbdev); +#else + usb_inc_dev_use(usbdev); +#endif + + usb_set_intfdata(udev, priv); + + CATCH(error) { + printk(KERN_ERR"%s: caught error\n", __FUNCTION__); + + CATCH(free_priv) { + + printk(KERN_ERR"%s: caught free_priv\n", __FUNCTION__); + + CATCH(unregister) { + + printk(KERN_ERR"%s: caught unregister\n", __FUNCTION__); + + printk(KERN_ERR"%s: unregister\n", __FUNCTION__); + unregister_netdev(&priv->net); + } + + printk(KERN_ERR"%s: free priv\n", __FUNCTION__); + kfree(priv); + } + printk(KERN_ERR"%s: return NULL\n", __FUNCTION__); + return usblan_probe_return(NULL); + } + //printk(KERN_ERR"%s: return %p\n", __FUNCTION__, priv); + return usblan_probe_return(priv); +} + +#if defined(CONFIG_USB_USBLAN) || defined(CONFIG_USB_USBLAN_MODULE) +STATIC void usblan_disconnect(struct usb_interface *intf) +{ + struct private *priv = (struct private *) usb_get_intfdata(intf); + struct usb_device *usbdev = interface_to_usbdev(intf); + + /* If we claimed more than one interface during probe, this will + be called more than once. */ + if (priv->intf_count == priv->intf_max) { + // This is the first interface to be disconnected, unregister the network interface + unregister_netdev(&priv->net); + + // remove from device list + mutex_lock(&usbd_mutex); + mutex_lock(&priv->mutex); + list_del(&priv->list); + mutex_unlock(&usbd_mutex); + } + // release interfaces + + //printk(KERN_INFO"%s: releasing interface: %p from configuration: %d\n", __FUNCTION__, intf, priv->configuration_number); + // XXX usb_driver_release_interface(&usblan_driver,intf); + //printk(KERN_INFO"%s: releasing interface: ok\n", __FUNCTION__); + priv->intf_count -= 1; + + if (priv->intf_count <= 0) { + // disable + usb_put_dev(usbdev); + + // destroy the network interface and free the private storage + kfree(priv); + } +} +#else +STATIC void usblan_disconnect(struct usb_device *usbdev, void *ptr) { + struct private *priv = (struct private *) ptr; + + // unregister the network interface + unregister_netdev(&priv->net); + + // remove from device list + mutex_lock(&usbd_mutex); + mutex_lock(&priv->mutex); + list_del(&priv->list); + mutex_unlock(&usbd_mutex); + + // release interfaces + + + switch (priv->usbdnet_device_type ) { + case usbdnet_cdc: + case usbdnet_rndis: + //printk(KERN_INFO"%s: releasing comm interface: configuration: %d interface: %d\n", __FUNCTION__, + // priv->configuration_number, priv->comm_interface); + usb_driver_release_interface( &usblan_driver, + USB_CONFIG2IFC(USB_DEV2CONFIG(usbdev,priv->configuration_number),priv->comm_interface)); +// &(usbdev->config[priv->configuration_number].interface[priv->comm_interface])); + /* FALL THROUGH */ + case usbdnet_safe: + case usbdnet_basic: + case usbdnet_blan: + //printk(KERN_INFO"%s: releasing data interface: configuration: %d interface: %d\n", __FUNCTION__, + // priv->configuration_number, priv->data_interface); + usb_driver_release_interface( &usblan_driver, + USB_CONFIG2IFC(USB_DEV2CONFIG(usbdev,priv->configuration_number),priv->data_interface)); +// &(usbdev->config[priv->configuration_number].interface[priv->data_interface])); + case usbdnet_unknown: + break; + } + + + // disable + usb_dec_dev_use(usbdev); // QQSV + + // destroy the network interface and free the private storage + kfree(priv); +} +#endif + + +static struct usb_driver usblan_driver = { + name: "usblan", + probe: usblan_probe, + disconnect: usblan_disconnect, // QQSV + id_table: id_table, +}; + + +/* Module loading functions - modinit and modexit*********************************************** */ + +/* + * usbdnet_modinit - module init + * + */ +STATIC int __init usbdnet_modinit(void) +{ + //USB_PROBE_SET(usblan_driver, probe); + //printk(KERN_INFO DRIVER_VERSION " " DRIVER_AUTHOR "XXX\n"); + + + info(DRIVER_DESC " " DRIVER_VERSION " " DRIVER_AUTHOR); + //info(DRIVER_DESC); + + get_random_bytes(default_addr, ETH_ALEN); + default_addr[0] = (default_addr[0] & 0xf0) | 0x02; + +#if defined(LONG_STRING_OF_ZEROES_HACK) + fermat_init(); +#endif + + // if we have vendor_id / product_id parameters patch them into id list + if (vendor_id && product_id) { + int i; + //printk(KERN_INFO "%s: vendor_id: %x product_id: %x\n", __FUNCTION__, vendor_id, product_id); + for (i = 0; i < (sizeof(id_table) / sizeof(struct usb_device_id) - 1); i++) { + + if (id_table[i].idVendor == vendor_id && id_table[i].idProduct == product_id) { + + //printk(KERN_INFO "%s: vendor_id: %x product_id: %x already in table\n", + // __FUNCTION__, vendor_id, product_id); + break; + } + if (!id_table[i].idVendor && !id_table[i].idProduct) { + //printk(KERN_INFO "%s: vendor_id: %x product_id: %x inserted into table\n", + // __FUNCTION__, vendor_id, product_id); + id_table[i].match_flags = USB_DEVICE_ID_MATCH_DEVICE; + id_table[i].idVendor = vendor_id; + id_table[i].idProduct = product_id; + id_table[i].bDeviceClass = class; + id_table[i].bDeviceSubClass = subclass; + break; + } + } + } + + // register us with the usb core layer + if (usb_register(&usblan_driver)) { + return -EINVAL; + } + + return 0; +} + + +/* + * function_exit - module cleanup + * + */ +STATIC void __exit usbdnet_modexit(void) { + // de-register from the usb core layer + usb_deregister(&usblan_driver); +} + +module_init(usbdnet_modinit); +module_exit(usbdnet_modexit); diff --git a/drivers/usb/usblan/usblan.h b/drivers/usb/usblan/usblan.h new file mode 100644 index 000000000000..0e92f3df2c6c --- /dev/null +++ b/drivers/usb/usblan/usblan.h @@ -0,0 +1,84 @@ +#ifndef _USB_NET_USBLAN_H +#define _USB_NET_USBLAN_H 1 + +#define USB_NEW_COMPLETE_T +//#define USB_ALLOC_URB(packets) usb_alloc_urb(packets, GFP_KERNEL) +#define USB_ALLOC_URB(packets,mem_flags) usb_alloc_urb(packets,mem_flags) + +#define FILL_BULK_URB(URB,DEV,PIPE,TRANSFER_BUFFER,BUFFER_LENGTH,COMPLETE,CONTEXT) \ + usb_fill_bulk_urb(URB,DEV,PIPE,TRANSFER_BUFFER,BUFFER_LENGTH,(usb_complete_t)COMPLETE,CONTEXT) + +#define FILL_CONTROL_URB(URB,DEV,PIPE,SETUP,TRANSFER_BUFFER,BUFFER_LENGTH,COMPLETE,CONTEXT) \ + usb_fill_control_urb(URB,DEV,PIPE,SETUP,TRANSFER_BUFFER,BUFFER_LENGTH,(usb_complete_t)COMPLETE,CONTEXT) + +#define USB_SUBMIT_URB(urb) usb_submit_urb(urb, 0) +#define USB_ALTSETTING(ifc,alt_index) &(ifc->altsetting[alt_index].desc) +#define USB_IFC2EP(ifc,index) &(container_of(ifc, struct usb_host_interface, desc)->endpoint[index].desc) +#define USB_IFC2HOST(ifc) container_of(ifc, struct usb_host_interface, desc) +#define USB_DEV2CONFIG(dev,index) &(dev->config[index].desc) +#define USB_CONFIG2IFC(cfg, index) (container_of(cfg, struct usb_host_config, desc)->interface[index]) +#define USB_CONFIG2DESC(cfg) &(cfg->desc) +#define USB_PROBE_DELAYED_SET NULL +#define USB_PROBE_ERROR -1 +#define USB_PROBE_SET(driver,proc) { \ + int __usb_probe_proc(struct usb_interface *intf, \ + const struct usb_device_id *id){\ + struct usb_device *usbdev = NULL;\ + if(!intf) { return USB_PROBE_ERROR; }\ + usbdev = interface_to_usbdev(intf); \ + return(!((*proc)(usbdev,0,id)) ? USB_PROBE_ERROR :0);\ + }\ + driver->probe = __usb_probe_proc; \ +} + + + +//Missing (obsolete??) XXX +//static void usb_inc_dev_use(struct usb_device * dev) {} +#define usb_inc_dev_use(dev) usb_get_dev(dev) +#define usb_dec_dev_use(dev) usb_put_dev(dev) +/*----------------------------------------------------------------------------* + * New USB Structures * + *----------------------------------------------------------------------------*/ + +/* + * urb->transfer_flags: + */ +#define USB_DISABLE_SPD 0x0001 +//#define URB_SHORT_NOT_OK USB_DISABLE_SPD +#define USB_ISO_ASAP 0x0002 +#define USB_ASYNC_UNLINK 0x0008 +#define USB_QUEUE_BULK 0x0010 +#define USB_NO_FSBR 0x0020 +#define USB_ZERO_PACKET 0x0040 // Finish bulk OUTs always with zero length packet +#define URB_NO_INTERRUPT 0x0080 /* HINT: no non-error interrupt needed */ + /* ... less overhead for QUEUE_BULK */ +#define USB_TIMEOUT_KILLED 0x1000 // only set by HCD! + +/* + * USB-status codes: + * USB_ST* maps to -E* and should go away in the future + */ + +#define USB_ST_NOERROR 0 +#define USB_ST_CRC (-EILSEQ) +#define USB_ST_BITSTUFF (-EPROTO) +#define USB_ST_NORESPONSE (-ETIMEDOUT) /* device not responding/handshaking */ +#define USB_ST_DATAOVERRUN (-EOVERFLOW) +#define USB_ST_DATAUNDERRUN (-EREMOTEIO) +#define USB_ST_BUFFEROVERRUN (-ECOMM) +#define USB_ST_BUFFERUNDERRUN (-ENOSR) +#define USB_ST_INTERNALERROR (-EPROTO) /* unknown error */ +#define USB_ST_SHORT_PACKET (-EREMOTEIO) +#define USB_ST_PARTIAL_ERROR (-EXDEV) /* ISO transfer only partially completed */ +#define USB_ST_URB_KILLED (-ENOENT) /* URB canceled by user */ +#define USB_ST_URB_PENDING (-EINPROGRESS) +#define USB_ST_REMOVED (-ENODEV) /* device not existing or removed */ +#define USB_ST_TIMEOUT (-ETIMEDOUT) /* communication timed out, also in urb->status**/ +#define USB_ST_NOTSUPPORTED (-ENOSYS) +#define USB_ST_BANDWIDTH_ERROR (-ENOSPC) /* too much bandwidth used */ +#define USB_ST_URB_INVALID_ERROR (-EINVAL) /* invalid value/transfer type */ +#define USB_ST_URB_REQUEST_ERROR (-ENXIO) /* invalid endpoint */ +#define USB_ST_STALL (-EPIPE) /* pipe stalled, also in urb->status*/ + +#endif diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig index 5b3dbcfcda48..62e578894a93 100644 --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig @@ -337,6 +337,10 @@ config FB_CLPS711X Say Y to enable the Framebuffer driver for the CLPS7111 and EP7212 processors. +if ARCH_MXC +source "drivers/video/mxc/Kconfig" +endif + config FB_SA1100 bool "SA-1100 LCD support" depends on (FB = y) && ARM && ARCH_SA1100 diff --git a/drivers/video/Makefile b/drivers/video/Makefile index 83e02b3429b6..61ef5a502f6c 100644 --- a/drivers/video/Makefile +++ b/drivers/video/Makefile @@ -108,6 +108,7 @@ obj-$(CONFIG_FB_IMX) += imxfb.o obj-$(CONFIG_FB_S3C2410) += s3c2410fb.o obj-$(CONFIG_FB_PNX4008_DUM) += pnx4008/ obj-$(CONFIG_FB_PNX4008_DUM_RGB) += pnx4008/ +obj-$(CONFIG_FB_MXC) += mxc/ obj-$(CONFIG_FB_IBM_GXT4500) += gxt4500.o obj-$(CONFIG_FB_PS3) += ps3fb.o obj-$(CONFIG_FB_SM501) += sm501fb.o diff --git a/drivers/video/backlight/Kconfig b/drivers/video/backlight/Kconfig index 9609a6c676be..d00e50da4427 100644 --- a/drivers/video/backlight/Kconfig +++ b/drivers/video/backlight/Kconfig @@ -90,3 +90,26 @@ config BACKLIGHT_CARILLO_RANCH help If you have a Intel LE80578 (Carillo Ranch) say Y to enable the backlight driver. + +menuconfig BACKLIGHT_MXC + bool "Freescale MXC/i.MX Backlight Drivers" + depends on BACKLIGHT_CLASS_DEVICE && ARCH_MXC + default y + help + If you have a Freescale MC13783 PMIC, say y to enable the + backlight driver. + +config BACKLIGHT_MXC_IPU + tristate "IPU PWM Backlight Driver" + depends on BACKLIGHT_MXC && MXC_IPU + default y + +config BACKLIGHT_MXC_LCDC + tristate "LCDC PWM Backlight Driver" + depends on BACKLIGHT_MXC && (ARCH_MX21 || ARCH_MX27) + default y + +config BACKLIGHT_MXC_PMIC + tristate "PMIC Backlight Driver" + depends on BACKLIGHT_MXC && MXC_MC13783_LIGHT && MXC_MC13783_POWER + default y diff --git a/drivers/video/backlight/Makefile b/drivers/video/backlight/Makefile index 965a78b18118..9280ca44c38d 100644 --- a/drivers/video/backlight/Makefile +++ b/drivers/video/backlight/Makefile @@ -9,3 +9,7 @@ obj-$(CONFIG_BACKLIGHT_HP680) += hp680_bl.o obj-$(CONFIG_BACKLIGHT_LOCOMO) += locomolcd.o obj-$(CONFIG_BACKLIGHT_PROGEAR) += progear_bl.o obj-$(CONFIG_BACKLIGHT_CARILLO_RANCH) += cr_bllcd.o + +obj-$(CONFIG_BACKLIGHT_MXC_LCDC) += mxc_lcdc_bl.o +obj-$(CONFIG_BACKLIGHT_MXC_IPU) += mxc_ipu_bl.o +obj-$(CONFIG_BACKLIGHT_MXC_PMIC) += mxc_pmic_bl.o diff --git a/drivers/video/backlight/mxc_ipu_bl.c b/drivers/video/backlight/mxc_ipu_bl.c new file mode 100644 index 000000000000..1e911f4c7ff5 --- /dev/null +++ b/drivers/video/backlight/mxc_ipu_bl.c @@ -0,0 +1,156 @@ +/* + * Copyright 2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/*! + * @defgroup IPU_BL MXC IPU Backlight Driver + */ +/*! + * @file mxc_ipu_bl.c + * + * @brief Backlight Driver for IPU PWM on Freescale MXC/i.MX platforms. + * + * This file contains API defined in include/linux/clk.h for setting up and + * retrieving clocks. + * + * Based on Sharp's Corgi Backlight Driver + * + * @ingroup IPU_BL + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/spinlock.h> +#include <linux/fb.h> +#include <linux/backlight.h> + +#include <asm/arch/ipu.h> + +#define MXC_MAX_INTENSITY 255 +#define MXC_DEFAULT_INTENSITY 127 +#define MXC_INTENSITY_OFF 0 + +struct mxcbl_dev_data { + int intensity; +}; + +static int fb_id; + +static int mxcbl_send_intensity(struct backlight_device *bd) +{ + int intensity = bd->props.brightness; + struct mxcbl_dev_data *devdata = class_get_devdata(&bd->class_dev); + + if (bd->props.power != FB_BLANK_UNBLANK) + intensity = 0; + if (bd->props.fb_blank != FB_BLANK_UNBLANK) + intensity = 0; + + ipu_sdc_set_brightness(intensity); + + devdata->intensity = intensity; + return 0; +} + +static int mxcbl_get_intensity(struct backlight_device *bd) +{ + struct mxcbl_dev_data *devdata = class_get_devdata(&bd->class_dev); + return devdata->intensity; +} + +static int mxcbl_check_fb(struct fb_info *info) +{ + int id = info->fix.id[4] - '0'; + if (id == fb_id) { + if ((id == 3) && !strcmp(info->fix.id, "DISP3 FG")) { + return 0; + } + return 1; + } + return 0; +} + +static struct backlight_ops mxcbl_ops = { + .get_brightness = mxcbl_get_intensity, + .update_status = mxcbl_send_intensity, + .check_fb = mxcbl_check_fb, +}; + +static int __init mxcbl_probe(struct platform_device *pdev) +{ + struct backlight_device *bd; + struct mxcbl_dev_data *devdata; + int ret = 0; + + devdata = kzalloc(sizeof(struct mxcbl_dev_data), GFP_KERNEL); + if (!devdata) + return -ENOMEM; + fb_id = (int)pdev->dev.platform_data; + + bd = backlight_device_register(pdev->dev.bus_id, &pdev->dev, devdata, + &mxcbl_ops); + if (IS_ERR(bd)) { + ret = PTR_ERR(bd); + goto err0; + } + platform_set_drvdata(pdev, bd); + + bd->props.brightness = MXC_DEFAULT_INTENSITY; + bd->props.max_brightness = MXC_MAX_INTENSITY; + bd->props.power = FB_BLANK_UNBLANK; + bd->props.fb_blank = FB_BLANK_UNBLANK; + backlight_update_status(bd); + + printk("MXC Backlight Device %s Initialized.\n", pdev->dev.bus_id); + return 0; + err0: + kfree(devdata); + return ret; +} + +static int mxcbl_remove(struct platform_device *pdev) +{ + struct backlight_device *bd = platform_get_drvdata(pdev); + + bd->props.brightness = MXC_INTENSITY_OFF; + backlight_update_status(bd); + + backlight_device_unregister(bd); + + return 0; +} + +static struct platform_driver mxcbl_driver = { + .probe = mxcbl_probe, + .remove = mxcbl_remove, + .driver = { + .name = "mxc_ipu_bl", + }, +}; + +static int __init mxcbl_init(void) +{ + return platform_driver_register(&mxcbl_driver); +} + +static void __exit mxcbl_exit(void) +{ + platform_driver_unregister(&mxcbl_driver); +} + +module_init(mxcbl_init); +module_exit(mxcbl_exit); + +MODULE_DESCRIPTION("Freescale MXC/i.MX IPU PWM Backlight Driver"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/backlight/mxc_lcdc_bl.c b/drivers/video/backlight/mxc_lcdc_bl.c new file mode 100644 index 000000000000..1e014ced715f --- /dev/null +++ b/drivers/video/backlight/mxc_lcdc_bl.c @@ -0,0 +1,160 @@ +/* + * Copyright 2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/*! + * @defgroup LCDC_BL MXC LCDC Backlight Driver + */ +/*! + * @file mxc_lcdc_bl.c + * + * @brief Backlight Driver for LCDC PWM on Freescale MXC/i.MX platforms. + * + * This file contains API defined in include/linux/clk.h for setting up and + * retrieving clocks. + * + * Based on Sharp's Corgi Backlight Driver + * + * @ingroup LCDC_BL + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/spinlock.h> +#include <linux/fb.h> +#include <linux/backlight.h> +#include <linux/clk.h> + +#define MXC_MAX_INTENSITY 255 +#define MXC_DEFAULT_INTENSITY 127 +#define MXC_INTENSITY_OFF 0 + +extern void mx2fb_set_brightness(uint8_t); + +struct mxcbl_dev_data { + struct clk *clk; + int intensity; +}; + +static int mxcbl_send_intensity(struct backlight_device *bd) +{ + int intensity = bd->props.brightness; + struct mxcbl_dev_data *devdata = class_get_devdata(&bd->class_dev); + + if (bd->props.power != FB_BLANK_UNBLANK) + intensity = 0; + if (bd->props.fb_blank != FB_BLANK_UNBLANK) + intensity = 0; + + if ((devdata->intensity == 0) && (intensity != 0)) + clk_enable(devdata->clk); + + /* PWM contrast control register */ + mx2fb_set_brightness(intensity); + + if ((devdata->intensity != 0) && (intensity == 0)) + clk_disable(devdata->clk); + + devdata->intensity = intensity; + return 0; +} + +static int mxcbl_get_intensity(struct backlight_device *bd) +{ + struct mxcbl_dev_data *devdata = class_get_devdata(&bd->class_dev); + return devdata->intensity; +} + +static int mxcbl_check_fb(struct fb_info *info) +{ + if (strcmp(info->fix.id, "DISP0 BG") == 0) { + return 1; + } + return 0; +} + +static struct backlight_ops mxcbl_ops = { + .get_brightness = mxcbl_get_intensity, + .update_status = mxcbl_send_intensity, + .check_fb = mxcbl_check_fb, +}; + +static int __init mxcbl_probe(struct platform_device *pdev) +{ + struct backlight_device *bd; + struct mxcbl_dev_data *devdata; + int ret = 0; + + devdata = kzalloc(sizeof(struct mxcbl_dev_data), GFP_KERNEL); + if (!devdata) + return -ENOMEM; + + devdata->clk = clk_get(NULL, "lcdc_clk"); + + bd = backlight_device_register(pdev->dev.bus_id, &pdev->dev, devdata, + &mxcbl_ops); + if (IS_ERR(bd)) { + ret = PTR_ERR(bd); + goto err0; + } + platform_set_drvdata(pdev, bd); + + bd->props.brightness = MXC_DEFAULT_INTENSITY; + bd->props.max_brightness = MXC_MAX_INTENSITY; + bd->props.power = FB_BLANK_UNBLANK; + bd->props.fb_blank = FB_BLANK_UNBLANK; + mx2fb_set_brightness(MXC_DEFAULT_INTENSITY); + + printk("MXC Backlight Device %s Initialized.\n", pdev->dev.bus_id); + return 0; + err0: + kfree(devdata); + return ret; +} + +static int mxcbl_remove(struct platform_device *pdev) +{ + struct backlight_device *bd = platform_get_drvdata(pdev); + + bd->props.brightness = MXC_INTENSITY_OFF; + backlight_update_status(bd); + + backlight_device_unregister(bd); + + return 0; +} + +static struct platform_driver mxcbl_driver = { + .probe = mxcbl_probe, + .remove = mxcbl_remove, + .driver = { + .name = "mxc_lcdc_bl", + }, +}; + +static int __init mxcbl_init(void) +{ + return platform_driver_register(&mxcbl_driver); +} + +static void __exit mxcbl_exit(void) +{ + platform_driver_unregister(&mxcbl_driver); +} + +module_init(mxcbl_init); +module_exit(mxcbl_exit); + +MODULE_DESCRIPTION("Freescale MXC/i.MX LCDC PWM Backlight Driver"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/backlight/mxc_pmic_bl.c b/drivers/video/backlight/mxc_pmic_bl.c new file mode 100644 index 000000000000..9df4f133185b --- /dev/null +++ b/drivers/video/backlight/mxc_pmic_bl.c @@ -0,0 +1,197 @@ +/* + * Copyright 2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/*! + * @defgroup PMIC_BL MXC PMIC Backlight Driver + */ +/*! + * @file mxc_pmic_bl.c + * + * @brief PMIC Backlight Driver for Freescale MXC/i.MX platforms. + * + * This file contains API defined in include/linux/clk.h for setting up and + * retrieving clocks. + * + * Based on Sharp's Corgi Backlight Driver + * + * @ingroup PMIC_BL + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/spinlock.h> +#include <linux/fb.h> +#include <linux/backlight.h> + +#include <asm/arch/pmic_power.h> +#include <asm/arch/pmic_light.h> + +#define MXC_MAX_INTENSITY 255 +#define MXC_DEFAULT_INTENSITY 127 +#define MXC_INTENSITY_OFF 0 + +struct mxcbl_dev_data { + int bl_id; + int intensity; + struct backlight_ops bl_ops; +}; + +static int pmic_bl_use_count; +static int main_fb_id; +static int sec_fb_id; + +static int mxcbl_send_intensity(struct backlight_device *bd) +{ + int intensity = bd->props.brightness; + struct mxcbl_dev_data *devdata = class_get_devdata(&bd->class_dev); + + if (bd->props.power != FB_BLANK_UNBLANK) + intensity = 0; + if (bd->props.fb_blank != FB_BLANK_UNBLANK) + intensity = 0; + + intensity = intensity / 16; + pmic_bklit_set_dutycycle(devdata->bl_id, intensity); + + devdata->intensity = intensity; + return 0; +} + +static int mxcbl_get_intensity(struct backlight_device *bd) +{ + struct mxcbl_dev_data *devdata = class_get_devdata(&bd->class_dev); + return devdata->intensity; +} + +static int mxcbl_check_main_fb(struct fb_info *info) +{ + int id = info->fix.id[4] - '0'; + + if (id == main_fb_id) { + return 1; + } else { + return 0; + } +} + +static int mxcbl_check_sec_fb(struct fb_info *info) +{ + int id = info->fix.id[4] - '0'; + + if (id == sec_fb_id) { + return 1; + } else { + return 0; + } +} + +static int __init mxcbl_probe(struct platform_device *pdev) +{ + int ret = 0; + struct backlight_device *bd; + struct mxcbl_dev_data *devdata; + + devdata = kzalloc(sizeof(struct mxcbl_dev_data), GFP_KERNEL); + if (!devdata) + return -ENOMEM; + devdata->bl_id = pdev->id; + + if (pdev->id == 0) { + devdata->bl_ops.check_fb = mxcbl_check_main_fb; + main_fb_id = (int)pdev->dev.platform_data; + } else { + devdata->bl_ops.check_fb = mxcbl_check_sec_fb; + sec_fb_id = (int)pdev->dev.platform_data; + } + + devdata->bl_ops.get_brightness = mxcbl_get_intensity; + devdata->bl_ops.update_status = mxcbl_send_intensity, + bd = + backlight_device_register(pdev->dev.bus_id, &pdev->dev, devdata, + &devdata->bl_ops); + if (IS_ERR(bd)) { + ret = PTR_ERR(bd); + goto err0; + } + + platform_set_drvdata(pdev, bd); + + if (pmic_bl_use_count++ == 0) { + pmic_power_regulator_on(SW_SW3); + pmic_power_regulator_set_lp_mode(SW_SW3, LOW_POWER_CTRL_BY_PIN); + + pmic_bklit_tcled_master_enable(); + pmic_bklit_enable_edge_slow(); + pmic_bklit_set_cycle_time(0); + } + + pmic_bklit_set_current(devdata->bl_id, 7); + bd->props.brightness = MXC_DEFAULT_INTENSITY; + bd->props.max_brightness = MXC_MAX_INTENSITY; + bd->props.power = FB_BLANK_UNBLANK; + bd->props.fb_blank = FB_BLANK_UNBLANK; + backlight_update_status(bd); + + printk("MXC Backlight Device %s Initialized.\n", pdev->dev.bus_id); + return 0; + err0: + kfree(devdata); + return ret; +} + +static int mxcbl_remove(struct platform_device *pdev) +{ + struct backlight_device *bd = platform_get_drvdata(pdev); + + bd->props.brightness = MXC_INTENSITY_OFF; + backlight_update_status(bd); + + if (--pmic_bl_use_count == 0) { + pmic_bklit_tcled_master_disable(); + + pmic_power_regulator_off(SW_SW3); + pmic_power_regulator_set_lp_mode(SW_SW3, LOW_POWER_CTRL_BY_PIN); + } + + backlight_device_unregister(bd); + + printk("MXC Backlight Driver Unloaded\n"); + + return 0; +} + +static struct platform_driver mxcbl_driver = { + .probe = mxcbl_probe, + .remove = mxcbl_remove, + .driver = { + .name = "mxc_pmic_bl", + }, +}; + +static int __init mxcbl_init(void) +{ + return platform_driver_register(&mxcbl_driver); +} + +static void __exit mxcbl_exit(void) +{ + platform_driver_unregister(&mxcbl_driver); +} + +module_init(mxcbl_init); +module_exit(mxcbl_exit); + +MODULE_DESCRIPTION("Freescale MXC/i.MX PMIC Backlight Driver"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/mxc/Kconfig b/drivers/video/mxc/Kconfig new file mode 100644 index 000000000000..48be1104d85c --- /dev/null +++ b/drivers/video/mxc/Kconfig @@ -0,0 +1,82 @@ +config FB_MXC + tristate "MXC Framebuffer support" + depends on FB && (MXC_IPU || ARCH_MX21 || ARCH_MX27) + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + default y + help + This is a framebuffer device for the MXC LCD Controller. + See <http://www.linux-fbdev.org/> for information on framebuffer + devices. + + If you plan to use the LCD display with your MXC system, say + Y here. + +config FB_MXC_SYNC_PANEL + depends on FB_MXC + tristate "Synchronous Panel Framebuffer" + default y + +config FB_MXC_TVOUT + bool "TV Out Encoder" + depends on FB_MXC_SYNC_PANEL + default n + +config FB_MXC_LOW_PWR_DISPLAY + bool "Low Power Display Refresh Mode" + depends on FB_MXC_SYNC_PANEL && MXC_FB_IRAM + default y + +config FB_MXC_INTERNAL_MEM + bool "Framebuffer in Internal RAM" + depends on FB_MXC_SYNC_PANEL && MXC_FB_IRAM + default y + +config FB_MXC_ASYNC_PANEL + depends on FB_MXC + bool "Asynchronous Panels" + default n + +menu "Asynchronous Panel Type" + depends on FB_MXC_ASYNC_PANEL && FB_MXC + +config FB_MXC_EPSON_PANEL + depends on FB_MXC_ASYNC_PANEL + default n + bool "Epson 176x220 Panel" + +if MACH_I30030ADS +config FB_MXC_EPSON_QVGA_PANEL + depends on FB_MXC_ASYNC_PANEL + default y + bool "Epson 240x320 Panel" +endif + +if (MACH_MXC30030ADS || MACH_MXC30031ADS) +config FB_MXC_TOSHIBA_QVGA_PANEL + depends on FB_MXC_ASYNC_PANEL + bool "Toshiba 240x320 Panel" + +config FB_MXC_SHARP_128_PANEL + depends on FB_MXC_ASYNC_PANEL + bool "Sharp 128x128 Panel" +endif + +endmenu + +choice + prompt "Async Panel Interface Type" + depends on FB_MXC_ASYNC_PANEL && FB_MXC + default FB_MXC_ASYNC_PANEL_IFC_16_BIT + +config FB_MXC_ASYNC_PANEL_IFC_8_BIT + bool "8-bit Parallel Bus Interface" + +config FB_MXC_ASYNC_PANEL_IFC_16_BIT + bool "16-bit Parallel Bus Interface" + +config FB_MXC_ASYNC_PANEL_IFC_SERIAL + bool "Serial Bus Interface" + +endchoice diff --git a/drivers/video/mxc/Makefile b/drivers/video/mxc/Makefile new file mode 100644 index 000000000000..c0ef8adc404f --- /dev/null +++ b/drivers/video/mxc/Makefile @@ -0,0 +1,12 @@ +ifeq ($(CONFIG_ARCH_MX21)$(CONFIG_ARCH_MX27),y) + obj-$(CONFIG_FB_MXC_TVOUT) += fs453.o + obj-$(CONFIG_FB_MXC_SYNC_PANEL) += mx2fb.o mxcfb_modedb.o + obj-$(CONFIG_FB_MXC_EPSON_PANEL) += mx2fb_epson.o +else + obj-$(CONFIG_FB_MXC_TVOUT) += fs453.o + obj-$(CONFIG_FB_MXC_SYNC_PANEL) += mxcfb.o mxcfb_modedb.o + obj-$(CONFIG_FB_MXC_EPSON_PANEL) += mxcfb_epson.o + obj-$(CONFIG_FB_MXC_EPSON_QVGA_PANEL) += mxcfb_epson_qvga.o + obj-$(CONFIG_FB_MXC_TOSHIBA_QVGA_PANEL) += mxcfb_toshiba_qvga.o + obj-$(CONFIG_FB_MXC_SHARP_128_PANEL) += mxcfb_sharp_128x128.o +endif diff --git a/drivers/video/mxc/fs453.c b/drivers/video/mxc/fs453.c new file mode 100644 index 000000000000..449675e83aef --- /dev/null +++ b/drivers/video/mxc/fs453.c @@ -0,0 +1,494 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @defgroup FS453 Focus FS453 TV Encoder Driver + */ +/*! + * @file fs453.c + * @brief Driver for FS453/4 TV encoder + * + * @ingroup FS453 + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/i2c.h> +#include <linux/ioctl.h> +#include <linux/video_encoder.h> + +#include "fs453.h" + +/* + * FIXME: VGA mode is not defined by video_encoder.h + * while FS453 supports VGA output. + */ +#ifndef VIDEO_ENCODER_VGA +#define VIDEO_ENCODER_VGA 32 +#endif + +/*! + * This stucture contains the status of FS453. + */ +struct fs453_data { + int norm; + int input; + int output; + int enable; +}; + +/*! + * This structure contains all the register values needed to program the + * TV encoder chip. This structure is instantiated and initialized for + * each supported output standard. + */ +struct fs453_presets { + u32 mode; /*! Video mode */ + u16 qpr; /*! Quick Program Register */ + u16 pwr_mgmt; /*! Power Management */ + u16 iho; /*! Input Horizontal Offset */ + u16 ivo; /*! Input Vertical Offset */ + u16 ihw; /*! Input Horizontal Width */ + u16 vsc; /*! Vertical Scaling Coefficient */ + u16 hsc; /*! Horizontal Scaling Coefficient */ + u16 bypass; /*! Bypass */ + u16 misc; /*! Miscellaneous Bits Register */ + u8 misc46; /*! Miscellaneous Bits Register 46 */ + u8 misc47; /*! Miscellaneous Bits Register 47 */ + u32 ncon; /*! Numerator of NCO Word */ + u32 ncod; /*! Denominator of NCO Word */ + u16 pllm; /*! PLL M and Pump Control */ + u16 plln; /*! PLL N */ + u16 pllpd; /*! PLL Post-Divider */ + u16 vid_cntl0; /*! Video Control 0 */ + u16 dac_cntl; /*! DAC Control */ + u16 fifo_lat; /*! FIFO Latency */ +}; + +static struct fs453_presets fs453_vga_presets = { + .mode = VIDEO_ENCODER_VGA, + .qpr = 0x9cb0, + .pwr_mgmt = 0x0408, + .misc = 0x0103, + .ncon = 0x00000000, + .ncod = 0x00000000, + .misc46 = 0xa9, + .misc47 = 0x00, + .pllm = 0x317f, + .plln = 0x008e, + .pllpd = 0x0202, + .vid_cntl0 = 0x4006, + .dac_cntl = 0x00e4, + .fifo_lat = 0x0082, +}; + +static struct fs453_presets fs453_ntsc_presets = { + .mode = VIDEO_ENCODER_NTSC, + .qpr = 0x9c48, + .pwr_mgmt = 0x0200, + .misc = 0x0103, + .ncon = 0x00000001, + .ncod = 0x00000001, + .misc46 = 0x01, + .misc47 = 0x00, + .pllm = 0x4000 | (296 - 17), + .plln = 30 - 1, + .pllpd = ((10 - 1) << 8) | (10 - 1), + .iho = 0, + .ivo = 40, + .ihw = 768, + .vsc = 789, + .hsc = 0x0000, + .bypass = 0x000a, + .vid_cntl0 = 0x0340, + .dac_cntl = 0x00e4, + .fifo_lat = 0x0082, +}; + +static struct fs453_presets fs453_pal_presets = { + .mode = VIDEO_ENCODER_PAL, + .qpr = 0x9c41, + .pwr_mgmt = 0x0200, + .misc = 0x0103, + .ncon = 0x00000001, + .ncod = 0x00000001, + .misc46 = 0x01, + .misc47 = 0x00, + .pllm = 0x4000 | (296 - 17), + .plln = 30 - 1, + .pllpd = ((10 - 1) << 8) | (10 - 1), + .iho = 0, + .ivo = 19, + .ihw = 768, + .vsc = 8200, + .hsc = 0x0000, + .bypass = 0x000a, + .vid_cntl0 = 0x0340, + .dac_cntl = 0x00e4, + .fifo_lat = 0x0082, +}; + +static int fs453_preset(struct i2c_client *client, + struct fs453_presets *presets); +static int fs453_enable(struct i2c_client *client, int enable); + +static struct i2c_driver fs453_driver; +/* + * FIXME: fs453_client will represent the first FS453 device found by + * the I2C subsystem, which means fs453_ioctl() always works on the + * first FS453 device. + */ +static struct i2c_client *fs453_client = 0; + +static int fs453_command(struct i2c_client *client, unsigned int cmd, void *arg) +{ + int val; + char *smode = 0; + struct video_encoder_capability *cap; + struct fs453_data *data = i2c_get_clientdata(client); + int ret = 0; + + switch (cmd) { + case ENCODER_GET_CAPABILITIES: + cap = arg; + cap->flags = + VIDEO_ENCODER_PAL | VIDEO_ENCODER_NTSC | VIDEO_ENCODER_VGA; + cap->inputs = 1; + cap->outputs = 1; + break; + case ENCODER_SET_NORM: + val = *(int *)arg; + switch (val) { + case VIDEO_ENCODER_PAL: + ret = fs453_preset(client, &fs453_pal_presets); + smode = "PAL"; + break; + case VIDEO_ENCODER_NTSC: + ret = fs453_preset(client, &fs453_ntsc_presets); + smode = "NTSC"; + break; + case VIDEO_ENCODER_VGA: + ret = fs453_preset(client, &fs453_vga_presets); + smode = "VGA"; + break; + default: + ret = -EINVAL; + break; + } + if (!ret) { + data->norm = val; + data->enable = 1; + pr_debug("FS453: switched to %s\n", smode); + } + break; + case ENCODER_SET_INPUT: + val = *(int *)arg; + /* We have only one input */ + if (val != 0) + return -EINVAL; + data->input = val; + break; + case ENCODER_SET_OUTPUT: + val = *(int *)arg; + /* We have only one output */ + if (val != 0) + return -EINVAL; + data->output = val; + break; + case ENCODER_ENABLE_OUTPUT: + val = *(int *)arg; + if ((ret = fs453_enable(client, val)) == 0) + data->enable = val; + break; + default: + return -EINVAL; + } + + return ret; +} + +static int i2c_fs453_detect_client(struct i2c_adapter *adapter, int address, + int kind) +{ + int chip_id; + struct i2c_client *client; + struct fs453_data *data; + const char *client_name = "FS453 I2C dev"; + + pr_debug("FS453: i2c-bus: %s; address: 0x%x\n", adapter->name, address); + + /* Let's see whether this adapter can support what we need */ + if (!i2c_check_functionality(adapter, + I2C_FUNC_SMBUS_WORD_DATA | + I2C_FUNC_SMBUS_BYTE_DATA)) { + pr_debug("FS453: SMBUS word/byte operations not permited.\n"); + return 0; + } + + client = + kmalloc(sizeof(struct i2c_client) + sizeof(struct fs453_data), + GFP_KERNEL); + if (!client) + return -ENOMEM; + + data = (struct fs453_data *)(client + 1); + client->addr = address; + client->adapter = adapter; + client->driver = &fs453_driver; + client->flags = 0; + + /* + * The generic detection, that is skipped if any force + * parameter was used. + */ + if (kind < 0) { + chip_id = i2c_smbus_read_word_data(client, FS453_ID); + if (chip_id != FS453_CHIP_ID) { + pr_info("FS453: TV encoder not present\n"); + kfree(client); + return 0; + } else + pr_info("FS453: TV encoder present, ID=0x%04X\n", + chip_id); + } + strcpy(client->name, client_name); + + /* FS453 default status */ + data->input = 0; + data->output = 0; + data->norm = 0; + data->enable = 0; + i2c_set_clientdata(client, data); + + if (i2c_attach_client(client)) { + pr_debug("FS453: i2c_attach_client() failed.\n"); + kfree(client); + } else if (fs453_client == 0) + fs453_client = client; + + return 0; +} + +static unsigned short normal_i2c[] = { FS453_I2C_ADDR, I2C_CLIENT_END }; + +/* Magic definition of all other variables and things */ +I2C_CLIENT_INSMOD; + +static int i2c_fs453_attach(struct i2c_adapter *adap) +{ + return i2c_probe(adap, &addr_data, &i2c_fs453_detect_client); +} + +static int i2c_fs453_detach(struct i2c_client *client) +{ + int err; + + if ((err = i2c_detach_client(client))) { + pr_debug("FS453: i2c_detach_client() failed\n"); + return err; + } + + if (fs453_client == client) + fs453_client = 0; + + kfree(client); + return 0; +} + +static struct i2c_driver fs453_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "FS453 driver", + }, + .attach_adapter = &i2c_fs453_attach, + .detach_client = &i2c_fs453_detach, + .command = fs453_command, +}; + +/*! + * @brief Function to read TV encoder registers on the i2c bus + * @param client I2C client structure + * @param reg The register number + * @param value Pointer to buffer to receive the read data + * @param len Number of 16-bit register words to read + * @return 0 on success, others on failure + */ +static int fs453_read(struct i2c_client *client, u8 reg, u32 * value, u32 len) +{ + if (len == 1) + *value = i2c_smbus_read_byte_data(client, reg); + else if (len == 2) + *value = i2c_smbus_read_word_data(client, reg); + else if (len == 4) { + *(u16 *) value = i2c_smbus_read_word_data(client, reg); + *((u16 *) value + 1) = + i2c_smbus_read_word_data(client, reg + 2); + } else + return -EINVAL; + + return 0; +} + +/*! + * @brief Function to write a TV encoder register on the i2c bus + * @param client I2C client structure + * @param reg The register number + * @param value The value to write + * @param len Number of words to write (must be 1) + * @return 0 on success, others on failure + */ +static int fs453_write(struct i2c_client *client, u8 reg, u32 value, u32 len) +{ + if (len == 1) + return i2c_smbus_write_byte_data(client, reg, (u8) value); + else if (len == 2) + return i2c_smbus_write_word_data(client, reg, (u16) value); + else if (len == 4) + return i2c_smbus_write_block_data(client, reg, len, + (u8 *) & value); + else + return -EINVAL; +} + +/*! + * @brief Function to initialize the TV encoder + * @param client I2C client structure + * @param presets FS453 pre-defined register values + * @return 0 on success; ENODEV if the encoder wasn't found + */ +static int fs453_preset(struct i2c_client *client, + struct fs453_presets *presets) +{ + u32 data; + + if (!client) + return -ENODEV; + + /* set the clock level */ + fs453_write(client, FS453_CR, CR_GCC_CK_LVL, 2); + + /* soft reset the encoder */ + fs453_read(client, FS453_CR, &data, 2); + fs453_write(client, FS453_CR, data | CR_SRESET, 2); + fs453_write(client, FS453_CR, data & ~CR_SRESET, 2); + + fs453_write(client, FS453_BYPASS, presets->bypass, 2); + + /* Write the QPR (Quick Programming Register). */ + fs453_write(client, FS453_QPR, presets->qpr, 2); + + if (presets->mode != VIDEO_ENCODER_VGA) { + /* set up the NCO and PLL */ + fs453_write(client, FS453_NCON, presets->ncon, 4); + fs453_write(client, FS453_NCOD, presets->ncod, 4); + fs453_write(client, FS453_PLL_M_PUMP, presets->pllm, 2); + fs453_write(client, FS453_PLL_N, presets->plln, 2); + fs453_write(client, FS453_PLL_PDIV, presets->pllpd, 2); + + /* latch the NCO and PLL settings */ + fs453_read(client, FS453_CR, &data, 2); + fs453_write(client, FS453_CR, data | CR_NCO_EN, 2); + fs453_write(client, FS453_CR, data & ~CR_NCO_EN, 2); + } + + /* customize */ + fs453_write(client, FS453_PWR_MGNT, presets->pwr_mgmt, 2); + + fs453_write(client, FS453_IHO, presets->iho, 2); + fs453_write(client, FS453_IVO, presets->ivo, 2); + fs453_write(client, FS453_IHW, presets->ihw, 2); + fs453_write(client, FS453_VSC, presets->vsc, 2); + fs453_write(client, FS453_HSC, presets->hsc, 2); + + fs453_write(client, FS453_MISC, presets->misc, 2); + + fs453_write(client, FS453_VID_CNTL0, presets->vid_cntl0, 2); + fs453_write(client, FS453_MISC_46, presets->misc46, 1); + fs453_write(client, FS453_MISC_47, presets->misc47, 1); + + fs453_write(client, FS453_DAC_CNTL, presets->dac_cntl, 2); + fs453_write(client, FS453_FIFO_LAT, presets->fifo_lat, 2); + + return 0; +} + +/*! + * @brief Function to enable/disable the TV encoder + * @param client I2C client structure + * @param enable 0 to disable, others to enable + * @return 0 on success; ENODEV if the encoder wasn't found + */ +static int fs453_enable(struct i2c_client *client, int enable) +{ + struct fs453_data *data; + + if (!client) + return -ENODEV; + + data = i2c_get_clientdata(client); + + if (enable) + return fs453_command(client, ENCODER_SET_NORM, &data->norm); + else + return fs453_write(client, FS453_PWR_MGNT, 0x3BFF, 2); +} + +/*! + * @brief FS453 control routine + * @param cmd Control command + * @param arg Control argument + * @return 0 on success, others on failure + */ +int fs453_ioctl(unsigned int cmd, void *arg) +{ + if (!fs453_client) + return -ENODEV; + + return fs453_command(fs453_client, cmd, arg); +} + +/*! + * @brief Probe for the TV enocder and initialize the driver + * @return 0 on success, others on failure + */ +static int __init fs453_init(void) +{ + int err; + + pr_info("FS453/4 driver, (c) 2005 Freescale Semiconductor, Inc.\n"); + + if ((err = i2c_add_driver(&fs453_driver))) { + pr_info("FS453: driver registration failed\n"); + return err; + } + + return 0; +} + +/*! + * @brief Module exit routine + */ +static void __exit fs453_exit(void) +{ + i2c_del_driver(&fs453_driver); +} + +module_init(fs453_init); +module_exit(fs453_exit); + +EXPORT_SYMBOL(fs453_ioctl); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("FS453/4 TV encoder driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/mxc/fs453.h b/drivers/video/mxc/fs453.h new file mode 100644 index 000000000000..1edd11481b76 --- /dev/null +++ b/drivers/video/mxc/fs453.h @@ -0,0 +1,134 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + + /*! + * @file fs453.h + * @brief Driver for FS453/4 TV encoder + * + * @ingroup FS453 + */ + +#ifndef __FS453_H__ +#define __FS453_H__ + +/* I2C address of the FS453 chip */ + +#define I2C1_BUS 0 +#define FS453_I2C_ADDR 0x6A + +/*! + * + * FS453 register file + * + */ +#define FS453_IHO 0x00 /*! Input Horizontal Offset */ +#define FS453_IVO 0x02 /*! Input Vertical Offset */ +#define FS453_IHW 0x04 /*! Input Horizontal Width */ +#define FS453_VSC 0x06 /*! Vertical Scaling Coefficient */ +#define FS453_HSC 0x08 /*! Horizontal Scaling Coefficient */ +#define FS453_BYPASS 0x0A /*! BYPASS */ +#define FS453_CR 0x0C /*! Command Register */ +#define FS453_MISC 0x0E /*! Miscellaneous Bits Register */ +#define FS453_NCON 0x10 /*! Numerator of NCO Word */ +#define FS453_NCOD 0x14 /*! Denominator of NCO Word */ +#define FS453_PLL_M_PUMP 0x18 /*! PLL M and Pump Control */ +#define FS453_PLL_N 0x1A /*! PLL N */ +#define FS453_PLL_PDIV 0x1C /*! PLL Post-Divider */ +#define FS453_SHP 0x24 /*! Sharpness Filter */ +#define FS453_FLK 0x26 /*! Filcker Filter Coefficient */ +#define FS453_GPIO 0x28 /*! General Purpose I/O, Output Enab */ +#define FS453_ID 0x32 /*! Part Identification Number */ +#define FS453_STATUS 0x34 /*! Status Port */ +#define FS453_FIFO_SP 0x36 /*! FIFO Status Port Fill/Underrun */ +#define FS453_FIFO_LAT 0x38 /*! FIFO Latency */ +#define FS453_CHR_FREQ 0x40 /*! Chroma Subcarrier Frequency */ +#define FS453_CHR_PHASE 0x44 /*! Chroma Phase */ +#define FS453_MISC_45 0x45 /*! Miscellaneous Bits Register 45 */ +#define FS453_MISC_46 0x46 /*! Miscellaneous Bits Register 46 */ +#define FS453_MISC_47 0x47 /*! Miscellaneous Bits Register 47 */ +#define FS453_HSYNC_WID 0x48 /*! HSync Width */ +#define FS453_BURST_WID 0x49 /*! Burst Width */ +#define FS453_BPORCH 0x4A /*! Back Porch Width */ +#define FS453_CB_BURST 0x4B /*! Cb Burst Amplitude */ +#define FS453_CR_BURST 0x4C /*! Cr Burst Amplitude */ +#define FS453_MISC_4D 0x4D /*! Miscellaneous Bits Register 4D */ +#define FS453_BLACK_LVL 0x4E /*! Black Level */ +#define FS453_BLANK_LVL 0x50 /*! Blank Level */ +#define FS453_NUM_LINES 0x57 /*! Number of Lines */ +#define FS453_WHITE_LVL 0x5E /*! White Level */ +#define FS453_CB_GAIN 0x60 /*! Cb Color Saturation */ +#define FS453_CR_GAIN 0x62 /*! Cr Color Saturation */ +#define FS453_TINT 0x65 /*! Tint */ +#define FS453_BR_WAY 0x69 /*! Width of Breezeway */ +#define FS453_FR_PORCH 0x6C /*! Front Porch */ +#define FS453_NUM_PIXELS 0x71 /*! Total num. of luma/chroma Pixels */ +#define FS453_1ST_LINE 0x73 /*! First Video Line */ +#define FS453_MISC_74 0x74 /*! Miscellaneous Bits Register 74 */ +#define FS453_SYNC_LVL 0x75 /*! Sync Level */ +#define FS453_VBI_BL_LVL 0x7C /*! VBI Blank Level */ +#define FS453_SOFT_RST 0x7E /*! Encoder Soft Reset */ +#define FS453_ENC_VER 0x7F /*! Encoder Version */ +#define FS453_WSS_CONFIG 0x80 /*! WSS Configuration Register */ +#define FS453_WSS_CLK 0x81 /*! WSS Clock */ +#define FS453_WSS_DATAF1 0x83 /*! WSS Data Field 1 */ +#define FS453_WSS_DATAF0 0x86 /*! WSS Data Field 0 */ +#define FS453_WSS_LNF1 0x89 /*! WSS Line Number Field 1 */ +#define FS453_WSS_LNF0 0x8A /*! WSS Line Number Field 0 */ +#define FS453_WSS_LVL 0x8B /*! WSS Level */ +#define FS453_MISC_8D 0x8D /*! Miscellaneous Bits Register 8D */ +#define FS453_VID_CNTL0 0x92 /*! Video Control 0 */ +#define FS453_HD_FP_SYNC 0x94 /*! Horiz. Front Porch & HSync Width */ +#define FS453_HD_YOFF_BP 0x96 /*! HDTV Lum. Offset & Back Porch */ +#define FS453_SYNC_DL 0x98 /*! Sync Delay Value */ +#define FS453_LD_DET 0x9C /*! DAC Load Detect */ +#define FS453_DAC_CNTL 0x9E /*! DAC Control */ +#define FS453_PWR_MGNT 0xA0 /*! Power Management */ +#define FS453_RED_MTX 0xA2 /*! RGB to YCrCb Matrix Red Coeff. */ +#define FS453_GRN_MTX 0xA4 /*! RGB to YCrCb Matrix Green Coeff. */ +#define FS453_BLU_MTX 0xA6 /*! RGB to YCrCb Matrix Blue Coeff. */ +#define FS453_RED_SCL 0xA8 /*! RGB to YCrCb Scaling Red Coeff. */ +#define FS453_GRN_SCL 0xAA /*! RGB to YCrCb Scaling Green Coeff. */ +#define FS453_BLU_SCL 0xAC /*! RGB to YCrCb Scaling Blue Coeff. */ +#define FS453_CC_FIELD_1 0xAE /*! Closed Caption Field 1 Data */ +#define FS453_CC_FIELD_2 0xB0 /*! Closed Caption Field 2 Data */ +#define FS453_CC_CONTROL 0xB2 /*! Closed Caption Control */ +#define FS453_CC_BLANK_VALUE 0xB4 /*! Closed Caption Blanking Value */ +#define FS453_CC_BLANK_SAMPLE 0xB6 /*! Closed Caption Blanking Sample */ +#define FS453_HACT_ST 0xB8 /*! HDTV Horizontal Active Start */ +#define FS453_HACT_WD 0xBA /*! HDTV Horizontal Active Width */ +#define FS453_VACT_ST 0xBC /*! HDTV Veritical Active Width */ +#define FS453_VACT_HT 0xBE /*! HDTV Veritical Active Height */ +#define FS453_PR_PB_SCALING 0xC0 /*! Pr and Pb Relative Scaling */ +#define FS453_LUMA_BANDWIDTH 0xC2 /*! Luminance Frequency Response */ +#define FS453_QPR 0xC4 /*! Quick Program Register */ + +/*! Command register bits */ + +#define CR_GCC_CK_LVL 0x2000 /*! Graphics Controller switching lev */ +#define CR_P656_LVL 0x1000 /*! Pixel Port Output switching level */ +#define CR_P656_IN 0x0800 /*! Pixel Port In */ +#define CR_P656_OUT 0x0400 /*! Pixel Port Out */ +#define CR_CBAR_480P 0x0200 /*! 480P Color Bars */ +#define CR_PAL_NTSCIN 0x0100 /*! PAL or NTSC input */ +#define CR_SYNC_MS 0x0080 /*! Sync Master or Slave */ +#define CR_FIFO_CLR 0x0040 /*! FIFO Clear */ +#define CR_CACQ_CLR 0x0020 /*! CACQ Clear */ +#define CR_CDEC_BP 0x0010 /*! Chroma Decimator Bypass */ +#define CR_NCO_EN 0x0002 /*! Enable NCO Latch */ +#define CR_SRESET 0x0001 /*! Soft Reset */ + +/*! Chip ID register bits */ + +#define FS453_CHIP_ID 0xFE05 /*! Chip ID register expected value */ + +#endif /* __FS453_H__ */ diff --git a/drivers/video/mxc/mx2fb.c b/drivers/video/mxc/mx2fb.c new file mode 100644 index 000000000000..1aac341d1c4c --- /dev/null +++ b/drivers/video/mxc/mx2fb.c @@ -0,0 +1,1346 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @defgroup Framebuffer_MX27 Framebuffer Driver for MX27. + */ + +/*! + * @file mx2fb.c + * + * @brief Frame buffer driver for MX27 ADS. + * + * @ingroup Framebuffer_MX27 + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/slab.h> +#include <linux/fb.h> +#include <linux/init.h> +#include <linux/dma-mapping.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/clk.h> +#include <asm/uaccess.h> +#include <asm/arch/mxcfb.h> + +#include "mx2fb.h" + +#define MX2FB_TYPE_BG 0 +#define MX2FB_TYPE_GW 1 + +extern void gpio_lcdc_active(void); +extern void gpio_lcdc_inactive(void); +extern void board_power_lcd(int on); + +static char *fb_mode = 0; +static int fb_enabled = 0; +static unsigned long default_bpp = 16; +static ATOMIC_NOTIFIER_HEAD(mx2fb_notifier_list); +static struct clk *lcdc_clk; +/*! + * @brief Structure containing the MX2 specific framebuffer information. + */ +struct mx2fb_info { + int type; + char *id; + int registered; + int blank; + unsigned long pseudo_palette[16]; +}; + +/* Framebuffer APIs */ +static int mx2fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info); +static int mx2fb_set_par(struct fb_info *info); +static int mx2fb_setcolreg(unsigned regno, unsigned red, unsigned green, + unsigned blue, unsigned transp, + struct fb_info *info); +static int mx2fb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info); +static int mx2fb_blank(int blank_mode, struct fb_info *info); +static int mx2fb_ioctl(struct fb_info *info, unsigned int cmd, + unsigned long arg); + +/* Driver entries */ +int __init mx2fb_init(void); +void __exit mx2fb_exit(void); +#ifndef MODULE +static int __init mx2fb_setup(char *); +#endif + +/* Internal functions */ +static int __init _init_fbinfo(struct fb_info *info, + struct platform_device *pdev); +static int __init _install_fb(struct fb_info *info, + struct platform_device *pdev); +static void __exit _uninstall_fb(struct fb_info *info); +static int _map_video_memory(struct fb_info *info); +static void _unmap_video_memory(struct fb_info *info); +static void _set_fix(struct fb_info *info); +static void _enable_lcdc(struct fb_info *info); +static void _disable_lcdc(struct fb_info *info); +static void _enable_graphic_window(struct fb_info *info); +static void _disable_graphic_window(struct fb_info *info); +static void _update_lcdc(struct fb_info *info); +static void _request_irq(void); +static void _free_irq(void); + +#ifdef CONFIG_PM +static int mx2fb_suspend(struct platform_device *pdev, pm_message_t state); +static int mx2fb_resume(struct platform_device *pdev); +#else +#define mx2fb_suspend 0 +#define mx2fb_resume 0 +#endif + +static int mx2fb_probe(struct platform_device *pdev); + +#ifdef CONFIG_FB_MXC_TVOUT +#include <linux/video_encoder.h> +/* + * FIXME: VGA mode is not defined by video_encoder.h + * while FS453 supports VGA output. + */ +#ifndef VIDEO_ENCODER_VGA +#define VIDEO_ENCODER_VGA 32 +#endif + +#define MODE_PAL "TV-PAL" +#define MODE_NTSC "TV-NTSC" +#define MODE_VGA "TV-VGA" + +extern int fs453_ioctl(unsigned int cmd, void *arg); +#endif + +struct mx2fb_info mx2fbi_bg = { + .type = MX2FB_TYPE_BG, + .id = "DISP0 BG", + .registered = 0, +}; + +static struct mx2fb_info mx2fbi_gw = { + .type = MX2FB_TYPE_GW, + .id = "DISP0 FG", + .registered = 0, +}; + +/*! Current graphic window information */ +static struct fb_gwinfo g_gwinfo = { + .enabled = 0, + .alpha_value = 255, + .ck_enabled = 0, + .ck_red = 0, + .ck_green = 0, + .ck_blue = 0, + .xpos = 0, + .ypos = 0, +}; + +/*! + * @brief Framebuffer information structures. + * There are up to 3 framebuffers: background, TVout, and graphic window. + * If graphic window is configured, it must be the last framebuffer. + */ +static struct fb_info mx2fb_info[] = { + {.par = &mx2fbi_bg}, + {.par = &mx2fbi_gw}, +}; + +/*! + * @brief This structure contains pointers to the power management + * callback functions. + */ +static struct platform_driver mx2fb_driver = { + .driver = { + .name = "mxc_sdc_fb", + .owner = THIS_MODULE, + .bus = &platform_bus_type, + }, + .probe = mx2fb_probe, + .suspend = mx2fb_suspend, + .resume = mx2fb_resume, +}; + +/*! + * @brief Framebuffer file operations + */ +static struct fb_ops mx2fb_ops = { + .owner = THIS_MODULE, + .fb_check_var = mx2fb_check_var, + .fb_set_par = mx2fb_set_par, + .fb_setcolreg = mx2fb_setcolreg, + .fb_blank = mx2fb_blank, + .fb_pan_display = mx2fb_pan_display, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, + //.fb_cursor = soft_cursor, + .fb_ioctl = mx2fb_ioctl, +}; + +/*! + * @brief Validates a var passed in. + * + * @param var Frame buffer variable screen structure + * @param info Frame buffer structure that represents a single frame buffer + * + * @return Negative errno on error, or zero on success. + * + * Checks to see if the hardware supports the state requested by var passed + * in. This function does not alter the hardware state! If the var passed in + * is slightly off by what the hardware can support then we alter the var + * PASSED in to what we can do. If the hardware doesn't support mode change + * a -EINVAL will be returned by the upper layers. + * + */ +static int mx2fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) +{ + unsigned long htotal, vtotal; + + if (var->xres_virtual < var->xres) + var->xres_virtual = var->xres; + if (var->yres_virtual < var->yres) + var->yres_virtual = var->yres; + + if (var->xoffset < 0) + var->xoffset = 0; + + if (var->yoffset < 0) + var->yoffset = 0; + + if (var->xoffset + info->var.xres > info->var.xres_virtual) + var->xoffset = info->var.xres_virtual - info->var.xres; + + if (var->yoffset + info->var.yres > info->var.yres_virtual) + var->yoffset = info->var.yres_virtual - info->var.yres; + + if ((var->bits_per_pixel != 32) && (var->bits_per_pixel != 24) && + (var->bits_per_pixel != 16)) { + var->bits_per_pixel = default_bpp; + } + + switch (var->bits_per_pixel) { + case 16: + var->red.length = 5; + var->red.offset = 11; + var->red.msb_right = 0; + + var->green.length = 6; + var->green.offset = 5; + var->green.msb_right = 0; + + var->blue.length = 5; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 0; + var->transp.offset = 0; + var->transp.msb_right = 0; + break; + case 24: + var->red.length = 8; + var->red.offset = 16; + var->red.msb_right = 0; + + var->green.length = 8; + var->green.offset = 8; + var->green.msb_right = 0; + + var->blue.length = 8; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 0; + var->transp.offset = 0; + var->transp.msb_right = 0; + break; + case 32: + var->red.length = 8; + var->red.offset = 16; + var->red.msb_right = 0; + + var->green.length = 8; + var->green.offset = 8; + var->green.msb_right = 0; + + var->blue.length = 8; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 8; + var->transp.offset = 24; + var->transp.msb_right = 0; + break; + } + + if (var->pixclock < 1000) { + htotal = var->xres + var->right_margin + var->hsync_len + + var->left_margin; + vtotal = var->yres + var->lower_margin + var->vsync_len + + var->upper_margin; + var->pixclock = (vtotal * htotal * 6UL) / 100UL; + var->pixclock = KHZ2PICOS(var->pixclock); + dev_dbg(info->device, + "pixclock set for 60Hz refresh = %u ps\n", + var->pixclock); + } + + var->height = -1; + var->width = -1; + var->grayscale = 0; + + /* Copy nonstd field to/from sync for fbset usage */ + var->sync |= var->nonstd; + var->nonstd |= var->sync; + + return 0; +} + +/*! + * @brief Alters the hardware state. + * + * @param info Frame buffer structure that represents a single frame buffer + * + * @return Zero on success others on failure + * + * Using the fb_var_screeninfo in fb_info we set the resolution of this + * particular framebuffer. This function alters the fb_fix_screeninfo stored + * in fb_info. It doesn't not alter var in fb_info since we are using that + * data. This means we depend on the data in var inside fb_info to be + * supported by the hardware. mx2fb_check_var is always called before + * mx2fb_set_par to ensure this. + */ +static int mx2fb_set_par(struct fb_info *info) +{ + unsigned long len; + struct mx2fb_info *mx2fbi = (struct mx2fb_info *)info->par; + + _set_fix(info); + + len = info->var.yres_virtual * info->fix.line_length; + if (len > info->fix.smem_len) { + if (info->fix.smem_start) + _unmap_video_memory(info); + + /* Memory allocation for framebuffer */ + if (_map_video_memory(info)) { + dev_err(info->device, "Unable to allocate fb memory\n"); + return -ENOMEM; + } + } + + _update_lcdc(info); + if (info->fbops->fb_blank) + info->fbops->fb_blank(mx2fbi->blank, info); + + return 0; +} + +/*! + * @brief Sets a color register. + * + * @param regno Which register in the CLUT we are programming + * @param red The red value which can be up to 16 bits wide + * @param green The green value which can be up to 16 bits wide + * @param blue The blue value which can be up to 16 bits wide. + * @param transp If supported the alpha value which can be up to + * 16 bits wide. + * @param info Frame buffer info structure + * + * @return Negative errno on error, or zero on success. + * + * Set a single color register. The values supplied have a 16 bit magnitude + * which needs to be scaled in this function for the hardware. Things to take + * into consideration are how many color registers, if any, are supported with + * the current color visual. With truecolor mode no color palettes are + * supported. Here a psuedo palette is created which we store the value in + * pseudo_palette in struct fb_info. For pseudocolor mode we have a limited + * color palette. + */ +static int mx2fb_setcolreg(unsigned regno, unsigned red, unsigned green, + unsigned blue, unsigned transp, struct fb_info *info) +{ + int ret = 1; + + /* + * If greyscale is true, then we convert the RGB value + * to greyscale no matter what visual we are using. + */ + if (info->var.grayscale) + red = green = blue = (19595 * red + 38470 * green + + 7471 * blue) >> 16; + switch (info->fix.visual) { + case FB_VISUAL_TRUECOLOR: + /* + * 16-bit True Colour. We encode the RGB value + * according to the RGB bitfield information. + */ + if (regno < 16) { + u32 *pal = info->pseudo_palette; + u32 v; + +#define CNVT_TOHW(val,width) ((((val)<<(width))+0x7FFF-(val))>>16) + red = CNVT_TOHW(red, info->var.red.length); + green = CNVT_TOHW(green, info->var.green.length); + blue = CNVT_TOHW(blue, info->var.blue.length); + transp = CNVT_TOHW(transp, info->var.transp.length); +#undef CNVT_TOHW + + v = (red << info->var.red.offset) | + (green << info->var.green.offset) | + (blue << info->var.blue.offset) | + (transp << info->var.transp.offset); + + pal[regno] = v; + ret = 0; + } + break; + case FB_VISUAL_STATIC_PSEUDOCOLOR: + case FB_VISUAL_PSEUDOCOLOR: + break; + } + + return ret; +} + +/*! + * @brief Pans the display. + * + * @param var Frame buffer variable screen structure + * @param info Frame buffer structure that represents a single frame buffer + * + * @return Negative errno on error, or zero on success. + * + * Pan (or wrap, depending on the `vmode' field) the display using the + * 'xoffset' and 'yoffset' fields of the 'var' structure. If the values + * don't fit, return -EINVAL. + */ +static int mx2fb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + if ((info->var.xoffset == var->xoffset) && + (info->var.yoffset == var->yoffset)) { + return 0; /* No change, do nothing */ + } + + if (var->xoffset < 0 || var->yoffset < 0 + || var->xoffset + info->var.xres > info->var.xres_virtual + || var->yoffset + info->var.yres > info->var.yres_virtual) + return -EINVAL; + + info->var.xoffset = var->xoffset; + info->var.yoffset = var->yoffset; + + _update_lcdc(info); + + if (var->vmode & FB_VMODE_YWRAP) { + info->var.vmode |= FB_VMODE_YWRAP; + } else { + info->var.vmode &= ~FB_VMODE_YWRAP; + } + + return 0; +} + +/*! + * @brief Blanks the display. + * + * @param blank_mode The blank mode we want. + * @param info Frame buffer structure that represents a single frame buffer + * + * @return Negative errno on error, or zero on success. + * + * Blank the screen if blank_mode != 0, else unblank. Return 0 if blanking + * succeeded, != 0 if un-/blanking failed. + * blank_mode == 2: suspend vsync + * blank_mode == 3: suspend hsync + * blank_mode == 4: powerdown + */ +static int mx2fb_blank(int blank_mode, struct fb_info *info) +{ + struct mx2fb_info *mx2fbi = (struct mx2fb_info *)info->par; + + dev_dbg(info->device, "blank mode = %d\n", blank_mode); + + mx2fbi->blank = blank_mode; + + switch (blank_mode) { + case FB_BLANK_POWERDOWN: + case FB_BLANK_VSYNC_SUSPEND: + case FB_BLANK_HSYNC_SUSPEND: + case FB_BLANK_NORMAL: + _disable_lcdc(info); + break; + case FB_BLANK_UNBLANK: + _enable_lcdc(info); + break; + } + + return 0; +} + +/*! + * @brief Ioctl function to support customized ioctl operations. + * + * @param info Framebuffer structure that represents a single frame buffer + * @param cmd The command number + * @param arg Argument which depends on cmd + * + * @return Negative errno on error, or zero on success. + */ +static int mx2fb_ioctl(struct fb_info *info, unsigned int cmd, + unsigned long arg) +{ + struct mx2fb_info *mx2fbi = (struct mx2fb_info *)info->par; + struct mx2fb_gbl_alpha ga; + struct mx2fb_color_key ck; + + switch (cmd) { + case MX2FB_SET_GBL_ALPHA: + if (mx2fbi->type != MX2FB_TYPE_GW) + return -ENODEV; + + if (!arg) + return -EINVAL; + + /* set graphic window information */ + if (copy_from_user((void *)&ga, (void *)arg, sizeof(ga))) + return -EFAULT; + + g_gwinfo.alpha_value = ga.alpha; + + if (g_gwinfo.enabled) + _enable_graphic_window(info); + else + _disable_graphic_window(info); + break; + case MX2FB_SET_CLR_KEY: + if (mx2fbi->type != MX2FB_TYPE_GW) + return -ENODEV; + + if (!arg) + return -EINVAL; + + /* set graphic window information */ + if (copy_from_user((void *)&ck, (void *)arg, sizeof(ck))) + return -EFAULT; + + g_gwinfo.ck_enabled = ck.enable; + g_gwinfo.ck_red = (ck.color_key & 0x003F0000) >> 16; + g_gwinfo.ck_green = (ck.color_key & 0x00003F00) >> 8; + g_gwinfo.ck_blue = ck.color_key & 0x0000003F; + + if (g_gwinfo.enabled) + _enable_graphic_window(info); + else + _disable_graphic_window(info); + break; + case FBIOGET_GWINFO: + if (mx2fbi->type != MX2FB_TYPE_GW) + return -ENODEV; + + if (!arg) + return -EINVAL; + + /* get graphic window information */ + if (copy_to_user((void *)arg, (void *)&g_gwinfo, + sizeof(g_gwinfo))) + return -EFAULT; + break; + case FBIOPUT_GWINFO: + if (mx2fbi->type != MX2FB_TYPE_GW) + return -ENODEV; + + if (!arg) + return -EINVAL; + + /* set graphic window information */ + if (copy_from_user((void *)&g_gwinfo, (void *)arg, + sizeof(g_gwinfo))) + return -EFAULT; + + if (g_gwinfo.enabled) + _enable_graphic_window(info); + else + _disable_graphic_window(info); + break; +#ifdef CONFIG_FB_MXC_TVOUT + case ENCODER_GET_CAPABILITIES:{ + int ret; + struct video_encoder_capability cap; + + if (mx2fbi->type != MX2FB_TYPE_BG) + return -ENODEV; + + ret = fs453_ioctl(cmd, &cap); + if (ret) + return ret; + + if (copy_to_user((void *)arg, &cap, sizeof(cap))) + return -EFAULT; + break; + } + case ENCODER_SET_NORM:{ + int ret; + unsigned long mode; + char *smode; + struct fb_var_screeninfo var; + + if (mx2fbi->type != MX2FB_TYPE_BG) + return -ENODEV; + + if (copy_from_user(&mode, (void *)arg, sizeof(mode))) + return -EFAULT; + if ((ret = fs453_ioctl(cmd, &mode))) + return ret; + + if (mode == VIDEO_ENCODER_PAL) + smode = MODE_PAL; + else if (mode == VIDEO_ENCODER_NTSC) + smode = MODE_NTSC; + else + smode = MODE_VGA; + + var = info->var; + var.nonstd = 0; + ret = fb_find_mode(&var, info, smode, mxcfb_modedb, + mxcfb_modedb_sz, NULL, default_bpp); + if ((ret != 1) && (ret != 2)) /* specified mode not found */ + return -ENODEV; + + info->var = var; + fb_mode = smode; + return mx2fb_set_par(info); + } + case ENCODER_SET_INPUT: + case ENCODER_SET_OUTPUT: + case ENCODER_ENABLE_OUTPUT:{ + unsigned long varg; + + if (mx2fbi->type != MX2FB_TYPE_BG) + return -ENODEV; + + if (copy_from_user(&varg, (void *)arg, sizeof(varg))) + return -EFAULT; + return fs453_ioctl(cmd, &varg); + } +#endif + default: + dev_dbg(info->device, "Unknown ioctl command (0x%08X)\n", cmd); + return -EINVAL; + } + + return 0; +} + +/*! + * @brief Set fixed framebuffer parameters based on variable settings. + * + * @param info framebuffer information pointer + * @return Negative errno on error, or zero on success. + */ +static void _set_fix(struct fb_info *info) +{ + struct fb_fix_screeninfo *fix = &info->fix; + struct fb_var_screeninfo *var = &info->var; + struct mx2fb_info *mx2fbi = (struct mx2fb_info *)info->par; + + strncpy(fix->id, mx2fbi->id, strlen(mx2fbi->id)); + fix->line_length = var->xres_virtual * var->bits_per_pixel / 8; + fix->type = FB_TYPE_PACKED_PIXELS; + fix->accel = FB_ACCEL_NONE; + fix->visual = FB_VISUAL_TRUECOLOR; + fix->xpanstep = 1; + fix->ypanstep = 1; +} + +/*! + * @brief Initialize framebuffer information structure. + * + * @param info framebuffer information pointer + * @param pdev pointer to struct device + * @return Negative errno on error, or zero on success. + */ +static int __init _init_fbinfo(struct fb_info *info, + struct platform_device *pdev) +{ + struct mx2fb_info *mx2fbi = (struct mx2fb_info *)info->par; + + info->device = &pdev->dev; + info->var.activate = FB_ACTIVATE_NOW; + info->fbops = &mx2fb_ops; + info->flags = FBINFO_FLAG_DEFAULT; + info->pseudo_palette = &mx2fbi->pseudo_palette; + + /* Allocate colormap */ + fb_alloc_cmap(&info->cmap, 16, 0); + + return 0; +} + +/*! + * @brief Install framebuffer into the system. + * + * @param info framebuffer information pointer + * @param pdev pointer to struct device + * @return Negative errno on error, or zero on success. + */ +static int __init _install_fb(struct fb_info *info, + struct platform_device *pdev) +{ + struct mx2fb_info *mx2fbi = (struct mx2fb_info *)info->par; + + if (_init_fbinfo(info, pdev)) + return -EINVAL; + + if (fb_mode == 0) + fb_mode = pdev->dev.platform_data; + + if (!fb_find_mode(&info->var, info, fb_mode, mxcfb_modedb, + mxcfb_modedb_sz, NULL, default_bpp)) { + fb_dealloc_cmap(&info->cmap); + return -EBUSY; + } + + /* Default Y virtual size is 2x panel size */ + /* info->var.yres_virtual = info->var.yres << 1; */ + + if (mx2fbi->type == MX2FB_TYPE_GW) + mx2fbi->blank = FB_BLANK_NORMAL; + else + mx2fbi->blank = FB_BLANK_UNBLANK; + + if (mx2fb_set_par(info)) { + fb_dealloc_cmap(&info->cmap); + return -EINVAL; + } + + if (register_framebuffer(info) < 0) { + _unmap_video_memory(info); + fb_dealloc_cmap(&info->cmap); + return -EINVAL; + } + + mx2fbi->registered = 1; + dev_info(info->device, "fb%d: %s fb device registered successfully.\n", + info->node, info->fix.id); + + return 0; +} + +/*! + * @brief Uninstall framebuffer from the system. + * + * @param info framebuffer information pointer + */ +static void __exit _uninstall_fb(struct fb_info *info) +{ + struct mx2fb_info *mx2fbi = (struct mx2fb_info *)info->par; + + if (!mx2fbi->registered) + return; + + unregister_framebuffer(info); + _unmap_video_memory(info); + if (&info->cmap) + fb_dealloc_cmap(&info->cmap); + + mx2fbi->registered = 0; +} + +/*! + * @brief Allocate memory for framebuffer. + * + * @param info framebuffer information pointer + * @return Negative errno on error, or zero on success. + */ +static int _map_video_memory(struct fb_info *info) +{ + info->fix.smem_len = info->fix.line_length * info->var.yres_virtual; + info->screen_base = dma_alloc_coherent(0, + info->fix.smem_len, + (dma_addr_t *) & info->fix. + smem_start, + GFP_DMA | GFP_KERNEL); + + if (info->screen_base == 0) { + dev_err(info->device, "Unable to allocate fb memory\n"); + return -EBUSY; + } + dev_dbg(info->device, "Allocated fb @ paddr=0x%08lX, size=%d.\n", + info->fix.smem_start, info->fix.smem_len); + + info->screen_size = info->fix.smem_len; + + /* Clear the screen */ + memset((char *)info->screen_base, 0, info->fix.smem_len); + + return 0; +} + +/*! + * @brief Release memory for framebuffer. + * @param info framebuffer information pointer + */ +static void _unmap_video_memory(struct fb_info *info) +{ + dma_free_coherent(0, info->fix.smem_len, info->screen_base, + (dma_addr_t) info->fix.smem_start); + + info->screen_base = 0; + info->fix.smem_start = 0; + info->fix.smem_len = 0; +} + +/*! + * @brief Enable LCD controller. + * @param info framebuffer information pointer + */ +static void _enable_lcdc(struct fb_info *info) +{ + static int first_enable = 1; + struct mx2fb_info *mx2fbi = (struct mx2fb_info *)info->par; + + /* + * Graphic window can only be enabled while the HCLK to the LCDC + * is disabled. Once enabled it can subsequently be disabled and + * enabled without turning off the HCLK. + * The graphic window is enabled and then disabled here. So next + * time to enable graphic window the HCLK to LCDC does not need + * to be disabled, and the flicker (due to disabling of HCLK to + * LCDC) is avoided. + */ + if (first_enable) { + _enable_graphic_window(info); + _disable_graphic_window(info); + first_enable = 0; + } + + if (mx2fbi->type == MX2FB_TYPE_GW) + _enable_graphic_window(info); + else if (!fb_enabled) { + clk_enable(lcdc_clk); + gpio_lcdc_active(); + board_power_lcd(1); + fb_enabled++; +#ifdef CONFIG_FB_MXC_TVOUT + if (fb_mode) { + unsigned long mode = 0; + + if (strcmp(fb_mode, MODE_VGA) == 0) + mode = VIDEO_ENCODER_VGA; + else if (strcmp(fb_mode, MODE_NTSC) == 0) + mode = VIDEO_ENCODER_NTSC; + else if (strcmp(fb_mode, MODE_PAL) == 0) + mode = VIDEO_ENCODER_PAL; + if (mode) + fs453_ioctl(ENCODER_SET_NORM, &mode); + } +#endif + } +} + +/*! + * @brief Disable LCD controller. + * @param info framebuffer information pointer + */ +static void _disable_lcdc(struct fb_info *info) +{ + struct mx2fb_info *mx2fbi = (struct mx2fb_info *)info->par; + + if (mx2fbi->type == MX2FB_TYPE_GW) + _disable_graphic_window(info); + else { + if (fb_enabled) { + gpio_lcdc_inactive(); + board_power_lcd(0); + clk_disable(lcdc_clk); + fb_enabled = 0; + } +#ifdef CONFIG_FB_MXC_TVOUT + if (fb_mode) { + int enable = 0; + + if ((strcmp(fb_mode, MODE_VGA) == 0) + || (strcmp(fb_mode, MODE_NTSC) == 0) + || (strcmp(fb_mode, MODE_PAL) == 0)) + fs453_ioctl(ENCODER_ENABLE_OUTPUT, &enable); + } +#endif + } +} + +/*! + * @brief Enable graphic window. + * @param info framebuffer information pointer + */ +static void _enable_graphic_window(struct fb_info *info) +{ + struct fb_var_screeninfo *var = &info->var; + + g_gwinfo.enabled = 1; + + g_gwinfo.base = (var->yoffset * var->xres_virtual + var->xoffset); + g_gwinfo.base *= (var->bits_per_pixel) / 8; + g_gwinfo.base += info->fix.smem_start; + + g_gwinfo.xres = var->xres; + g_gwinfo.yres = var->yres; + g_gwinfo.xres_virtual = var->xres_virtual; + + mx2_gw_set(&g_gwinfo); +} + +/*! + * @brief Disable graphic window. + * @param info framebuffer information pointer + */ +static void _disable_graphic_window(struct fb_info *info) +{ + unsigned long i = 0; + + g_gwinfo.enabled = 0; + + /* + * Set alpha value to zero and reduce gw size, otherwise the graphic + * window will not be able to be enabled again. + */ + __raw_writel(__raw_readl(LCDC_REG(LCDC_LGWCR)) & 0x00FFFFFF, + LCDC_REG(LCDC_LGWCR)); + __raw_writel(((16 >> 4) << 20) + 16, LCDC_REG(LCDC_LGWSR)); + while (i < 1000) + i++; + + /* Now disable graphic window */ + __raw_writel(__raw_readl(LCDC_REG(LCDC_LGWCR)) & ~0x00400000, + LCDC_REG(LCDC_LGWCR)); + + dev_dbg(info->device, "Graphic window disabled.\n"); +} + +/*! + * @brief Setup graphic window properties. + * @param gwinfo graphic window information pointer + */ +void mx2_gw_set(struct fb_gwinfo *gwinfo) +{ + int width, height, xpos, ypos; + int width_bg, height_bg; + unsigned long lgwcr = 0x00400000; /* Graphic window control register */ + + if (!gwinfo->enabled) { + _disable_graphic_window(0); + return; + } + + /* Graphic window start address register */ + __raw_writel(gwinfo->base, LCDC_REG(LCDC_LGWSAR)); + + /* + * The graphic window width, height, x position and y position + * must be synced up width the background window, otherwise there + * may be flickering. + */ + width_bg = (__raw_readl(LCDC_REG(LCDC_LSR)) & 0x03F00000) >> 16; + height_bg = __raw_readl(LCDC_REG(LCDC_LSR)) & 0x000003FF; + + width = (gwinfo->xres > width_bg) ? width_bg : gwinfo->xres; + height = (gwinfo->yres > height_bg) ? height_bg : gwinfo->yres; + + xpos = gwinfo->xpos; + ypos = gwinfo->ypos; + + if (xpos + width > width_bg) + xpos = width_bg - width; + if (ypos + height > height_bg) + ypos = height_bg - height; + + /* Graphic window size register */ + __raw_writel(((width >> 4) << 20) + height, LCDC_REG(LCDC_LGWSR)); + + /* Graphic window virtual page width register */ + __raw_writel(gwinfo->xres_virtual >> 1, LCDC_REG(LCDC_LGWVPWR)); + + /* Graphic window position register */ + __raw_writel(((xpos & 0x000003FF) << 16) | (ypos & 0x000003FF), + LCDC_REG(LCDC_LGWPR)); + + /* Graphic window panning offset register */ + __raw_writel(0, LCDC_REG(LCDC_LGWPOR)); + + /* Graphic window DMA control register */ + if (cpu_is_mx27_rev(CHIP_REV_2_0) > 0) + __raw_writel(0x00040060, LCDC_REG(LCDC_LGWDCR)); + else + __raw_writel(0x00020010, LCDC_REG(LCDC_LGWDCR)); + + /* Graphic window control register */ + lgwcr |= (gwinfo->alpha_value & 0x000000FF) << 24; + lgwcr |= gwinfo->ck_enabled ? 0x00800000 : 0; + lgwcr |= gwinfo->vs_reversed ? 0x00200000 : 0; + + /* + * Color keying value + * Todo: assume always use RGB565 + */ + lgwcr |= (gwinfo->ck_red & 0x0000003F) << 12; + lgwcr |= (gwinfo->ck_green & 0x0000003F) << 6; + lgwcr |= gwinfo->ck_blue & 0x0000003F; + + __raw_writel(lgwcr, LCDC_REG(LCDC_LGWCR)); + + pr_debug("Graphic window enabled.\n"); +} + +/*! + * @brief Update LCDC registers + * @param info framebuffer information pointer + */ +static void _update_lcdc(struct fb_info *info) +{ + unsigned long base; + unsigned long perclk3, pcd, pcr; + struct fb_var_screeninfo *var = &info->var; + struct mx2fb_info *mx2fbi = (struct mx2fb_info *)info->par; + + if (mx2fbi->type == MX2FB_TYPE_GW) { + _enable_graphic_window(info); + return; + } + + base = (var->yoffset * var->xres_virtual + var->xoffset); + base *= (var->bits_per_pixel) / 8; + base += info->fix.smem_start; + + /* Screen start address register */ + __raw_writel(base, LCDC_REG(LCDC_LSSAR)); + + /* Size register */ + dev_dbg(info->device, "xres = %d, yres = %d\n", + info->var.xres, info->var.yres); + __raw_writel(((info->var.xres >> 4) << 20) + info->var.yres, + LCDC_REG(LCDC_LSR)); + + /* Virtual page width register */ + __raw_writel(info->var.xres_virtual >> 1, LCDC_REG(LCDC_LVPWR)); + + /* To setup LCDC pixel clock */ + perclk3 = clk_round_rate(lcdc_clk, 134000000); + if (clk_set_rate(lcdc_clk, perclk3)) { + printk(KERN_INFO "mx2fb: Unable to set clock to %lu\n", + perclk3); + perclk3 = clk_get_rate(lcdc_clk); + } + + /* Calculate pixel clock divider, and round to the nearest integer */ + pcd = (perclk3 * 8 / (PICOS2KHZ(var->pixclock) * 1000UL) + 4) / 8; + if (--pcd > 0x3F) + pcd = 0x3F; + + /* Panel configuration register */ + pcr = 0xFA008B80 | pcd; + pcr |= (var->sync & FB_SYNC_CLK_INVERT) ? 0x01000000 : 0; + pcr |= (var->sync & FB_SYNC_SHARP_MODE) ? 0x00000040 : 0; + pcr |= (var->sync & FB_SYNC_OE_ACT_HIGH) ? 0 : 0x00100000; + __raw_writel(pcr, LCDC_REG(LCDC_LPCR)); + + /* Horizontal and vertical configuration register */ + __raw_writel(((var->hsync_len - 1) << 26) + + ((var->right_margin - 1) << 8) + + (var->left_margin - 3), LCDC_REG(LCDC_LHCR)); + __raw_writel((var->vsync_len << 26) + + (var->lower_margin << 8) + + var->upper_margin, LCDC_REG(LCDC_LVCR)); + + /* Sharp configuration register */ + __raw_writel(0x00120300, LCDC_REG(LCDC_LSCR)); + + /* Refresh mode control reigster */ + __raw_writel(0x00000000, LCDC_REG(LCDC_LRMCR)); + + /* DMA control register */ + if (cpu_is_mx27_rev(CHIP_REV_2_0) > 0) + __raw_writel(0x00040060, LCDC_REG(LCDC_LDCR)); + else + __raw_writel(0x00020010, LCDC_REG(LCDC_LDCR)); +} + +/*! + * @brief Set LCD brightness + * @param level brightness level + */ +void mx2fb_set_brightness(uint8_t level) +{ + /* Set LCDC PWM contract control register */ + __raw_writel(0x00A90300 | level, LCDC_REG(LCDC_LPCCR)); +} + +EXPORT_SYMBOL(mx2fb_set_brightness); + +/* + * @brief LCDC interrupt handler + */ +static irqreturn_t mx2fb_isr(int irq, void *dev_id) +{ + struct fb_event event; + unsigned long status = __raw_readl(LCDC_REG(LCDC_LISR)); + + if (status & MX2FB_INT_EOF) { + event.info = &mx2fb_info[0]; + atomic_notifier_call_chain(&mx2fb_notifier_list, + FB_EVENT_MXC_EOF, &event); + } + + if (status & MX2FB_INT_GW_EOF) { + event.info = &mx2fb_info[1]; + atomic_notifier_call_chain(&mx2fb_notifier_list, + FB_EVENT_MXC_EOF, &event); + } + + return IRQ_HANDLED; +} + +/*! + * @brief Config and request LCDC interrupt + */ +static void _request_irq(void) +{ + unsigned long status; + unsigned long flags; + + /* Read to clear the status */ + status = __raw_readl(LCDC_REG(LCDC_LISR)); + + if (request_irq(INT_LCDC, mx2fb_isr, 0, "LCDC", 0)) + pr_info("Request LCDC IRQ failed.\n"); + else { + spin_lock_irqsave(&mx2fb_notifier_list.lock, flags); + + /* Enable interrupt in case client has registered */ + if (mx2fb_notifier_list.head != NULL) { + unsigned long status; + unsigned long ints = MX2FB_INT_EOF; + + ints |= MX2FB_INT_GW_EOF; + + /* Read to clear the status */ + status = __raw_readl(LCDC_REG(LCDC_LISR)); + + /* Configure interrupt condition for EOF */ + __raw_writel(0x0, LCDC_REG(LCDC_LICR)); + + /* Enable EOF and graphic window EOF interrupt */ + __raw_writel(ints, LCDC_REG(LCDC_LIER)); + } + + spin_unlock_irqrestore(&mx2fb_notifier_list.lock, flags); + } +} + +/*! + * @brief Free LCDC interrupt handler + */ +static void _free_irq(void) +{ + /* Disable all LCDC interrupt */ + __raw_writel(0x0, LCDC_REG(LCDC_LIER)); + + free_irq(INT_LCDC, 0); +} + +/*! + * @brief Register a client notifier + * @param nb notifier block to callback on events + */ +int mx2fb_register_client(struct notifier_block *nb) +{ + unsigned long flags; + int ret; + + ret = atomic_notifier_chain_register(&mx2fb_notifier_list, nb); + + spin_lock_irqsave(&mx2fb_notifier_list.lock, flags); + + /* Enable interrupt in case client has registered */ + if (mx2fb_notifier_list.head != NULL) { + unsigned long status; + unsigned long ints = MX2FB_INT_EOF; + + ints |= MX2FB_INT_GW_EOF; + + /* Read to clear the status */ + status = __raw_readl(LCDC_REG(LCDC_LISR)); + + /* Configure interrupt condition for EOF */ + __raw_writel(0x0, LCDC_REG(LCDC_LICR)); + + /* Enable EOF and graphic window EOF interrupt */ + __raw_writel(ints, LCDC_REG(LCDC_LIER)); + } + + spin_unlock_irqrestore(&mx2fb_notifier_list.lock, flags); + + return ret; +} + +/*! + * @brief Unregister a client notifier + * @param nb notifier block to callback on events + */ +int mx2fb_unregister_client(struct notifier_block *nb) +{ + unsigned long flags; + int ret; + + ret = atomic_notifier_chain_unregister(&mx2fb_notifier_list, nb); + + spin_lock_irqsave(&mx2fb_notifier_list.lock, flags); + + /* Mask interrupt in case no client registered */ + if (mx2fb_notifier_list.head == NULL) + __raw_writel(0x0, LCDC_REG(LCDC_LIER)); + + spin_unlock_irqrestore(&mx2fb_notifier_list.lock, flags); + + return ret; +} + +#ifdef CONFIG_PM +/* + * Power management hooks. Note that we won't be called from IRQ context, + * unlike the blank functions above, so we may sleep. + */ + +/*! + * @brief Suspends the framebuffer and blanks the screen. + * Power management support + */ +static int mx2fb_suspend(struct platform_device *pdev, pm_message_t state) +{ + _disable_lcdc(&mx2fb_info[0]); + + return 0; +} + +/*! + * @brief Resumes the framebuffer and unblanks the screen. + * Power management support + */ +static int mx2fb_resume(struct platform_device *pdev) +{ + _enable_lcdc(&mx2fb_info[0]); + + return 0; +} + +#endif /* CONFIG_PM */ + +/*! + * @brief Probe routine for the framebuffer driver. It is called during the + * driver binding process. + * + * @return Appropriate error code to the kernel common code + */ +static int mx2fb_probe(struct platform_device *pdev) +{ + int ret, i; + + lcdc_clk = clk_get(&pdev->dev, "lcdc_clk"); + + for (i = 0; i < sizeof(mx2fb_info) / sizeof(struct fb_info); i++) { + if ((ret = _install_fb(&mx2fb_info[i], pdev))) { + dev_err(&pdev->dev, + "Failed to register framebuffer %d\n", i); + return ret; + } + } + _request_irq(); + + return 0; +} + +/*! + * @brief Initialization + */ +int __init mx2fb_init(void) +{ + /* + * For kernel boot options (in 'video=xxxfb:<options>' format) + */ +#ifndef MODULE + { + char *option; + + if (fb_get_options("mxcfb", &option)) + return -ENODEV; + mx2fb_setup(option); + } +#endif + return platform_driver_register(&mx2fb_driver); +} + +/*! + * @brief Cleanup + */ +void __exit mx2fb_exit(void) +{ + int i; + + _free_irq(); + for (i = sizeof(mx2fb_info) / sizeof(struct fb_info); i > 0; i--) + _uninstall_fb(&mx2fb_info[i - 1]); + + platform_driver_unregister(&mx2fb_driver); +} + +#ifndef MODULE +/*! + * @brief Setup + * Parse user specified options + * Example: video=mxcfb:240x320,bpp=16,Sharp-QVGA + */ +static int __init mx2fb_setup(char *options) +{ + char *opt; + + if (!options || !*options) + return 0; + + while ((opt = strsep(&options, ",")) != NULL) { + if (!*opt) + continue; + + if (!strncmp(opt, "bpp=", 4)) + default_bpp = simple_strtoul(opt + 4, NULL, 0); + else + fb_mode = opt; + } + + return 0; +} +#endif + +/* Modularization */ +module_init(mx2fb_init); +module_exit(mx2fb_exit); + +EXPORT_SYMBOL(mx2_gw_set); +EXPORT_SYMBOL(mx2fb_register_client); +EXPORT_SYMBOL(mx2fb_unregister_client); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MX2 framebuffer driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/mxc/mx2fb.h b/drivers/video/mxc/mx2fb.h new file mode 100644 index 000000000000..ed20d78289ce --- /dev/null +++ b/drivers/video/mxc/mx2fb.h @@ -0,0 +1,141 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mx2fb.h + * + * @brief Header file for the MX27 Frame buffer + * + * @ingroup Framebuffer + */ +#ifndef __MX2FB_H__ +#define __MX2FB_H__ + +/*! @brief MX27 LCDC graphic window information */ +struct fb_gwinfo { + /*! Non-zero if graphic window is enabled */ + __u32 enabled; + + /* The fields below are valid only when graphic window is enabled */ + + /*! Graphic window alpha value from 0 to 255 */ + __u32 alpha_value; + + /*! Non-zero if graphic window color keying is enabled. */ + __u32 ck_enabled; + + /* + * The fields ck_red, ck_green and ck_blue are valid only when + * graphic window and the color keying are enabled. They are the + * color component of graphic window color keying. + */ + + /*! Color keying red component */ + __u32 ck_red; + + /*! Color keying green component */ + __u32 ck_green; + + /*! Color keying blue component */ + __u32 ck_blue; + + /*! Graphic window x position */ + __u32 xpos; + + /*! Graphic window y position */ + __u32 ypos; + + /*! Non-zero if graphic window vertical scan in reverse direction. */ + __u32 vs_reversed; + + /* + * The following fields are valid for FBIOGET_GWINFO and + * mx2_gw_set(). FBIOPUT_GWINFO ignores these fields. + */ + __u32 base; /* Graphic window start address */ + __u32 xres; /* Visible x resolution */ + __u32 yres; /* Visible y resolution */ + __u32 xres_virtual; /* Virtual x resolution */ +}; + +/* 0x46E0-0x46FF are reserved for MX27 */ +#define FBIOGET_GWINFO 0x46E0 /*!< Get graphic window information */ +#define FBIOPUT_GWINFO 0x46E1 /*!< Set graphic window information */ + +struct mx2fb_gbl_alpha { + int enable; + int alpha; +}; + +struct mx2fb_color_key { + int enable; + __u32 color_key; +}; + +#define MX2FB_SET_GBL_ALPHA _IOW('M', 0, struct mx2fb_gbl_alpha) +#define MX2FB_SET_CLR_KEY _IOW('M', 1, struct mx2fb_color_key) +#define MX2FB_WAIT_FOR_VSYNC _IOW('F', 0x20, u_int32_t) + +#ifdef __KERNEL__ + +/* + * LCDC register definitions + */ +#define LCDC_LSSAR 0x00 +#define LCDC_LSR 0x04 +#define LCDC_LVPWR 0x08 +#define LCDC_LCPR 0x0C +#define LCDC_LCWHBR 0x10 +#define LCDC_LCCMR 0x14 +#define LCDC_LPCR 0x18 +#define LCDC_LHCR 0x1C +#define LCDC_LVCR 0x20 +#define LCDC_LPOR 0x24 +#define LCDC_LSCR 0x28 +#define LCDC_LPCCR 0x2C +#define LCDC_LDCR 0x30 +#define LCDC_LRMCR 0x34 +#define LCDC_LICR 0x38 +#define LCDC_LIER 0x3C +#define LCDC_LISR 0x40 +#define LCDC_LGWSAR 0x50 +#define LCDC_LGWSR 0x54 +#define LCDC_LGWVPWR 0x58 +#define LCDC_LGWPOR 0x5C +#define LCDC_LGWPR 0x60 +#define LCDC_LGWCR 0x64 +#define LCDC_LGWDCR 0x68 +#define LCDC_LAUSCR 0x80 +#define LCDC_LAUSCCR 0x84 + +#define LCDC_REG(reg) (IO_ADDRESS(LCDC_BASE_ADDR) + reg) + +#define MX2FB_INT_BOF 0x0001 /* Beginning of Frame */ +#define MX2FB_INT_EOF 0x0002 /* End of Frame */ +#define MX2FB_INT_ERR_RES 0x0004 /* Error Response */ +#define MX2FB_INT_UDR_ERR 0x0008 /* Under Run Error */ +#define MX2FB_INT_GW_BOF 0x0010 /* Graphic Window BOF */ +#define MX2FB_INT_GW_EOF 0x0020 /* Graphic Window EOF */ +#define MX2FB_INT_GW_ERR_RES 0x0040 /* Graphic Window ERR_RES */ +#define MX2FB_INT_GW_UDR_ERR 0x0080 /* Graphic Window UDR_ERR */ + +#define FB_EVENT_MXC_EOF 0x8001 /* End of Frame event */ + +int mx2fb_register_client(struct notifier_block *nb); +int mx2fb_unregister_client(struct notifier_block *nb); + +void mx2_gw_set(struct fb_gwinfo *gwinfo); + +#endif /* __KERNEL__ */ + +#endif /* __MX2FB_H__ */ diff --git a/drivers/video/mxc/mx2fb_epson.c b/drivers/video/mxc/mx2fb_epson.c new file mode 100644 index 000000000000..3a613e5dbb26 --- /dev/null +++ b/drivers/video/mxc/mx2fb_epson.c @@ -0,0 +1,2173 @@ +#error "please port this file to linux 2.6.18" +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*!@example slcdctest.c test driver using EPSON L2F50032T00 serial mode */ + +/** + * + * @defgroup SLCDC SLCDC driver + **/ +/**@{*/ +/** + * @file mx2fb_epson.c + * @brief slcdc driver source code + * + * This is the basic release for SLCDC driver, which is to support EPSON + * L2F50032T00 16bit seria mode5-6-5 and parallel mode. This driver acts as a + * standard character device, which can be dinamically loaded. + * For example, you can refer to slcdctest.c file. + * + * Modification History: + * 15,Dec,2003 Karen Kang + * + * 21,Feb,2006 Vasanthan S + * + * @bug + **/ + +#ifndef __KERNEL__ +# define __KERNEL__ +#endif + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/version.h> +#include <linux/init.h> +#include <linux/mm.h> +#include <linux/fs.h> +#include <linux/delay.h> +#include <linux/poll.h> +#include <linux/miscdevice.h> +#include <linux/string.h> +#include <linux/sched.h> +#include <linux/types.h> +#include <linux/fcntl.h> +#include <linux/interrupt.h> +#include <linux/ptrace.h> +#include <linux/ioport.h> +#include <linux/in.h> +#include <linux/slab.h> +#include <linux/vmalloc.h> +#include <linux/string.h> +#include <linux/init.h> +#include <linux/fb.h> +#include <linux/errno.h> +#include <linux/wait.h> +#include <linux/pm.h> + +#include "../console/fbcon.h" +#include "../../mxc/mempool/mempool.h" +#include <linux/timer.h> + +#include <asm/bitops.h> +#include <asm/io.h> +#include <asm/uaccess.h> /* get_user,copy_to_user */ +#include <asm/irq.h> +#include <asm/arch/hardware.h> +#include <asm/arch/irqs.h> +#include <asm/arch/mx27.h> +#include <asm/page.h> +#include <asm/pgtable.h> + +#include <linux/devfs_fs_kernel.h> + +#ifdef CONFIG_ARCH_MX2ADS +#include <asm/arch/mx2.h> +#endif + +#define MODULE_NAME "slcdc" + +#define DBMX_DEBUG 1 +#ifdef DBMX_DEBUG +#define TRACE(fmt, args...) \ + { \ + printk("\n %s:%d:%s:",__FILE__, __LINE__,__FUNCTION__); \ + printk(fmt, ## args);\ + } +#else +#define TRACE(fmt, args...) +#endif + +#define FAILED(fmt, args...) \ + { \ + printk("\n %s:%d:%s:",__FILE__, __LINE__,__FUNCTION__); \ + printk(fmt, ## args);\ + } + +#define INFO(fmt, args...) \ + { \ + printk("\n"); \ + printk(fmt, ## args);\ + } + +/*@brief definition for SLCDC_LCD_TXCONFIG */ +#define SLCDC_IMG_8L 0x00020000 + +/*@name SLCDC connection configuration + *@brief config macro for serial mode or parallel mode. SLCDC can operate in + * serial or parallel mode. This macro reflects the hardware configuration + * and is not a software configuration. + */ +/**@{*/ +#define SLCDC_SERIAL_MODE +/* #define SLCDC_PARALLE_MODE */ +/**@}*/ + +/*@name SLCDC IO MUX configuration + *@brief configuration macro for the pin mux detail. This tells which pins are + * configured for SLCDC. For more information refer processor data sheet. + * Most cases only one of the following macros should be enabled. + */ +/**@{*/ +#define SLCDC_MUX_SSI3_SLCDC2 /*!< 1. SLCDC is mux.ed with SSI3 */ + //#define SLCDC_MUX_SD_SLCDC1 /*!< 2. SLCDC is mux.ed with SD2 */ + //#define SLCDC_MUX_LCD_SLCDC1 /*!< 3. SLCDC is mux.ed with LCD */ +/**@}*/ + +/*@brief configuration macro which tells whether the interrupt should be used + * or not for SLCDC. SLCDC will provide an interrupt on a completed + * transfer, which can be used for refreshing the data. + */ +#define USING_INTERRUPT_SLCDC + +/** + *@name ioctl command macro definitions + *@brief definition for SLCDC ioctl cmd,these command definition is different + * for serial \nmode and paralel mode ,in this driver, we have not + * supported all these commands, \n please check the ioctl function for + * details. + **/ +/**@{*/ +/* definition for SLCDC cmd */ +/* same as Epson 10043 & 50052 */ +#ifdef SLCDC_SERIAL_MODE +#define SLCDC_CMD_DISON 0xaf00 /*!< 1. display on */ +#define SLCDC_CMD_DISOFF 0xae00 /*!< 2. display off */ +#define SLCDC_CMD_DISNOR 0xa600 /*!< 3. normal display */ +#define SLCDC_CMD_DISINV 0xa700 /*!< 4. inverse display */ +#define SLCDC_CMD_DISCTL 0xca00 /*!< 5. display control */ +#define SLCDC_CMD_SLPIN 0x9500 /*!< 10.sleep in */ +#define SLCDC_CMD_SLPOUT 0x9400 /*!< 11.sleep out */ +#define SLCDC_CMD_SD_PSET 0x7500 /*!< 12.page address set */ +#define SLCDC_CMD_SD_CSET 0x1500 /*!< 14.column address set */ + +#define SLCDC_CMD_DATCTL 0xbc /*!< 16.data scan direction, etc. */ +#define SLCDC_CMD_RAMWR 0x5c00 /*!< 17.writing to memory */ +#define SLCDC_CMD_PTLIN 0xa800 /*!< 19.partial display in */ +#define SLCDC_CMD_PTLOUT 0xa900 /*!< 20.partial display out */ + +/* value different from 10043 but same as 50052 */ +#define SLCDC_CMD_VOLCTR 0xc600 /*!< 25.Electronic volume control */ + +/* commands not found in 10043 but in 50052*/ +#define SLCDC_CMD_GCP64 0xcb00 /*!< 6. 64 grayscale pulse positon set */ +#define SLCDC_CMD_GCP16 0xcc00 /*!< 7. 16 grayscale pulse positon set */ +#define SLCDC_CMD_GSSET 0xcd00 /*!< 8. grayscale set */ +#define SLCDC_CMD_RAMRD 0x5d00 /*!< 18.memory read */ +#define SLCDC_CMD_ASCSET 0xaa00 /*!< 21.area scroll set */ +#define SLCDC_CMD_SCSTART 0xab00 /*!< 22.scroll start set */ +#define SLCDC_CMD_EPCTIN 0x6100 /*!< 26.Power IC control for EVR */ +#define SLCDC_CMD_EPCTOUT 0x6200 /*!< 27.Power IC control for EVR */ + +#else +#define SLCDC_CMD_DISON 0xaf /*!<1. display on */ +#define SLCDC_CMD_DISOFF 0xae /*!<2. display off */ +#define SLCDC_CMD_DISNOR 0xa6 /*!<3. normal display */ +#define SLCDC_CMD_DISINV 0xa7 /*!<4. inverse display */ +#define SLCDC_CMD_DISCTL 0xca /*!<5. display control */ +#define SLCDC_CMD_SLPIN 0x95 /*!<10.sleep in */ +#define SLCDC_CMD_SLPOUT 0x94 /*!<11.sleep out */ +#define SLCDC_CMD_SD_PSET 0x75 /*!<12.page address set */ +#define SLCDC_CMD_SD_CSET 0x15 /*!<14.column address set */ + +#define SLCDC_CMD_DATCTL 0xbc /*!<16.data scan direction, etc. */ +//#define SLCDC_CMD_DATCTL 0xbc00 /*!<16.data scan direction, etc.*/ +#define SLCDC_CMD_RAMWR 0x5c /*!<17.writing to memory */ +#define SLCDC_CMD_PTLIN 0xa8 /*!<19.partial display in */ +#define SLCDC_CMD_PTLOUT 0xa9 /*!<20.partial display out */ + +/* value different from 10043 but same as 50052 */ +#define SLCDC_CMD_VOLCTR 0xc6 /*!<25.Electronic volume control */ + +/* commands not found in 10043 but in 50052*/ +#define SLCDC_CMD_GCP64 0xcb /*!<6. 64 grayscale pulse positon set */ +#define SLCDC_CMD_GCP16 0xcc /*!<7. 16 grayscale pulse positon set */ +#define SLCDC_CMD_GSSET 0xcd /*!<8. grayscale set */ +#define SLCDC_CMD_RAMRD 0x5d /*!<18.memory read */ +#define SLCDC_CMD_ASCSET 0xaa /*!<21.area scroll set */ +#define SLCDC_CMD_SCSTART 0xab /*!<22.scroll start set */ +#define SLCDC_CMD_EPCTIN 0x61 /*!<26.Power IC control for EVR */ +#define SLCDC_CMD_EPCTOUT 0x62 /*!<27.Power IC control for EVR */ +#endif + +/**@}*/ + +#define SLCDC_IRQ INT_SLCDC +#define SLCDC_CMD_MEM_SIZE 4 +#define SLCDC_WIDTH 176 +#define SLCDC_HIGH 220 +#define SLCDC_BPP 16 +#define SLCDC_PIXEL_MEM_SIZE (SLCDC_WIDTH*SLCDC_HIGH*SLCDC_BPP)/8 +#define SLCDC_MEM_SIZE (SLCDC_WIDTH*SLCDC_HIGH) +#define _SLCDC_DATA_SIZE_ (SLCDC_PIXEL_MEM_SIZE + 32) +#define SLCDC_DATA_MEM_SIZE \ + ((unsigned)(PAGE_ALIGN(_SLCDC_DATA_SIZE_ + PAGE_SIZE * 2))) + +//bit mask definition in STAT/CTRL register +#define SLCDC_TRANSFER_BUSY 0x4 +#define SLCDC_TRANSFER_ERROR 0x10 + +//<<<<<< Global Variable +/*used for SLCDC data buffer */ +__attribute__ ((aligned(4))) +u16 *g_slcdc_dbuffer_address; + +/* physical address for SLCDC data buffer*/ +__attribute__ ((aligned(4))) +u16 *g_slcdc_dbuffer_phyaddress; + +/* used for SLCDC command buffer */ +__attribute__ ((aligned(4))) +u16 *g_slcdc_cbuffer_address; + +/* physical address for SLCDC command buffer */ +__attribute__ ((aligned(4))) +u16 *g_slcdc_cbuffer_phyaddress; + +static wait_queue_head_t slcdc_wait; +static int slcdc_device_num; +static int g_slcdc_status; + +static int slcdc_major; +module_param(slcdc_major, int, 0444); +MODULE_PARM_DESC(slcdc_major, + "slcdc char device major number. If this number is" + " set to zero, then automatic major number allocation will be done"); + +static int slcdc_minor; +module_param(slcdc_minor, int, 0444); +MODULE_PARM_DESC(slcdc_minor, "slcdc char device minor number"); + +#define SLCDC_OPEN_STATUS 0x0001 +#define SLCDC_SUSPEND_STATUS 0x0002 + +#define SLCDC_MAJOR_NUM 10 /*!< SLCDC char dev default major number */ +#define SLCDC_MINOR_NUM 156 /*!< SLCDC char dev default minor number */ + +#define FBCON_HAS_CFB4 +#define FBCON_HAS_CFB8 +#define FBCON_HAS_CFB16 + +static struct cdev slcdc_dev; + +extern void gpio_slcdc_active(int type); +extern void gpio_slcdc_inactive(int type); + +int slcdc_open(struct inode *inode, struct file *filp); +int slcdc_release(struct inode *inode, struct file *filp); +static int slcdc_ioctl(struct inode *inode, struct file *filp, + u_int cmd, u_long arg); +static int slcdc_mmap(struct file *filp, struct vm_area_struct *vma); +static void __init _init_fbinfo(void); + +typedef struct { + u16 *screen_start_address; + u16 *v_screen_start_address; +} slcdc_par_t; + +slcdc_par_t slcdc_par; + +struct file_operations g_slcdc_fops = { + open:slcdc_open, + release:slcdc_release, + ioctl:slcdc_ioctl, + mmap:slcdc_mmap, +}; + +//>>>>>> Global Variable + +#ifdef DBMX_DEBUG +#define FUNC_START TRACE(KERN_ERR"start of %s\n", __FUNCTION__); +#define FUNC_END TRACE(KERN_ERR"end of %s\n", __FUNCTION__); +#else +#define FUNC_START +#define FUNC_END +#endif + +#define RED 0xf00 +#define GREEN 0xf0 +#define BLUE 0x0f + +/**@brief Local LCD controller parameters*/ +struct slcdcfb_par { + u_char *screen_start_address; /*!< Screen Start Address */ + u_char *v_screen_start_address; /*!< Virtul Screen Start Address */ + unsigned long screen_memory_size; /*!< Screen memory size */ + unsigned int palette_size; /*!<Palette size */ + unsigned int max_xres; /*!<Maximum x resolution */ + unsigned int max_yres; /*!<Maximum x resolution */ + unsigned int xres; /*!<X resolution */ + unsigned int yres; /*!<Y resolution */ + unsigned int xres_virtual; /*!<Vitual x resolution */ + unsigned int yres_virtual; /*!<Vitual y resolution */ + unsigned int max_bpp; /*!<Maximum bit per pixel */ + unsigned int bits_per_pixel; /*!<Bits per pixel */ + unsigned int currcon; /*!<Current console ID */ + unsigned int visual; /*!<Vitual color type */ + unsigned int TFT:1; /*!<TFT flag */ + unsigned int color:1; /*!<Color flag */ + unsigned int sharp:1; /*!< Sharp LCD flag */ +}; + +/* Frame buffer device API */ +static int slcdcfb_set_var(struct fb_info *info); +/* perform fb specific mmap */ +static int slcdcfb_mmap(struct fb_info *info, struct file *file, + struct vm_area_struct *vma); +/* perform fb specific ioctl (optional) */ +static int slcdcfb_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg, + struct fb_info *info); +static int slcdcfb_open(struct fb_info *info, int user); +static int slcdcfb_release(struct fb_info *info, int user); + +static int _check_var(struct fb_var_screeninfo *var, struct fb_info *info); +static int _decode_var(struct fb_info *info); +static int slcdcfb_blank(int blank, struct fb_info *info); + +/* + * Framebuffer file operations + */ +static struct fb_ops slcdcfb_ops = { + .owner = THIS_MODULE, + .fb_open = slcdcfb_open, + .fb_release = slcdcfb_release, + .fb_check_var = _check_var, + .fb_set_par = _decode_var, + .fb_blank = slcdcfb_blank, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, + .fb_cursor = soft_cursor, + .fb_ioctl = slcdcfb_ioctl, + .fb_mmap = slcdcfb_mmap, +}; + +static struct display global_disp; /* Initial (default) Display Settings */ +static struct fb_info slcdc_fb_info; +static struct fb_var_screeninfo init_var; +static struct slcdcfb_par current_par; +static struct timer_list slcdc_timer; +static int start_fb_timer_flag = 0; //0 is stop, 1 is start + +#define MIN_XRES 64 +#define MIN_YRES 64 +#define LCD_MAX_BPP SLCDC_BPP +#define MAX_PIXEL_MEM_SIZE SLCDC_PIXEL_MEM_SIZE +#define SLCDC_REFRESH_RATE (HZ / 100) + +#define SLCDC2_CLK_PC31 31 /* refer iMX27 Pin Mux details */ +#define SLCDC2_CS_PC30 30 +#define SLCDC2_RS_PC29 29 +#define SLCDC2_D0_PC28 28 + +#define SLCDC1_CLK_PB5 5 +#define SLCDC1_CS_PB8 8 +#define SLCDC1_RS_PB7 7 +#define SLCDC1_D0_PB6 6 + +#define SLCDC1_DAT0_PA6 6 + +/* Fake monspecs to fill in fbinfo structure */ +static struct fb_monspecs monspecs __initdata = { + .hfmin = 30000, + .hfmax = 70000, + .vfmin = 50, + .vfmax = 65, + .dpms = 0 /* Generic */ +}; + +void slcdc_delay(int num) +{ + udelay(num); +} + +/** + *@brief slcdc gpio configure routine for serial mode + * + * Function Name: slcdc_gpio_serial + * + * + * Description:This routine will implement gpio configurations for serial mode + * both for\n LCDC mux and SDHC2 mux, you can use macro + * SLCDC_MUX_SSI3_SLCDC2 or SLCDC_MUX_SD_SLCDC1 \n to choose the + * right way according to your hardware configuration. + * + * + *@return None + * + * Modification History: + * Dec,2003 Karen update for MX21 TO2 + * Jun,2004 Shirley update for LCDC mux + * Mar,2006 Update for MX27 mux + **/ + +void slcdc_gpio_serial(void) +{ +#ifdef SLCDC_MUX_SSI3_SLCDC2 + /* we have to set SLCDC2_CLK, SLCDC2_CS, SLCDC2_RS and SLCDC2_D0 */ + //gpio_request_mux(MX27_PIN_SSI3_CLK, GPIO_MUX_ALT); /* CLK */ + //gpio_request_mux(MX27_PIN_SSI3_TXDAT,GPIO_MUX_ALT); /* CS */ + //gpio_request_mux(MX27_PIN_SSI3_RXDAT,GPIO_MUX_ALT); /* RS */ + //gpio_request_mux(MX27_PIN_SSI3_FS, GPIO_MUX_ALT); /* D0 */ + gpio_slcdc_active(0); + +#endif +#ifdef SLCDC_MUX_SD_SLCDC1 + //gpio_request_mux(MX27_PIN_SD2_D1, GPIO_MUX_GPIO); /* CLK */ + //gpio_request_mux(MX27_PIN_SD2_D2, GPIO_MUX_GPIO); /* D0 */ + //gpio_request_mux(MX27_PIN_SD2_D3, GPIO_MUX_GPIO); /* RS */ + //gpio_request_mux(MX27_PIN_SD2_CMD,GPIO_MUX_GPIO); /* CS */ + gpio_slcdc_active(1); + +#endif +} + +/** + *@brief slcdc gpio configure routine for parallel mode + * + * Function Name: slcdc_gpio_paralle + * + * + * Description:This routine will implement gpio configurations for parralel + * mode both for\n LCDC mux and SDHC2 mux, you can use macro + * SLCDC_MUX_SSI3_SLCDC2 or SLCDC_MUX_SD_SLCDC1 \n to choose the + * right way according to your hardware configuration. + * + * + *@return None + * + * Modification History: + * Jun,2004 Shirley update for LCDC mux + * Mar,2006 Update for MX27 mux + **/ +#ifndef SLCDC_MUX_SSI3_SLCDC2 +void slcdc_gpio_paralle(void) +{ +#ifdef SLCDC_MUX_LCD_SLCDC1 + /* Make sure the actual hardware connection is based on this, if other port + is used then the following code has to be modified accordingly. + For further details refer Pin Mux details in iMX27 Spec. */ + /* gpio_request_mux(MX27_PIN_LD0, GPIO_MUX_GPIO); + gpio_request_mux(MX27_PIN_LD1, GPIO_MUX_GPIO); + gpio_request_mux(MX27_PIN_LD2, GPIO_MUX_GPIO); + gpio_request_mux(MX27_PIN_LD3, GPIO_MUX_GPIO); + gpio_request_mux(MX27_PIN_LD4, GPIO_MUX_GPIO); + gpio_request_mux(MX27_PIN_LD5, GPIO_MUX_GPIO); + gpio_request_mux(MX27_PIN_LD6, GPIO_MUX_GPIO); + gpio_request_mux(MX27_PIN_LD7, GPIO_MUX_GPIO); + gpio_request_mux(MX27_PIN_LD8, GPIO_MUX_GPIO); + gpio_request_mux(MX27_PIN_LD9, GPIO_MUX_GPIO); + gpio_request_mux(MX27_PIN_LD10, GPIO_MUX_GPIO); + gpio_request_mux(MX27_PIN_LD11, GPIO_MUX_GPIO); + gpio_request_mux(MX27_PIN_LD12, GPIO_MUX_GPIO); + gpio_request_mux(MX27_PIN_LD13, GPIO_MUX_GPIO); + gpio_request_mux(MX27_PIN_LD14, GPIO_MUX_GPIO); + gpio_request_mux(MX27_PIN_LD15, GPIO_MUX_GPIO); + */ + gpio_slcdc_active(2); + +#endif +} +#endif + +void slcdc_reset(int level) +{ + if (level == 0) { +#if 0 +#ifdef SLCDC_MUX_SSI3_SLCDC2 + /* OE_ACD as a reset pin */ + _reg_GPIO_GIUS(GPIOA) |= 0x80000000; + _reg_GPIO_OCR2(GPIOA) |= 0xc0000000; + _reg_GPIO_DDIR(GPIOA) |= 0x80000000; + + /* set reset pin to low */ + _reg_GPIO_DR(GPIOA) &= 0x7fffffff; +#endif +#ifdef SLCDC_MUX_SD_SLCDC1 + /* SD2_D1 as reset pin */ + _reg_GPIO_GIUS(GPIOB) |= 0x00000020; + _reg_GPIO_OCR1(GPIOB) |= 0x00000c00; + _reg_GPIO_DDIR(GPIOB) |= 0x00000020; + + /* set reset pin to low */ + _reg_GPIO_DR(GPIOB) &= 0xffffffdf; +#endif + } else { +#ifdef SLCDC_MUX_SSI3_SLCDC2 + /* set reset pin to high */ + _reg_GPIO_DR(GPIOA) |= 0x80000000; +#endif +#ifdef SLCDC_MUX_SD_SLCDC1 + /* set reset pin to high */ + _reg_GPIO_DR(GPIOB) |= 0x00000020; +#endif +#endif + } + +} + +/** + *@brief slcdc hardware initialization + * + * Function Name: slcdc_init_dev + * + * Description : This routine will enable the SLCDC and the clock for it + * + **/ +void slcdc_init_dev(void) +{ + volatile unsigned long reg; + + reg = __raw_readl(IO_ADDRESS(MAX_BASE_ADDR + 0x100 * 3 + 0x10)); + reg = reg | 0x00040000; + __raw_writel(reg, IO_ADDRESS(MAX_BASE_ADDR + 0x100 * 3 + 0x10)); + + reg = __raw_readl(IO_ADDRESS(SYSCTRL_BASE_ADDR + 0x58)); /* _reg_SYS_PCSR */ + reg = reg | 0x00000004; /* set LCD/SLCD bus master high priority */ + __raw_writel(reg, IO_ADDRESS(SYSCTRL_BASE_ADDR + 0x58)); + + /* enable the slcd clk */ + reg = __raw_readl(IO_ADDRESS(CCM_BASE_ADDR + 0x20)); /* _reg_CRM_PCCR0 */ + reg |= 0x02200000; + __raw_writel(reg, IO_ADDRESS(CCM_BASE_ADDR + 0x20)); +} + +/** + *@brief slcdc register initialization + * + * Function Name: slcdc_init_reg + * + * Description:This routine will setup the SLCDC register for first time + * + **/ +void slcdc_init_reg(void) +{ + __raw_writel(0, IO_ADDRESS(SLCDC_BASE_ADDR + 0x00)); /* _reg_SLCDC_DBADDR */ + __raw_writel(0, IO_ADDRESS(SLCDC_BASE_ADDR + 0x04)); /* _reg_SLCDC_DBUF_SIZE */ + __raw_writel(0, IO_ADDRESS(SLCDC_BASE_ADDR + 0x08)); /* _reg_SLCDC_CBADDR */ + __raw_writel(0, IO_ADDRESS(SLCDC_BASE_ADDR + 0x0C)); /* _reg_SLCDC_CBUF_SIZE */ + __raw_writel(0, IO_ADDRESS(SLCDC_BASE_ADDR + 0x10)); /* _reg_SLCDC_CBUF_SSIZE */ + __raw_writel(0, IO_ADDRESS(SLCDC_BASE_ADDR + 0x14)); /* _reg_SLCDC_FIFO_CONFIG */ + __raw_writel(0, IO_ADDRESS(SLCDC_BASE_ADDR + 0x18)); /* _reg_SLCDC_LCD_CONFIG */ + __raw_writel(0, IO_ADDRESS(SLCDC_BASE_ADDR + 0x1C)); + /*_reg_SLCDC_LCD_TXCONFIG*/ + __raw_writel(0, IO_ADDRESS(SLCDC_BASE_ADDR + 0x20)); + /*_reg_SLCDC_LCD_CTRL_STAT*/ + __raw_writel(0, IO_ADDRESS(SLCDC_BASE_ADDR + 0x24)); + /*_reg_SLCDC_LCD_CLKCONFIG*/ +} + +/** + *@brief slcdc initial configuration + * + * Function Name: slcdc_config + * + * Description:This routine will fist time configuration like buffer address + * FIFO configuration etc. + * + **/ +void slcdc_config(int datlen) +{ + u32 xfrmode, sckpol, worddefcom, imgend = 0, worddefwrite = + 0, worddefdat = 0; + volatile unsigned long reg; + + if (datlen == 8) { + imgend = 0x2; /*8-bit little endian; */ + worddefdat = 0; /* 8-bit data */ + worddefwrite = 10; + } else if (datlen == 16) { + imgend = 0x1; /* 16-bit little endian; */ + worddefdat = 1; /* 16-bit data */ + worddefwrite = 1; + } else { + FAILED(":invaild parameter, 8 or 16 is the value required"); + } + worddefcom = 1; +#ifdef SLCDC_SERIAL_MODE + xfrmode = 0; /* serial mode */ +#else + xfrmode = 1; /* paralle mode */ +#endif + sckpol = 1; /* falling edge */ + /* config to be little endian serial 16bit */ + reg = __raw_readl(IO_ADDRESS(SLCDC_BASE_ADDR + 0x1C)); + /*_reg_SLCDC_LCD_TXCONFIG*/ + reg = + (imgend << 16) | (worddefdat << 4) | (worddefcom << 3) | (xfrmode << + 2) | + (sckpol); + __raw_writel(reg, IO_ADDRESS(SLCDC_BASE_ADDR + 0x1C)); + + /* printk("SLCDC_TXCONFIG = %x \n",_reg_SLCDC_LCD_TXCONFIG); */ + /* config dma setting */ + __raw_writel(5, IO_ADDRESS(SLCDC_BASE_ADDR + 0x14)); /* _reg_SLCDC_FIFO_CONFIG, + burst length is 4 32-bit words */ + /* config buffer address setting */ + __raw_writel((u32) (slcdc_par.screen_start_address), IO_ADDRESS(SLCDC_BASE_ADDR + 0x00)); /* _reg_SLCDC_DBADDR */ + __raw_writel((u32) g_slcdc_cbuffer_phyaddress, IO_ADDRESS(SLCDC_BASE_ADDR + 0x08)); /* _reg_SLCDC_CBADDR */ + + /* config clk setting */ + __raw_writel((u32) 0x3, IO_ADDRESS(SLCDC_BASE_ADDR + 0x24)); + /*_reg_SLCDC_LCD_CLKCONFIG*/ + /* set GO 0 */ + __raw_writel((u32) 0x0, IO_ADDRESS(SLCDC_BASE_ADDR + 0x20)); + /*_reg_SLCDC_LCD_CTRL_STAT*/ + + slcdc_delay(5000); + +} + +/** + *@brief slcdc send command routine + * + * Function Name: slcdc_send_cmd + * + * Description:This help routine sends command to the SLCD from SLCDC + * + *@return 0 on success, any other value otherwise + **/ +/* for command transfer, it is very short, will not use interrupt */ +int slcdc_send_cmd(u32 length) +{ + u32 status; + volatile unsigned long reg; + + /* disable interrupt */ + reg = __raw_readl(IO_ADDRESS(SLCDC_BASE_ADDR + 0x20)); + /*_reg_SLCDC_LCD_CTRL_STAT*/ + reg = reg & 0xffffff7f; + __raw_writel(reg, IO_ADDRESS(SLCDC_BASE_ADDR + 0x20)); + + /* set length */ + __raw_writel(length, IO_ADDRESS(SLCDC_BASE_ADDR + 0x04)); /* _reg_SLCDC_DBUF_SIZE */ + + /* set automode 00 for command */ + reg = __raw_readl(IO_ADDRESS(SLCDC_BASE_ADDR + 0x20)); /* _reg_SLCDC_LCD_CTRL_STAT */ + reg = reg & 0x000001ff; + __raw_writel(reg, IO_ADDRESS(SLCDC_BASE_ADDR + 0x20)); + + /* set GO */ + reg = __raw_readl(IO_ADDRESS(SLCDC_BASE_ADDR + 0x20)); /* _reg_SLCDC_LCD_CTRL_STAT */ + reg |= 0x1; + __raw_writel(reg, IO_ADDRESS(SLCDC_BASE_ADDR + 0x20)); + + /* polling for data transfer finish */ + + status = __raw_readl(IO_ADDRESS(SLCDC_BASE_ADDR + 0x20)); /* _reg_SLCDC_LCD_CTRL_STAT */ + while ((!(status & SLCDC_TRANSFER_ERROR)) + && (status & SLCDC_TRANSFER_BUSY)) { + status = __raw_readl(IO_ADDRESS(SLCDC_BASE_ADDR + 0x20)); /* _reg_SLCDC_LCD_CTRL_STAT */ + } + + if (status & SLCDC_TRANSFER_ERROR) { + TRACE("send cmd error status=0x%x \n", status); + return 1; + } else { + reg = __raw_readl(IO_ADDRESS(SLCDC_BASE_ADDR + 0x20)); /* _reg_SLCDC_LCD_CTRL_STAT */ + reg |= 0x40; + __raw_writel(reg, IO_ADDRESS(SLCDC_BASE_ADDR + 0x20)); + + reg = __raw_readl(IO_ADDRESS(SLCDC_BASE_ADDR + 0x20)); /* _reg_SLCDC_LCD_CTRL_STAT */ + reg |= 0x20; + __raw_writel(reg, IO_ADDRESS(SLCDC_BASE_ADDR + 0x20)); + return 0; + } +} + +/** + *@brief slcdc send data routine + * + * Function Name: slcdc_send_data + * + * Description: This help routine sends data to the SLCD from SLCDC + * + *@return 0 on success, any other value otherwise + **/ +void slcdc_send_data(u32 length) +{ + volatile unsigned long reg; + + /* enable interrupt */ +#ifdef USING_INTERRUPT_SLCDC + reg = __raw_readl(IO_ADDRESS(SLCDC_BASE_ADDR + 0x20)); + /*_reg_SLCDC_LCD_CTRL_STAT*/ + reg |= ~0xffffff7f; + __raw_writel(reg, IO_ADDRESS(SLCDC_BASE_ADDR + 0x20)); +#endif + /* set length */ + __raw_writel(length, IO_ADDRESS(SLCDC_BASE_ADDR + 0x04)); /* _reg_SLCDC_DBUF_SIZE */ + /* set automode 01 for data */ + reg = __raw_readl(IO_ADDRESS(SLCDC_BASE_ADDR + 0x20)); + /*_reg_SLCDC_LCD_CTRL_STAT*/ + reg |= 0x00000800; + __raw_writel(reg, IO_ADDRESS(SLCDC_BASE_ADDR + 0x20)); + + /* set GO */ + reg = __raw_readl(IO_ADDRESS(SLCDC_BASE_ADDR + 0x20)); + /*_reg_SLCDC_LCD_CTRL_STAT*/ + reg |= 0x1; + __raw_writel(reg, IO_ADDRESS(SLCDC_BASE_ADDR + 0x20)); + +#ifdef USING_INTERRUPT_SLCDC + interruptible_sleep_on(&slcdc_wait); +#else + do { + reg = __raw_readl(IO_ADDRESS(SLCDC_BASE_ADDR + 0x20)); + /*_reg_SLCDC_LCD_CTRL_STAT*/ + } while ((reg & 0x00000004) != 0); + + reg = __raw_readl(IO_ADDRESS(SLCDC_BASE_ADDR + 0x20)); + /*_reg_SLCDC_LCD_CTRL_STAT*/ + reg |= 0x40; + __raw_writel(reg, IO_ADDRESS(SLCDC_BASE_ADDR + 0x20)); + + reg = __raw_readl(IO_ADDRESS(SLCDC_BASE_ADDR + 0x20)); + /*_reg_SLCDC_LCD_CTRL_STAT*/ + reg |= 0x20; + __raw_writel(reg, IO_ADDRESS(SLCDC_BASE_ADDR + 0x20)); + +#endif + return; + +} + +/** + *@brief slcdc isr + * + * Function Name: slcdc_isr + * + * Description: This ISR routine takes interrupt from SLCDC and does refresh of + * display data if necessary + * + *@return 0 on success, any other value otherwise + **/ +static irqreturn_t slcdc_isr(int irq, void *dev_id, struct pt_regs *regs) +{ + volatile u32 reg; + reg = __raw_readl(IO_ADDRESS(SLCDC_BASE_ADDR + 0x20)); + /*_reg_SLCDC_LCD_CTRL_STAT*/ + + /* clear interrupt */ + reg = __raw_readl(IO_ADDRESS(SLCDC_BASE_ADDR + 0x20)); + /*_reg_SLCDC_LCD_CTRL_STAT*/ + reg |= 0x40; + __raw_writel(reg, IO_ADDRESS(SLCDC_BASE_ADDR + 0x20)); + + wake_up_interruptible(&slcdc_wait); + + if (start_fb_timer_flag == 1) { + /* while((_reg_SLCDC_LCD_CTRL_STAT &0x00000004)!=0); */ + reg = __raw_readl(IO_ADDRESS(SLCDC_BASE_ADDR + 0x20)); + /*_reg_SLCDC_LCD_CTRL_STAT*/ + reg |= 0x40; + __raw_writel(reg, IO_ADDRESS(SLCDC_BASE_ADDR + 0x20)); + + reg = __raw_readl(IO_ADDRESS(SLCDC_BASE_ADDR + 0x20)); + /*_reg_SLCDC_LCD_CTRL_STAT*/ + reg |= 0x20; + __raw_writel(reg, IO_ADDRESS(SLCDC_BASE_ADDR + 0x20)); + mod_timer(&slcdc_timer, jiffies + SLCDC_REFRESH_RATE); + TRACE("slcdc_isr\n"); + } + + return IRQ_HANDLED; +} + +/** + *@brief slcdc buffer initialization + * + * Function Name: slcdc_init_buffer + * + * + * Description:This routine will allocate physical memory for SLCDC data and + * for SLCDC command. + * + *@return 0 on success, appropriate error value on error + **/ +int slcdc_init_buffer(void) +{ + + TRACE("slcdc data buffer size = %x \n", + (unsigned int)SLCDC_DATA_MEM_SIZE); + + if (g_slcdc_dbuffer_phyaddress != NULL) + return -EINVAL; + + if (g_slcdc_cbuffer_phyaddress != NULL) + return -EINVAL; + + g_slcdc_dbuffer_phyaddress = (u16 *) mxc_malloc(SLCDC_DATA_MEM_SIZE); + g_slcdc_cbuffer_phyaddress = (u16 *) mxc_malloc(SLCDC_CMD_MEM_SIZE); + + if (!g_slcdc_dbuffer_phyaddress || !g_slcdc_cbuffer_phyaddress) { + if (g_slcdc_dbuffer_phyaddress != (u16 *) 0) + mxc_free((u32) g_slcdc_dbuffer_phyaddress); + + if (g_slcdc_cbuffer_phyaddress != (u16 *) 0) + mxc_free((u32) g_slcdc_cbuffer_phyaddress); + + FAILED("can not allocated memory\n"); + return -ENOMEM; + } else { + + TRACE + ("allocated cmd_buffer=0x%x size=%d, data_buffer=0x%x size=%d", + (int)g_slcdc_cbuffer_phyaddress, SLCDC_CMD_MEM_SIZE, + (int)g_slcdc_dbuffer_phyaddress, SLCDC_DATA_MEM_SIZE); + + if ((!request_mem_region((u32) g_slcdc_dbuffer_phyaddress, + SLCDC_DATA_MEM_SIZE, + slcdc_fb_info.fix.id)) + || + (!request_mem_region + ((u32) g_slcdc_cbuffer_phyaddress, SLCDC_CMD_MEM_SIZE, + slcdc_fb_info.fix.id))) { + FAILED("request mem region failed."); + return -EBUSY; + } + + if (!(slcdc_fb_info.screen_base = ioremap((u32) + g_slcdc_dbuffer_phyaddress, + SLCDC_DATA_MEM_SIZE))) + { + release_mem_region((u32) g_slcdc_dbuffer_phyaddress, + SLCDC_DATA_MEM_SIZE); + FAILED("Unable to map fb memory to virtual address"); + return -EIO; + } else { + g_slcdc_dbuffer_address = + (u16 *) slcdc_fb_info.screen_base; + } + + if (! + (g_slcdc_cbuffer_address = + ioremap((u32) g_slcdc_cbuffer_phyaddress, + SLCDC_CMD_MEM_SIZE))) { + release_mem_region((u32) g_slcdc_dbuffer_phyaddress, + SLCDC_DATA_MEM_SIZE); + release_mem_region((u32) g_slcdc_cbuffer_phyaddress, + SLCDC_CMD_MEM_SIZE); + FAILED("Unable to map fb memory to virtual address"); + return -EIO; + } + } + + TRACE("slcdc data buffer address = %x cmd buffer address= %x \n", + (unsigned int)slcdc_par.screen_start_address, + (unsigned int)g_slcdc_cbuffer_address); + + return 0; +} + +/** + *@brief slcdc buffer de initialization + * + * Function Name: slcdc_free_buffer + * + * + * Description:This routine will deallocate the physical memory allocated by + * slcdc_init_buffer. + * + *@return 0 on success + **/ +int slcdc_free_buffer(void) +{ + + FUNC_START; + + iounmap(g_slcdc_dbuffer_address); + iounmap(g_slcdc_cbuffer_address); + + release_mem_region((unsigned long)g_slcdc_dbuffer_phyaddress, + SLCDC_DATA_MEM_SIZE); + release_mem_region((unsigned long)g_slcdc_cbuffer_phyaddress, + SLCDC_CMD_MEM_SIZE); + + mxc_free((u32) g_slcdc_dbuffer_phyaddress); + mxc_free((u32) g_slcdc_cbuffer_phyaddress); + + FUNC_END; + return 0; +} + +/** + *@brief slcdc mmap function + * + * Function Name: slcdc_mmap + * + * + * Description: This is the memory map routine for this driver,will setup the + * memory map used in this driver. + * + *@param filp the pointer to the file descripter + *@param vma the pointer to the vma structure related to the driver + * + *@return int return status + * @li 0 sucessful + * @li other failed + * Modification History: + * Dec,2003, Karen first version + * + **/ + +static int slcdc_mmap(struct file *filp, struct vm_area_struct *vma) +{ + unsigned long page, pos; + unsigned long start = (unsigned long)vma->vm_start; + unsigned long size = (unsigned long)(vma->vm_end - vma->vm_start); + + if (size > SLCDC_DATA_MEM_SIZE) + return -EINVAL; + + TRACE("slcdc_mmap is called 1\n"); + pos = (unsigned long)slcdc_par.v_screen_start_address; + + while (size > 0) { + page = virt_to_phys((void *)pos); + + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + /* This is an IO map - tell maydump to skip this VMA */ + vma->vm_flags |= VM_IO; + + if (remap_pfn_range + (vma, start, page >> PAGE_SHIFT, PAGE_SIZE, PAGE_SHARED)) + return -EAGAIN; + start += PAGE_SIZE; + pos += PAGE_SIZE; + size -= PAGE_SIZE; + } + + return 0; +} + +/** + *@brief slcdc mmap function called from framebuffer + * + * Function Name: slcdcfb_mmap + * + * + * Description: This is the memory map routine for this driver,will setup the + * memory map used in this driver. + * + *@return 0 on success any other value for failure + **/ +static int slcdcfb_mmap(struct fb_info *info, struct file *file, + struct vm_area_struct *vma) +{ + + return slcdc_mmap(file, vma); + +} + +/** + *@brief slcdc display-on function + * + * Function Name: slcdc_display_on + * + * Description:This helper routine will send the command to the SLCD for display + * on. + * + **/ +void slcdc_display_on(void) +{ + u16 *databuffer = NULL; + + __raw_writel((u32) g_slcdc_cbuffer_phyaddress, + IO_ADDRESS(SLCDC_BASE_ADDR + 0x00)); + + /* put cmd into cmd buffer */ + databuffer = g_slcdc_cbuffer_address; + /* put cmd into cmd buffer */ + *databuffer = SLCDC_CMD_DISON; + /* send cmd */ + slcdc_send_cmd(1); + /* delay */ + slcdc_delay(0xffff); + /* send cmd */ + slcdc_send_cmd(1); + /* delay */ + slcdc_delay(0xffff); +} + +/** + *@brief slcdc display-off function + * + * Function Name: slcdc_display_off + * + * Description:This helper routine will send the command to the SLCD for display + * off. + * + **/ +void slcdc_display_off(void) +{ + u16 *databuffer = NULL; + /* Cause LCD module to enter sleep mode.Bu sure to input display OFF command + to turn off the display before inputting the SLPIN command + Keep the logic power supply turned on for 40ms after the LCD module + enters the sleep mode. */ + /* put cmd into cmd buffer */ + databuffer = slcdc_par.v_screen_start_address; + /* send cmd SLPIN */ + *databuffer = SLCDC_CMD_SLPIN; + slcdc_send_cmd(1); + /* put reset high */ + +#if 0 +#ifdef SLCDC_MUX_SSI3_SLCDC2 + /* set reset pin to high */ + _reg_GPIO_DR(GPIOA) |= 0x80000000; +#endif +#ifdef SLCDC_MUX_SD_SLCDC1 + /* set reset pin to high */ + _reg_GPIO_DR(GPIOB) |= 0x00000020; +#endif +#endif + + /* delay for 50ms */ + slcdc_delay(0xffff); + /* put cmd into cmd buffer */ + *databuffer = SLCDC_CMD_DISOFF; + /* send cmd DISOFF */ + slcdc_send_cmd(1); + TRACE("disoff \n"); + slcdc_delay(0xffff); +} + +/** + *@brief slcdc display-off function + * + * Function Name: slcdc_display_normal + * + * Description:This helper routine will send the command to the SLCD for display + * normal. + * + **/ +void slcdc_display_normal(void) +{ + u16 *databuffer = NULL; + databuffer = slcdc_par.v_screen_start_address; + *databuffer = SLCDC_CMD_DISNOR; + /* send cmd */ + slcdc_send_cmd(1); + /* delay */ + slcdc_delay(100); +} + +/** + *@brief slcdc display sleep out function + * + * Function Name: slcdc_sleep_out + * + * Description:This helper routine will send the command to the SLCD for wakeup + * from sleep. + * + **/ +void slcdc_sleep_out(void) +{ + u16 *databuffer = NULL; + databuffer = slcdc_par.v_screen_start_address; + *databuffer = SLCDC_CMD_SLPOUT; + /* send cmd */ + slcdc_send_cmd(1); + /* delay */ + slcdc_delay(0xffff); +} + +/** @brief sends control command to SLCD */ +void slcdc_display_ctl(void) +{ + int i; +#ifdef SLCDC_SERIAL_MODE + u16 disctl[11] = { 0x1c00, 0x0200, 0x8200, 0x0000, + 0x1e00, 0xe000, 0x0000, 0xdc00, + 0x0000, 0x0200, 0x0000 + }; +#else + u16 disctl[11] = { 0x1c, 0x02, 0x82, 0x00, + 0x1e, 0xe0, 0x00, 0xdc, + 0x00, 0x02, 0x00 + }; +#endif + u16 *databuffer = NULL; + /* It make various display timing settings. */ + databuffer = slcdc_par.v_screen_start_address; + /* put cmd into cmd buffer */ + *databuffer = SLCDC_CMD_DISCTL; + /* send cmd */ + slcdc_send_cmd(1); + slcdc_delay(2000); + /* put parameter into data buffer */ + for (i = 0; i < 11; i++) { + *databuffer = disctl[i]; + databuffer++; + } + /* send data */ + slcdc_send_data(11); + +} + +/** + *@brief slcd page set function + * + * Function Name: slcdc_page_set + * + * Description:This helper routine will send the command to the SLCD to set the + * page inside video ram + * + **/ +void slcdc_page_set(void) +{ + /* It specify a page address area in order to access the display data + RAM from the MPU. Be sure to set both starting and ending pages. */ + int i; + u16 *databuffer = NULL; +#ifdef SLCDC_SERIAL_MODE + u16 pset[4] = { 0x0000, 0x0000, 0xb100, 0x0000 }; +#else + u16 pset[4] = { 0x00, 0x00, 0xb1, 0x00 }; +#endif + databuffer = (u16 *) (slcdc_par.v_screen_start_address); + /* put cmd into cmd buffer */ + *databuffer = SLCDC_CMD_SD_PSET; + /* send cmd */ + slcdc_send_cmd(1); + slcdc_delay(20000); + + /* put parameter into data buffer */ + for (i = 0; i < 4; i++) { + *databuffer = pset[i]; + databuffer++; + } + /* send data */ + slcdc_send_data(4); + + slcdc_delay(20000); +} + +/** + *@brief slcd column set function + * + * Function Name: slcdc_col_set + * + * Description:This helper routine will send the command to the SLCD to set the + * column inside video ram + * + **/ +void slcdc_col_set(void) +{ + /* It specify starting and ending collumns. */ + int i; +#ifdef SLCDC_SERIAL_MODE + u16 cset[4] = { 0x0200, 0x0000, 0xb100, 0x0000 }; +#else + u16 cset[4] = { 0x02, 0x00, 0xb1, 0x00 }; +#endif + u16 *databuffer = NULL; + databuffer = (u16 *) (slcdc_par.v_screen_start_address); + /* put cmd into cmd buffer */ + *databuffer = SLCDC_CMD_SD_CSET; + /* send cmd */ + slcdc_send_cmd(1); + slcdc_delay(20000); + /* put parameter into data buffer */ + for (i = 0; i < 4; i++) { + *databuffer = cset[i]; + databuffer++; + } + /* send data */ + slcdc_send_data(4); + slcdc_delay(20000); + +} + +void slcdc_data_ctl(void) +{ + u8 *databuffer = NULL; + databuffer = (u8 *) (slcdc_par.v_screen_start_address); + *databuffer = SLCDC_CMD_DATCTL; + /* send cmd */ + slcdc_send_cmd(1); + slcdc_delay(20000); + *databuffer = 0x28; + /* send data */ + slcdc_send_data(1); + slcdc_delay(20000); +} + +void slcdc_volctl(void) +{ + u16 *databuffer = NULL; + databuffer = slcdc_par.v_screen_start_address; + *databuffer = SLCDC_CMD_VOLCTR; + /* send cmd */ + slcdc_send_cmd(1); + slcdc_delay(20000); +#ifdef SLCDC_SERIAL_MODE + *databuffer = 0x9f00; +#else + *databuffer = 0x9f; +#endif + /* send data */ + slcdc_send_data(1); + slcdc_delay(20000); +} + +void slcdc_ascset(void) +{ + int i; +#ifdef SLCDC_SERIAL_MODE + u16 lcd_para_ASCSET[7] = { 0x0000, 0x0000, 0x1f00, 0x0100, + 0x1f00, 0x0100, 0x0300 + }; +#else + u16 lcd_para_ASCSET[7] = { 0x00, 0x00, 0x1f, 0x01, + 0x1f, 0x01, 0x03 + }; + +#endif + u16 *databuffer = NULL; + databuffer = (u16 *) (slcdc_par.v_screen_start_address); + /* put cmd into cmd buffer */ + *databuffer = SLCDC_CMD_ASCSET; + /* put parameter into data buffer */ + /* send cmd */ + slcdc_send_cmd(1); + slcdc_delay(2000); + for (i = 0; i < 7; i++) { + *databuffer = lcd_para_ASCSET[i]; + databuffer++; + } + /* send data */ + slcdc_send_data(7); + slcdc_delay(20000); +} + +void slcdc_scstart(void) +{ + int i; + u16 lcd_para_SCSTART[2] = { 0x00, 0x00 }; + u16 *databuffer = NULL; + databuffer = (u16 *) (slcdc_par.v_screen_start_address); + /* put cmd into cmd buffer */ + *databuffer = SLCDC_CMD_SCSTART; + /* put parameter into data buffer */ + /* send cmd */ + slcdc_send_cmd(1); + slcdc_delay(10000); + for (i = 0; i < 2; i++) { + *databuffer = lcd_para_SCSTART[i]; + databuffer++; + } + /* send data */ + slcdc_send_data(2); + slcdc_delay(20000); + +} + +void slcdc_config_panel(void) +{ + int i; +#if 1 + /* set data format */ + slcdc_config(8); + slcdc_delay(0xffff); + slcdc_reset(1); /* pull reset signal high */ + + /* set data format */ + slcdc_delay(20000); + slcdc_data_ctl(); + slcdc_delay(1000); + slcdc_config(16); + + /* sleep out */ + slcdc_sleep_out(); + slcdc_delay(20000); + + for (i = 0; i < 8; i++) { + slcdc_volctl(); + slcdc_delay(2000); + } + slcdc_delay(0xffff); + slcdc_delay(0xffff); + slcdc_delay(0xffff); + + slcdc_display_on(); + slcdc_delay(0xffff); + /* set col address */ + slcdc_col_set(); + /* set page address */ + slcdc_page_set(); + /* set area in screen to be used for scrolling */ + slcdc_ascset(); + slcdc_delay(20000); + /* set top scroll page within the scroll area */ + slcdc_scstart(); + mdelay(4); +#endif +#if 0 + + //set data format + slcdc_config(8); + slcdc_delay(0xffff); + slcdc_reset(1); //pull reset signal high + + //set data format + slcdc_delay(20000); + slcdc_data_ctl(); + slcdc_delay(1000); + slcdc_config(16); + //sleep out + slcdc_sleep_out(); + slcdc_delay(0xffff); + //set col address + slcdc_col_set(); + //set page address + slcdc_page_set(); + //set area in screen to be used for scrolling + slcdc_ascset(); + slcdc_delay(20000); + //set top scroll page within the scroll area + slcdc_scstart(); + slcdc_delay(0xffff); + //turn on slcd display + slcdc_display_on(); + slcdc_delay(0xffff); + slcdc_delay(0xffff); + + for (i = 0; i < 8; i++) { + slcdc_volctl(); + slcdc_delay(2000); + } +#endif +} + +/** + *@brief slcdc ioctl routine + * + * Function Name: slcdc_ioctl + * Description:This routine will implement driver-specific functions + * + *@param inode : the pointer to driver-related inode. + *@param filp : the pointer to driver-related file structure. + *@param cmd : the command number. + *@param arg: argument which depends on command. + * + *@return int return status + * @li 0 sucess + * @li 1 failure + * + * Modification History: + * Dec,2003 Karen first version for MX21 TO2 + * + **/ + +static int slcdc_ioctl(struct inode *inode, struct file *filp, + u_int cmd, u_long arg) +{ + u16 *databuffer = NULL; + + switch (cmd) { + case SLCDC_CMD_DISON: + slcdc_display_on(); + break; + + case SLCDC_CMD_DISOFF: + slcdc_display_off(); + break; + + case SLCDC_CMD_DISNOR: + slcdc_display_normal(); + break; + + case SLCDC_CMD_DISINV: + /* put cmd into cmd buffer */ + databuffer = slcdc_par.v_screen_start_address; + *databuffer = SLCDC_CMD_DISINV; + /* send cmd */ + slcdc_send_cmd(1); + slcdc_delay(100); + break; + + case SLCDC_CMD_DATCTL: + break; + + case SLCDC_CMD_RAMWR: + /* Causes the MPU to be a data entry mode,allowing it to serite data + in the display memory. Inputting any other cmds other than NOP + cancels the data entry mode. */ + + __raw_writel((u32) g_slcdc_cbuffer_phyaddress, IO_ADDRESS(SLCDC_BASE_ADDR + 0x00)); /* _reg_SLCDC_DBADDR */ + + /* put cmd into cmd buffer */ + databuffer = g_slcdc_cbuffer_address; + /* put cmd into cmd buffer */ + *databuffer = SLCDC_CMD_RAMWR; + /* send cmd */ + slcdc_send_cmd(1); + slcdc_delay(2000); + + /* this is to display one data per time, it is ok. */ + slcdc_delay(0xffff); + __raw_writel((u32) slcdc_par.screen_start_address, IO_ADDRESS(SLCDC_BASE_ADDR + 0x00)); /* _reg_SLCDC_DBADDR */ + + slcdc_delay(0xffff); + slcdc_send_data(SLCDC_MEM_SIZE); + slcdc_delay(0xffff); + + break; + + case SLCDC_CMD_RAMRD: + break; + + case SLCDC_CMD_PTLIN: + /* This command is used to display a partial screen for power saving. */ + break; + + case SLCDC_CMD_PTLOUT: + /* This command is used to exit the partila diaplay mode. */ + break; + + case SLCDC_CMD_GCP64: + /* make 63 pulse position settings of GCP for 64 gray scales. */ + /* send cmd into cmd buffer + send parameter into data buffer + send cmd + send data */ + break; + + case SLCDC_CMD_GCP16: + break; + + case SLCDC_CMD_GSSET: + break; + + case SLCDC_CMD_ASCSET: + /* make partial screen scroll settings. */ + /* send cmd into cmd buffer + send parameter into data buffer + send cmd + delay + send data + delay */ + break; + + case SLCDC_CMD_SCSTART: + /* set a scroll starting page in the scrolling area.Be sure to send + this cmd after ASCSET . */ + /* send cmd into cmd buffer + send parameter into data buffer + send cmd + delay + send data + delay */ + break; + default: + break; + } + return 0; +} + +static int slcdcfb_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg, + struct fb_info *info) +{ + return slcdc_ioctl(inode, file, cmd, arg); +} + +/** + *@brief slcdc close function + * + * Function Name: slcdc_release + * + * + * Description: This is the release routine for the driver. And this function \n + * will be called while the module being closed. In this function, it will\n + * unregister the apm + * + *@param inode the pointer to the inode descripter + *@param filp the pointer to the file descripter + * + *@return int return status + * @li 0 sucessful + * @li other failed + * Modification History: + * Dec,2003 Karen first version + * + **/ + +int slcdc_release(struct inode *inode, struct file *filp) +{ + TRACE("slcdc_release: ----\n"); + del_timer_sync(&slcdc_timer); + return 0; +} + +/** + *@brief slcdc timer call back function + * + * Function Name: slcdc_timer_func + * + * Description:This helper routine prepare the SLCDC for data transfer + * + **/ +static void slcdc_timer_func(unsigned long args) +{ + volatile unsigned long reg; + /* Causes the MPU to be a data entry mode,allowing it to serite data in the + display memory. Inputting any other cmds other than NOP cancels the data + entry mode. */ + + __raw_writel((u32) g_slcdc_cbuffer_phyaddress, IO_ADDRESS(SLCDC_BASE_ADDR + 0x00)); /* _reg_SLCDC_DBADDR */ + + /* put cmd into cmd buffer */ + *g_slcdc_cbuffer_address = SLCDC_CMD_RAMWR; + /* send cmd */ + slcdc_send_cmd(1); + slcdc_delay(2000); + + /* this is to display one data per time, it is ok. */ + slcdc_delay(0xffff); + __raw_writel((u32) slcdc_par.screen_start_address, IO_ADDRESS(SLCDC_BASE_ADDR + 0x00)); /* _reg_SLCDC_DBADDR */ + + slcdc_delay(0xffff); + /* slcdc_send_data( SLCDC_MEM_SIZE); */ + + /* enable interrupt */ +#ifdef USING_INTERRUPT_SLCDC + reg = __raw_readl(IO_ADDRESS(SLCDC_BASE_ADDR + 0x20)); + /*_reg_SLCDC_LCD_CTRL_STAT*/ + reg |= ~0xffffff7f; + __raw_writel(reg, IO_ADDRESS(SLCDC_BASE_ADDR + 0x20)); +#endif + /* set length */ + __raw_writel(SLCDC_MEM_SIZE, IO_ADDRESS(SLCDC_BASE_ADDR + 0x04)); /* _reg_SLCDC_DBUF_SIZE */ + /* set automode 01 for data */ + reg = __raw_readl(IO_ADDRESS(SLCDC_BASE_ADDR + 0x20)); + /*_reg_SLCDC_LCD_CTRL_STAT*/ + reg |= 0x00000800; + __raw_writel(reg, IO_ADDRESS(SLCDC_BASE_ADDR + 0x20)); + + /* set GO */ + reg = __raw_readl(IO_ADDRESS(SLCDC_BASE_ADDR + 0x20)); + /*_reg_SLCDC_LCD_CTRL_STAT*/ + + reg |= 0x1; + __raw_writel(reg, IO_ADDRESS(SLCDC_BASE_ADDR + 0x20)); + slcdc_delay(0xffff); + + return; +} + +/** + *@brief slcdc timer initialization + * + * Function Name: init_slcdc_timer + * + * Description:This helper routine prepare the SLCDC timer for refreshing the + * screen + * + **/ +static void init_slcdc_timer(void) +{ + + init_timer(&slcdc_timer); + slcdc_timer.expires = jiffies + SLCDC_REFRESH_RATE; + slcdc_timer.data = 0; + slcdc_timer.function = slcdc_timer_func; + add_timer(&slcdc_timer); + start_fb_timer_flag = 1; +} + +/** + *@brief slcdc open function + * + * Function Name: slcdc_open + * + * + * Description: This is the open routine for the driver. And this function \n + * will be called while the module being opened. In this function, it will\n + * @li configure GPIO for serial/parallel + * @li slcdc reset + * @li init slcd registers + * @li init waitqueue + * @li get rca, select the card + * @li send some command for panel configuration + * + *@param inode the pointer to the inode descripter + *@param filp the pointer to the file descripter + * + *@return int return status + * @li 0 sucessful + * @li other failed + * Modification History: + * Dec,2003, Karen first version + * June,2004, Shirley update for Parallel mode + * + **/ + +int slcdc_open(struct inode *inode, struct file *filp) +{ + volatile unsigned long reg; + + TRACE("slcdc_open: ----\n"); + + /* init dev */ +#ifdef SLCDC_SERIAL_MODE + slcdc_gpio_serial(); +#else + slcdc_gpio_paralle(); +#endif + + slcdc_init_dev(); + slcdc_reset(0); /*pull reset low */ + /* init slcd registers */ + slcdc_init_reg(); + + /* init waitqueue */ + init_waitqueue_head(&slcdc_wait); + /* send some command for panel configuration */ + slcdc_config_panel(); + reg = __raw_readl(IO_ADDRESS(SLCDC_BASE_ADDR + 0x1C)); + /*_reg_SLCDC_LCD_TXCONFIG*/ + TRACE("TRANS_CONFIG_REG=%x \n", (unsigned int)reg); + + /* init slcdc timer, and start timer */ + init_slcdc_timer(); + return 0; +} + +/** + *@brief slcdc init function + * + * Function Name: slcdc_init + * + * + *@return int return status + * @li 0 sucess + * @li other failure + * + * Description: This is the initialization routine for the driver. And this + * function \n will be called while the module being installed. + * In this function, it will \n register char device,request + * slcdc irq, initialize the buffer,register to\npower management. + * + * Modification History: + * Dec,2003, Karen update for MX21 TO2 + * + **/ + +int __init slcdc_init(void) +{ + int tmp, err; + + INFO("SLCDC Driver \n"); + INFO("Motorola SPS-SuZhou \n"); + + _init_fbinfo(); + + if (slcdcfb_set_var(&slcdc_fb_info)) ; /* current_par.allow_modeset = 0; */ + + register_framebuffer(&slcdc_fb_info); + + devfs_mk_cdev(slcdc_device_num, S_IFCHR | S_IRUGO | S_IWUSR, "slcdc"); + + if (slcdc_major != 0) { + /* use user supplied major number */ + slcdc_device_num = MKDEV(slcdc_major, slcdc_minor); + + err = register_chrdev_region(slcdc_device_num, 1, MODULE_NAME); + } else { + /* auto create device major number */ + err = + alloc_chrdev_region(&slcdc_device_num, slcdc_minor, 1, + MODULE_NAME); + } + + if (err < 0) { + TRACE("%s driver: Unable to register chrdev region\n", + MODULE_NAME); + return err; + } + + cdev_init(&slcdc_dev, &g_slcdc_fops); + slcdc_dev.owner = THIS_MODULE; + slcdc_dev.ops = &g_slcdc_fops; + + if ((err = cdev_add(&slcdc_dev, slcdc_device_num, 1))) { + TRACE + ("%s driver: Unable to create character device. Error code=%d\n", + MODULE_NAME, err); + return -ENODEV; + } + + /* init interrupt */ + tmp = request_irq(SLCDC_IRQ, + (void *)slcdc_isr, + SA_INTERRUPT | SA_SHIRQ, MODULE_NAME, MODULE_NAME); + if (tmp) { + printk("slcdc_init:cannot init major= %d irq=%d\n", + MAJOR(slcdc_device_num), SLCDC_IRQ); + devfs_remove(MODULE_NAME); + cdev_del(&slcdc_dev); + return -1; + } + + /* init buffer */ + /* initialize buffer address */ + g_slcdc_dbuffer_address = NULL; + g_slcdc_dbuffer_phyaddress = NULL; + g_slcdc_cbuffer_address = NULL; + g_slcdc_cbuffer_phyaddress = NULL; + slcdc_init_buffer(); + + g_slcdc_status = 0; + + return 0; +} + +/** + *@brief slcdc cleanup function + * + * Function Name: slcdc_cleanup + * + *@return None + * + * Description: This is the cleanup routine for the driver. And this function \n + * will be called while the module being removed. In this function, it will \n + * cleanup all the registered entries + * + * Modification History: + * Dec 2003, Karen update for MX21 TO2 + * + **/ + +void __exit slcdc_cleanup(void) +{ + + unregister_chrdev_region(slcdc_device_num, 1); + + devfs_remove(MODULE_NAME); + + /*Do some cleanup work */ + free_irq(SLCDC_IRQ, MODULE_NAME); + + unregister_framebuffer(&slcdc_fb_info); + + slcdc_free_buffer(); + + cdev_del(&slcdc_dev); + + return; +} + +module_init(slcdc_init); +module_exit(slcdc_cleanup); + +/*////////////// Frame Buffer Suport /////////////////////////////////// */ + +/** + * _check_var - Validates a var passed in. + * @var: frame buffer variable screen structure + * @info: frame buffer structure that represents a single frame buffer + * + * Checks to see if the hardware supports the state requested by var passed + * in. This function does not alter the hardware state! If the var passed in + * is slightly off by what the hardware can support then we alter the var + * PASSED in to what we can do. If the hardware doesn't support mode change + * a -EINVAL will be returned by the upper layers. + * + * Returns negative errno on error, or zero on success. + */ +static int _check_var(struct fb_var_screeninfo *var, struct fb_info *info) +{ + const struct slcdcfb_par *par = (const struct slcdcfb_par *)info->par; + + if (var->xres > current_par.max_xres) + var->xres = current_par.max_xres; + if (var->yres > current_par.max_yres) + var->yres = current_par.max_yres; + + var->xres_virtual = var->xres_virtual < par->xres + ? par->xres : var->xres_virtual; + var->yres_virtual = var->yres_virtual < par->yres + ? par->yres : var->yres_virtual; + var->bits_per_pixel = par->bits_per_pixel; + + switch (var->bits_per_pixel) { + case 2: + case 4: + case 8: + var->red.length = 4; + var->green = var->red; + var->blue = var->red; + var->transp.length = 0; + break; + + case 12: /* RGB 444 */ + case 16: /* RGB 565 */ + TRACE("16->a\n"); + var->red.length = 4; + var->blue.length = 4; + var->green.length = 4; + var->transp.length = 0; +#ifdef __LITTLE_ENDIAN + TRACE("16->b\n"); + var->red.offset = 8; + var->green.offset = 4; + var->blue.offset = 0; + var->transp.offset = 0; +#endif /* __LITTLE_ENDIAN */ + break; + + default: + return -EINVAL; + } + + /* *var->screen_start_address=(u_char*)((u_long)g_slcdc_dbuffer_phyaddress ); + *var->v_screen_start_address=(u_char*)((u_long)g_slcdc_dbuffer_address ); */ + + var->height = -1; + var->width = -1; + var->grayscale = 0; + var->nonstd = 0; + + var->pixclock = -1; + var->left_margin = -1; + var->right_margin = -1; + var->upper_margin = -1; + var->lower_margin = -1; + var->hsync_len = -1; + var->vsync_len = -1; + + var->vmode = FB_VMODE_NONINTERLACED; + var->sync = 0; + + return 0; +} + +/** + *@brief Use current_par to set a var structure + * + *@param var Input var data + *@param par LCD controller parameters + * + *@return If no error, return 0 + * + */ +static int _encode_var(struct fb_var_screeninfo *var, struct slcdcfb_par *par) +{ + FUNC_START + /* Don't know if really want to zero var on entry. + Look at set_var to see. If so, may need to add extra params to par */ + memset(var, 0, sizeof(*var)); + var->xres = par->xres; + TRACE("var->xress=%d\n", var->xres); + var->yres = par->yres; + TRACE("var->yres=%d\n", var->yres); + var->xres_virtual = par->xres_virtual; + TRACE("var->xres_virtual=%d\n", var->xres_virtual); + var->yres_virtual = par->yres_virtual; + TRACE("var->yres_virtual=%d\n", var->yres_virtual); + + var->bits_per_pixel = par->bits_per_pixel; + TRACE("var->bits_per_pixel=%d\n", var->bits_per_pixel); + + switch (var->bits_per_pixel) { + case 2: + case 4: + case 8: + var->red.length = 4; + var->green = var->red; + var->blue = var->red; + var->transp.length = 0; + break; + case 12: /* This case should differ for Active/Passive mode */ + case 16: + TRACE("16->a\n"); + var->red.length = 4; + var->blue.length = 4; + var->green.length = 4; + var->transp.length = 0; +#ifdef __LITTLE_ENDIAN + TRACE("16->b\n"); + var->red.offset = 8; + var->green.offset = 4; + var->blue.offset = 0; + var->transp.offset = 0; +#endif /* __LITTLE_ENDIAN */ + break; + } + + FUNC_END return 0; +} + +/** + *@brief Get the video params out of 'var'. If a value doesn't fit, + * round it up,if it's too big, return -EINVAL. + * + *@warning Round up in the following order: bits_per_pixel, xres, + * yres, xres_virtual, yres_virtual, xoffset, yoffset, grayscale, + * bitfields, horizontal timing, vertical timing. + * + *@param var Input var data + *@param par LCD controller parameters + * + *@return If no error, return 0 + */ +static int _decode_var(struct fb_info *info) +{ + struct fb_var_screeninfo *var = &info->var; + struct slcdcfb_par par_var; + struct slcdcfb_par *par = &par_var; + + FUNC_START *par = current_par; + + if ((par->xres = var->xres) < MIN_XRES) + par->xres = MIN_XRES; + if ((par->yres = var->yres) < MIN_YRES) + par->yres = MIN_YRES; + if (par->xres > current_par.max_xres) + par->xres = current_par.max_xres; + if (par->yres > current_par.max_yres) + par->yres = current_par.max_yres; + par->xres_virtual = var->xres_virtual < par->xres + ? par->xres : var->xres_virtual; + par->yres_virtual = var->yres_virtual < par->yres + ? par->yres : var->yres_virtual; + par->bits_per_pixel = var->bits_per_pixel; + + switch (par->bits_per_pixel) { + + case 4: + par->visual = FB_VISUAL_PSEUDOCOLOR; + par->palette_size = 16; + break; + + case 8: + par->visual = FB_VISUAL_PSEUDOCOLOR; + par->palette_size = 256; + break; + + case 12: /* RGB 444 */ + case 16: /* RGB 565 */ + par->visual = FB_VISUAL_TRUECOLOR; + par->palette_size = 0; + break; + + default: + return -EINVAL; + } + + par->screen_start_address = + (u_char *) ((u_long) g_slcdc_dbuffer_phyaddress); + par->v_screen_start_address = + (u_char *) ((u_long) g_slcdc_dbuffer_address); + + /* update_lcd ? */ + + FUNC_END return 0; +} + +/** + *@brief Set current_par by var, also set display data, specially the console + * related file operations, then enable the SLCD controller, and set cmap to + * hardware. + * + *@param var Iuput data pointer + *@param con Console ID + *@param info Frame buffer information + * + *@return If no error, return 0. + * + **/ +static int slcdcfb_set_var(struct fb_info *info) +{ + struct display *display; + int err; + struct slcdcfb_par par; + struct fb_var_screeninfo *var = &info->var; + + FUNC_START; + + display = &global_disp; /* Default display settings */ + + /* Decode var contents into a par structure, adjusting any */ + /* out of range values. */ + if ((err = _decode_var(info))) { + TRACE("decode var error!"); + return err; + } + + /* Store adjusted par values into var structure */ + _encode_var(var, &par); + + if ((var->activate & FB_ACTIVATE_MASK) == FB_ACTIVATE_TEST) + return 0; + + else if (((var->activate & FB_ACTIVATE_MASK) != FB_ACTIVATE_NOW) && + ((var->activate & FB_ACTIVATE_MASK) != FB_ACTIVATE_NXTOPEN)) + return -EINVAL; + + display->inverse = 0; + + init_var = *var; /* TODO:gcc support structure copy? */ + + FUNC_END; + return 0; +} + +/** + *@brief Blank the screen, if blank, disable LCD controller, while if no blank + * set cmap and enable LCD controller + * + *@param blank Blank flag + *@param info Frame buffer database + * + *@return VOID + */ +static int slcdcfb_blank(int blank, struct fb_info *info) +{ + + if (blank) { + slcdc_display_off(); + } else { + slcdc_display_normal(); + } + + return 0; +} + +/** + *@brief Initialize frame buffer. While 16bpp is used to store a 12 bits pixels + * packet, it is not a really 16bpp system, maybe in-compatiable with + * other system or GUI.There are some field in var which specify + * the red/green/blue offset in a 16bit word, just little endian is + * concerned + * + *@return VOID + **/ +static void __init _init_fbinfo(void) +{ + FUNC_START; + + slcdc_fb_info.node = -1; + slcdc_fb_info.flags = 0; /* Low-level driver is not a module */ + slcdc_fb_info.fbops = &slcdcfb_ops; + slcdc_fb_info.monspecs = monspecs; + + strcpy(slcdc_fb_info.fix.id, "SLCDC"); + /* FIXME... set fix parameters */ + /* + * * setup initial parameters + * */ + memset(&init_var, 0, sizeof(init_var)); + memset(¤t_par, 0, sizeof(current_par)); + + init_var.transp.length = 0; + init_var.nonstd = 0; + init_var.activate = FB_ACTIVATE_NOW; + init_var.xoffset = 0; + init_var.yoffset = 0; + init_var.height = -1; + init_var.width = -1; + init_var.vmode = FB_VMODE_NONINTERLACED; + + /*xres and yres might be set when loading the module, if this driver is + built as module */ + current_par.max_xres = SLCDC_WIDTH; + current_par.max_yres = SLCDC_HIGH; + + current_par.max_bpp = SLCDC_BPP; + init_var.red.length = 5; + init_var.green.length = 6; + init_var.blue.length = 5; +#ifdef __LITTLE_ENDIAN + init_var.red.offset = 11; + init_var.green.offset = 5; + init_var.blue.offset = 0; +#endif /* __LITTLE_ENDIAN */ + init_var.grayscale = 16; + init_var.sync = 0; + init_var.pixclock = 171521; + + current_par.screen_start_address = NULL; + current_par.v_screen_start_address = NULL; + current_par.screen_memory_size = MAX_PIXEL_MEM_SIZE; + current_par.currcon = -1; + + init_var.xres = current_par.max_xres; + init_var.yres = current_par.max_yres; + init_var.xres_virtual = init_var.xres; + init_var.yres_virtual = init_var.yres; + init_var.bits_per_pixel = current_par.max_bpp; + + current_par.xres = init_var.xres; + current_par.yres = init_var.yres; + current_par.xres_virtual = init_var.xres; + current_par.yres_virtual = init_var.yres; + + /* initialize current screen information */ + memcpy(&slcdc_fb_info.var, &init_var, sizeof(init_var)); + + FUNC_END; +} + +/** + *@brief slcdc framebuffer open call + * + * Function Name: slcdcfb_open + * + * Description : This function is called by the framebuffer when the + * application requests. + * + *@return 0 on success any other value for failure + **/ +static int slcdcfb_open(struct fb_info *info, int user) +{ + return slcdc_open(NULL, 0); +} + +/** + *@brief slcdc framebuffer release call + * + * Function Name: slcdcfb_release + * + * Description : This function is called by the framebuffer when the + * application closes the link to framebuffer. + * + *@return 0 on success any other value for failure + **/ +static int slcdcfb_release(struct fb_info *info, int user) +{ + start_fb_timer_flag = 0; + return slcdc_release(NULL, 0); +} + +/*\@}*/ + +MODULE_PARM(irq_mask, "i"); +MODULE_PARM(irq_list, "1-4i"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/mxc/mxcfb.c b/drivers/video/mxc/mxcfb.c new file mode 100644 index 000000000000..ba631c1b1d7c --- /dev/null +++ b/drivers/video/mxc/mxcfb.c @@ -0,0 +1,1476 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @defgroup Framebuffer Framebuffer Driver for SDC and ADC. + */ + +/*! + * @file mxcfb.c + * + * @brief MXC Frame buffer driver for SDC + * + * @ingroup Framebuffer + */ + +/*! + * Include files + */ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/platform_device.h> +#include <linux/sched.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/fb.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/ioport.h> +#include <linux/dma-mapping.h> +#include <linux/clk.h> +#include <linux/console.h> +#include <asm/hardware.h> +#include <asm/io.h> +#include <asm/mach-types.h> +#include <asm/uaccess.h> +#include <asm/arch/ipu.h> +#include <asm/arch/mxcfb.h> + +/* + * Driver name + */ +#define MXCFB_NAME "mxc_sdc_fb" +/*! + * Structure containing the MXC specific framebuffer information. + */ +struct mxcfb_info { + int blank; + ipu_channel_t ipu_ch; + uint32_t ipu_ch_irq; + uint32_t cur_ipu_buf; + + u32 pseudo_palette[16]; + + struct semaphore flip_sem; + spinlock_t fb_lock; +}; + +struct mxcfb_data { + struct fb_info *fbi; + struct fb_info *fbi_ovl; + volatile int32_t vsync_flag; + wait_queue_head_t vsync_wq; + wait_queue_head_t suspend_wq; + bool suspended; + int backlight_level; +}; + +struct mxcfb_alloc_list { + struct list_head list; + dma_addr_t phy_addr; + void *cpu_addr; + u32 size; +}; + +static struct mxcfb_data mxcfb_drv_data; + +static char *fb_mode = NULL; +static unsigned long default_bpp = 16; +#ifdef CONFIG_FB_MXC_INTERNAL_MEM +static struct clk *iram_clk; +#endif +LIST_HEAD(fb_alloc_list); + +static uint32_t bpp_to_pixfmt(int bpp) +{ + uint32_t pixfmt = 0; + switch (bpp) { + case 24: + pixfmt = IPU_PIX_FMT_BGR24; + break; + case 32: + pixfmt = IPU_PIX_FMT_BGR32; + break; + case 16: + pixfmt = IPU_PIX_FMT_RGB565; + break; + } + return pixfmt; +} + +extern void gpio_lcd_active(void); +extern void gpio_lcd_inactive(void); +#ifdef CONFIG_FB_MXC_TVOUT +#include <linux/video_encoder.h> +/* + * FIXME: VGA mode is not defined by video_encoder.h + * while FS453 supports VGA output. + */ +#ifndef VIDEO_ENCODER_VGA +#define VIDEO_ENCODER_VGA 32 +#endif + +#define MODE_PAL "TV-PAL" +#define MODE_NTSC "TV-NTSC" +#define MODE_VGA "TV-VGA" + +extern int fs453_ioctl(unsigned int cmd, void *arg); +#endif +static irqreturn_t mxcfb_irq_handler(int irq, void *dev_id); +static int mxcfb_blank(int blank, struct fb_info *info); +static int mxcfb_map_video_memory(struct fb_info *fbi, bool use_internal_ram); +static int mxcfb_unmap_video_memory(struct fb_info *fbi); + +/* + * Set fixed framebuffer parameters based on variable settings. + * + * @param info framebuffer information pointer + */ +static int mxcfb_set_fix(struct fb_info *info) +{ + struct fb_fix_screeninfo *fix = &info->fix; + struct fb_var_screeninfo *var = &info->var; + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)info->par; + + if (mxc_fbi->ipu_ch == MEM_SDC_FG) + strncpy(fix->id, "DISP3 FG", 8); + else + strncpy(fix->id, "DISP3 BG", 8); + + fix->line_length = var->xres_virtual * var->bits_per_pixel / 8; + + fix->type = FB_TYPE_PACKED_PIXELS; + fix->accel = FB_ACCEL_NONE; + fix->visual = FB_VISUAL_TRUECOLOR; + fix->xpanstep = 1; + fix->ypanstep = 1; + + return 0; +} + +/* + * Set framebuffer parameters and change the operating mode. + * + * @param info framebuffer information pointer + */ +static int mxcfb_set_par(struct fb_info *fbi) +{ + int retval; + bool use_iram = false; + u32 mem_len; + ipu_di_signal_cfg_t sig_cfg; + ipu_panel_t mode = IPU_PANEL_TFT; + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par; + + if ((retval = wait_event_interruptible(mxcfb_drv_data.suspend_wq, + (mxcfb_drv_data.suspended == + false))) < 0) { + return retval; + } + + ipu_disable_irq(mxc_fbi->ipu_ch_irq); + ipu_disable_channel(mxc_fbi->ipu_ch, true); + ipu_uninit_channel(mxc_fbi->ipu_ch); + ipu_clear_irq(mxc_fbi->ipu_ch_irq); + mxcfb_set_fix(fbi); + + mem_len = fbi->var.yres_virtual * fbi->fix.line_length; + if (mem_len > fbi->fix.smem_len) { + if (fbi->fix.smem_start) + mxcfb_unmap_video_memory(fbi); + +#ifdef CONFIG_FB_MXC_INTERNAL_MEM + if (mxc_fbi->ipu_ch == MEM_SDC_BG) { + use_iram = true; + } +#endif + if (mxcfb_map_video_memory(fbi, use_iram) < 0) + return -ENOMEM; + } + + ipu_init_channel(mxc_fbi->ipu_ch, NULL); + + if (mxc_fbi->ipu_ch == MEM_SDC_BG) { + memset(&sig_cfg, 0, sizeof(sig_cfg)); + if (fbi->var.sync & FB_SYNC_HOR_HIGH_ACT) + sig_cfg.Hsync_pol = true; + if (fbi->var.sync & FB_SYNC_VERT_HIGH_ACT) + sig_cfg.Vsync_pol = true; + if (fbi->var.sync & FB_SYNC_CLK_INVERT) + sig_cfg.clk_pol = true; + if (fbi->var.sync & FB_SYNC_DATA_INVERT) + sig_cfg.data_pol = true; + if (fbi->var.sync & FB_SYNC_OE_ACT_HIGH) + sig_cfg.enable_pol = true; + if (fbi->var.sync & FB_SYNC_CLK_IDLE_EN) + sig_cfg.clkidle_en = true; + if (fbi->var.sync & FB_SYNC_SHARP_MODE) + mode = IPU_PANEL_SHARP_TFT; + + dev_dbg(fbi->device, "pixclock = %ul Hz\n", + (u32) (PICOS2KHZ(fbi->var.pixclock) * 1000UL)); + + if (ipu_sdc_init_panel(mode, + (PICOS2KHZ(fbi->var.pixclock)) * 1000UL, + fbi->var.xres, fbi->var.yres, + (fbi->var.sync & FB_SYNC_SWAP_RGB) ? + IPU_PIX_FMT_BGR666 : IPU_PIX_FMT_RGB666, + fbi->var.left_margin, + fbi->var.hsync_len, + fbi->var.right_margin + + fbi->var.hsync_len, + fbi->var.upper_margin, + fbi->var.vsync_len, + fbi->var.lower_margin + + fbi->var.vsync_len, sig_cfg) != 0) { + dev_err(fbi->device, + "mxcfb: Error initializing panel.\n"); + return -EINVAL; + } + } + + ipu_sdc_set_window_pos(mxc_fbi->ipu_ch, 0, 0); + + mxc_fbi->cur_ipu_buf = 0; + sema_init(&mxc_fbi->flip_sem, 1); + + retval = ipu_init_channel_buffer(mxc_fbi->ipu_ch, IPU_INPUT_BUFFER, + bpp_to_pixfmt(fbi->var.bits_per_pixel), + fbi->var.xres, fbi->var.yres, + fbi->var.xres_virtual, + IPU_ROTATE_NONE, + fbi->fix.smem_start, + fbi->fix.smem_start + + (fbi->fix.line_length * fbi->var.yres), + 0, 0); + if (retval) { + dev_err(fbi->device, + "ipu_init_channel_buffer error %d\n", retval); + return retval; + } + + if (mxc_fbi->blank == FB_BLANK_UNBLANK) { + ipu_select_buffer(mxc_fbi->ipu_ch, IPU_INPUT_BUFFER, 0); + ipu_enable_channel(mxc_fbi->ipu_ch); + } + + return 0; +} + +/* + * Check framebuffer variable parameters and adjust to valid values. + * + * @param var framebuffer variable parameters + * + * @param info framebuffer information pointer + */ +static int mxcfb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) +{ + u32 vtotal; + u32 htotal; + + if (var->xres_virtual < var->xres) + var->xres_virtual = var->xres; + if (var->yres_virtual < var->yres) + var->yres_virtual = var->yres; + +#ifdef CONFIG_FB_MXC_INTERNAL_MEM + if ((info->fix.smem_start == FB_RAM_BASE_ADDR) && + ((var->yres_virtual * var->xres_virtual * var->bits_per_pixel / 8) > + FB_RAM_SIZE)) { + return -EINVAL; + } +#endif + + if ((var->bits_per_pixel != 32) && (var->bits_per_pixel != 24) && + (var->bits_per_pixel != 16)) { + var->bits_per_pixel = default_bpp; + } + + switch (var->bits_per_pixel) { + case 16: + var->red.length = 5; + var->red.offset = 11; + var->red.msb_right = 0; + + var->green.length = 6; + var->green.offset = 5; + var->green.msb_right = 0; + + var->blue.length = 5; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 0; + var->transp.offset = 0; + var->transp.msb_right = 0; + break; + case 24: + var->red.length = 8; + var->red.offset = 16; + var->red.msb_right = 0; + + var->green.length = 8; + var->green.offset = 8; + var->green.msb_right = 0; + + var->blue.length = 8; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 0; + var->transp.offset = 0; + var->transp.msb_right = 0; + break; + case 32: + var->red.length = 8; + var->red.offset = 16; + var->red.msb_right = 0; + + var->green.length = 8; + var->green.offset = 8; + var->green.msb_right = 0; + + var->blue.length = 8; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 8; + var->transp.offset = 24; + var->transp.msb_right = 0; + break; + } + + if (var->pixclock < 1000) { + htotal = var->xres + var->right_margin + var->hsync_len + + var->left_margin; + vtotal = var->yres + var->lower_margin + var->vsync_len + + var->upper_margin; + var->pixclock = (vtotal * htotal * 6UL) / 100UL; + var->pixclock = KHZ2PICOS(var->pixclock); + dev_dbg(info->device, + "pixclock set for 60Hz refresh = %u ps\n", + var->pixclock); + } + + var->height = -1; + var->width = -1; + var->grayscale = 0; + + /* Copy nonstd field to/from sync for fbset usage */ + var->sync |= var->nonstd; + var->nonstd |= var->sync; + + return 0; +} + +static inline u_int _chan_to_field(u_int chan, struct fb_bitfield *bf) +{ + chan &= 0xffff; + chan >>= 16 - bf->length; + return chan << bf->offset; +} +static int +mxcfb_setcolreg(u_int regno, u_int red, u_int green, u_int blue, + u_int trans, struct fb_info *fbi) +{ + unsigned int val; + int ret = 1; + + /* + * If greyscale is true, then we convert the RGB value + * to greyscale no matter what visual we are using. + */ + if (fbi->var.grayscale) + red = green = blue = (19595 * red + 38470 * green + + 7471 * blue) >> 16; + switch (fbi->fix.visual) { + case FB_VISUAL_TRUECOLOR: + /* + * 16-bit True Colour. We encode the RGB value + * according to the RGB bitfield information. + */ + if (regno < 16) { + u32 *pal = fbi->pseudo_palette; + + val = _chan_to_field(red, &fbi->var.red); + val |= _chan_to_field(green, &fbi->var.green); + val |= _chan_to_field(blue, &fbi->var.blue); + + pal[regno] = val; + ret = 0; + } + break; + + case FB_VISUAL_STATIC_PSEUDOCOLOR: + case FB_VISUAL_PSEUDOCOLOR: + break; + } + + return ret; +} + +/* + * Function to handle custom ioctls for MXC framebuffer. + * + * @param inode inode struct + * + * @param file file struct + * + * @param cmd Ioctl command to handle + * + * @param arg User pointer to command arguments + * + * @param fbi framebuffer information pointer + */ +static int mxcfb_ioctl(struct fb_info *fbi, unsigned int cmd, unsigned long arg) +{ + int retval = 0; + + if ((retval = wait_event_interruptible(mxcfb_drv_data.suspend_wq, + (mxcfb_drv_data.suspended == + false))) < 0) { + return retval; + } + + switch (cmd) { + case MXCFB_SET_GBL_ALPHA: + { + struct mxcfb_gbl_alpha ga; + if (copy_from_user(&ga, (void *)arg, sizeof(ga))) { + retval = -EFAULT; + break; + } + retval = + ipu_sdc_set_global_alpha((bool) ga.enable, + ga.alpha); + dev_dbg(fbi->device, "Set global alpha to %d\n", + ga.alpha); + break; + } + case MXCFB_SET_CLR_KEY: + { + struct mxcfb_color_key key; + if (copy_from_user(&key, (void *)arg, sizeof(key))) { + retval = -EFAULT; + break; + } + retval = ipu_sdc_set_color_key(MEM_SDC_BG, key.enable, + key.color_key); + dev_dbg(fbi->device, "Set color key to 0x%08X\n", + key.color_key); + break; + } + case MXCFB_WAIT_FOR_VSYNC: + { +#ifndef CONFIG_ARCH_MX3 + mxcfb_drv_data.vsync_flag = 0; + ipu_enable_irq(IPU_IRQ_SDC_DISP3_VSYNC); + if (!wait_event_interruptible_timeout + (mxcfb_drv_data.vsync_wq, + mxcfb_drv_data.vsync_flag != 0, 1 * HZ)) { + dev_err(fbi->device, + "MXCFB_WAIT_FOR_VSYNC: timeout\n"); + retval = -ETIME; + break; + } else if (signal_pending(current)) { + dev_err(fbi->device, + "MXCFB_WAIT_FOR_VSYNC: interrupt received\n"); + retval = -ERESTARTSYS; + break; + } +#endif + break; + } +#ifdef CONFIG_FB_MXC_TVOUT + case ENCODER_GET_CAPABILITIES: + { + struct video_encoder_capability cap; + + if ((retval = fs453_ioctl(cmd, &cap))) + break; + + if (copy_to_user((void *)arg, &cap, sizeof(cap))) + retval = -EFAULT; + break; + } + case ENCODER_SET_NORM: + { + unsigned long mode; + char *smode; + struct fb_var_screeninfo var; + + if (copy_from_user(&mode, (void *)arg, sizeof(mode))) { + retval = -EFAULT; + break; + } + if ((retval = fs453_ioctl(cmd, &mode))) + break; + + if (mode == VIDEO_ENCODER_PAL) + smode = MODE_PAL; + else if (mode == VIDEO_ENCODER_NTSC) + smode = MODE_NTSC; + else + smode = MODE_VGA; + + var = fbi->var; + var.nonstd = 0; + retval = fb_find_mode(&var, fbi, smode, mxcfb_modedb, + mxcfb_modedb_sz, NULL, + default_bpp); + if ((retval != 1) && (retval != 2)) { /* specified mode not found */ + retval = -ENODEV; + break; + } + + fbi->var = var; + fb_mode = smode; + retval = mxcfb_set_par(fbi); + break; + } + case ENCODER_SET_INPUT: + case ENCODER_SET_OUTPUT: + case ENCODER_ENABLE_OUTPUT: + { + unsigned long varg; + + if (copy_from_user(&varg, (void *)arg, sizeof(varg))) { + retval = -EFAULT; + break; + } + retval = fs453_ioctl(cmd, &varg); + break; + } +#endif + default: + retval = -EINVAL; + } + return retval; +} + +/* + * Function to handle custom ioctls for MXC framebuffer. + * + * @param inode inode struct + * + * @param file file struct + * + * @param cmd Ioctl command to handle + * + * @param arg User pointer to command arguments + * + * @param fbi framebuffer information pointer + */ +static int mxcfb_ioctl_ovl(struct fb_info *fbi, unsigned int cmd, + unsigned long arg) +{ + int retval = 0; + int __user *argp = (void __user *)arg; + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par; + + if ((retval = wait_event_interruptible(mxcfb_drv_data.suspend_wq, + (mxcfb_drv_data.suspended == + false))) < 0) { + return retval; + } + + switch (cmd) { + case FBIO_ALLOC: + { + int size; + struct mxcfb_alloc_list *mem; + + mem = kzalloc(sizeof(*mem), GFP_KERNEL); + if (mem == NULL) + return -ENOMEM; + + if (get_user(size, argp)) + return -EFAULT; + + mem->size = PAGE_ALIGN(size); + + mem->cpu_addr = dma_alloc_coherent(fbi->device, size, + &mem->phy_addr, + GFP_DMA); + if (mem->cpu_addr == NULL) { + kfree(mem); + return -ENOMEM; + } + + list_add(&mem->list, &fb_alloc_list); + + dev_dbg(fbi->device, "allocated %d bytes @ 0x%08X\n", + mem->size, mem->phy_addr); + + if (put_user(mem->phy_addr, argp)) + return -EFAULT; + + break; + } + case FBIO_FREE: + { + unsigned long offset; + struct mxcfb_alloc_list *mem; + + if (get_user(offset, argp)) + return -EFAULT; + + retval = -EINVAL; + list_for_each_entry(mem, &fb_alloc_list, list) { + if (mem->phy_addr == offset) { + list_del(&mem->list); + dma_free_coherent(fbi->device, + mem->size, + mem->cpu_addr, + mem->phy_addr); + kfree(mem); + retval = 0; + break; + } + } + + break; + } + case MXCFB_SET_OVERLAY_POS: + { + struct mxcfb_pos pos; + if (copy_from_user(&pos, (void *)arg, sizeof(pos))) { + retval = -EFAULT; + break; + } + retval = ipu_sdc_set_window_pos(mxc_fbi->ipu_ch, + pos.x, pos.y); + break; + } + default: + retval = -EINVAL; + } + return retval; +} + +/* + * mxcfb_blank(): + * Blank the display. + */ +static int mxcfb_blank(int blank, struct fb_info *info) +{ + int retval; + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)info->par; + + dev_dbg(info->device, "blank = %d\n", blank); + + if (mxc_fbi->blank == blank) + return 0; + + if ((retval = wait_event_interruptible(mxcfb_drv_data.suspend_wq, + (mxcfb_drv_data.suspended == + false))) < 0) { + return retval; + } + + mxc_fbi->blank = blank; + + switch (blank) { + case FB_BLANK_POWERDOWN: + case FB_BLANK_VSYNC_SUSPEND: + case FB_BLANK_HSYNC_SUSPEND: + case FB_BLANK_NORMAL: + ipu_disable_channel(MEM_SDC_BG, true); + gpio_lcd_inactive(); +#ifdef CONFIG_FB_MXC_TVOUT + if (fb_mode) { + int enable = 0; + + if ((strcmp(fb_mode, MODE_VGA) == 0) + || (strcmp(fb_mode, MODE_NTSC) == 0) + || (strcmp(fb_mode, MODE_PAL) == 0)) + fs453_ioctl(ENCODER_ENABLE_OUTPUT, &enable); + } +#endif + break; + case FB_BLANK_UNBLANK: + gpio_lcd_active(); + ipu_enable_channel(MEM_SDC_BG); +#ifdef CONFIG_FB_MXC_TVOUT + if (fb_mode) { + unsigned long mode = 0; + + if (strcmp(fb_mode, MODE_VGA) == 0) + mode = VIDEO_ENCODER_VGA; + else if (strcmp(fb_mode, MODE_NTSC) == 0) + mode = VIDEO_ENCODER_NTSC; + else if (strcmp(fb_mode, MODE_PAL) == 0) + mode = VIDEO_ENCODER_PAL; + if (mode) + fs453_ioctl(ENCODER_SET_NORM, &mode); + } +#endif + break; + } + return 0; +} + +/* + * mxcfb_blank_ovl(): + * Blank the display. + */ +static int mxcfb_blank_ovl(int blank, struct fb_info *info) +{ + int retval; + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)info->par; + + dev_dbg(info->device, "ovl blank = %d\n", blank); + + if (mxc_fbi->blank == blank) + return 0; + + if ((retval = wait_event_interruptible(mxcfb_drv_data.suspend_wq, + (mxcfb_drv_data.suspended == + false))) < 0) { + return retval; + } + + mxc_fbi->blank = blank; + + switch (blank) { + case FB_BLANK_POWERDOWN: + case FB_BLANK_VSYNC_SUSPEND: + case FB_BLANK_HSYNC_SUSPEND: + case FB_BLANK_NORMAL: + ipu_disable_channel(MEM_SDC_FG, true); + break; + case FB_BLANK_UNBLANK: + ipu_enable_channel(MEM_SDC_FG); + break; + } + return 0; +} + +/* + * Pan or Wrap the Display + * + * This call looks only at xoffset, yoffset and the FB_VMODE_YWRAP flag + * + * @param var Variable screen buffer information + * @param info Framebuffer information pointer + */ +static int +mxcfb_pan_display(struct fb_var_screeninfo *var, struct fb_info *info) +{ + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)info->par; + unsigned long lock_flags = 0; + int retval; + u_int y_bottom; + unsigned long base; + + if ((retval = wait_event_interruptible(mxcfb_drv_data.suspend_wq, + (mxcfb_drv_data.suspended == + false))) < 0) { + return retval; + } + + if (var->xoffset > 0) { + dev_dbg(info->device, "x panning not supported\n"); + return -EINVAL; + } + + if ((info->var.xoffset == var->xoffset) && + (info->var.yoffset == var->yoffset)) { + return 0; // No change, do nothing + } + + y_bottom = var->yoffset; + + if (!(var->vmode & FB_VMODE_YWRAP)) { + y_bottom += var->yres; + } + + if (y_bottom > info->var.yres_virtual) { + return -EINVAL; + } + + base = (var->yoffset * var->xres_virtual + var->xoffset); + base *= (var->bits_per_pixel) / 8; + base += info->fix.smem_start; + + down(&mxc_fbi->flip_sem); + + spin_lock_irqsave(&mxc_fbi->fb_lock, lock_flags); + + dev_dbg(info->device, "Updating SDC BG buf %d address=0x%08lX\n", + mxc_fbi->cur_ipu_buf, base); + + mxc_fbi->cur_ipu_buf = !mxc_fbi->cur_ipu_buf; + if (ipu_update_channel_buffer(mxc_fbi->ipu_ch, IPU_INPUT_BUFFER, + mxc_fbi->cur_ipu_buf, base) == 0) { + ipu_select_buffer(mxc_fbi->ipu_ch, IPU_INPUT_BUFFER, + mxc_fbi->cur_ipu_buf); + ipu_clear_irq(mxc_fbi->ipu_ch_irq); + ipu_enable_irq(mxc_fbi->ipu_ch_irq); + } else { + dev_err(info->device, + "Error updating SDC buf %d to address=0x%08lX\n", + mxc_fbi->cur_ipu_buf, base); + } + + spin_unlock_irqrestore(&mxc_fbi->fb_lock, lock_flags); + + dev_dbg(info->device, "Update complete\n"); + + info->var.xoffset = var->xoffset; + info->var.yoffset = var->yoffset; + + if (var->vmode & FB_VMODE_YWRAP) { + info->var.vmode |= FB_VMODE_YWRAP; + } else { + info->var.vmode &= ~FB_VMODE_YWRAP; + } + + return 0; +} + +/* + * Function to handle custom mmap for MXC framebuffer. + * + * @param fbi framebuffer information pointer + * + * @param vma Pointer to vm_area_struct + */ +static int mxcfb_mmap(struct fb_info *fbi, struct vm_area_struct *vma) +{ + bool found = false; + u32 len; + unsigned long offset = vma->vm_pgoff << PAGE_SHIFT; + struct mxcfb_alloc_list *mem; + + if (offset < fbi->fix.smem_len) { + /* mapping framebuffer memory */ + len = fbi->fix.smem_len - offset; + vma->vm_pgoff = (fbi->fix.smem_start + offset) >> PAGE_SHIFT; + } else { + list_for_each_entry(mem, &fb_alloc_list, list) { + if (offset == mem->phy_addr) { + found = true; + len = mem->size; + break; + } + } + if (!found) { + return -EINVAL; + } + } + + len = PAGE_ALIGN(len); + if (vma->vm_end - vma->vm_start > len) { + return -EINVAL; + } + + /* make buffers write-thru cacheable */ + vma->vm_page_prot = __pgprot(pgprot_val(vma->vm_page_prot) & + ~L_PTE_BUFFERABLE); + + vma->vm_flags |= VM_IO | VM_RESERVED; + + if (remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff, + vma->vm_end - vma->vm_start, vma->vm_page_prot)) { + dev_dbg(fbi->device, "mmap remap_pfn_range failed\n"); + return -ENOBUFS; + + } + + return 0; +} + +/*! + * This structure contains the pointers to the control functions that are + * invoked by the core framebuffer driver to perform operations like + * blitting, rectangle filling, copy regions and cursor definition. + */ +static struct fb_ops mxcfb_ops = { + .owner = THIS_MODULE, + .fb_set_par = mxcfb_set_par, + .fb_check_var = mxcfb_check_var, + .fb_setcolreg = mxcfb_setcolreg, + .fb_pan_display = mxcfb_pan_display, + .fb_ioctl = mxcfb_ioctl, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, + .fb_blank = mxcfb_blank, +}; + +static struct fb_ops mxcfb_ovl_ops = { + .owner = THIS_MODULE, + .fb_set_par = mxcfb_set_par, + .fb_check_var = mxcfb_check_var, + .fb_setcolreg = mxcfb_setcolreg, + .fb_pan_display = mxcfb_pan_display, + .fb_ioctl = mxcfb_ioctl_ovl, + .fb_mmap = mxcfb_mmap, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, + .fb_blank = mxcfb_blank_ovl, +}; + +static irqreturn_t mxcfb_vsync_irq_handler(int irq, void *dev_id) +{ + struct mxcfb_data *fb_data = dev_id; + + ipu_disable_irq(irq); + + fb_data->vsync_flag = 1; + wake_up_interruptible(&fb_data->vsync_wq); + return IRQ_HANDLED; +} + +static irqreturn_t mxcfb_irq_handler(int irq, void *dev_id) +{ + struct fb_info *fbi = dev_id; + struct mxcfb_info *mxc_fbi = fbi->par; + + up(&mxc_fbi->flip_sem); + ipu_disable_irq(irq); + return IRQ_HANDLED; +} + +#ifdef CONFIG_PM +/* + * Power management hooks. Note that we won't be called from IRQ context, + * unlike the blank functions above, so we may sleep. + */ + +/* + * Suspends the framebuffer and blanks the screen. Power management support + */ +static int mxcfb_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct mxcfb_data *drv_data = platform_get_drvdata(pdev); + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)drv_data->fbi->par; + struct mxcfb_info *mxc_fbi_ovl = + (struct mxcfb_info *)drv_data->fbi_ovl->par; +#ifdef CONFIG_FB_MXC_LOW_PWR_DISPLAY + void *fbmem; +#endif + + drv_data->suspended = true; + + acquire_console_sem(); + fb_set_suspend(drv_data->fbi, 1); + fb_set_suspend(drv_data->fbi_ovl, 1); + release_console_sem(); + + if (mxc_fbi_ovl->blank == FB_BLANK_UNBLANK) { + ipu_disable_channel(MEM_SDC_FG, true); + } + + if (mxc_fbi->blank == FB_BLANK_UNBLANK) { +#ifdef CONFIG_FB_MXC_LOW_PWR_DISPLAY + if (drv_data->fbi->fix.smem_start != FB_RAM_BASE_ADDR) { + fbmem = ioremap(FB_RAM_BASE_ADDR, FB_RAM_SIZE); + memcpy(fbmem, drv_data->fbi->screen_base, FB_RAM_SIZE); + iounmap(fbmem); + mxc_fbi->cur_ipu_buf = !mxc_fbi->cur_ipu_buf; + ipu_update_channel_buffer(MEM_SDC_BG, IPU_INPUT_BUFFER, + mxc_fbi->cur_ipu_buf, + FB_RAM_BASE_ADDR); + ipu_select_buffer(MEM_SDC_BG, IPU_INPUT_BUFFER, + mxc_fbi->cur_ipu_buf); + } + ipu_lowpwr_display_enable(); +#else + ipu_disable_channel(MEM_SDC_BG, true); + gpio_lcd_inactive(); +#endif +#ifdef CONFIG_FB_MXC_TVOUT + if (fb_mode) { + int enable = 0; + + if ((strcmp(fb_mode, MODE_VGA) == 0) + || (strcmp(fb_mode, MODE_NTSC) == 0) + || (strcmp(fb_mode, MODE_PAL) == 0)) + fs453_ioctl(ENCODER_ENABLE_OUTPUT, &enable); + } +#endif + } + return 0; +} + +/* + * Resumes the framebuffer and unblanks the screen. Power management support + */ +static int mxcfb_resume(struct platform_device *pdev) +{ + struct mxcfb_data *drv_data = platform_get_drvdata(pdev); + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)drv_data->fbi->par; + struct mxcfb_info *mxc_fbi_ovl = + (struct mxcfb_info *)drv_data->fbi_ovl->par; + + drv_data->suspended = false; + + if (mxc_fbi->blank == FB_BLANK_UNBLANK) { +#ifdef CONFIG_FB_MXC_LOW_PWR_DISPLAY + ipu_lowpwr_display_disable(); + if (drv_data->fbi->fix.smem_start != FB_RAM_BASE_ADDR) { + mxc_fbi->cur_ipu_buf = !mxc_fbi->cur_ipu_buf; + ipu_update_channel_buffer(MEM_SDC_BG, IPU_INPUT_BUFFER, + mxc_fbi->cur_ipu_buf, + drv_data->fbi->fix. + smem_start); + ipu_select_buffer(MEM_SDC_BG, IPU_INPUT_BUFFER, + mxc_fbi->cur_ipu_buf); + } +#else + gpio_lcd_active(); + ipu_enable_channel(MEM_SDC_BG); +#endif +#ifdef CONFIG_FB_MXC_TVOUT + if (fb_mode) { + u32 mode = 0; + + if (strcmp(fb_mode, MODE_VGA) == 0) + mode = VIDEO_ENCODER_VGA; + else if (strcmp(fb_mode, MODE_NTSC) == 0) + mode = VIDEO_ENCODER_NTSC; + else if (strcmp(fb_mode, MODE_PAL) == 0) + mode = VIDEO_ENCODER_PAL; + if (mode) + fs453_ioctl(ENCODER_SET_NORM, &mode); + } +#endif + } + + if (mxc_fbi_ovl->blank == FB_BLANK_UNBLANK) { + ipu_enable_channel(MEM_SDC_FG); + } + + acquire_console_sem(); + fb_set_suspend(drv_data->fbi, 0); + fb_set_suspend(drv_data->fbi_ovl, 0); + release_console_sem(); + + wake_up_interruptible(&drv_data->suspend_wq); + return 0; +} +#else +#define mxcfb_suspend NULL +#define mxcfb_resume NULL +#endif + +/* + * Main framebuffer functions + */ + +/*! + * Allocates the DRAM memory for the frame buffer. This buffer is remapped + * into a non-cached, non-buffered, memory region to allow palette and pixel + * writes to occur without flushing the cache. Once this area is remapped, + * all virtual memory access to the video memory should occur at the new region. + * + * @param fbi framebuffer information pointer + * + * @param use_internal_ram flag on whether to use internal RAM for memory + * + * @return Error code indicating success or failure + */ +static int mxcfb_map_video_memory(struct fb_info *fbi, bool use_internal_ram) +{ + int retval = 0; + +#ifdef CONFIG_FB_MXC_INTERNAL_MEM + if (use_internal_ram) { + fbi->fix.smem_len = FB_RAM_SIZE; + fbi->fix.smem_start = FB_RAM_BASE_ADDR; + if (fbi->fix.smem_len < + (fbi->var.yres_virtual * fbi->fix.line_length)) { + dev_err(fbi->device, + "Not enough internal RAM for framebuffer configuration\n"); + retval = -EINVAL; + goto err0; + } + + if (request_mem_region(fbi->fix.smem_start, fbi->fix.smem_len, + fbi->device->driver->name) == NULL) { + dev_err(fbi->device, + "Unable to request internal RAM\n"); + retval = -ENOMEM; + goto err0; + } + + if (!(fbi->screen_base = ioremap(fbi->fix.smem_start, + fbi->fix.smem_len))) { + dev_err(fbi->device, + "Unable to map fb memory to virtual address\n"); + release_mem_region(fbi->fix.smem_start, + fbi->fix.smem_len); + retval = -EIO; + goto err0; + } + + iram_clk = clk_get(NULL, "iram_clk"); + clk_enable(iram_clk); + } else +#endif + { + fbi->fix.smem_len = fbi->var.yres_virtual * + fbi->fix.line_length; + fbi->screen_base = + dma_alloc_writecombine(fbi->device, + fbi->fix.smem_len, + (dma_addr_t *) & fbi->fix.smem_start, + GFP_DMA); + + if (fbi->screen_base == 0) { + dev_err(fbi->device, + "Unable to allocate framebuffer memory\n"); + retval = -EBUSY; + goto err0; + } + } + + dev_dbg(fbi->device, "allocated fb @ paddr=0x%08X, size=%d.\n", + (uint32_t) fbi->fix.smem_start, fbi->fix.smem_len); + + fbi->screen_size = fbi->fix.smem_len; + + /* Clear the screen */ + memset((char *)fbi->screen_base, 0, fbi->fix.smem_len); + + return 0; + + err0: + fbi->fix.smem_len = 0; + fbi->fix.smem_start = 0; + fbi->screen_base = NULL; + return retval; +} + +/*! + * De-allocates the DRAM memory for the frame buffer. + * + * @param fbi framebuffer information pointer + * + * @return Error code indicating success or failure + */ +static int mxcfb_unmap_video_memory(struct fb_info *fbi) +{ +#ifdef CONFIG_FB_MXC_INTERNAL_MEM + if (fbi->fix.smem_start == FB_RAM_BASE_ADDR) { + iounmap(fbi->screen_base); + release_mem_region(fbi->fix.smem_start, fbi->fix.smem_len); + fbi->fix.smem_start = 0; + fbi->fix.smem_len = 0; + clk_disable(iram_clk); + } else +#endif + { + dma_free_writecombine(fbi->device, fbi->fix.smem_len, + fbi->screen_base, fbi->fix.smem_start); + } + fbi->screen_base = 0; + fbi->fix.smem_start = 0; + fbi->fix.smem_len = 0; + return 0; +} + +/*! + * Initializes the framebuffer information pointer. After allocating + * sufficient memory for the framebuffer structure, the fields are + * filled with custom information passed in from the configurable + * structures. This includes information such as bits per pixel, + * color maps, screen width/height and RGBA offsets. + * + * @return Framebuffer structure initialized with our information + */ +static struct fb_info *mxcfb_init_fbinfo(struct device *dev, struct fb_ops *ops) +{ + struct fb_info *fbi; + struct mxcfb_info *mxcfbi; + + /* + * Allocate sufficient memory for the fb structure + */ + fbi = framebuffer_alloc(sizeof(struct mxcfb_info), dev); + if (!fbi) + return NULL; + + mxcfbi = (struct mxcfb_info *)fbi->par; + + fbi->var.activate = FB_ACTIVATE_NOW; + + fbi->fbops = ops; + fbi->flags = FBINFO_FLAG_DEFAULT; + fbi->pseudo_palette = mxcfbi->pseudo_palette; + + spin_lock_init(&mxcfbi->fb_lock); + + /* + * Allocate colormap + */ + fb_alloc_cmap(&fbi->cmap, 16, 0); + + return fbi; +} + +/*! + * Probe routine for the framebuffer driver. It is called during the + * driver binding process. The following functions are performed in + * this routine: Framebuffer initialization, Memory allocation and + * mapping, Framebuffer registration, IPU initialization. + * + * @return Appropriate error code to the kernel common code + */ +static int mxcfb_probe(struct platform_device *pdev) +{ + char *mode = pdev->dev.platform_data; + struct fb_info *fbi; + struct mxcfb_info *mxcfbi; + struct fb_info *fbi_ovl; + int ret = 0; + + /* + * Initialize FB structures + */ + fbi = mxcfb_init_fbinfo(&pdev->dev, &mxcfb_ops); + if (!fbi) { + ret = -ENOMEM; + goto err0; + } + mxcfbi = (struct mxcfb_info *)fbi->par; + + mxcfbi->ipu_ch_irq = IPU_IRQ_SDC_BG_EOF; + mxcfbi->cur_ipu_buf = 0; + mxcfbi->ipu_ch = MEM_SDC_BG; + + ipu_sdc_set_global_alpha(true, 0xFF); + ipu_sdc_set_color_key(MEM_SDC_BG, false, 0); + + if (ipu_request_irq(IPU_IRQ_SDC_BG_EOF, mxcfb_irq_handler, 0, + MXCFB_NAME, fbi) != 0) { + dev_err(&pdev->dev, "Error registering BG irq handler.\n"); + ret = -EBUSY; + goto err1; + } + ipu_disable_irq(IPU_IRQ_SDC_BG_EOF); + + if (fb_mode == NULL) { + fb_mode = mode; + } + + if (!fb_find_mode(&fbi->var, fbi, fb_mode, mxcfb_modedb, + mxcfb_modedb_sz, NULL, default_bpp)) { + ret = -EBUSY; + goto err2; + } + fb_videomode_to_modelist(mxcfb_modedb, mxcfb_modedb_sz, &fbi->modelist); + + /* Default Y virtual size is 2x panel size */ +#ifndef CONFIG_FB_MXC_INTERNAL_MEM + fbi->var.yres_virtual = fbi->var.yres * 2; +#endif + + mxcfb_drv_data.fbi = fbi; + mxcfb_drv_data.backlight_level = 255; + mxcfb_drv_data.suspended = false; + init_waitqueue_head(&mxcfb_drv_data.suspend_wq); + + mxcfbi->blank = FB_BLANK_NORMAL; + ret = mxcfb_set_par(fbi); + if (ret < 0) { + goto err2; + } + mxcfb_blank(FB_BLANK_UNBLANK, fbi); + + /* + * Register framebuffer + */ + ret = register_framebuffer(fbi); + if (ret < 0) { + goto err2; + } + + /* + * Initialize Overlay FB structures + */ + fbi_ovl = mxcfb_init_fbinfo(&pdev->dev, &mxcfb_ovl_ops); + if (!fbi_ovl) { + ret = -ENOMEM; + goto err3; + } + mxcfb_drv_data.fbi_ovl = fbi_ovl; + mxcfbi = (struct mxcfb_info *)fbi_ovl->par; + + mxcfbi->ipu_ch_irq = IPU_IRQ_SDC_FG_EOF; + mxcfbi->cur_ipu_buf = 0; + mxcfbi->ipu_ch = MEM_SDC_FG; + + if (ipu_request_irq(IPU_IRQ_SDC_FG_EOF, mxcfb_irq_handler, 0, + MXCFB_NAME, fbi_ovl) != 0) { + dev_err(fbi->device, "Error registering FG irq handler.\n"); + ret = -EBUSY; + goto err4; + } + ipu_disable_irq(mxcfbi->ipu_ch_irq); + + /* Default Y virtual size is 2x panel size */ + fbi_ovl->var = fbi->var; + fbi_ovl->var.yres_virtual = fbi->var.yres * 2; + + /* Overlay is blanked by default */ + mxcfbi->blank = FB_BLANK_NORMAL; + + ret = mxcfb_set_par(fbi_ovl); + if (ret < 0) { + goto err5; + } + + /* + * Register overlay framebuffer + */ + ret = register_framebuffer(fbi_ovl); + if (ret < 0) { + goto err5; + } + + platform_set_drvdata(pdev, &mxcfb_drv_data); + + init_waitqueue_head(&mxcfb_drv_data.vsync_wq); + if (!cpu_is_mx31() && !cpu_is_mx32()) { + if ((ret = ipu_request_irq(IPU_IRQ_SDC_DISP3_VSYNC, + mxcfb_vsync_irq_handler, + 0, MXCFB_NAME, + &mxcfb_drv_data)) < 0) { + goto err6; + } + ipu_disable_irq(IPU_IRQ_SDC_DISP3_VSYNC); + } + + printk(KERN_INFO "mxcfb: fb registered, using mode %s\n", fb_mode); + return 0; + + err6: + unregister_framebuffer(fbi_ovl); + err5: + ipu_free_irq(IPU_IRQ_SDC_FG_EOF, fbi_ovl); + err4: + fb_dealloc_cmap(&fbi_ovl->cmap); + framebuffer_release(fbi_ovl); + err3: + unregister_framebuffer(fbi); + err2: + ipu_free_irq(IPU_IRQ_SDC_BG_EOF, fbi); + err1: + fb_dealloc_cmap(&fbi->cmap); + framebuffer_release(fbi); + err0: + printk(KERN_ERR "mxcfb: failed to register fb\n"); + return ret; +} + +/*! + * This structure contains pointers to the power management callback functions. + */ +static struct platform_driver mxcfb_driver = { + .driver = { + .name = MXCFB_NAME, + }, + .probe = mxcfb_probe, + .suspend = mxcfb_suspend, + .resume = mxcfb_resume, +}; + +/* + * Parse user specified options (`video=trident:') + * example: + * video=trident:800x600,bpp=16,noaccel + */ +int mxcfb_setup(char *options) +{ + char *opt; + if (!options || !*options) + return 0; + while ((opt = strsep(&options, ",")) != NULL) { + if (!*opt) + continue; + if (!strncmp(opt, "bpp=", 4)) + default_bpp = simple_strtoul(opt + 4, NULL, 0); + else + fb_mode = opt; + } + return 0; +} + +/*! + * Main entry function for the framebuffer. The function registers the power + * management callback functions with the kernel and also registers the MXCFB + * callback functions with the core Linux framebuffer driver \b fbmem.c + * + * @return Error code indicating success or failure + */ +int __init mxcfb_init(void) +{ + int ret = 0; +#ifndef MODULE + char *option = NULL; +#endif + +#ifndef MODULE + if (fb_get_options("mxcfb", &option)) + return -ENODEV; + mxcfb_setup(option); +#endif + + ret = platform_driver_register(&mxcfb_driver); + return ret; +} + +void mxcfb_exit(void) +{ + struct fb_info *fbi = mxcfb_drv_data.fbi; + + if (fbi) { + mxcfb_unmap_video_memory(fbi); + + if (&fbi->cmap) + fb_dealloc_cmap(&fbi->cmap); + + unregister_framebuffer(fbi); + framebuffer_release(fbi); + } + + fbi = mxcfb_drv_data.fbi_ovl; + if (fbi) { + mxcfb_unmap_video_memory(fbi); + + if (&fbi->cmap) + fb_dealloc_cmap(&fbi->cmap); + + unregister_framebuffer(fbi); + framebuffer_release(fbi); + } +#ifndef CONFIG_ARCH_MX3 + ipu_free_irq(IPU_IRQ_SDC_DISP3_VSYNC, &mxcfb_drv_data); +#endif + + platform_driver_unregister(&mxcfb_driver); +} + +module_init(mxcfb_init); +module_exit(mxcfb_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MXC framebuffer driver"); +MODULE_SUPPORTED_DEVICE("fb"); diff --git a/drivers/video/mxc/mxcfb_epson.c b/drivers/video/mxc/mxcfb_epson.c new file mode 100644 index 000000000000..673ca26f4684 --- /dev/null +++ b/drivers/video/mxc/mxcfb_epson.c @@ -0,0 +1,1158 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mxcfb_epson.c + * + * @brief MXC Frame buffer driver for ADC + * + * @ingroup Framebuffer + */ + +/*! + * Include files + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/fb.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/ioport.h> +#include <linux/platform_device.h> +#include <linux/dma-mapping.h> +#include <asm/hardware.h> +#include <asm/io.h> +#include <asm/mach-types.h> +#include <asm/uaccess.h> +#include <asm/arch/ipu.h> +#include <asm/arch/mxcfb.h> + +#define PARTIAL_REFRESH +#define MXCFB_REFRESH_DEFAULT MXCFB_REFRESH_PARTIAL +/* + * Driver name + */ +#define MXCFB_NAME "MXCFB_EPSON" + +#define MXCFB_SCREEN_TOP_OFFSET 0 +#define MXCFB_SCREEN_LEFT_OFFSET 2 +#define MXCFB_SCREEN_WIDTH 176 +#define MXCFB_SCREEN_HEIGHT 220 + +/*! + * Enum defining Epson panel commands. + */ +enum { + DISON = 0xAF, + DISOFF = 0xAE, + DISCTL = 0xCA, + SD_CSET = 0x15, + SD_PSET = 0x75, + DATCTL = 0xBC, + SLPIN = 0x95, + SLPOUT = 0x94, + DISNOR = 0xA6, + RAMWR = 0x5C, + VOLCTR = 0xC6, + GCP16 = 0xCC, + GCP64 = 0xCB, +}; + +struct mxcfb_info { + int open_count; + int blank; + uint32_t disp_num; + + u32 pseudo_palette[16]; + + int32_t cur_update_mode; + dma_addr_t alloc_start_paddr; + void *alloc_start_vaddr; + u32 alloc_size; + uint32_t snoop_window_size; +}; + +struct mxcfb_data { + struct fb_info *fbi; + volatile int32_t vsync_flag; + wait_queue_head_t vsync_wq; + wait_queue_head_t suspend_wq; + bool suspended; +}; + +static struct mxcfb_data mxcfb_drv_data; +static unsigned long default_bpp = 16; + +void slcd_gpio_config(void); +extern void gpio_lcd_active(void); +static int mxcfb_blank(int blank, struct fb_info *fbi); + +static uint32_t bpp_to_pixfmt(int bpp) +{ + uint32_t pixfmt = 0; + switch (bpp) { + case 24: + pixfmt = IPU_PIX_FMT_BGR24; + break; + case 32: + pixfmt = IPU_PIX_FMT_BGR32; + break; + case 16: + pixfmt = IPU_PIX_FMT_RGB565; + break; + } + return pixfmt; +} + +/*! + * This function sets display region in the Epson panel + * + * @param disp display panel to config + * @param x1 x-coordinate of one vertex. + * @param x2 x-coordinate of second vertex. + * @param y1 y-coordinate of one vertex. + * @param y2 y-coordinate of second vertex. + */ +void set_panel_region(int disp, uint32_t x1, uint32_t x2, + uint32_t y1, uint32_t y2) +{ + uint32_t param[8]; + + memset(param, 0, sizeof(uint32_t) * 8); + param[0] = x1; + param[2] = x2; + param[4] = y1; + param[6] = y2; + + // SD_CSET + ipu_adc_write_cmd(disp, CMD, SD_CSET, param, 4); + // SD_PSET + + ipu_adc_write_cmd(disp, CMD, SD_PSET, &(param[4]), 4); +} + +/*! + * Function to create and initiate template command buffer for ADC. This + * template will be written to Panel memory. + */ +static void init_channel_template(int disp) +{ + /* template command buffer for ADC is 32 */ + uint32_t tempCmd[TEMPLATE_BUF_SIZE]; + uint32_t i = 0; + + memset(tempCmd, 0, sizeof(uint32_t) * TEMPLATE_BUF_SIZE); + /* setup update display region */ + /* whole the screen during init */ + /*WRITE Y COORDINATE CMND */ + tempCmd[i++] = ipu_adc_template_gen(WR_CMND, 0, SINGLE_STEP, SD_PSET); + /*WRITE Y START ADDRESS CMND LSB[22:8] */ + tempCmd[i++] = ipu_adc_template_gen(WR_YADDR, 1, SINGLE_STEP, 0x01); + /*WRITE Y START ADDRESS CMND MSB[22:16] */ + tempCmd[i++] = ipu_adc_template_gen(WR_YADDR, 1, SINGLE_STEP, 0x09); + /*WRITE Y STOP ADDRESS CMND LSB */ + tempCmd[i++] = ipu_adc_template_gen(WR_CMND, 1, SINGLE_STEP, + MXCFB_SCREEN_HEIGHT - 1); + /*WRITE Y STOP ADDRESS CMND MSB */ + tempCmd[i++] = ipu_adc_template_gen(WR_CMND, 1, SINGLE_STEP, 0); + /*WRITE X COORDINATE CMND */ + tempCmd[i++] = ipu_adc_template_gen(WR_CMND, 0, SINGLE_STEP, SD_CSET); + /*WRITE X ADDRESS CMND LSB[7:0] */ + tempCmd[i++] = ipu_adc_template_gen(WR_XADDR, 1, SINGLE_STEP, 0x01); + /*WRITE X ADDRESS CMND MSB[22:8] */ + tempCmd[i++] = ipu_adc_template_gen(WR_CMND, 1, SINGLE_STEP, 0); + /*WRITE X STOP ADDRESS CMND LSB */ + tempCmd[i++] = ipu_adc_template_gen(WR_CMND, 1, SINGLE_STEP, + MXCFB_SCREEN_WIDTH + 1); + /*WRITE X STOP ADDRESS CMND MSB */ + tempCmd[i++] = ipu_adc_template_gen(WR_CMND, 1, SINGLE_STEP, 0); + /*WRITE MEMORY CMND MSB */ + tempCmd[i++] = ipu_adc_template_gen(WR_CMND, 0, SINGLE_STEP, RAMWR); + /*WRITE DATA CMND and STP */ + tempCmd[i++] = ipu_adc_template_gen(WR_DATA, 1, STOP, 0); + + ipu_adc_write_template(disp, tempCmd, true); +} + +/*! + * Function to initialize the panel. First it resets the panel and then + * initilizes panel. + */ +static void _init_panel(int disp) +{ + uint32_t cmd_param; + uint32_t i; + + gpio_lcd_active(); + slcd_gpio_config(); + + // DATCTL +#ifdef CONFIG_FB_MXC_ASYNC_PANEL_IFC_16_BIT + // 16-bit 565 mode + cmd_param = 0x28; +#else + // 8-bit 666 mode + cmd_param = 0x08; +#endif + ipu_adc_write_cmd(disp, CMD, DATCTL, &cmd_param, 1); + + // Sleep OUT + ipu_adc_write_cmd(disp, CMD, SLPOUT, 0, 0); + + // Set display to white + // Setup page and column addresses + set_panel_region(disp, MXCFB_SCREEN_LEFT_OFFSET, + MXCFB_SCREEN_WIDTH + MXCFB_SCREEN_LEFT_OFFSET - 1, + 0, MXCFB_SCREEN_HEIGHT - 1); + // Do RAM write cmd + ipu_adc_write_cmd(disp, CMD, RAMWR, 0, 0); +#ifdef CONFIG_FB_MXC_ASYNC_PANEL_IFC_16_BIT + for (i = 0; i < (MXCFB_SCREEN_WIDTH * MXCFB_SCREEN_HEIGHT); i++) +#else + for (i = 0; i < (MXCFB_SCREEN_WIDTH * MXCFB_SCREEN_HEIGHT * 3); i++) +#endif + ipu_adc_write_cmd(disp, DAT, 0xFFFF, 0, 0); + + // Pause 80 ms + mdelay(80); + + // Display ON + ipu_adc_write_cmd(disp, CMD, DISON, 0, 0); + // Pause 200 ms + mdelay(200); + + pr_debug("initialized panel\n"); +} + +#ifdef PARTIAL_REFRESH +static irqreturn_t mxcfb_sys2_eof_irq_handler(int irq, void *dev_id) +{ + ipu_channel_params_t params; + struct fb_info *fbi = dev_id; + struct mxcfb_info *mxc_fbi = fbi->par; + uint32_t stat[2], seg_size; + uint32_t lsb, msb; + uint32_t update_height, start_line, start_addr, end_line, end_addr; + uint32_t stride_pixels = (fbi->fix.line_length * 8) / + fbi->var.bits_per_pixel; + + ipu_adc_get_snooping_status(&stat[0], &stat[1]); + //DPRINTK("snoop status = 0x%08X%08X\n", stat[1], stat[0]); + + if (!stat[0] && !stat[1]) { + dev_err(fbi->device, "error no bus snooping bits set\n"); + return IRQ_HANDLED; + } + ipu_disable_irq(IPU_IRQ_ADC_SYS2_EOF); + + lsb = ffs(stat[0]); + if (lsb) { + lsb--; + } else { + lsb = ffs(stat[1]); + lsb += 32 - 1; + } + msb = fls(stat[1]); + if (msb) { + msb += 32; + } else { + msb = fls(stat[0]); + } + + seg_size = mxc_fbi->snoop_window_size / 64; + + start_addr = lsb * seg_size; // starting address offset + start_line = start_addr / fbi->fix.line_length; + start_addr = start_line * fbi->fix.line_length; // Addr aligned to line + start_addr += fbi->fix.smem_start; + + end_addr = msb * seg_size; // ending address offset + end_line = end_addr / fbi->fix.line_length; + end_line++; + + if (end_line > fbi->var.yres) { + end_line = fbi->var.yres; + } + + update_height = end_line - start_line; + dev_dbg(fbi->device, "updating rows %d to %d, start addr = 0x%08X\n", + start_line, end_line, start_addr); + + ipu_uninit_channel(ADC_SYS1); + params.adc_sys1.disp = mxc_fbi->disp_num; + params.adc_sys1.ch_mode = WriteTemplateNonSeq; + params.adc_sys1.out_left = MXCFB_SCREEN_LEFT_OFFSET; + params.adc_sys1.out_top = start_line; + ipu_init_channel(ADC_SYS1, ¶ms); + + ipu_init_channel_buffer(ADC_SYS1, IPU_INPUT_BUFFER, + bpp_to_pixfmt(fbi->var.bits_per_pixel), + MXCFB_SCREEN_WIDTH, + update_height, + stride_pixels, + IPU_ROTATE_NONE, (dma_addr_t) start_addr, 0, + 0, 0); + ipu_enable_channel(ADC_SYS1); + ipu_select_buffer(ADC_SYS1, IPU_INPUT_BUFFER, 0); + ipu_enable_irq(IPU_IRQ_ADC_SYS1_EOF); + + return IRQ_HANDLED; +} + +static irqreturn_t mxcfb_sys1_eof_irq_handler(int irq, void *dev_id) +{ + ipu_disable_irq(IPU_IRQ_ADC_SYS1_EOF); + ipu_disable_channel(ADC_SYS1, false); + + ipu_enable_channel(ADC_SYS2); + ipu_enable_irq(IPU_IRQ_ADC_SYS2_EOF); + + return IRQ_HANDLED; +} +#endif + +/*! + * Function to initialize Asynchronous Display Controller. It also initilizes + * the ADC System 1 channel. Configure ADC display 0 parallel interface for + * the panel. + * + * @param fbi framebuffer information pointer + */ +static void mxcfb_init_panel(struct fb_info *fbi) +{ + int msb; + int panel_stride; + ipu_channel_params_t params; + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par; + +#ifdef CONFIG_FB_MXC_ASYNC_PANEL_IFC_16_BIT + uint32_t pix_fmt = IPU_PIX_FMT_RGB565; + ipu_adc_sig_cfg_t sig = { 0, 0, 0, 0, 0, 0, 0, 0, + IPU_ADC_BURST_WCS, + IPU_ADC_IFC_MODE_SYS80_TYPE2, + 16, 0, 0, IPU_ADC_SER_NO_RW + }; + mxc_fbi->disp_num = DISP0; +#elif defined(CONFIG_FB_MXC_ASYNC_PANEL_IFC_8_BIT) + uint32_t pix_fmt = IPU_PIX_FMT_RGB666; + ipu_adc_sig_cfg_t sig = { 0, 0, 0, 0, 0, 0, 0, 0, + IPU_ADC_BURST_WCS, + IPU_ADC_IFC_MODE_SYS80_TYPE2, + 8, 0, 0, IPU_ADC_SER_NO_RW + }; + mxc_fbi->disp_num = DISP0; +#else + uint32_t pix_fmt = IPU_PIX_FMT_RGB565; + ipu_adc_sig_cfg_t sig = { 0, 1, 0, 0, 0, 0, 0, 0, + IPU_ADC_BURST_SERIAL, + IPU_ADC_IFC_MODE_5WIRE_SERIAL_CLK, + 16, 0, 0, IPU_ADC_SER_NO_RW + }; + fbi->disp_num = DISP1; +#endif + +#ifdef PARTIAL_REFRESH + if (ipu_request_irq(IPU_IRQ_ADC_SYS2_EOF, mxcfb_sys2_eof_irq_handler, 0, + MXCFB_NAME, fbi) != 0) { + dev_err(fbi->device, "Error registering SYS2 irq handler.\n"); + return; + } + + if (ipu_request_irq(IPU_IRQ_ADC_SYS1_EOF, mxcfb_sys1_eof_irq_handler, 0, + MXCFB_NAME, fbi) != 0) { + dev_err(fbi->device, "Error registering SYS1 irq handler.\n"); + return; + } + ipu_disable_irq(IPU_IRQ_ADC_SYS1_EOF); + ipu_disable_irq(IPU_IRQ_ADC_SYS2_EOF); +#endif + // Init DI interface + msb = fls(MXCFB_SCREEN_WIDTH); + if (!(MXCFB_SCREEN_WIDTH & ((1UL << msb) - 1))) + msb--; // Already aligned to power 2 + panel_stride = 1UL << msb; + ipu_adc_init_panel(mxc_fbi->disp_num, + MXCFB_SCREEN_WIDTH + MXCFB_SCREEN_LEFT_OFFSET, + MXCFB_SCREEN_HEIGHT, + pix_fmt, panel_stride, sig, XY, 0, VsyncInternal); + + ipu_adc_init_ifc_timing(mxc_fbi->disp_num, true, + 190, 17, 104, 190, 5000000); + ipu_adc_init_ifc_timing(mxc_fbi->disp_num, false, 123, 17, 68, 0, 0); + + // Needed to turn on ADC clock for panel init + memset(¶ms, 0, sizeof(params)); + params.adc_sys1.disp = mxc_fbi->disp_num; + params.adc_sys1.ch_mode = WriteTemplateNonSeq; + params.adc_sys1.out_left = MXCFB_SCREEN_LEFT_OFFSET; + params.adc_sys1.out_top = MXCFB_SCREEN_TOP_OFFSET; + ipu_init_channel(ADC_SYS1, ¶ms); + + _init_panel(mxc_fbi->disp_num); + init_channel_template(mxc_fbi->disp_num); +} + +int mxcfb_set_refresh_mode(struct fb_info *fbi, int mode, + struct mxcfb_rect *update_region) +{ + unsigned long start_addr; + int ret_mode; + uint32_t dummy; + ipu_channel_params_t params; + struct mxcfb_rect rect; + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par; + uint32_t stride_pixels = (fbi->fix.line_length * 8) / + fbi->var.bits_per_pixel; + uint32_t memsize = fbi->fix.smem_len; + + if (mxc_fbi->cur_update_mode == mode) + return mode; + + ret_mode = mxc_fbi->cur_update_mode; + + ipu_disable_irq(IPU_IRQ_ADC_SYS1_EOF); + ipu_adc_set_update_mode(ADC_SYS1, IPU_ADC_REFRESH_NONE, 0, 0, 0); +#ifdef PARTIAL_REFRESH + ipu_disable_irq(IPU_IRQ_ADC_SYS2_EOF); + ipu_adc_set_update_mode(ADC_SYS2, IPU_ADC_REFRESH_NONE, 0, 0, 0); +#endif + + ipu_disable_channel(ADC_SYS1, true); + ipu_clear_irq(IPU_IRQ_ADC_SYS1_EOF); +#ifdef PARTIAL_REFRESH + ipu_disable_channel(ADC_SYS2, true); + ipu_clear_irq(IPU_IRQ_ADC_SYS2_EOF); +#endif + ipu_adc_get_snooping_status(&dummy, &dummy); + + mxc_fbi->cur_update_mode = mode; + + switch (mode) { + case MXCFB_REFRESH_OFF: + if (ipu_adc_set_update_mode(ADC_SYS1, IPU_ADC_REFRESH_NONE, + 0, 0, 0) < 0) + dev_err(fbi->device, "Error enabling auto refesh.\n"); + if (ipu_adc_set_update_mode(ADC_SYS2, IPU_ADC_REFRESH_NONE, + 0, 0, 0) < 0) + dev_err(fbi->device, "Error enabling auto refesh.\n"); +#if 0 + ipu_init_channel_buffer(ADC_SYS2, IPU_INPUT_BUFFER, + bpp_to_pixfmt(fbi->var.bits_per_pixel), + 1, 1, 4, + IPU_ROTATE_NONE, + fbi->fix.smem_start, + fbi->fix.smem_start, 0, 0); + ipu_enable_channel(ADC_SYS2); + ipu_select_buffer(ADC_SYS2, IPU_INPUT_BUFFER, 0); + ipu_select_buffer(ADC_SYS2, IPU_INPUT_BUFFER, 1); + msleep(10); +#endif + ipu_uninit_channel(ADC_SYS1); +#ifdef PARTIAL_REFRESH + ipu_uninit_channel(ADC_SYS2); +#endif + break; + case MXCFB_REFRESH_PARTIAL: +#ifdef PARTIAL_REFRESH + ipu_adc_get_snooping_status(&dummy, &dummy); + + params.adc_sys2.disp = DISP0; + params.adc_sys2.ch_mode = WriteTemplateNonSeq; + params.adc_sys2.out_left = 0; + params.adc_sys2.out_top = 0; + ipu_init_channel(ADC_SYS2, ¶ms); + + if (ipu_adc_set_update_mode(ADC_SYS1, IPU_ADC_REFRESH_NONE, + 0, 0, 0) < 0) { + dev_err(fbi->device, "Error enabling auto refesh.\n"); + } + if (ipu_adc_set_update_mode + (ADC_SYS2, IPU_ADC_AUTO_REFRESH_SNOOP, 30, + fbi->fix.smem_start, &memsize) < 0) { + dev_err(fbi->device, "Error enabling auto refesh.\n"); + } + mxc_fbi->snoop_window_size = memsize; + + ipu_init_channel_buffer(ADC_SYS2, IPU_INPUT_BUFFER, + bpp_to_pixfmt(fbi->var.bits_per_pixel), + 1, 1, 4, + IPU_ROTATE_NONE, + fbi->fix.smem_start, 0, 0, 0); + + params.adc_sys1.disp = mxc_fbi->disp_num; + params.adc_sys1.ch_mode = WriteTemplateNonSeq; + params.adc_sys1.out_left = MXCFB_SCREEN_LEFT_OFFSET; + params.adc_sys1.out_top = MXCFB_SCREEN_TOP_OFFSET; + ipu_init_channel(ADC_SYS1, ¶ms); + + ipu_init_channel_buffer(ADC_SYS1, IPU_INPUT_BUFFER, + bpp_to_pixfmt(fbi->var.bits_per_pixel), + MXCFB_SCREEN_WIDTH, MXCFB_SCREEN_HEIGHT, + stride_pixels, IPU_ROTATE_NONE, + fbi->fix.smem_start, 0, 0, 0); + ipu_enable_channel(ADC_SYS1); + ipu_select_buffer(ADC_SYS1, IPU_INPUT_BUFFER, 0); + ipu_enable_irq(IPU_IRQ_ADC_SYS1_EOF); + break; +#endif + case MXCFB_REFRESH_AUTO: + if (update_region == NULL) { + update_region = ▭ + rect.top = 0; + rect.left = 0; + rect.height = MXCFB_SCREEN_HEIGHT; + rect.width = MXCFB_SCREEN_WIDTH; + } + params.adc_sys1.disp = mxc_fbi->disp_num; + params.adc_sys1.ch_mode = WriteTemplateNonSeq; + params.adc_sys1.out_left = MXCFB_SCREEN_LEFT_OFFSET + + update_region->left; + params.adc_sys1.out_top = MXCFB_SCREEN_TOP_OFFSET + + update_region->top; + ipu_init_channel(ADC_SYS1, ¶ms); + + // Address aligned to line + start_addr = update_region->top * fbi->fix.line_length; + start_addr += fbi->fix.smem_start; + start_addr += update_region->left * fbi->var.bits_per_pixel / 8; + + ipu_init_channel_buffer(ADC_SYS1, IPU_INPUT_BUFFER, + bpp_to_pixfmt(fbi->var.bits_per_pixel), + update_region->width, + update_region->height, stride_pixels, + IPU_ROTATE_NONE, start_addr, 0, 0, 0); + ipu_enable_channel(ADC_SYS1); + ipu_select_buffer(ADC_SYS1, IPU_INPUT_BUFFER, 0); + + if (ipu_adc_set_update_mode + (ADC_SYS1, IPU_ADC_AUTO_REFRESH_SNOOP, 30, + fbi->fix.smem_start, &memsize) < 0) + dev_err(fbi->device, "Error enabling auto refesh.\n"); + + mxc_fbi->snoop_window_size = memsize; + + break; + } + return ret_mode; +} + +/* + * Open the main framebuffer. + * + * @param fbi framebuffer information pointer + * + * @param user Set if opened by user or clear if opened by kernel + */ +static int mxcfb_open(struct fb_info *fbi, int user) +{ + int retval = 0; + struct mxcfb_info *mxc_fbi = fbi->par; + + if ((retval = wait_event_interruptible(mxcfb_drv_data.suspend_wq, + (mxcfb_drv_data.suspended == + false))) < 0) { + return retval; + } + + mxc_fbi->open_count++; + + retval = mxcfb_blank(FB_BLANK_UNBLANK, fbi); + return retval; +} + +/* + * Close the main framebuffer. + * + * @param fbi framebuffer information pointer + * + * @param user Set if opened by user or clear if opened by kernel + */ +static int mxcfb_release(struct fb_info *fbi, int user) +{ + int retval = 0; + struct mxcfb_info *mxc_fbi = fbi->par; + + if ((retval = wait_event_interruptible(mxcfb_drv_data.suspend_wq, + (mxcfb_drv_data.suspended == + false))) < 0) { + return retval; + } + + --mxc_fbi->open_count; + if (mxc_fbi->open_count == 0) { + retval = mxcfb_blank(FB_BLANK_POWERDOWN, fbi); + } + return retval; +} + +/* + * Set fixed framebuffer parameters based on variable settings. + * + * @param info framebuffer information pointer + */ +static int mxcfb_set_fix(struct fb_info *info) +{ + struct fb_fix_screeninfo *fix = &info->fix; + struct fb_var_screeninfo *var = &info->var; + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)info->par; + + // Set framebuffer id to IPU display number. + strcpy(fix->id, "DISP0 FB"); + fix->id[4] = '0' + mxc_fbi->disp_num; + + // Init settings based on the panel size + fix->line_length = MXCFB_SCREEN_WIDTH * var->bits_per_pixel / 8; + + fix->type = FB_TYPE_PACKED_PIXELS; + fix->accel = FB_ACCEL_NONE; + fix->visual = FB_VISUAL_TRUECOLOR; + fix->xpanstep = 0; + fix->ypanstep = 0; + + return 0; +} + +/* + * Set framebuffer parameters and change the operating mode. + * + * @param info framebuffer information pointer + */ +static int mxcfb_set_par(struct fb_info *fbi) +{ + int retval = 0; + int mode; + + if ((retval = wait_event_interruptible(mxcfb_drv_data.suspend_wq, + (mxcfb_drv_data.suspended == + false))) < 0) { + return retval; + } + + mode = mxcfb_set_refresh_mode(fbi, MXCFB_REFRESH_OFF, NULL); + + mxcfb_set_fix(fbi); + + if (mode != MXCFB_REFRESH_OFF) { +#ifdef PARTIAL_REFRESH + mxcfb_set_refresh_mode(fbi, MXCFB_REFRESH_PARTIAL, NULL); +#else + mxcfb_set_refresh_mode(fbi, MXCFB_REFRESH_AUTO, NULL); +#endif + } + return 0; +} + +/* + * Check framebuffer variable parameters and adjust to valid values. + * + * @param var framebuffer variable parameters + * + * @param info framebuffer information pointer + */ +static int mxcfb_check_var(struct fb_var_screeninfo *var, struct fb_info *fbi) +{ + if (var->xres > MXCFB_SCREEN_WIDTH) + var->xres = MXCFB_SCREEN_WIDTH; + if (var->yres > MXCFB_SCREEN_HEIGHT) + var->yres = MXCFB_SCREEN_HEIGHT; + if (var->xres_virtual < var->xres) + var->xres_virtual = var->xres; + if (var->yres_virtual < var->yres) + var->yres_virtual = var->yres; + + if ((var->bits_per_pixel != 32) && (var->bits_per_pixel != 24) && + (var->bits_per_pixel != 16)) { + var->bits_per_pixel = default_bpp; + } + + switch (var->bits_per_pixel) { + case 16: + var->red.length = 5; + var->red.offset = 11; + var->red.msb_right = 0; + + var->green.length = 6; + var->green.offset = 5; + var->green.msb_right = 0; + + var->blue.length = 5; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 0; + var->transp.offset = 0; + var->transp.msb_right = 0; + break; + case 24: + var->red.length = 8; + var->red.offset = 16; + var->red.msb_right = 0; + + var->green.length = 8; + var->green.offset = 8; + var->green.msb_right = 0; + + var->blue.length = 8; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 0; + var->transp.offset = 0; + var->transp.msb_right = 0; + break; + case 32: + var->red.length = 8; + var->red.offset = 16; + var->red.msb_right = 0; + + var->green.length = 8; + var->green.offset = 8; + var->green.msb_right = 0; + + var->blue.length = 8; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 8; + var->transp.offset = 24; + var->transp.msb_right = 0; + break; + } + + var->height = -1; + var->width = -1; + var->grayscale = 0; + var->nonstd = 0; + + var->pixclock = -1; + var->left_margin = -1; + var->right_margin = -1; + var->upper_margin = -1; + var->lower_margin = -1; + var->hsync_len = -1; + var->vsync_len = -1; + + var->vmode = FB_VMODE_NONINTERLACED; + var->sync = 0; + + return 0; +} + +static inline u_int _chan_to_field(u_int chan, struct fb_bitfield *bf) +{ + chan &= 0xffff; + chan >>= 16 - bf->length; + return chan << bf->offset; +} + +static int +mxcfb_setcolreg(u_int regno, u_int red, u_int green, u_int blue, + u_int trans, struct fb_info *fbi) +{ + unsigned int val; + int ret = 1; + + /* + * If greyscale is true, then we convert the RGB value + * to greyscale no matter what visual we are using. + */ + if (fbi->var.grayscale) + red = green = blue = (19595 * red + 38470 * green + + 7471 * blue) >> 16; + switch (fbi->fix.visual) { + case FB_VISUAL_TRUECOLOR: + /* + * 16-bit True Colour. We encode the RGB value + * according to the RGB bitfield information. + */ + if (regno < 16) { + u32 *pal = fbi->pseudo_palette; + + val = _chan_to_field(red, &fbi->var.red); + val |= _chan_to_field(green, &fbi->var.green); + val |= _chan_to_field(blue, &fbi->var.blue); + + pal[regno] = val; + ret = 0; + } + break; + + case FB_VISUAL_STATIC_PSEUDOCOLOR: + case FB_VISUAL_PSEUDOCOLOR: + break; + } + + return ret; +} + +/* + * mxcfb_blank(): + * Blank the display. + */ +static int mxcfb_blank(int blank, struct fb_info *fbi) +{ + int retval = 0; + struct mxcfb_info *mxc_fbi = fbi->par; + + dev_dbg(fbi->device, "blank = %d\n", blank); + + if ((retval = wait_event_interruptible(mxcfb_drv_data.suspend_wq, + (mxcfb_drv_data.suspended == + false))) < 0) { + return retval; + } + + mxc_fbi->blank = blank; + + switch (blank) { + case FB_BLANK_POWERDOWN: + case FB_BLANK_VSYNC_SUSPEND: + case FB_BLANK_HSYNC_SUSPEND: + case FB_BLANK_NORMAL: + mxcfb_set_refresh_mode(fbi, MXCFB_REFRESH_OFF, NULL); + break; + case FB_BLANK_UNBLANK: + mxcfb_set_refresh_mode(fbi, MXCFB_REFRESH_DEFAULT, NULL); + break; + } + return 0; +} + +/*! + * This structure contains the pointers to the control functions that are + * invoked by the core framebuffer driver to perform operations like + * blitting, rectangle filling, copy regions and cursor definition. + */ +static struct fb_ops mxcfb_ops = { + .owner = THIS_MODULE, + .fb_open = mxcfb_open, + .fb_release = mxcfb_release, + .fb_set_par = mxcfb_set_par, + .fb_check_var = mxcfb_check_var, + .fb_setcolreg = mxcfb_setcolreg, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, + .fb_blank = mxcfb_blank, +}; + +/*! + * Allocates the DRAM memory for the frame buffer. This buffer is remapped + * into a non-cached, non-buffered, memory region to allow palette and pixel + * writes to occur without flushing the cache. Once this area is remapped, + * all virtual memory access to the video memory should occur at the new region. + * + * @param fbi framebuffer information pointer + * + * @return Error code indicating success or failure + */ +static int mxcfb_map_video_memory(struct fb_info *fbi) +{ + u32 msb; + u32 offset; + struct mxcfb_info *mxcfbi = fbi->par; + + fbi->fix.smem_len = fbi->var.xres_virtual * fbi->var.yres_virtual * 4; + + // Set size to power of 2. + msb = fls(fbi->fix.smem_len); + if (!(fbi->fix.smem_len & ((1UL << msb) - 1))) + msb--; // Already aligned to power 2 + if (msb < 11) + msb = 11; + mxcfbi->alloc_size = (1UL << msb) * 2; + + mxcfbi->alloc_start_vaddr = dma_alloc_coherent(fbi->device, + mxcfbi->alloc_size, + &mxcfbi-> + alloc_start_paddr, + GFP_KERNEL | GFP_DMA); + + if (mxcfbi->alloc_start_vaddr == 0) { + dev_err(fbi->device, "Unable to allocate framebuffer memory\n"); + return -ENOMEM; + } + dev_dbg(fbi->device, "allocated fb memory @ paddr=0x%08X, size=%d.\n", + (uint32_t) mxcfbi->alloc_start_paddr, mxcfbi->alloc_size); + + offset = + ((mxcfbi->alloc_size / 2) - 1) & ~((mxcfbi->alloc_size / 2) - 1); + fbi->fix.smem_start = mxcfbi->alloc_start_paddr + offset; + dev_dbg(fbi->device, "aligned fb start @ paddr=0x%08lX, size=%u.\n", + fbi->fix.smem_start, fbi->fix.smem_len); + + fbi->screen_base = mxcfbi->alloc_start_vaddr + offset; + + /* Clear the screen */ + memset(fbi->screen_base, 0, fbi->fix.smem_len); + return 0; +} + +/*! + * De-allocates the DRAM memory for the frame buffer. + * + * @param fbi framebuffer information pointer + * + * @return Error code indicating success or failure + */ +static int mxcfb_unmap_video_memory(struct fb_info *fbi) +{ + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par; + + dma_free_coherent(fbi->device, mxc_fbi->alloc_size, + mxc_fbi->alloc_start_vaddr, + mxc_fbi->alloc_start_paddr); + return 0; +} + +/*! + * Initializes the framebuffer information pointer. After allocating + * sufficient memory for the framebuffer structure, the fields are + * filled with custom information passed in from the configurable + * structures. This includes information such as bits per pixel, + * color maps, screen width/height and RGBA offsets. + * + * @return Framebuffer structure initialized with our information + */ +static struct fb_info *mxcfb_init_fbinfo(struct device *dev, struct fb_ops *ops) +{ + struct fb_info *fbi; + struct mxcfb_info *mxcfbi; + + /* + * Allocate sufficient memory for the fb structure + */ + fbi = framebuffer_alloc(sizeof(struct mxcfb_info), dev); + if (!fbi) + return NULL; + + mxcfbi = (struct mxcfb_info *)fbi->par; + + /* + * Fill in fb_info structure information + */ + fbi->var.xres = fbi->var.xres_virtual = MXCFB_SCREEN_WIDTH; + fbi->var.yres = fbi->var.yres_virtual = MXCFB_SCREEN_HEIGHT; + fbi->var.activate = FB_ACTIVATE_NOW; + mxcfb_check_var(&fbi->var, fbi); + + mxcfb_set_fix(fbi); + + fbi->fbops = ops; + fbi->flags = FBINFO_FLAG_DEFAULT; + fbi->pseudo_palette = mxcfbi->pseudo_palette; + + /* + * Allocate colormap + */ + fb_alloc_cmap(&fbi->cmap, 16, 0); + + return fbi; +} + +/*! + * Probe routine for the framebuffer driver. It is called during the + * driver binding process. The following functions are performed in + * this routine: Framebuffer initialization, Memory allocation and + * mapping, Framebuffer registration, IPU initialization. + * + * @return Appropriate error code to the kernel common code + */ +static int mxcfb_probe(struct platform_device *pdev) +{ + struct fb_info *fbi; + struct mxcfb_info *mxc_fbi; + int ret; + + platform_set_drvdata(pdev, &mxcfb_drv_data); + + /* + * Initialize FB structures + */ + fbi = mxcfb_init_fbinfo(&pdev->dev, &mxcfb_ops); + if (!fbi) { + ret = -ENOMEM; + goto err0; + } + mxcfb_drv_data.fbi = fbi; + mxc_fbi = fbi->par; + + mxcfb_drv_data.suspended = false; + init_waitqueue_head(&mxcfb_drv_data.suspend_wq); + + /* + * Allocate memory + */ + ret = mxcfb_map_video_memory(fbi); + if (ret < 0) { + goto err1; + } + + mxcfb_init_panel(fbi); + + /* + * Register framebuffer + */ + ret = register_framebuffer(fbi); + if (ret < 0) { + goto err2; + } + + dev_info(&pdev->dev, "%s registered\n", MXCFB_NAME); + + return 0; + + err2: + mxcfb_unmap_video_memory(fbi); + err1: + if (&fbi->cmap) + fb_dealloc_cmap(&fbi->cmap); + framebuffer_release(fbi); + err0: + return ret; +} + +#ifdef CONFIG_PM +/*! + * Power management hooks. Note that we won't be called from IRQ context, + * unlike the blank functions above, so we may sleep. + */ + +/*! + * Suspends the framebuffer and blanks the screen. Power management support + * + * @param pdev pointer to device structure. + * @param state state of the device. + * + * @return success + */ +static int mxcfb_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct mxcfb_data *drv_data = platform_get_drvdata(pdev); + struct fb_info *fbi = drv_data->fbi; + struct mxcfb_info *mxc_fbi = fbi->par; + + drv_data->suspended = true; + + if (mxc_fbi->blank == FB_BLANK_UNBLANK) + mxcfb_set_refresh_mode(fbi, MXCFB_REFRESH_OFF, NULL); + /* Display OFF */ + ipu_adc_write_cmd(mxc_fbi->disp_num, CMD, DISOFF, 0, 0); + + return 0; +} + +/*! + * Resumes the framebuffer and unblanks the screen. Power management support + * + * @param pdev pointer to device structure. + * + * @return success + */ +static int mxcfb_resume(struct platform_device *pdev) +{ + struct mxcfb_data *drv_data = platform_get_drvdata(pdev); + struct fb_info *fbi = drv_data->fbi; + struct mxcfb_info *mxc_fbi = fbi->par; + + // Display ON + ipu_adc_write_cmd(mxc_fbi->disp_num, CMD, DISON, 0, 0); + drv_data->suspended = false; + + if (mxc_fbi->blank == FB_BLANK_UNBLANK) + mxcfb_set_refresh_mode(fbi, MXCFB_REFRESH_DEFAULT, NULL); + wake_up_interruptible(&drv_data->suspend_wq); + + return 0; +} +#else +#define mxcfb_suspend NULL +#define mxcfb_resume NULL +#endif + +/*! + * This structure contains pointers to the power management callback functions. + */ +static struct platform_driver mxcfb_driver = { + .driver = { + .name = MXCFB_NAME, + }, + .probe = mxcfb_probe, + .suspend = mxcfb_suspend, + .resume = mxcfb_resume, +}; + +/*! + * Device definition for the Framebuffer + */ +static struct platform_device mxcfb_device = { + .name = MXCFB_NAME, + .id = 0, + .dev = { + .coherent_dma_mask = 0xFFFFFFFF, + } +}; + +/*! + * Main entry function for the framebuffer. The function registers the power + * management callback functions with the kernel and also registers the MXCFB + * callback functions with the core Linux framebuffer driver \b fbmem.c + * + * @return Error code indicating success or failure + */ +static int mxcfb_init(void) +{ + int ret = 0; + + ret = platform_driver_register(&mxcfb_driver); + if (ret == 0) { + ret = platform_device_register(&mxcfb_device); + if (ret != 0) { + platform_driver_unregister(&mxcfb_driver); + } + } + return ret; +} + +static void mxcfb_exit(void) +{ + struct fb_info *fbi = dev_get_drvdata(&mxcfb_device.dev); + + if (fbi) { + mxcfb_unmap_video_memory(fbi); + + if (&fbi->cmap) + fb_dealloc_cmap(&fbi->cmap); + + unregister_framebuffer(fbi); + framebuffer_release(fbi); + } + + platform_device_unregister(&mxcfb_device); + platform_driver_unregister(&mxcfb_driver); +} + +module_init(mxcfb_init); +module_exit(mxcfb_exit); + +EXPORT_SYMBOL(mxcfb_set_refresh_mode); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MXC Epson framebuffer driver"); +MODULE_SUPPORTED_DEVICE("fb"); diff --git a/drivers/video/mxc/mxcfb_epson_qvga.c b/drivers/video/mxc/mxcfb_epson_qvga.c new file mode 100644 index 000000000000..f7c26fd46558 --- /dev/null +++ b/drivers/video/mxc/mxcfb_epson_qvga.c @@ -0,0 +1,1146 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mxcfb_epson_qvga.c + * + * @brief MXC Frame buffer driver for ADC + * + * @ingroup Framebuffer + */ + +/*! + * Include files + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/fb.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/ioport.h> +#include <linux/platform_device.h> +#include <linux/dma-mapping.h> +#include <asm/hardware.h> +#include <asm/io.h> +#include <asm/mach-types.h> +#include <asm/uaccess.h> +#include <asm/arch/ipu.h> +#include <asm/arch/mxcfb.h> + +#define PARTIAL_REFRESH +#define MXCFB_REFRESH_DEFAULT MXCFB_REFRESH_PARTIAL +/* + * Driver name + */ +#define MXCFB_NAME "MXCFB_EPSON" + +#define MXCFB_SCREEN_TOP_OFFSET 0 +#define MXCFB_SCREEN_LEFT_OFFSET 12 +#define MXCFB_SCREEN_WIDTH 240 +#define MXCFB_SCREEN_HEIGHT 320 + +/*! + * Enum defining Epson panel commands. + */ +enum { + DISON = 0x29, + DISOFF = 0x28, + DISCTL = 0xB0, + SD_CSET = 0x2A, + SD_PSET = 0x2B, + SLPIN = 0x10, + SLPOUT = 0x11, + DISINOFF = 0x20, + RAMWR = 0x2C, + VOLCTL = 0xBE, +}; + +struct mxcfb_info { + int open_count; + int blank; + uint32_t disp_num; + + u32 pseudo_palette[16]; + + int32_t cur_update_mode; + dma_addr_t alloc_start_paddr; + void *alloc_start_vaddr; + u32 alloc_size; + uint32_t snoop_window_size; +}; + +struct mxcfb_data { + struct fb_info *fbi; + volatile int32_t vsync_flag; + wait_queue_head_t vsync_wq; + wait_queue_head_t suspend_wq; + bool suspended; +}; + +static struct mxcfb_data mxcfb_drv_data; +static unsigned long default_bpp = 16; + +void slcd_gpio_config(void); +extern void gpio_lcd_active(void); +static int mxcfb_blank(int blank, struct fb_info *fbi); + +static uint32_t bpp_to_pixfmt(int bpp) +{ + uint32_t pixfmt = 0; + switch (bpp) { + case 24: + pixfmt = IPU_PIX_FMT_BGR24; + break; + case 32: + pixfmt = IPU_PIX_FMT_BGR32; + break; + case 16: + pixfmt = IPU_PIX_FMT_RGB565; + break; + } + return pixfmt; +} + +/*! + * This function sets display region in the Epson panel + * + * @param disp display panel to config + * @param x1 x-coordinate of one vertex. + * @param x2 x-coordinate of second vertex. + * @param y1 y-coordinate of one vertex. + * @param y2 y-coordinate of second vertex. + */ +void set_panel_region(int disp, uint32_t x1, uint32_t x2, + uint32_t y1, uint32_t y2) +{ + uint32_t param[8]; + + memset(param, 0, sizeof(uint32_t) * 8); + param[0] = x1; + param[2] = x2; + param[4] = y1; + param[6] = y2; + + // SD_CSET + ipu_adc_write_cmd(disp, CMD, SD_CSET, param, 4); + // SD_PSET + + ipu_adc_write_cmd(disp, CMD, SD_PSET, &(param[4]), 4); +} + +/*! + * Function to create and initiate template command buffer for ADC. This + * template will be written to Panel memory. + */ +static void init_channel_template(int disp) +{ + /* template command buffer for ADC is 32 */ + uint32_t tempCmd[TEMPLATE_BUF_SIZE]; + uint32_t i = 0; + + memset(tempCmd, 0, sizeof(uint32_t) * TEMPLATE_BUF_SIZE); + /* setup update display region */ + /* whole the screen during init */ + /*WRITE Y COORDINATE CMND */ + tempCmd[i++] = ipu_adc_template_gen(WR_CMND, 0, SINGLE_STEP, SD_PSET); + /*WRITE Y START ADDRESS CMND MSB[22:16] */ + tempCmd[i++] = ipu_adc_template_gen(WR_YADDR, 1, SINGLE_STEP, 0x09); + /*WRITE Y START ADDRESS CMND LSB[22:8] */ + tempCmd[i++] = ipu_adc_template_gen(WR_YADDR, 1, SINGLE_STEP, 0x01); + /*WRITE Y STOP ADDRESS CMND MSB */ + tempCmd[i++] = + ipu_adc_template_gen(WR_CMND, 1, SINGLE_STEP, + (MXCFB_SCREEN_HEIGHT - 1) >> 8); + /*WRITE Y STOP ADDRESS CMND LSB */ + tempCmd[i++] = + ipu_adc_template_gen(WR_CMND, 1, SINGLE_STEP, + MXCFB_SCREEN_HEIGHT - 1); + /*WRITE X COORDINATE CMND */ + tempCmd[i++] = ipu_adc_template_gen(WR_CMND, 0, SINGLE_STEP, SD_CSET); + /*WRITE X ADDRESS CMND MSB[22:8] */ + tempCmd[i++] = ipu_adc_template_gen(WR_CMND, 1, SINGLE_STEP, 0); + /*WRITE X ADDRESS CMND LSB[7:0] */ + tempCmd[i++] = ipu_adc_template_gen(WR_XADDR, 1, SINGLE_STEP, 0x01); + /*WRITE X STOP ADDRESS CMND MSB */ + tempCmd[i++] = ipu_adc_template_gen(WR_CMND, 1, SINGLE_STEP, 0); + /*WRITE X STOP ADDRESS CMND LSB */ + tempCmd[i++] = ipu_adc_template_gen(WR_CMND, 1, SINGLE_STEP, + MXCFB_SCREEN_WIDTH + 11); + /*WRITE MEMORY CMND MSB */ + tempCmd[i++] = ipu_adc_template_gen(WR_CMND, 0, SINGLE_STEP, RAMWR); + /*WRITE DATA CMND and STP */ + tempCmd[i++] = ipu_adc_template_gen(WR_DATA, 1, STOP, 0); + + ipu_adc_write_template(disp, tempCmd, true); +} + +/*! + * Function to initialize the panel. First it resets the panel and then + * initilizes panel. + */ +static void _init_panel(int disp) +{ + uint32_t cmd_param; + uint32_t disctl_param[] = + { 0x01, 0x98, 0x01, 0x48, 0x01, 0x40, 0x04, 0x01, 0x01, 0x41, 0x09, + 0x00, 0x00, 0x40, 0x00 + }; + uint32_t i; + + gpio_lcd_active(); + slcd_gpio_config(); + + // 16-bit 565 mode + cmd_param = 0x03; + ipu_adc_write_cmd(disp, CMD, 0xC2, &cmd_param, 1); + + ipu_adc_write_cmd(disp, CMD, DISCTL, disctl_param, 15); + + cmd_param = 0x48; + ipu_adc_write_cmd(disp, CMD, 0x36, &cmd_param, 1); + + // Sleep OUT + ipu_adc_write_cmd(disp, CMD, SLPOUT, 0, 0); + + // Set display to white + // Setup page and column addresses + set_panel_region(disp, MXCFB_SCREEN_LEFT_OFFSET, + MXCFB_SCREEN_WIDTH + MXCFB_SCREEN_LEFT_OFFSET - 1, + 0, MXCFB_SCREEN_HEIGHT - 1); + // Do RAM write cmd + ipu_adc_write_cmd(disp, CMD, RAMWR, 0, 0); + for (i = 0; i < (MXCFB_SCREEN_WIDTH * MXCFB_SCREEN_HEIGHT); i++) + ipu_adc_write_cmd(disp, DAT, 0xFFFF, 0, 0); + + // Pause 80 ms + mdelay(80); + + // Display ON + ipu_adc_write_cmd(disp, CMD, DISON, 0, 0); + // Pause 200 ms + mdelay(200); + + pr_debug("initialized panel\n"); +} + +#ifdef PARTIAL_REFRESH +static irqreturn_t mxcfb_sys2_eof_irq_handler(int irq, void *dev_id) +{ + ipu_channel_params_t params; + struct fb_info *fbi = dev_id; + struct mxcfb_info *mxc_fbi = fbi->par; + uint32_t stat[2], seg_size; + uint32_t lsb, msb; + uint32_t update_height, start_line, start_addr, end_line, end_addr; + uint32_t stride_pixels = (fbi->fix.line_length * 8) / + fbi->var.bits_per_pixel; + + ipu_adc_get_snooping_status(&stat[0], &stat[1]); + //DPRINTK("snoop status = 0x%08X%08X\n", stat[1], stat[0]); + + if (!stat[0] && !stat[1]) { + dev_err(fbi->device, "error no bus snooping bits set\n"); + return IRQ_HANDLED; + } + ipu_disable_irq(IPU_IRQ_ADC_SYS2_EOF); + + lsb = ffs(stat[0]); + if (lsb) { + lsb--; + } else { + lsb = ffs(stat[1]); + lsb += 32 - 1; + } + msb = fls(stat[1]); + if (msb) { + msb += 32; + } else { + msb = fls(stat[0]); + } + + seg_size = mxc_fbi->snoop_window_size / 64; + + start_addr = lsb * seg_size; // starting address offset + start_line = start_addr / fbi->fix.line_length; + start_addr = start_line * fbi->fix.line_length; // Addr aligned to line + start_addr += fbi->fix.smem_start; + + end_addr = msb * seg_size; // ending address offset + end_line = end_addr / fbi->fix.line_length; + end_line++; + + if (end_line > fbi->var.yres) { + end_line = fbi->var.yres; + } + + update_height = end_line - start_line; + dev_dbg(fbi->device, "updating rows %d to %d, start addr = 0x%08X\n", + start_line, end_line, start_addr); + + ipu_uninit_channel(ADC_SYS1); + params.adc_sys1.disp = mxc_fbi->disp_num; + params.adc_sys1.ch_mode = WriteTemplateNonSeq; + params.adc_sys1.out_left = MXCFB_SCREEN_LEFT_OFFSET; + params.adc_sys1.out_top = start_line; + ipu_init_channel(ADC_SYS1, ¶ms); + + ipu_init_channel_buffer(ADC_SYS1, IPU_INPUT_BUFFER, + bpp_to_pixfmt(fbi->var.bits_per_pixel), + MXCFB_SCREEN_WIDTH, + update_height, + stride_pixels, + IPU_ROTATE_NONE, (dma_addr_t) start_addr, 0, 0, + 0); + ipu_enable_channel(ADC_SYS1); + ipu_select_buffer(ADC_SYS1, IPU_INPUT_BUFFER, 0); + ipu_enable_irq(IPU_IRQ_ADC_SYS1_EOF); + + return IRQ_HANDLED; +} + +static irqreturn_t mxcfb_sys1_eof_irq_handler(int irq, void *dev_id) +{ + ipu_disable_irq(IPU_IRQ_ADC_SYS1_EOF); + ipu_disable_channel(ADC_SYS1, false); + + ipu_enable_channel(ADC_SYS2); + ipu_enable_irq(IPU_IRQ_ADC_SYS2_EOF); + + return IRQ_HANDLED; +} +#endif + +/*! + * Function to initialize Asynchronous Display Controller. It also initilizes + * the ADC System 1 channel. Configure ADC display 0 parallel interface for + * the panel. + * + * @param fbi framebuffer information pointer + */ +static void mxcfb_init_panel(struct fb_info *fbi) +{ + int msb; + int panel_stride; + ipu_channel_params_t params; + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par; + +#ifdef CONFIG_FB_MXC_ASYNC_PANEL_IFC_16_BIT + uint32_t pix_fmt = IPU_PIX_FMT_RGB565; + ipu_adc_sig_cfg_t sig = { 0, 0, 0, 0, 0, 0, 0, 0, + IPU_ADC_BURST_WCS, + IPU_ADC_IFC_MODE_SYS80_TYPE2, + 16, 0, 0, IPU_ADC_SER_NO_RW + }; + mxc_fbi->disp_num = DISP0; +#elif defined(CONFIG_FB_MXC_ASYNC_PANEL_IFC_8_BIT) + uint32_t pix_fmt = IPU_PIX_FMT_RGB666; + ipu_adc_sig_cfg_t sig = { 0, 0, 0, 0, 0, 0, 0, 0, + IPU_ADC_BURST_WCS, + IPU_ADC_IFC_MODE_SYS80_TYPE2, + 8, 0, 0, IPU_ADC_SER_NO_RW + }; + mxc_fbi->disp_num = DISP0; +#endif + +#ifdef PARTIAL_REFRESH + if (ipu_request_irq(IPU_IRQ_ADC_SYS2_EOF, mxcfb_sys2_eof_irq_handler, 0, + MXCFB_NAME, fbi) != 0) { + dev_err(fbi->device, "Error registering SYS2 irq handler.\n"); + return; + } + + if (ipu_request_irq(IPU_IRQ_ADC_SYS1_EOF, mxcfb_sys1_eof_irq_handler, 0, + MXCFB_NAME, fbi) != 0) { + dev_err(fbi->device, "Error registering SYS1 irq handler.\n"); + return; + } + ipu_disable_irq(IPU_IRQ_ADC_SYS1_EOF); + ipu_disable_irq(IPU_IRQ_ADC_SYS2_EOF); +#endif + // Init DI interface + msb = fls(MXCFB_SCREEN_WIDTH); + if (!(MXCFB_SCREEN_WIDTH & ((1UL << msb) - 1))) + msb--; // Already aligned to power 2 + panel_stride = 1UL << msb; + ipu_adc_init_panel(mxc_fbi->disp_num, + MXCFB_SCREEN_WIDTH + MXCFB_SCREEN_LEFT_OFFSET, + MXCFB_SCREEN_HEIGHT, + pix_fmt, panel_stride, sig, XY, 0, VsyncInternal); + + ipu_adc_init_ifc_timing(mxc_fbi->disp_num, true, + 190, 17, 104, 190, 5000000); + ipu_adc_init_ifc_timing(mxc_fbi->disp_num, false, 200, 17, 90, 0, 0); + + // Needed to turn on ADC clock for panel init + memset(¶ms, 0, sizeof(params)); + params.adc_sys1.disp = mxc_fbi->disp_num; + params.adc_sys1.ch_mode = WriteTemplateNonSeq; + params.adc_sys1.out_left = MXCFB_SCREEN_LEFT_OFFSET; + params.adc_sys1.out_top = MXCFB_SCREEN_TOP_OFFSET; + ipu_init_channel(ADC_SYS1, ¶ms); + + _init_panel(mxc_fbi->disp_num); + init_channel_template(mxc_fbi->disp_num); +} + +int mxcfb_set_refresh_mode(struct fb_info *fbi, int mode, + struct mxcfb_rect *update_region) +{ + unsigned long start_addr; + int ret_mode; + uint32_t dummy; + ipu_channel_params_t params; + struct mxcfb_rect rect; + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par; + uint32_t stride_pixels = (fbi->fix.line_length * 8) / + fbi->var.bits_per_pixel; + uint32_t memsize = fbi->fix.smem_len; + + if (mxc_fbi->cur_update_mode == mode) + return mode; + + ret_mode = mxc_fbi->cur_update_mode; + + ipu_disable_irq(IPU_IRQ_ADC_SYS1_EOF); + ipu_adc_set_update_mode(ADC_SYS1, IPU_ADC_REFRESH_NONE, 0, 0, 0); +#ifdef PARTIAL_REFRESH + ipu_disable_irq(IPU_IRQ_ADC_SYS2_EOF); + ipu_adc_set_update_mode(ADC_SYS2, IPU_ADC_REFRESH_NONE, 0, 0, 0); +#endif + + ipu_disable_channel(ADC_SYS1, true); + ipu_clear_irq(IPU_IRQ_ADC_SYS1_EOF); +#ifdef PARTIAL_REFRESH + ipu_disable_channel(ADC_SYS2, true); + ipu_clear_irq(IPU_IRQ_ADC_SYS2_EOF); +#endif + ipu_adc_get_snooping_status(&dummy, &dummy); + + mxc_fbi->cur_update_mode = mode; + + switch (mode) { + case MXCFB_REFRESH_OFF: + if (ipu_adc_set_update_mode(ADC_SYS1, IPU_ADC_REFRESH_NONE, + 0, 0, 0) < 0) + dev_err(fbi->device, "Error enabling auto refesh.\n"); + if (ipu_adc_set_update_mode(ADC_SYS2, IPU_ADC_REFRESH_NONE, + 0, 0, 0) < 0) + dev_err(fbi->device, "Error enabling auto refesh.\n"); +#if 1 + ipu_init_channel_buffer(ADC_SYS2, IPU_INPUT_BUFFER, + bpp_to_pixfmt(fbi->var.bits_per_pixel), + 1, 1, 4, + IPU_ROTATE_NONE, + fbi->fix.smem_start, + fbi->fix.smem_start, 0, 0); + ipu_enable_channel(ADC_SYS2); + ipu_select_buffer(ADC_SYS2, IPU_INPUT_BUFFER, 0); + ipu_select_buffer(ADC_SYS2, IPU_INPUT_BUFFER, 1); + msleep(10); +#endif +#ifdef PARTIAL_REFRESH + ipu_uninit_channel(ADC_SYS2); +#endif + break; + case MXCFB_REFRESH_PARTIAL: +#ifdef PARTIAL_REFRESH + params.adc_sys2.disp = DISP0; + params.adc_sys2.ch_mode = WriteTemplateNonSeq; + params.adc_sys2.out_left = 0; + params.adc_sys2.out_top = 0; + ipu_init_channel(ADC_SYS2, ¶ms); + + if (ipu_adc_set_update_mode(ADC_SYS1, IPU_ADC_REFRESH_NONE, + 0, 0, 0) < 0) { + dev_err(fbi->device, "Error enabling auto refesh.\n"); + } + if (ipu_adc_set_update_mode + (ADC_SYS2, IPU_ADC_AUTO_REFRESH_SNOOP, 30, + fbi->fix.smem_start, &memsize) < 0) { + dev_err(fbi->device, "Error enabling auto refesh.\n"); + } + mxc_fbi->snoop_window_size = memsize; + + ipu_init_channel_buffer(ADC_SYS2, IPU_INPUT_BUFFER, + bpp_to_pixfmt(fbi->var.bits_per_pixel), + 1, 1, 4, + IPU_ROTATE_NONE, + fbi->fix.smem_start, 0, 0, 0); + + params.adc_sys1.disp = mxc_fbi->disp_num; + params.adc_sys1.ch_mode = WriteTemplateNonSeq; + params.adc_sys1.out_left = MXCFB_SCREEN_LEFT_OFFSET; + params.adc_sys1.out_top = MXCFB_SCREEN_TOP_OFFSET; + ipu_init_channel(ADC_SYS1, ¶ms); + + ipu_init_channel_buffer(ADC_SYS1, IPU_INPUT_BUFFER, + bpp_to_pixfmt(fbi->var.bits_per_pixel), + MXCFB_SCREEN_WIDTH, MXCFB_SCREEN_HEIGHT, + stride_pixels, IPU_ROTATE_NONE, + fbi->fix.smem_start, 0, 0, 0); + ipu_enable_channel(ADC_SYS1); + ipu_select_buffer(ADC_SYS1, IPU_INPUT_BUFFER, 0); + ipu_enable_irq(IPU_IRQ_ADC_SYS1_EOF); + break; +#endif + case MXCFB_REFRESH_AUTO: + if (update_region == NULL) { + update_region = ▭ + rect.top = 0; + rect.left = 0; + rect.height = MXCFB_SCREEN_HEIGHT; + rect.width = MXCFB_SCREEN_WIDTH; + } + params.adc_sys1.disp = mxc_fbi->disp_num; + params.adc_sys1.ch_mode = WriteTemplateNonSeq; + params.adc_sys1.out_left = MXCFB_SCREEN_LEFT_OFFSET + + update_region->left; + params.adc_sys1.out_top = MXCFB_SCREEN_TOP_OFFSET + + update_region->top; + ipu_init_channel(ADC_SYS1, ¶ms); + + // Address aligned to line + start_addr = update_region->top * fbi->fix.line_length; + start_addr += fbi->fix.smem_start; + start_addr += update_region->left * fbi->var.bits_per_pixel / 8; + + ipu_init_channel_buffer(ADC_SYS1, IPU_INPUT_BUFFER, + bpp_to_pixfmt(fbi->var.bits_per_pixel), + update_region->width, + update_region->height, stride_pixels, + IPU_ROTATE_NONE, start_addr, 0, 0, 0); + ipu_enable_channel(ADC_SYS1); + ipu_select_buffer(ADC_SYS1, IPU_INPUT_BUFFER, 0); + + if (ipu_adc_set_update_mode + (ADC_SYS1, IPU_ADC_AUTO_REFRESH_SNOOP, 30, + fbi->fix.smem_start, &memsize) < 0) + dev_err(fbi->device, "Error enabling auto refesh.\n"); + + mxc_fbi->snoop_window_size = memsize; + + break; + } + return ret_mode; +} + +/* + * Open the main framebuffer. + * + * @param fbi framebuffer information pointer + * + * @param user Set if opened by user or clear if opened by kernel + */ +static int mxcfb_open(struct fb_info *fbi, int user) +{ + int retval = 0; + struct mxcfb_info *mxc_fbi = fbi->par; + + if ((retval = wait_event_interruptible(mxcfb_drv_data.suspend_wq, + (mxcfb_drv_data.suspended == + false))) < 0) { + return retval; + } + + mxc_fbi->open_count++; + + retval = mxcfb_blank(FB_BLANK_UNBLANK, fbi); + return retval; +} + +/* + * Close the main framebuffer. + * + * @param fbi framebuffer information pointer + * + * @param user Set if opened by user or clear if opened by kernel + */ +static int mxcfb_release(struct fb_info *fbi, int user) +{ + int retval = 0; + struct mxcfb_info *mxc_fbi = fbi->par; + + if ((retval = wait_event_interruptible(mxcfb_drv_data.suspend_wq, + (mxcfb_drv_data.suspended == + false))) < 0) { + return retval; + } + + --mxc_fbi->open_count; + if (mxc_fbi->open_count == 0) { + retval = mxcfb_blank(FB_BLANK_POWERDOWN, fbi); + } + return retval; +} + +/* + * Set fixed framebuffer parameters based on variable settings. + * + * @param info framebuffer information pointer + */ +static int mxcfb_set_fix(struct fb_info *info) +{ + struct fb_fix_screeninfo *fix = &info->fix; + struct fb_var_screeninfo *var = &info->var; + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)info->par; + + // Set framebuffer id to IPU display number. + strcpy(fix->id, "DISP0 FB"); + fix->id[4] = '0' + mxc_fbi->disp_num; + + // Init settings based on the panel size + fix->line_length = MXCFB_SCREEN_WIDTH * var->bits_per_pixel / 8; + + fix->type = FB_TYPE_PACKED_PIXELS; + fix->accel = FB_ACCEL_NONE; + fix->visual = FB_VISUAL_TRUECOLOR; + fix->xpanstep = 0; + fix->ypanstep = 0; + + return 0; +} + +/* + * Set framebuffer parameters and change the operating mode. + * + * @param info framebuffer information pointer + */ +static int mxcfb_set_par(struct fb_info *fbi) +{ + int retval = 0; + int mode; + + if ((retval = wait_event_interruptible(mxcfb_drv_data.suspend_wq, + (mxcfb_drv_data.suspended == + false))) < 0) { + return retval; + } + + mode = mxcfb_set_refresh_mode(fbi, MXCFB_REFRESH_OFF, NULL); + + mxcfb_set_fix(fbi); + + if (mode != MXCFB_REFRESH_OFF) { +#ifdef PARTIAL_REFRESH + mxcfb_set_refresh_mode(fbi, MXCFB_REFRESH_PARTIAL, NULL); +#else + mxcfb_set_refresh_mode(fbi, MXCFB_REFRESH_AUTO, NULL); +#endif + } + return 0; +} + +/* + * Check framebuffer variable parameters and adjust to valid values. + * + * @param var framebuffer variable parameters + * + * @param info framebuffer information pointer + */ +static int mxcfb_check_var(struct fb_var_screeninfo *var, struct fb_info *fbi) +{ + if (var->xres > MXCFB_SCREEN_WIDTH) + var->xres = MXCFB_SCREEN_WIDTH; + if (var->yres > MXCFB_SCREEN_HEIGHT) + var->yres = MXCFB_SCREEN_HEIGHT; + if (var->xres_virtual < var->xres) + var->xres_virtual = var->xres; + if (var->yres_virtual < var->yres) + var->yres_virtual = var->yres; + + if ((var->bits_per_pixel != 32) && (var->bits_per_pixel != 24) && + (var->bits_per_pixel != 16)) { + var->bits_per_pixel = default_bpp; + } + + switch (var->bits_per_pixel) { + case 16: + var->red.length = 5; + var->red.offset = 11; + var->red.msb_right = 0; + + var->green.length = 6; + var->green.offset = 5; + var->green.msb_right = 0; + + var->blue.length = 5; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 0; + var->transp.offset = 0; + var->transp.msb_right = 0; + break; + case 24: + var->red.length = 8; + var->red.offset = 16; + var->red.msb_right = 0; + + var->green.length = 8; + var->green.offset = 8; + var->green.msb_right = 0; + + var->blue.length = 8; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 0; + var->transp.offset = 0; + var->transp.msb_right = 0; + break; + case 32: + var->red.length = 8; + var->red.offset = 16; + var->red.msb_right = 0; + + var->green.length = 8; + var->green.offset = 8; + var->green.msb_right = 0; + + var->blue.length = 8; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 8; + var->transp.offset = 24; + var->transp.msb_right = 0; + break; + } + + var->height = -1; + var->width = -1; + var->grayscale = 0; + var->nonstd = 0; + + var->pixclock = -1; + var->left_margin = -1; + var->right_margin = -1; + var->upper_margin = -1; + var->lower_margin = -1; + var->hsync_len = -1; + var->vsync_len = -1; + + var->vmode = FB_VMODE_NONINTERLACED; + var->sync = 0; + + return 0; +} + +static inline u_int _chan_to_field(u_int chan, struct fb_bitfield *bf) +{ + chan &= 0xffff; + chan >>= 16 - bf->length; + return chan << bf->offset; +} + +static int +mxcfb_setcolreg(u_int regno, u_int red, u_int green, u_int blue, + u_int trans, struct fb_info *fbi) +{ + unsigned int val; + int ret = 1; + + /* + * If greyscale is true, then we convert the RGB value + * to greyscale no matter what visual we are using. + */ + if (fbi->var.grayscale) + red = green = blue = (19595 * red + 38470 * green + + 7471 * blue) >> 16; + switch (fbi->fix.visual) { + case FB_VISUAL_TRUECOLOR: + /* + * 16-bit True Colour. We encode the RGB value + * according to the RGB bitfield information. + */ + if (regno < 16) { + u32 *pal = fbi->pseudo_palette; + + val = _chan_to_field(red, &fbi->var.red); + val |= _chan_to_field(green, &fbi->var.green); + val |= _chan_to_field(blue, &fbi->var.blue); + + pal[regno] = val; + ret = 0; + } + break; + + case FB_VISUAL_STATIC_PSEUDOCOLOR: + case FB_VISUAL_PSEUDOCOLOR: + break; + } + + return ret; +} + +/* + * mxcfb_blank(): + * Blank the display. + */ +static int mxcfb_blank(int blank, struct fb_info *fbi) +{ + int retval = 0; + struct mxcfb_info *mxc_fbi = fbi->par; + + dev_dbg(fbi->device, "blank = %d\n", blank); + + if ((retval = wait_event_interruptible(mxcfb_drv_data.suspend_wq, + (mxcfb_drv_data.suspended == + false))) < 0) { + return retval; + } + + mxc_fbi->blank = blank; + + switch (blank) { + case FB_BLANK_POWERDOWN: + case FB_BLANK_VSYNC_SUSPEND: + case FB_BLANK_HSYNC_SUSPEND: + case FB_BLANK_NORMAL: + mxcfb_set_refresh_mode(fbi, MXCFB_REFRESH_OFF, NULL); + break; + case FB_BLANK_UNBLANK: + mxcfb_set_refresh_mode(fbi, MXCFB_REFRESH_DEFAULT, NULL); + break; + } + return 0; +} + +/*! + * This structure contains the pointers to the control functions that are + * invoked by the core framebuffer driver to perform operations like + * blitting, rectangle filling, copy regions and cursor definition. + */ +static struct fb_ops mxcfb_ops = { + .owner = THIS_MODULE, + .fb_open = mxcfb_open, + .fb_release = mxcfb_release, + .fb_set_par = mxcfb_set_par, + .fb_check_var = mxcfb_check_var, + .fb_setcolreg = mxcfb_setcolreg, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, + .fb_blank = mxcfb_blank, +}; + +/*! + * Allocates the DRAM memory for the frame buffer. This buffer is remapped + * into a non-cached, non-buffered, memory region to allow palette and pixel + * writes to occur without flushing the cache. Once this area is remapped, + * all virtual memory access to the video memory should occur at the new region. + * + * @param fbi framebuffer information pointer + * + * @return Error code indicating success or failure + */ +static int mxcfb_map_video_memory(struct fb_info *fbi) +{ + u32 msb; + u32 offset; + struct mxcfb_info *mxcfbi = fbi->par; + + fbi->fix.smem_len = fbi->var.xres_virtual * fbi->var.yres_virtual * 4; + + // Set size to power of 2. + msb = fls(fbi->fix.smem_len); + if (!(fbi->fix.smem_len & ((1UL << msb) - 1))) + msb--; // Already aligned to power 2 + if (msb < 11) + msb = 11; + mxcfbi->alloc_size = (1UL << msb) * 2; + + mxcfbi->alloc_start_vaddr = dma_alloc_coherent(fbi->device, + mxcfbi->alloc_size, + &mxcfbi-> + alloc_start_paddr, + GFP_KERNEL | GFP_DMA); + + if (mxcfbi->alloc_start_vaddr == 0) { + dev_err(fbi->device, "Unable to allocate framebuffer memory\n"); + return -ENOMEM; + } + dev_dbg(fbi->device, "allocated fb memory @ paddr=0x%08X, size=%d.\n", + (uint32_t) mxcfbi->alloc_start_paddr, mxcfbi->alloc_size); + + offset = + ((mxcfbi->alloc_size / 2) - 1) & ~((mxcfbi->alloc_size / 2) - 1); + fbi->fix.smem_start = mxcfbi->alloc_start_paddr + offset; + dev_dbg(fbi->device, "aligned fb start @ paddr=0x%08lX, size=%u.\n", + fbi->fix.smem_start, fbi->fix.smem_len); + + fbi->screen_base = mxcfbi->alloc_start_vaddr + offset; + + /* Clear the screen */ + memset(fbi->screen_base, 0, fbi->fix.smem_len); + return 0; +} + +/*! + * De-allocates the DRAM memory for the frame buffer. + * + * @param fbi framebuffer information pointer + * + * @return Error code indicating success or failure + */ +static int mxcfb_unmap_video_memory(struct fb_info *fbi) +{ + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par; + + dma_free_coherent(fbi->device, mxc_fbi->alloc_size, + mxc_fbi->alloc_start_vaddr, + mxc_fbi->alloc_start_paddr); + return 0; +} + +/*! + * Initializes the framebuffer information pointer. After allocating + * sufficient memory for the framebuffer structure, the fields are + * filled with custom information passed in from the configurable + * structures. This includes information such as bits per pixel, + * color maps, screen width/height and RGBA offsets. + * + * @return Framebuffer structure initialized with our information + */ +static struct fb_info *mxcfb_init_fbinfo(struct device *dev, struct fb_ops *ops) +{ + struct fb_info *fbi; + struct mxcfb_info *mxcfbi; + + /* + * Allocate sufficient memory for the fb structure + */ + fbi = framebuffer_alloc(sizeof(struct mxcfb_info), dev); + if (!fbi) + return NULL; + + mxcfbi = (struct mxcfb_info *)fbi->par; + + /* + * Fill in fb_info structure information + */ + fbi->var.xres = fbi->var.xres_virtual = MXCFB_SCREEN_WIDTH; + fbi->var.yres = fbi->var.yres_virtual = MXCFB_SCREEN_HEIGHT; + fbi->var.activate = FB_ACTIVATE_NOW; + mxcfb_check_var(&fbi->var, fbi); + + mxcfb_set_fix(fbi); + + fbi->fbops = ops; + fbi->flags = FBINFO_FLAG_DEFAULT; + fbi->pseudo_palette = mxcfbi->pseudo_palette; + + /* + * Allocate colormap + */ + fb_alloc_cmap(&fbi->cmap, 16, 0); + + return fbi; +} + +/*! + * Probe routine for the framebuffer driver. It is called during the + * driver binding process. The following functions are performed in + * this routine: Framebuffer initialization, Memory allocation and + * mapping, Framebuffer registration, IPU initialization. + * + * @return Appropriate error code to the kernel common code + */ +static int mxcfb_probe(struct platform_device *pdev) +{ + struct fb_info *fbi; + struct mxcfb_info *mxc_fbi; + int ret; + + platform_set_drvdata(pdev, &mxcfb_drv_data); + + /* + * Initialize FB structures + */ + fbi = mxcfb_init_fbinfo(&pdev->dev, &mxcfb_ops); + if (!fbi) { + ret = -ENOMEM; + goto err0; + } + mxcfb_drv_data.fbi = fbi; + mxc_fbi = fbi->par; + + mxcfb_drv_data.suspended = false; + init_waitqueue_head(&mxcfb_drv_data.suspend_wq); + + /* + * Allocate memory + */ + ret = mxcfb_map_video_memory(fbi); + if (ret < 0) { + goto err1; + } + + mxcfb_init_panel(fbi); + + /* + * Register framebuffer + */ + ret = register_framebuffer(fbi); + if (ret < 0) { + goto err2; + } + + dev_info(&pdev->dev, "%s registered\n", MXCFB_NAME); + + return 0; + + err2: + mxcfb_unmap_video_memory(fbi); + err1: + if (&fbi->cmap) + fb_dealloc_cmap(&fbi->cmap); + framebuffer_release(fbi); + err0: + return ret; +} + +#ifdef CONFIG_PM +/*! + * Power management hooks. Note that we won't be called from IRQ context, + * unlike the blank functions above, so we may sleep. + */ + +/*! + * Suspends the framebuffer and blanks the screen. Power management support + * + * @param pdev pointer to device structure. + * @param state state of the device. + * + * @return success + */ +static int mxcfb_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct mxcfb_data *drv_data = platform_get_drvdata(pdev); + struct fb_info *fbi = drv_data->fbi; + struct mxcfb_info *mxc_fbi = fbi->par; + + drv_data->suspended = true; + + if (mxc_fbi->blank == FB_BLANK_UNBLANK) + mxcfb_set_refresh_mode(fbi, MXCFB_REFRESH_OFF, NULL); + /* Display OFF */ + ipu_adc_write_cmd(mxc_fbi->disp_num, CMD, DISOFF, 0, 0); + + return 0; +} + +/*! + * Resumes the framebuffer and unblanks the screen. Power management support + * + * @param pdev pointer to device structure. + * + * @return success + */ +static int mxcfb_resume(struct platform_device *pdev) +{ + struct mxcfb_data *drv_data = platform_get_drvdata(pdev); + struct fb_info *fbi = drv_data->fbi; + struct mxcfb_info *mxc_fbi = fbi->par; + + // Display ON + ipu_adc_write_cmd(mxc_fbi->disp_num, CMD, DISON, 0, 0); + drv_data->suspended = false; + + if (mxc_fbi->blank == FB_BLANK_UNBLANK) + mxcfb_set_refresh_mode(fbi, MXCFB_REFRESH_DEFAULT, NULL); + wake_up_interruptible(&drv_data->suspend_wq); + + return 0; +} +#else +#define mxcfb_suspend NULL +#define mxcfb_resume NULL +#endif + +/*! + * This structure contains pointers to the power management callback functions. + */ +static struct platform_driver mxcfb_driver = { + .driver = { + .name = MXCFB_NAME, + }, + .probe = mxcfb_probe, + .suspend = mxcfb_suspend, + .resume = mxcfb_resume, +}; + +/*! + * Device definition for the Framebuffer + */ +static struct platform_device mxcfb_device = { + .name = MXCFB_NAME, + .id = 0, + .dev = { + .coherent_dma_mask = 0xFFFFFFFF, + } +}; + +/*! + * Main entry function for the framebuffer. The function registers the power + * management callback functions with the kernel and also registers the MXCFB + * callback functions with the core Linux framebuffer driver \b fbmem.c + * + * @return Error code indicating success or failure + */ +static int mxcfb_init(void) +{ + int ret = 0; + + ret = platform_driver_register(&mxcfb_driver); + if (ret == 0) { + ret = platform_device_register(&mxcfb_device); + if (ret != 0) { + platform_driver_unregister(&mxcfb_driver); + } + } + return ret; +} + +static void mxcfb_exit(void) +{ + struct fb_info *fbi = dev_get_drvdata(&mxcfb_device.dev); + + if (fbi) { + mxcfb_unmap_video_memory(fbi); + + if (&fbi->cmap) + fb_dealloc_cmap(&fbi->cmap); + + unregister_framebuffer(fbi); + framebuffer_release(fbi); + } + + platform_device_unregister(&mxcfb_device); + platform_driver_unregister(&mxcfb_driver); +} + +module_init(mxcfb_init); +module_exit(mxcfb_exit); + +EXPORT_SYMBOL(mxcfb_set_refresh_mode); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MXC Epson framebuffer driver"); +MODULE_SUPPORTED_DEVICE("fb"); diff --git a/drivers/video/mxc/mxcfb_modedb.c b/drivers/video/mxc/mxcfb_modedb.c new file mode 100644 index 000000000000..26622115733a --- /dev/null +++ b/drivers/video/mxc/mxcfb_modedb.c @@ -0,0 +1,63 @@ +/* + * Copyright 2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/kernel.h> +#include <asm/arch/mxcfb.h> + +struct fb_videomode mxcfb_modedb[] = { + { + /* 240x320 @ 60 Hz */ + "Sharp-QVGA", 60, 240, 320, 185925, 9, 16, 7, 9, 1, 1, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_SHARP_MODE | + FB_SYNC_CLK_INVERT | FB_SYNC_DATA_INVERT | FB_SYNC_CLK_IDLE_EN, + FB_VMODE_NONINTERLACED, + 0,}, + { + /* 240x33 @ 60 Hz */ + "Sharp-CLI", 60, 240, 33, 185925, 9, 16, 7, 9 + 287, 1, 1, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_SHARP_MODE | + FB_SYNC_CLK_INVERT | FB_SYNC_DATA_INVERT | FB_SYNC_CLK_IDLE_EN, + FB_VMODE_NONINTERLACED, + 0,}, + { + /* 640x480 @ 60 Hz */ + "NEC-VGA", 60, 640, 480, 38255, 144, 0, 34, 40, 1, 1, + FB_SYNC_VERT_HIGH_ACT | FB_SYNC_OE_ACT_HIGH, + FB_VMODE_NONINTERLACED, + 0,}, + { + /* NTSC TV output */ + "TV-NTSC", 60, 640, 480, 37538, + 38, 858 - 640 - 38 - 3, + 36, 518 - 480 - 36 - 1, + 3, 1, + 0, + FB_VMODE_NONINTERLACED, + 0,}, + { + /* PAL TV output */ + "TV-PAL", 50, 640, 480, 37538, + 38, 960 - 640 - 38 - 32, + 32, 555 - 480 - 32 - 3, + 32, 3, + 0, + FB_VMODE_NONINTERLACED, + 0,}, + { + /* TV output VGA mode, 640x480 @ 65 Hz */ + "TV-VGA", 60, 640, 480, 40574, 35, 45, 9, 1, 46, 5, + 0, FB_VMODE_NONINTERLACED, 0, + }, +}; + +int mxcfb_modedb_sz = ARRAY_SIZE(mxcfb_modedb); diff --git a/drivers/video/mxc/mxcfb_sharp_128x128.c b/drivers/video/mxc/mxcfb_sharp_128x128.c new file mode 100644 index 000000000000..fd28e9328700 --- /dev/null +++ b/drivers/video/mxc/mxcfb_sharp_128x128.c @@ -0,0 +1,1167 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mxcfb_sharp_128x128.c + * + * @brief MXC Frame buffer driver for Sharp 128x128 panel + * + * @ingroup Framebuffer + */ + +/*! + * Include files + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/fb.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/ioport.h> +#include <linux/platform_device.h> +#include <linux/dma-mapping.h> +#include <asm/hardware.h> +#include <asm/io.h> +#include <asm/mach-types.h> +#include <asm/uaccess.h> +#include <asm/arch/ipu.h> +#include <asm/arch/mxcfb.h> + +//#define PARTIAL_REFRESH +#define MXCFB_REFRESH_DEFAULT MXCFB_REFRESH_AUTO +/* + * Driver name + */ +#define MXCFB_NAME "mxcfb_sharp_128" + +#define MXCFB_SCREEN_TOP_OFFSET 0 +#define MXCFB_SCREEN_LEFT_OFFSET 2 +#define MXCFB_SCREEN_WIDTH 128 +#define MXCFB_SCREEN_HEIGHT 128 + +/*! + * Enum defining panel commands. + */ +enum { + SWRESET = 0x01, + DISON = 0x29, + DISOFF = 0x28, + DISCTL = 0xB6, + TMPGRD = 0xB7, + SD_CSET = 0x2A, + SD_PSET = 0x2B, + SLPIN = 0x10, + SLPOUT = 0x11, + DISINOFF = 0x20, + RAMWR = 0x2C, + MADCTL = 0x36, + COLMOD = 0x3A, + GCPSET0 = 0xB3, + FRSET = 0xB1, + VOLCTL = 0xBA, + WRCNTR = 0x25, + GAMSET = 0x26, + DRPOS = 0xBB, + PAGEND = 0xBE, + MODSEL = 0xC0, + COLEND = 0xC3, +}; + +struct mxcfb_info { + int open_count; + int blank; + uint32_t disp_num; + + u32 pseudo_palette[16]; + + int32_t cur_update_mode; + dma_addr_t alloc_start_paddr; + void *alloc_start_vaddr; + u32 alloc_size; + uint32_t snoop_window_size; +}; + +struct mxcfb_data { + struct fb_info *fbi; + volatile int32_t vsync_flag; + wait_queue_head_t vsync_wq; + wait_queue_head_t suspend_wq; + bool suspended; +}; + +static struct mxcfb_data mxcfb_drv_data; +static unsigned long default_bpp = 16; + +void slcd_gpio_config(void); +extern void gpio_lcd_active(void); +static int mxcfb_blank(int blank, struct fb_info *fbi); + +static uint32_t bpp_to_pixfmt(int bpp) +{ + uint32_t pixfmt = 0; + switch (bpp) { + case 24: + pixfmt = IPU_PIX_FMT_BGR24; + break; + case 32: + pixfmt = IPU_PIX_FMT_BGR32; + break; + case 16: + pixfmt = IPU_PIX_FMT_RGB565; + break; + } + return pixfmt; +} + +/*! + * This function sets display region in the panel + * + * @param disp display panel to config + * @param x1 x-coordinate of one vertex. + * @param x2 x-coordinate of second vertex. + * @param y1 y-coordinate of one vertex. + * @param y2 y-coordinate of second vertex. + */ +static void set_panel_region(int disp, uint32_t x1, uint32_t x2, + uint32_t y1, uint32_t y2) +{ + uint32_t param[4]; + + param[0] = x1; + param[1] = x2; + param[2] = y1; + param[3] = y2; + + // SD_CSET + ipu_adc_write_cmd(disp, CMD, SD_CSET, param, 2); + // SD_PSET + + ipu_adc_write_cmd(disp, CMD, SD_PSET, &(param[2]), 2); +} + +/*! + * Function to create and initiate template command buffer for ADC. This + * template will be written to Panel memory. + */ +static void init_channel_template(int disp) +{ + /* template command buffer for ADC is 32 */ + uint32_t tempCmd[TEMPLATE_BUF_SIZE]; + uint32_t i = 0; + + memset(tempCmd, 0, sizeof(uint32_t) * TEMPLATE_BUF_SIZE); + /* setup update display region */ + /* whole the screen during init */ + /*WRITE Y COORDINATE CMND */ + tempCmd[i++] = ipu_adc_template_gen(WR_CMND, 0, SINGLE_STEP, SD_PSET); + /*WRITE Y START ADDRESS CMND LSB[22:8] */ + tempCmd[i++] = ipu_adc_template_gen(WR_YADDR, 1, SINGLE_STEP, 0x01); + /*WRITE Y STOP ADDRESS CMND */ + tempCmd[i++] = + ipu_adc_template_gen(WR_CMND, 1, SINGLE_STEP, + MXCFB_SCREEN_HEIGHT - 1); + /*WRITE X COORDINATE CMND */ + tempCmd[i++] = ipu_adc_template_gen(WR_CMND, 0, SINGLE_STEP, SD_CSET); + /*WRITE X ADDRESS CMND LSB[7:0] */ + tempCmd[i++] = ipu_adc_template_gen(WR_XADDR, 1, SINGLE_STEP, 0x01); + /*WRITE X STOP ADDRESS CMND */ + tempCmd[i++] = ipu_adc_template_gen(WR_CMND, 1, SINGLE_STEP, + MXCFB_SCREEN_WIDTH + + MXCFB_SCREEN_LEFT_OFFSET - 1); + /*WRITE MEMORY CMND MSB */ + tempCmd[i++] = ipu_adc_template_gen(WR_CMND, 0, SINGLE_STEP, RAMWR); + /*WRITE DATA CMND and STP */ + tempCmd[i++] = ipu_adc_template_gen(WR_DATA, 1, STOP, 0); + + ipu_adc_write_template(disp, tempCmd, true); +} + +/*! + * Function to initialize the panel. First it resets the panel and then + * initilizes panel. + */ +static void _init_panel(int disp) +{ + uint32_t cmd_param[2]; + uint32_t disctl_param[] = { 0x0C, 0x48, 0x20 }; + uint32_t gcpset0_param[] = { + 0xAF, 0x92, 0x83, 0x24, + 0x52, 0x94, 0x4A, 0x47, + 0xAB, 0x75, 0x6A, 0xDF, + 0xAB, 0x4B, 0xA5, 0x54, + 0x00 + }; + uint32_t i; + + (void)gcpset0_param; + + gpio_lcd_active(); + + ipu_adc_write_cmd(disp, CMD, SWRESET, 0, 0); + msleep(1); + + ipu_adc_write_cmd(disp, CMD, DISCTL, disctl_param, 3); + + // 16-bit 565 mode + cmd_param[0] = 0x05; + ipu_adc_write_cmd(disp, CMD, COLMOD, cmd_param, 1); + + cmd_param[0] = 0x01; + ipu_adc_write_cmd(disp, CMD, TMPGRD, cmd_param, 1); + + cmd_param[0] = 0x2F; + cmd_param[1] = 0x02; + ipu_adc_write_cmd(disp, CMD, VOLCTL, cmd_param, 2); + + cmd_param[0] = 0x3F; + ipu_adc_write_cmd(disp, CMD, WRCNTR, cmd_param, 1); + + cmd_param[0] = 0x00; + ipu_adc_write_cmd(disp, CMD, DRPOS, cmd_param, 1); + + cmd_param[0] = 0x00; + ipu_adc_write_cmd(disp, CMD, MADCTL, cmd_param, 1); + + cmd_param[0] = 0x05; + ipu_adc_write_cmd(disp, CMD, FRSET, cmd_param, 1); + + cmd_param[0] = 0x80; + ipu_adc_write_cmd(disp, CMD, PAGEND, cmd_param, 1); + + cmd_param[0] = 0x01; + ipu_adc_write_cmd(disp, CMD, GAMSET, cmd_param, 1); + + cmd_param[0] = 0x04; + ipu_adc_write_cmd(disp, CMD, MODSEL, cmd_param, 1); + + // Sleep OUT + ipu_adc_write_cmd(disp, CMD, SLPOUT, 0, 0); + + // Set display to white + // Setup page and column addresses + set_panel_region(disp, MXCFB_SCREEN_LEFT_OFFSET, + MXCFB_SCREEN_WIDTH + MXCFB_SCREEN_LEFT_OFFSET - 1, + 0, MXCFB_SCREEN_HEIGHT - 1); + // Do RAM write cmd + ipu_adc_write_cmd(disp, CMD, RAMWR, 0, 0); + for (i = 0; i < (MXCFB_SCREEN_WIDTH * MXCFB_SCREEN_HEIGHT); i++) { + ipu_adc_write_cmd(disp, DAT, 0xFF, 0, 0); + ipu_adc_write_cmd(disp, DAT, 0xFF, 0, 0); + } + + msleep(100); + + // Display ON + ipu_adc_write_cmd(disp, CMD, DISON, 0, 0); + + pr_debug("initialized panel\n"); +} + +#ifdef PARTIAL_REFRESH +static irqreturn_t mxcfb_sys2_eof_irq_handler(int irq, void *dev_id) +{ + ipu_channel_params_t params; + struct fb_info *fbi = dev_id; + struct mxcfb_info *mxc_fbi = fbi->par; + uint32_t stat[2], seg_size; + uint32_t lsb, msb; + uint32_t update_height, start_line, start_addr, end_line, end_addr; + uint32_t stride_pixels = (fbi->fix.line_length * 8) / + fbi->var.bits_per_pixel; + + ipu_adc_get_snooping_status(&stat[0], &stat[1]); + //DPRINTK("snoop status = 0x%08X%08X\n", stat[1], stat[0]); + + if (!stat[0] && !stat[1]) { + dev_err(fbi->device, "error no bus snooping bits set\n"); + return IRQ_HANDLED; + } + ipu_disable_irq(IPU_IRQ_ADC_SYS2_EOF); + + lsb = ffs(stat[0]); + if (lsb) { + lsb--; + } else { + lsb = ffs(stat[1]); + lsb += 32 - 1; + } + msb = fls(stat[1]); + if (msb) { + msb += 32; + } else { + msb = fls(stat[0]); + } + + seg_size = mxc_fbi->snoop_window_size / 64; + + start_addr = lsb * seg_size; // starting address offset + start_line = start_addr / fbi->fix.line_length; + start_addr = start_line * fbi->fix.line_length; // Addr aligned to line + start_addr += fbi->fix.smem_start; + + end_addr = msb * seg_size; // ending address offset + end_line = end_addr / fbi->fix.line_length; + end_line++; + + if (end_line > fbi->var.yres) { + end_line = fbi->var.yres; + } + + update_height = end_line - start_line; + dev_dbg(fbi->device, "updating rows %d to %d, start addr = 0x%08X\n", + start_line, end_line, start_addr); + + ipu_uninit_channel(ADC_SYS1); + params.adc_sys1.disp = mxc_fbi->disp_num; + params.adc_sys1.ch_mode = WriteTemplateNonSeq; + params.adc_sys1.out_left = MXCFB_SCREEN_LEFT_OFFSET; + params.adc_sys1.out_top = start_line; + ipu_init_channel(ADC_SYS1, ¶ms); + + ipu_init_channel_buffer(ADC_SYS1, IPU_INPUT_BUFFER, + bpp_to_pixfmt(fbi->var.bits_per_pixel), + MXCFB_SCREEN_WIDTH, + update_height, + stride_pixels, + IPU_ROTATE_NONE, (dma_addr_t) start_addr, 0, 0, + 0); + ipu_enable_channel(ADC_SYS1); + ipu_select_buffer(ADC_SYS1, IPU_INPUT_BUFFER, 0); + ipu_enable_irq(IPU_IRQ_ADC_SYS1_EOF); + + return IRQ_HANDLED; +} + +static irqreturn_t mxcfb_sys1_eof_irq_handler(int irq, void *dev_id) +{ + ipu_disable_irq(IPU_IRQ_ADC_SYS1_EOF); + ipu_disable_channel(ADC_SYS1, false); + + ipu_enable_channel(ADC_SYS2); + ipu_enable_irq(IPU_IRQ_ADC_SYS2_EOF); + + return IRQ_HANDLED; +} +#endif + +/*! + * Function to initialize Asynchronous Display Controller. It also initilizes + * the ADC System 1 channel. Configure ADC display 0 parallel interface for + * the panel. + * + * @param fbi framebuffer information pointer + */ +static void mxcfb_init_panel(struct fb_info *fbi) +{ + int msb; + int panel_stride; + ipu_channel_params_t params; + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par; + + uint32_t pix_fmt = IPU_PIX_FMT_RGB565; + ipu_adc_sig_cfg_t sig = { 0, 0, 0, 0, 0, 0, 0, 0, + IPU_ADC_BURST_WCS, + IPU_ADC_IFC_MODE_SYS80_TYPE2, + 8, 0, 0, IPU_ADC_SER_NO_RW + }; + mxc_fbi->disp_num = DISP2; + +#ifdef PARTIAL_REFRESH + if (ipu_request_irq(IPU_IRQ_ADC_SYS2_EOF, mxcfb_sys2_eof_irq_handler, 0, + MXCFB_NAME, fbi) != 0) { + dev_err(fbi->device, "Error registering SYS2 irq handler.\n"); + return; + } + + if (ipu_request_irq(IPU_IRQ_ADC_SYS1_EOF, mxcfb_sys1_eof_irq_handler, 0, + MXCFB_NAME, fbi) != 0) { + dev_err(fbi->device, "Error registering SYS1 irq handler.\n"); + return; + } + ipu_disable_irq(IPU_IRQ_ADC_SYS1_EOF); + ipu_disable_irq(IPU_IRQ_ADC_SYS2_EOF); +#endif + // Init DI interface + msb = fls(MXCFB_SCREEN_WIDTH); + if (!(MXCFB_SCREEN_WIDTH & ((1UL << msb) - 1))) + msb--; // Already aligned to power 2 + panel_stride = 1UL << msb; + ipu_adc_init_panel(mxc_fbi->disp_num, + MXCFB_SCREEN_WIDTH + MXCFB_SCREEN_LEFT_OFFSET, + MXCFB_SCREEN_HEIGHT, + pix_fmt, panel_stride, sig, XY, 0, VsyncInternal); + + ipu_adc_init_ifc_timing(mxc_fbi->disp_num, true, + 190, 17, 104, 190, 5000000); + ipu_adc_init_ifc_timing(mxc_fbi->disp_num, false, 110, 25, 80, 0, 0); + + // Needed to turn on ADC clock for panel init + memset(¶ms, 0, sizeof(params)); + params.adc_sys2.disp = mxc_fbi->disp_num; + params.adc_sys2.ch_mode = WriteTemplateNonSeq; + params.adc_sys2.out_left = MXCFB_SCREEN_LEFT_OFFSET; + params.adc_sys2.out_top = MXCFB_SCREEN_TOP_OFFSET; + ipu_init_channel(ADC_SYS2, ¶ms); + + _init_panel(mxc_fbi->disp_num); + init_channel_template(mxc_fbi->disp_num); +} + +static int _mxcfb_set_refresh_mode(struct fb_info *fbi, int mode, + struct mxcfb_rect *update_region) +{ + unsigned long start_addr; + int ret_mode; + ipu_channel_params_t params; + struct mxcfb_rect rect; + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par; + uint32_t stride_pixels = (fbi->fix.line_length * 8) / + fbi->var.bits_per_pixel; + uint32_t memsize = fbi->fix.smem_len; + + if (mxc_fbi->cur_update_mode == mode) + return mode; + + ret_mode = mxc_fbi->cur_update_mode; + + ipu_adc_set_update_mode(ADC_SYS2, IPU_ADC_REFRESH_NONE, 0, 0, 0); + ipu_disable_channel(ADC_SYS2, true); +#ifdef PARTIAL_REFRESH + ipu_disable_irq(IPU_IRQ_ADC_SYS1_EOF); + ipu_adc_set_update_mode(ADC_SYS1, IPU_ADC_REFRESH_NONE, 0, 0, 0); + ipu_disable_irq(IPU_IRQ_ADC_SYS2_EOF); + + ipu_disable_channel(ADC_SYS1, true); + ipu_clear_irq(IPU_IRQ_ADC_SYS1_EOF); + ipu_disable_channel(ADC_SYS2, true); + ipu_clear_irq(IPU_IRQ_ADC_SYS2_EOF); +#endif +// ipu_adc_get_snooping_status(&dummy, &dummy); + + mxc_fbi->cur_update_mode = mode; + + switch (mode) { + case MXCFB_REFRESH_OFF: +// if (ipu_adc_set_update_mode(ADC_SYS1, IPU_ADC_REFRESH_NONE, +// 0, 0, 0) < 0) +// dev_err(fbi->device, "Error enabling auto refesh.\n"); + if (ipu_adc_set_update_mode(ADC_SYS2, IPU_ADC_REFRESH_NONE, + 0, 0, 0) < 0) + dev_err(fbi->device, "Error enabling auto refesh.\n"); +#if 1 + ipu_init_channel_buffer(ADC_SYS2, IPU_INPUT_BUFFER, + bpp_to_pixfmt(fbi->var.bits_per_pixel), + 1, 1, 4, + IPU_ROTATE_NONE, + fbi->fix.smem_start, + fbi->fix.smem_start, 0, 0); + ipu_enable_channel(ADC_SYS2); + ipu_select_buffer(ADC_SYS2, IPU_INPUT_BUFFER, 0); + ipu_select_buffer(ADC_SYS2, IPU_INPUT_BUFFER, 1); + msleep(10); +#endif +#ifdef PARTIAL_REFRESH + ipu_uninit_channel(ADC_SYS2); +#endif + break; + case MXCFB_REFRESH_PARTIAL: +#ifdef PARTIAL_REFRESH + params.adc_sys2.disp = mxc_fbi->disp_num; + params.adc_sys2.ch_mode = WriteTemplateNonSeq; + params.adc_sys2.out_left = 0; + params.adc_sys2.out_top = 0; + ipu_init_channel(ADC_SYS2, ¶ms); + + if (ipu_adc_set_update_mode(ADC_SYS1, IPU_ADC_REFRESH_NONE, + 0, 0, 0) < 0) { + dev_err(fbi->device, "Error enabling auto refesh.\n"); + } + if (ipu_adc_set_update_mode + (ADC_SYS2, IPU_ADC_AUTO_REFRESH_SNOOP, 30, + fbi->fix.smem_start, &memsize) < 0) { + dev_err(fbi->device, "Error enabling auto refesh.\n"); + } + mxc_fbi->snoop_window_size = memsize; + + ipu_init_channel_buffer(ADC_SYS2, IPU_INPUT_BUFFER, + bpp_to_pixfmt(fbi->var.bits_per_pixel), + 1, 1, 4, + IPU_ROTATE_NONE, + fbi->fix.smem_start, 0, 0, 0); + + params.adc_sys1.disp = mxc_fbi->disp_num; + params.adc_sys1.ch_mode = WriteTemplateNonSeq; + params.adc_sys1.out_left = MXCFB_SCREEN_LEFT_OFFSET; + params.adc_sys1.out_top = MXCFB_SCREEN_TOP_OFFSET; + ipu_init_channel(ADC_SYS1, ¶ms); + + ipu_init_channel_buffer(ADC_SYS1, IPU_INPUT_BUFFER, + bpp_to_pixfmt(fbi->var.bits_per_pixel), + MXCFB_SCREEN_WIDTH, MXCFB_SCREEN_HEIGHT, + stride_pixels, IPU_ROTATE_NONE, + fbi->fix.smem_start, 0, 0, 0); + ipu_enable_channel(ADC_SYS1); + ipu_select_buffer(ADC_SYS1, IPU_INPUT_BUFFER, 0); + ipu_enable_irq(IPU_IRQ_ADC_SYS1_EOF); + break; +#endif + case MXCFB_REFRESH_AUTO: + if (update_region == NULL) { + update_region = ▭ + rect.top = 0; + rect.left = 0; + rect.height = MXCFB_SCREEN_HEIGHT; + rect.width = MXCFB_SCREEN_WIDTH; + } + params.adc_sys2.disp = mxc_fbi->disp_num; + params.adc_sys2.ch_mode = WriteTemplateNonSeq; + params.adc_sys2.out_left = MXCFB_SCREEN_LEFT_OFFSET + + update_region->left; + params.adc_sys2.out_top = MXCFB_SCREEN_TOP_OFFSET + + update_region->top; + ipu_init_channel(ADC_SYS2, ¶ms); + + // Address aligned to line + start_addr = update_region->top * fbi->fix.line_length; + start_addr += fbi->fix.smem_start; + start_addr += update_region->left * fbi->var.bits_per_pixel / 8; + + ipu_init_channel_buffer(ADC_SYS2, IPU_INPUT_BUFFER, + bpp_to_pixfmt(fbi->var.bits_per_pixel), + update_region->width, + update_region->height, stride_pixels, + IPU_ROTATE_NONE, start_addr, 0, 0, 0); + ipu_enable_channel(ADC_SYS2); + ipu_select_buffer(ADC_SYS2, IPU_INPUT_BUFFER, 0); + + if (ipu_adc_set_update_mode + (ADC_SYS2, IPU_ADC_AUTO_REFRESH, 15, + fbi->fix.smem_start, &memsize) < 0) + dev_err(fbi->device, "Error enabling auto refesh.\n"); + + mxc_fbi->snoop_window_size = memsize; + + break; + } + return ret_mode; +} + +/* + * Open the main framebuffer. + * + * @param fbi framebuffer information pointer + * + * @param user Set if opened by user or clear if opened by kernel + */ +static int mxcfb_open(struct fb_info *fbi, int user) +{ + int retval = 0; + struct mxcfb_info *mxc_fbi = fbi->par; + + if ((retval = wait_event_interruptible(mxcfb_drv_data.suspend_wq, + (mxcfb_drv_data.suspended == + false))) < 0) { + return retval; + } + + mxc_fbi->open_count++; + + retval = mxcfb_blank(FB_BLANK_UNBLANK, fbi); + return retval; +} + +/* + * Close the main framebuffer. + * + * @param fbi framebuffer information pointer + * + * @param user Set if opened by user or clear if opened by kernel + */ +static int mxcfb_release(struct fb_info *fbi, int user) +{ + int retval = 0; + struct mxcfb_info *mxc_fbi = fbi->par; + + if ((retval = wait_event_interruptible(mxcfb_drv_data.suspend_wq, + (mxcfb_drv_data.suspended == + false))) < 0) { + return retval; + } + + --mxc_fbi->open_count; + if (mxc_fbi->open_count == 0) { + retval = mxcfb_blank(FB_BLANK_POWERDOWN, fbi); + } + return retval; +} + +/* + * Set fixed framebuffer parameters based on variable settings. + * + * @param info framebuffer information pointer + */ +static int mxcfb_set_fix(struct fb_info *info) +{ + struct fb_fix_screeninfo *fix = &info->fix; + struct fb_var_screeninfo *var = &info->var; + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)info->par; + + // Set framebuffer id to IPU display number. + strcpy(fix->id, "DISP0 FB"); + fix->id[4] = '0' + mxc_fbi->disp_num; + + // Init settings based on the panel size + fix->line_length = MXCFB_SCREEN_WIDTH * var->bits_per_pixel / 8; + + fix->type = FB_TYPE_PACKED_PIXELS; + fix->accel = FB_ACCEL_NONE; + fix->visual = FB_VISUAL_TRUECOLOR; + fix->xpanstep = 0; + fix->ypanstep = 0; + + return 0; +} + +/* + * Set framebuffer parameters and change the operating mode. + * + * @param info framebuffer information pointer + */ +static int mxcfb_set_par(struct fb_info *fbi) +{ + int retval = 0; + int mode; + + if ((retval = wait_event_interruptible(mxcfb_drv_data.suspend_wq, + (mxcfb_drv_data.suspended == + false))) < 0) { + return retval; + } + + mode = _mxcfb_set_refresh_mode(fbi, MXCFB_REFRESH_OFF, NULL); + + mxcfb_set_fix(fbi); + + if (mode != MXCFB_REFRESH_OFF) { +#ifdef PARTIAL_REFRESH + _mxcfb_set_refresh_mode(fbi, MXCFB_REFRESH_PARTIAL, NULL); +#else + _mxcfb_set_refresh_mode(fbi, MXCFB_REFRESH_AUTO, NULL); +#endif + } + return 0; +} + +/* + * Check framebuffer variable parameters and adjust to valid values. + * + * @param var framebuffer variable parameters + * + * @param info framebuffer information pointer + */ +static int mxcfb_check_var(struct fb_var_screeninfo *var, struct fb_info *fbi) +{ + if (var->xres > MXCFB_SCREEN_WIDTH) + var->xres = MXCFB_SCREEN_WIDTH; + if (var->yres > MXCFB_SCREEN_HEIGHT) + var->yres = MXCFB_SCREEN_HEIGHT; + if (var->xres_virtual < var->xres) + var->xres_virtual = var->xres; + if (var->yres_virtual < var->yres) + var->yres_virtual = var->yres; + + if ((var->bits_per_pixel != 32) && (var->bits_per_pixel != 24) && + (var->bits_per_pixel != 16)) { + var->bits_per_pixel = default_bpp; + } + + switch (var->bits_per_pixel) { + case 16: + var->red.length = 5; + var->red.offset = 11; + var->red.msb_right = 0; + + var->green.length = 6; + var->green.offset = 5; + var->green.msb_right = 0; + + var->blue.length = 5; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 0; + var->transp.offset = 0; + var->transp.msb_right = 0; + break; + case 24: + var->red.length = 8; + var->red.offset = 16; + var->red.msb_right = 0; + + var->green.length = 8; + var->green.offset = 8; + var->green.msb_right = 0; + + var->blue.length = 8; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 0; + var->transp.offset = 0; + var->transp.msb_right = 0; + break; + case 32: + var->red.length = 8; + var->red.offset = 16; + var->red.msb_right = 0; + + var->green.length = 8; + var->green.offset = 8; + var->green.msb_right = 0; + + var->blue.length = 8; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 8; + var->transp.offset = 24; + var->transp.msb_right = 0; + break; + } + + var->height = -1; + var->width = -1; + var->grayscale = 0; + var->nonstd = 0; + + var->pixclock = -1; + var->left_margin = -1; + var->right_margin = -1; + var->upper_margin = -1; + var->lower_margin = -1; + var->hsync_len = -1; + var->vsync_len = -1; + + var->vmode = FB_VMODE_NONINTERLACED; + var->sync = 0; + + return 0; +} + +static inline u_int _chan_to_field(u_int chan, struct fb_bitfield *bf) +{ + chan &= 0xffff; + chan >>= 16 - bf->length; + return chan << bf->offset; +} + +static int +mxcfb_setcolreg(u_int regno, u_int red, u_int green, u_int blue, + u_int trans, struct fb_info *fbi) +{ + unsigned int val; + int ret = 1; + + /* + * If greyscale is true, then we convert the RGB value + * to greyscale no matter what visual we are using. + */ + if (fbi->var.grayscale) + red = green = blue = (19595 * red + 38470 * green + + 7471 * blue) >> 16; + switch (fbi->fix.visual) { + case FB_VISUAL_TRUECOLOR: + /* + * 16-bit True Colour. We encode the RGB value + * according to the RGB bitfield information. + */ + if (regno < 16) { + u32 *pal = fbi->pseudo_palette; + + val = _chan_to_field(red, &fbi->var.red); + val |= _chan_to_field(green, &fbi->var.green); + val |= _chan_to_field(blue, &fbi->var.blue); + + pal[regno] = val; + ret = 0; + } + break; + + case FB_VISUAL_STATIC_PSEUDOCOLOR: + case FB_VISUAL_PSEUDOCOLOR: + break; + } + + return ret; +} + +/* + * mxcfb_blank(): + * Blank the display. + */ +static int mxcfb_blank(int blank, struct fb_info *fbi) +{ + int retval = 0; + struct mxcfb_info *mxc_fbi = fbi->par; + + dev_dbg(fbi->device, "blank = %d\n", blank); + + if ((retval = wait_event_interruptible(mxcfb_drv_data.suspend_wq, + (mxcfb_drv_data.suspended == + false))) < 0) { + return retval; + } + + mxc_fbi->blank = blank; + + switch (blank) { + case FB_BLANK_POWERDOWN: + case FB_BLANK_VSYNC_SUSPEND: + case FB_BLANK_HSYNC_SUSPEND: + case FB_BLANK_NORMAL: + _mxcfb_set_refresh_mode(fbi, MXCFB_REFRESH_OFF, NULL); + break; + case FB_BLANK_UNBLANK: + _mxcfb_set_refresh_mode(fbi, MXCFB_REFRESH_DEFAULT, NULL); + break; + } + return 0; +} + +/*! + * This structure contains the pointers to the control functions that are + * invoked by the core framebuffer driver to perform operations like + * blitting, rectangle filling, copy regions and cursor definition. + */ +static struct fb_ops mxcfb_ops = { + .owner = THIS_MODULE, + .fb_open = mxcfb_open, + .fb_release = mxcfb_release, + .fb_set_par = mxcfb_set_par, + .fb_check_var = mxcfb_check_var, + .fb_setcolreg = mxcfb_setcolreg, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, + .fb_blank = mxcfb_blank, +}; + +/*! + * Allocates the DRAM memory for the frame buffer. This buffer is remapped + * into a non-cached, non-buffered, memory region to allow palette and pixel + * writes to occur without flushing the cache. Once this area is remapped, + * all virtual memory access to the video memory should occur at the new region. + * + * @param fbi framebuffer information pointer + * + * @return Error code indicating success or failure + */ +static int mxcfb_map_video_memory(struct fb_info *fbi) +{ + u32 msb; + u32 offset; + struct mxcfb_info *mxcfbi = fbi->par; + + fbi->fix.smem_len = fbi->var.xres_virtual * fbi->var.yres_virtual * 4; + + // Set size to power of 2. + msb = fls(fbi->fix.smem_len); + if (!(fbi->fix.smem_len & ((1UL << msb) - 1))) + msb--; // Already aligned to power 2 + if (msb < 11) + msb = 11; + mxcfbi->alloc_size = (1UL << msb) * 2; + + mxcfbi->alloc_start_vaddr = dma_alloc_coherent(fbi->device, + mxcfbi->alloc_size, + &mxcfbi-> + alloc_start_paddr, + GFP_KERNEL | GFP_DMA); + + if (mxcfbi->alloc_start_vaddr == 0) { + dev_err(fbi->device, "Unable to allocate framebuffer memory\n"); + return -ENOMEM; + } + dev_dbg(fbi->device, "allocated fb memory @ paddr=0x%08X, size=%d.\n", + (uint32_t) mxcfbi->alloc_start_paddr, mxcfbi->alloc_size); + + offset = + ((mxcfbi->alloc_size / 2) - 1) & ~((mxcfbi->alloc_size / 2) - 1); + fbi->fix.smem_start = mxcfbi->alloc_start_paddr + offset; + dev_dbg(fbi->device, "aligned fb start @ paddr=0x%08lX, size=%u.\n", + fbi->fix.smem_start, fbi->fix.smem_len); + + fbi->screen_base = mxcfbi->alloc_start_vaddr + offset; + + /* Clear the screen */ + memset(fbi->screen_base, 0, fbi->fix.smem_len); + return 0; +} + +/*! + * De-allocates the DRAM memory for the frame buffer. + * + * @param fbi framebuffer information pointer + * + * @return Error code indicating success or failure + */ +static int mxcfb_unmap_video_memory(struct fb_info *fbi) +{ + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par; + + dma_free_coherent(fbi->device, mxc_fbi->alloc_size, + mxc_fbi->alloc_start_vaddr, + mxc_fbi->alloc_start_paddr); + return 0; +} + +/*! + * Initializes the framebuffer information pointer. After allocating + * sufficient memory for the framebuffer structure, the fields are + * filled with custom information passed in from the configurable + * structures. This includes information such as bits per pixel, + * color maps, screen width/height and RGBA offsets. + * + * @return Framebuffer structure initialized with our information + */ +static struct fb_info *mxcfb_init_fbinfo(struct device *dev, struct fb_ops *ops) +{ + struct fb_info *fbi; + struct mxcfb_info *mxcfbi; + + /* + * Allocate sufficient memory for the fb structure + */ + fbi = framebuffer_alloc(sizeof(struct mxcfb_info), dev); + if (!fbi) + return NULL; + + mxcfbi = (struct mxcfb_info *)fbi->par; + + /* + * Fill in fb_info structure information + */ + fbi->var.xres = fbi->var.xres_virtual = MXCFB_SCREEN_WIDTH; + fbi->var.yres = fbi->var.yres_virtual = MXCFB_SCREEN_HEIGHT; + fbi->var.activate = FB_ACTIVATE_NOW; + mxcfb_check_var(&fbi->var, fbi); + + mxcfbi->disp_num = DISP2; + mxcfb_set_fix(fbi); + + fbi->fbops = ops; + fbi->flags = FBINFO_FLAG_DEFAULT; + fbi->pseudo_palette = mxcfbi->pseudo_palette; + + /* + * Allocate colormap + */ + fb_alloc_cmap(&fbi->cmap, 16, 0); + + return fbi; +} + +/*! + * Probe routine for the framebuffer driver. It is called during the + * driver binding process. The following functions are performed in + * this routine: Framebuffer initialization, Memory allocation and + * mapping, Framebuffer registration, IPU initialization. + * + * @return Appropriate error code to the kernel common code + */ +static int mxcfb_probe(struct platform_device *pdev) +{ + struct fb_info *fbi; + struct mxcfb_info *mxc_fbi; + int ret; + + platform_set_drvdata(pdev, &mxcfb_drv_data); + + /* + * Initialize FB structures + */ + fbi = mxcfb_init_fbinfo(&pdev->dev, &mxcfb_ops); + if (!fbi) { + ret = -ENOMEM; + goto err0; + } + mxcfb_drv_data.fbi = fbi; + mxc_fbi = fbi->par; + + mxcfb_drv_data.suspended = false; + init_waitqueue_head(&mxcfb_drv_data.suspend_wq); + + /* + * Allocate memory + */ + ret = mxcfb_map_video_memory(fbi); + if (ret < 0) { + goto err1; + } + + mxcfb_init_panel(fbi); + + /* + * Register framebuffer + */ + ret = register_framebuffer(fbi); + if (ret < 0) { + goto err2; + } + + dev_info(&pdev->dev, "%s registered\n", MXCFB_NAME); + + return 0; + + err2: + mxcfb_unmap_video_memory(fbi); + err1: + if (&fbi->cmap) + fb_dealloc_cmap(&fbi->cmap); + framebuffer_release(fbi); + err0: + return ret; +} + +#ifdef CONFIG_PM +/*! + * Power management hooks. Note that we won't be called from IRQ context, + * unlike the blank functions above, so we may sleep. + */ + +/*! + * Suspends the framebuffer and blanks the screen. Power management support + * + * @param pdev pointer to device structure. + * @param state state of the device. + * + * @return success + */ +static int mxcfb_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct mxcfb_data *drv_data = platform_get_drvdata(pdev); + struct fb_info *fbi = drv_data->fbi; + struct mxcfb_info *mxc_fbi = fbi->par; + + drv_data->suspended = true; + + if (mxc_fbi->blank == FB_BLANK_UNBLANK) + _mxcfb_set_refresh_mode(fbi, MXCFB_REFRESH_OFF, NULL); + /* Display OFF */ + ipu_adc_write_cmd(mxc_fbi->disp_num, CMD, DISOFF, 0, 0); + + return 0; +} + +/*! + * Resumes the framebuffer and unblanks the screen. Power management support + * + * @param pdev pointer to device structure. + * + * @return success + */ +static int mxcfb_resume(struct platform_device *pdev) +{ + struct mxcfb_data *drv_data = platform_get_drvdata(pdev); + struct fb_info *fbi = drv_data->fbi; + struct mxcfb_info *mxc_fbi = fbi->par; + + // Display ON + ipu_adc_write_cmd(mxc_fbi->disp_num, CMD, DISON, 0, 0); + drv_data->suspended = false; + + if (mxc_fbi->blank == FB_BLANK_UNBLANK) + _mxcfb_set_refresh_mode(fbi, MXCFB_REFRESH_DEFAULT, NULL); + wake_up_interruptible(&drv_data->suspend_wq); + + return 0; +} +#else +#define mxcfb_suspend NULL +#define mxcfb_resume NULL +#endif + +/*! + * This structure contains pointers to the power management callback functions. + */ +static struct platform_driver mxcfb_driver = { + .driver = { + .name = MXCFB_NAME, + }, + .probe = mxcfb_probe, + .suspend = mxcfb_suspend, + .resume = mxcfb_resume, +}; + +/*! + * Device definition for the Framebuffer + */ +static struct platform_device mxcfb_device = { + .name = MXCFB_NAME, + .id = 0, + .dev = { + .coherent_dma_mask = 0xFFFFFFFF, + } +}; + +/*! + * Main entry function for the framebuffer. The function registers the power + * management callback functions with the kernel and also registers the MXCFB + * callback functions with the core Linux framebuffer driver \b fbmem.c + * + * @return Error code indicating success or failure + */ +static int mxcfb_init(void) +{ + int ret = 0; + + ret = platform_driver_register(&mxcfb_driver); + if (ret == 0) { + ret = platform_device_register(&mxcfb_device); + if (ret != 0) { + platform_driver_unregister(&mxcfb_driver); + } + } + return ret; +} + +static void mxcfb_exit(void) +{ + struct fb_info *fbi = dev_get_drvdata(&mxcfb_device.dev); + + if (fbi) { + mxcfb_unmap_video_memory(fbi); + + if (&fbi->cmap) + fb_dealloc_cmap(&fbi->cmap); + + unregister_framebuffer(fbi); + framebuffer_release(fbi); + } + + platform_device_unregister(&mxcfb_device); + platform_driver_unregister(&mxcfb_driver); +} + +module_init(mxcfb_init); +module_exit(mxcfb_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MXC Sharp 128x128 framebuffer driver"); +MODULE_SUPPORTED_DEVICE("fb"); diff --git a/drivers/video/mxc/mxcfb_toshiba_qvga.c b/drivers/video/mxc/mxcfb_toshiba_qvga.c new file mode 100644 index 000000000000..a8e56bc6ed03 --- /dev/null +++ b/drivers/video/mxc/mxcfb_toshiba_qvga.c @@ -0,0 +1,1202 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mxcfb_toshiba_qvga.c + * + * @brief MXC Frame buffer driver for ADC + * + * @ingroup Framebuffer + */ + +/*! + * Include files + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/fb.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/ioport.h> +#include <linux/platform_device.h> +#include <linux/dma-mapping.h> +#include <asm/hardware.h> +#include <asm/io.h> +#include <asm/mach-types.h> +#include <asm/uaccess.h> +#include <asm/arch/ipu.h> +#include <asm/arch/mxcfb.h> + +#define PARTIAL_REFRESH +#define MXCFB_REFRESH_DEFAULT MXCFB_REFRESH_PARTIAL +/* + * Driver name + */ +#define MXCFB_NAME "mxcfb_toshiba_qvga" + +#define MXCFB_SCREEN_TOP_OFFSET 0 +#define MXCFB_SCREEN_LEFT_OFFSET 0 +#define MXCFB_SCREEN_WIDTH 240 +#define MXCFB_SCREEN_HEIGHT 320 + +struct mxcfb_info { + int open_count; + int blank; + uint32_t disp_num; + + u32 pseudo_palette[16]; + + int32_t cur_update_mode; + dma_addr_t alloc_start_paddr; + void *alloc_start_vaddr; + u32 alloc_size; + uint32_t snoop_window_size; +}; + +struct mxcfb_data { + struct fb_info *fbi; + volatile int32_t vsync_flag; + wait_queue_head_t vsync_wq; + wait_queue_head_t suspend_wq; + bool suspended; +}; + +static struct mxcfb_data mxcfb_drv_data; +static unsigned long default_bpp = 16; + +void slcd_gpio_config(void); +extern void gpio_lcd_active(void); +static int mxcfb_blank(int blank, struct fb_info *fbi); + +static uint32_t bpp_to_pixfmt(int bpp) +{ + uint32_t pixfmt = 0; + switch (bpp) { + case 24: + pixfmt = IPU_PIX_FMT_BGR24; + break; + case 32: + pixfmt = IPU_PIX_FMT_BGR32; + break; + case 16: + pixfmt = IPU_PIX_FMT_RGB565; + break; + } + return pixfmt; +} + +/*! + * Function to create and initiate template command buffer for ADC. This + * template will be written to Panel memory. + */ +static void init_channel_template(int disp) +{ + /* template command buffer for ADC is 32 */ + uint32_t tempCmd[TEMPLATE_BUF_SIZE]; + uint32_t i = 0; + + memset(tempCmd, 0, sizeof(uint32_t) * TEMPLATE_BUF_SIZE); + /* setup update display region */ + /* whole the screen during init */ + /*WRITE Y COORDINATE CMND */ + tempCmd[i++] = ipu_adc_template_gen(WR_CMND, 0, SINGLE_STEP, 0x07); + /*WRITE Y START ADDRESS CMND [22:8] */ + tempCmd[i++] = ipu_adc_template_gen(WR_YADDR, 1, SINGLE_STEP, 0x01); + /*WRITE X COORDINATE CMND */ + tempCmd[i++] = ipu_adc_template_gen(WR_CMND, 0, SINGLE_STEP, 0x06); + /*WRITE X START ADDRESS CMND [7:0] */ + tempCmd[i++] = ipu_adc_template_gen(WR_XADDR, 1, SINGLE_STEP, 0x01); + /*WRITE RAM CMND */ + tempCmd[i++] = ipu_adc_template_gen(WR_CMND, 0, SINGLE_STEP, 0x0E); + /*WRITE DATA CMND and STP */ + tempCmd[i++] = ipu_adc_template_gen(WR_DATA, 1, STOP, 0); + + ipu_adc_write_template(disp, tempCmd, true); +} + +/*! + * Function to initialize the panel. First it resets the panel and then + * initilizes panel. + */ +static void _init_panel(int disp) +{ + uint32_t cmd_param; + uint32_t i; + + slcd_gpio_config(); + + // Reset + cmd_param = 0x01; + ipu_adc_write_cmd(disp, CMD, 0x03, &cmd_param, 1); + + // Turn on oscillator + cmd_param = 0x01; + ipu_adc_write_cmd(disp, CMD, 0x3A, &cmd_param, 1); + + cmd_param = 0x02; + ipu_adc_write_cmd(disp, CMD, 0x32, &cmd_param, 1); + + cmd_param = 0x01; + ipu_adc_write_cmd(disp, CMD, 0x33, &cmd_param, 1); + + cmd_param = 0x00; + ipu_adc_write_cmd(disp, CMD, 0x37, &cmd_param, 1); + + cmd_param = 0x0FFF; + ipu_adc_write_cmd(disp, CMD, 0x77, &cmd_param, 1); + + cmd_param = 0x01; + ipu_adc_write_cmd(disp, CMD, 0x72, &cmd_param, 1); + + cmd_param = 0x1C3B; + ipu_adc_write_cmd(disp, CMD, 0x1C, &cmd_param, 1); + + cmd_param = 0x21; + ipu_adc_write_cmd(disp, CMD, 0x52, &cmd_param, 1); + + cmd_param = 0x11; + ipu_adc_write_cmd(disp, CMD, 0x53, &cmd_param, 1); + + cmd_param = 0x79; + ipu_adc_write_cmd(disp, CMD, 0x24, &cmd_param, 1); + + cmd_param = 0x79; + ipu_adc_write_cmd(disp, CMD, 0x25, &cmd_param, 1); + + cmd_param = 0x10; + ipu_adc_write_cmd(disp, CMD, 0x26, &cmd_param, 1); + + cmd_param = 0x10; + ipu_adc_write_cmd(disp, CMD, 0x27, &cmd_param, 1); + + cmd_param = 0x28; + ipu_adc_write_cmd(disp, CMD, 0x61, &cmd_param, 1); + + cmd_param = 0x1A; + ipu_adc_write_cmd(disp, CMD, 0x62, &cmd_param, 1); + + cmd_param = 0x1E; + ipu_adc_write_cmd(disp, CMD, 0x63, &cmd_param, 1); + + cmd_param = 0x21; + ipu_adc_write_cmd(disp, CMD, 0x64, &cmd_param, 1); + + cmd_param = 0x1B; + ipu_adc_write_cmd(disp, CMD, 0x65, &cmd_param, 1); + + cmd_param = 0x29; + ipu_adc_write_cmd(disp, CMD, 0x66, &cmd_param, 1); + + cmd_param = 0x205; + ipu_adc_write_cmd(disp, CMD, 0x4D, &cmd_param, 1); + + cmd_param = 0x01; + ipu_adc_write_cmd(disp, CMD, 0x4E, &cmd_param, 1); + + cmd_param = 0x104; + ipu_adc_write_cmd(disp, CMD, 0x4F, &cmd_param, 1); + + cmd_param = 0x2F; + ipu_adc_write_cmd(disp, CMD, 0x2E, &cmd_param, 1); + + cmd_param = 0x0; + ipu_adc_write_cmd(disp, CMD, 0x29, &cmd_param, 1); + + cmd_param = 0x0; + ipu_adc_write_cmd(disp, CMD, 0x2A, &cmd_param, 1); + + cmd_param = 0xEF; + ipu_adc_write_cmd(disp, CMD, 0x2B, &cmd_param, 1); + + cmd_param = 0x13F; + ipu_adc_write_cmd(disp, CMD, 0x2C, &cmd_param, 1); + + /* Window area setting */ + cmd_param = 0x0; + ipu_adc_write_cmd(disp, CMD, 0x08, &cmd_param, 1); + cmd_param = 0xEF; + ipu_adc_write_cmd(disp, CMD, 0x09, &cmd_param, 1); + cmd_param = 0x0; + ipu_adc_write_cmd(disp, CMD, 0x0A, &cmd_param, 1); + cmd_param = 0x13F; + ipu_adc_write_cmd(disp, CMD, 0x0B, &cmd_param, 1); + + /* Window mode setting */ + cmd_param = 0x00; + ipu_adc_write_cmd(disp, CMD, 0x05, &cmd_param, 1); + + /* Ram address setting */ + cmd_param = 0x0; + ipu_adc_write_cmd(disp, CMD, 0x06, &cmd_param, 1); + cmd_param = 0x0; + ipu_adc_write_cmd(disp, CMD, 0x07, &cmd_param, 1); + + /* Initialize RAM */ + ipu_adc_write_cmd(disp, CMD, 0x0E, 0, 0); + for (i = 0; i < (MXCFB_SCREEN_WIDTH * MXCFB_SCREEN_HEIGHT / 2); i++) + ipu_adc_write_cmd(disp, DAT, 0x3FFFF, 0, 0); + for (i = 0; i < (MXCFB_SCREEN_WIDTH * MXCFB_SCREEN_HEIGHT / 2); i++) + ipu_adc_write_cmd(disp, DAT, 0, 0, 0); + + cmd_param = 0x1F6A; + ipu_adc_write_cmd(disp, CMD, 0x18, &cmd_param, 1); + + cmd_param = 0x00A2; + ipu_adc_write_cmd(disp, CMD, 0x1A, &cmd_param, 1); + + cmd_param = 0x0028; + ipu_adc_write_cmd(disp, CMD, 0x1B, &cmd_param, 1); + + cmd_param = 0x1C3B; + ipu_adc_write_cmd(disp, CMD, 0x1C, &cmd_param, 1); + + cmd_param = 0x0075; + ipu_adc_write_cmd(disp, CMD, 0x1D, &cmd_param, 1); + + cmd_param = 0x003D; + ipu_adc_write_cmd(disp, CMD, 0x1F, &cmd_param, 1); + + cmd_param = 0x0080; + ipu_adc_write_cmd(disp, CMD, 0x20, &cmd_param, 1); + + /* DC/DC on */ + cmd_param = 0x1F6B; + ipu_adc_write_cmd(disp, CMD, 0x18, &cmd_param, 1); + msleep(100); + + /* VCOM on */ + cmd_param = 0x0021; + ipu_adc_write_cmd(disp, CMD, 0x1E, &cmd_param, 1); + + /* GOE1,GOE2 setting (Gate output enable) */ + cmd_param = 0x0001; + ipu_adc_write_cmd(disp, CMD, 0x3B, &cmd_param, 1); + + cmd_param = 0x00; + ipu_adc_write_cmd(disp, CMD, 0x2, &cmd_param, 1); + /* Display on */ + cmd_param = 0x00; + ipu_adc_write_cmd(disp, CMD, 0x0, &cmd_param, 1); + + msleep(10); + + pr_debug("initialized panel\n"); +} + +#ifdef PARTIAL_REFRESH +static void mxcfb_update_region(struct fb_info *fbi, uint32_t statl, + uint32_t stath) +{ + ipu_channel_params_t params; + struct mxcfb_info *mxc_fbi = fbi->par; + uint32_t seg_size; + uint32_t lsb, msb; + uint32_t update_height, start_line, start_addr, end_line, end_addr; + uint32_t stride_pixels = (fbi->fix.line_length * 8) / + fbi->var.bits_per_pixel; + + lsb = ffs(statl); + if (lsb) { + lsb--; + } else { + lsb = ffs(stath); + lsb += 32 - 1; + } + msb = fls(stath); + if (msb) { + msb += 32; + } else { + msb = fls(statl); + } + + seg_size = mxc_fbi->snoop_window_size / 64; + + start_addr = lsb * seg_size; // starting address offset + start_line = start_addr / fbi->fix.line_length; + start_addr = start_line * fbi->fix.line_length; // Addr aligned to line + start_addr += fbi->fix.smem_start; + + end_addr = msb * seg_size; // ending address offset + end_line = end_addr / fbi->fix.line_length; + end_line++; + + if (end_line > fbi->var.yres) { + end_line = fbi->var.yres; + } + + update_height = end_line - start_line; + dev_dbg(fbi->device, "updating rows %d to %d, start addr = 0x%08X\n", + start_line, end_line, start_addr); + + ipu_uninit_channel(ADC_SYS1); + params.adc_sys1.disp = mxc_fbi->disp_num; + params.adc_sys1.ch_mode = WriteTemplateNonSeq; + params.adc_sys1.out_left = MXCFB_SCREEN_LEFT_OFFSET; + params.adc_sys1.out_top = start_line; + ipu_init_channel(ADC_SYS1, ¶ms); + + ipu_init_channel_buffer(ADC_SYS1, IPU_INPUT_BUFFER, + bpp_to_pixfmt(fbi->var.bits_per_pixel), + MXCFB_SCREEN_WIDTH, + update_height, + stride_pixels, + IPU_ROTATE_NONE, (dma_addr_t) start_addr, 0, 0, + 0); + ipu_enable_channel(ADC_SYS1); + ipu_select_buffer(ADC_SYS1, IPU_INPUT_BUFFER, 0); + ipu_enable_irq(IPU_IRQ_ADC_SYS1_EOF); +} + +static irqreturn_t mxcfb_sys2_eof_irq_handler(int irq, void *dev_id) +{ + struct fb_info *fbi = dev_id; + uint32_t stat[2]; + + ipu_adc_get_snooping_status(&stat[0], &stat[1]); + if (stat[0] || stat[1]) { + mxcfb_update_region(fbi, stat[0], stat[1]); + ipu_disable_irq(IPU_IRQ_ADC_SYS2_EOF); + } + return IRQ_HANDLED; +} + +static irqreturn_t mxcfb_sys1_eof_irq_handler(int irq, void *dev_id) +{ + struct fb_info *fbi = dev_id; + uint32_t stat[2]; + + ipu_disable_irq(IPU_IRQ_ADC_SYS1_EOF); + ipu_disable_channel(ADC_SYS1, false); + + ipu_adc_get_snooping_status(&stat[0], &stat[1]); + if (stat[0] || stat[1]) { + mxcfb_update_region(fbi, stat[0], stat[1]); + } else { + ipu_enable_channel(ADC_SYS2); + ipu_enable_irq(IPU_IRQ_ADC_SYS2_EOF); + } + + return IRQ_HANDLED; +} +#endif + +/*! + * Function to initialize Asynchronous Display Controller. It also initilizes + * the ADC System 1 channel. Configure ADC display 0 parallel interface for + * the panel. + * + * @param fbi framebuffer information pointer + */ +static void mxcfb_init_panel(struct fb_info *fbi) +{ + int msb; + int panel_stride; + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par; + +#ifdef CONFIG_FB_MXC_ASYNC_PANEL_IFC_16_BIT + uint32_t pix_fmt = IPU_PIX_FMT_RGB666; + ipu_adc_sig_cfg_t sig = { 0, 0, 0, 0, 0, 0, 0, 0, + IPU_ADC_BURST_WCS, + IPU_ADC_IFC_MODE_SYS80_TYPE2, + 16, 0, 0, IPU_ADC_SER_NO_RW + }; + mxc_fbi->disp_num = DISP0; +#elif defined(CONFIG_FB_MXC_ASYNC_PANEL_IFC_8_BIT) + uint32_t pix_fmt = IPU_PIX_FMT_RGB666; + ipu_adc_sig_cfg_t sig = { 0, 0, 0, 0, 0, 0, 0, 0, + IPU_ADC_BURST_WCS, + IPU_ADC_IFC_MODE_SYS80_TYPE2, + 8, 0, 0, IPU_ADC_SER_NO_RW + }; + mxc_fbi->disp_num = DISP0; +#endif + +#ifdef PARTIAL_REFRESH + if (ipu_request_irq(IPU_IRQ_ADC_SYS2_EOF, mxcfb_sys2_eof_irq_handler, 0, + MXCFB_NAME, fbi) != 0) { + dev_err(fbi->device, "Error registering SYS2 irq handler.\n"); + return; + } + + if (ipu_request_irq(IPU_IRQ_ADC_SYS1_EOF, mxcfb_sys1_eof_irq_handler, 0, + MXCFB_NAME, fbi) != 0) { + dev_err(fbi->device, "Error registering SYS1 irq handler.\n"); + return; + } + ipu_disable_irq(IPU_IRQ_ADC_SYS1_EOF); + ipu_disable_irq(IPU_IRQ_ADC_SYS2_EOF); +#endif + // Init DI interface + msb = fls(MXCFB_SCREEN_WIDTH); + if (!(MXCFB_SCREEN_WIDTH & ((1UL << msb) - 1))) + msb--; // Already aligned to power 2 + panel_stride = 1UL << msb; + ipu_adc_init_panel(mxc_fbi->disp_num, + MXCFB_SCREEN_WIDTH + MXCFB_SCREEN_LEFT_OFFSET, + MXCFB_SCREEN_HEIGHT, + pix_fmt, panel_stride, sig, XY, 0, VsyncInternal); + + ipu_adc_init_ifc_timing(mxc_fbi->disp_num, true, + 190, 17, 104, 190, 5000000); + ipu_adc_init_ifc_timing(mxc_fbi->disp_num, false, 90, 10, 60, 0, 0); + + _init_panel(mxc_fbi->disp_num); + + init_channel_template(mxc_fbi->disp_num); +} + +int mxcfb_set_refresh_mode(struct fb_info *fbi, int mode, + struct mxcfb_rect *update_region) +{ + unsigned long start_addr; + int ret_mode; + uint32_t dummy; + ipu_channel_params_t params; + struct mxcfb_rect rect; + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par; + uint32_t stride_pixels = (fbi->fix.line_length * 8) / + fbi->var.bits_per_pixel; + uint32_t memsize = fbi->fix.smem_len; + + if (mxc_fbi->cur_update_mode == mode) + return mode; + + ret_mode = mxc_fbi->cur_update_mode; + + ipu_disable_irq(IPU_IRQ_ADC_SYS1_EOF); + ipu_adc_set_update_mode(ADC_SYS1, IPU_ADC_REFRESH_NONE, 0, 0, 0); +#ifdef PARTIAL_REFRESH + ipu_disable_irq(IPU_IRQ_ADC_SYS2_EOF); + ipu_adc_set_update_mode(ADC_SYS2, IPU_ADC_REFRESH_NONE, 0, 0, 0); +#endif + + ipu_disable_channel(ADC_SYS1, true); + ipu_clear_irq(IPU_IRQ_ADC_SYS1_EOF); +#ifdef PARTIAL_REFRESH + ipu_disable_channel(ADC_SYS2, true); + ipu_clear_irq(IPU_IRQ_ADC_SYS2_EOF); +#endif + ipu_adc_get_snooping_status(&dummy, &dummy); + + mxc_fbi->cur_update_mode = mode; + + switch (mode) { + case MXCFB_REFRESH_OFF: + if (ipu_adc_set_update_mode(ADC_SYS1, IPU_ADC_REFRESH_NONE, + 0, 0, 0) < 0) + dev_err(fbi->device, "Error enabling auto refesh.\n"); + if (ipu_adc_set_update_mode(ADC_SYS2, IPU_ADC_REFRESH_NONE, + 0, 0, 0) < 0) + dev_err(fbi->device, "Error enabling auto refesh.\n"); + + ipu_uninit_channel(ADC_SYS1); +#ifdef PARTIAL_REFRESH + ipu_uninit_channel(ADC_SYS2); +#endif + break; + case MXCFB_REFRESH_PARTIAL: +#ifdef PARTIAL_REFRESH + params.adc_sys2.disp = DISP0; + params.adc_sys2.ch_mode = WriteTemplateNonSeq; + params.adc_sys2.out_left = 0; + params.adc_sys2.out_top = 0; + ipu_init_channel(ADC_SYS2, ¶ms); + + if (ipu_adc_set_update_mode(ADC_SYS1, IPU_ADC_REFRESH_NONE, + 0, 0, 0) < 0) { + dev_err(fbi->device, "Error enabling auto refesh.\n"); + } + if (ipu_adc_set_update_mode + (ADC_SYS2, IPU_ADC_AUTO_REFRESH_SNOOP, 30, + fbi->fix.smem_start, &memsize) < 0) { + dev_err(fbi->device, "Error enabling auto refesh.\n"); + } + mxc_fbi->snoop_window_size = memsize; + + ipu_init_channel_buffer(ADC_SYS2, IPU_INPUT_BUFFER, + bpp_to_pixfmt(fbi->var.bits_per_pixel), + 1, 1, 4, + IPU_ROTATE_NONE, + fbi->fix.smem_start, 0, 0, 0); + + params.adc_sys1.disp = mxc_fbi->disp_num; + params.adc_sys1.ch_mode = WriteTemplateNonSeq; + params.adc_sys1.out_left = MXCFB_SCREEN_LEFT_OFFSET; + params.adc_sys1.out_top = MXCFB_SCREEN_TOP_OFFSET; + ipu_init_channel(ADC_SYS1, ¶ms); + + ipu_init_channel_buffer(ADC_SYS1, IPU_INPUT_BUFFER, + bpp_to_pixfmt(fbi->var.bits_per_pixel), + MXCFB_SCREEN_WIDTH, MXCFB_SCREEN_HEIGHT, + stride_pixels, IPU_ROTATE_NONE, + fbi->fix.smem_start, 0, 0, 0); + ipu_enable_channel(ADC_SYS1); + ipu_select_buffer(ADC_SYS1, IPU_INPUT_BUFFER, 0); + ipu_enable_irq(IPU_IRQ_ADC_SYS1_EOF); + break; +#endif + case MXCFB_REFRESH_AUTO: + if (update_region == NULL) { + update_region = ▭ + rect.top = 0; + rect.left = 0; + rect.height = MXCFB_SCREEN_HEIGHT; + rect.width = MXCFB_SCREEN_WIDTH; + } + params.adc_sys1.disp = mxc_fbi->disp_num; + params.adc_sys1.ch_mode = WriteTemplateNonSeq; + params.adc_sys1.out_left = MXCFB_SCREEN_LEFT_OFFSET + + update_region->left; + params.adc_sys1.out_top = MXCFB_SCREEN_TOP_OFFSET + + update_region->top; + ipu_init_channel(ADC_SYS1, ¶ms); + + // Address aligned to line + start_addr = update_region->top * fbi->fix.line_length; + start_addr += fbi->fix.smem_start; + start_addr += update_region->left * fbi->var.bits_per_pixel / 8; + + ipu_init_channel_buffer(ADC_SYS1, IPU_INPUT_BUFFER, + bpp_to_pixfmt(fbi->var.bits_per_pixel), + update_region->width, + update_region->height, stride_pixels, + IPU_ROTATE_NONE, start_addr, 0, 0, 0); + ipu_enable_channel(ADC_SYS1); + ipu_select_buffer(ADC_SYS1, IPU_INPUT_BUFFER, 0); + + if (ipu_adc_set_update_mode + (ADC_SYS1, IPU_ADC_AUTO_REFRESH_SNOOP, 30, + fbi->fix.smem_start, &memsize) < 0) + dev_err(fbi->device, "Error enabling auto refesh.\n"); + + mxc_fbi->snoop_window_size = memsize; + + break; + } + return ret_mode; +} + +/* + * Open the main framebuffer. + * + * @param fbi framebuffer information pointer + * + * @param user Set if opened by user or clear if opened by kernel + */ +static int mxcfb_open(struct fb_info *fbi, int user) +{ + int retval = 0; + struct mxcfb_info *mxc_fbi = fbi->par; + + if ((retval = wait_event_interruptible(mxcfb_drv_data.suspend_wq, + (mxcfb_drv_data.suspended == + false))) < 0) { + return retval; + } + + mxc_fbi->open_count++; + + retval = mxcfb_blank(FB_BLANK_UNBLANK, fbi); + return retval; +} + +/* + * Close the main framebuffer. + * + * @param fbi framebuffer information pointer + * + * @param user Set if opened by user or clear if opened by kernel + */ +static int mxcfb_release(struct fb_info *fbi, int user) +{ + int retval = 0; + struct mxcfb_info *mxc_fbi = fbi->par; + + if ((retval = wait_event_interruptible(mxcfb_drv_data.suspend_wq, + (mxcfb_drv_data.suspended == + false))) < 0) { + return retval; + } + + --mxc_fbi->open_count; + if (mxc_fbi->open_count == 0) { + retval = mxcfb_blank(FB_BLANK_POWERDOWN, fbi); + } + return retval; +} + +/* + * Set fixed framebuffer parameters based on variable settings. + * + * @param info framebuffer information pointer + */ +static int mxcfb_set_fix(struct fb_info *info) +{ + struct fb_fix_screeninfo *fix = &info->fix; + struct fb_var_screeninfo *var = &info->var; + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)info->par; + + // Set framebuffer id to IPU display number. + strcpy(fix->id, "DISP0 FB"); + fix->id[4] = '0' + mxc_fbi->disp_num; + + // Init settings based on the panel size + fix->line_length = MXCFB_SCREEN_WIDTH * var->bits_per_pixel / 8; + + fix->type = FB_TYPE_PACKED_PIXELS; + fix->accel = FB_ACCEL_NONE; + fix->visual = FB_VISUAL_TRUECOLOR; + fix->xpanstep = 0; + fix->ypanstep = 0; + + return 0; +} + +/* + * Set framebuffer parameters and change the operating mode. + * + * @param info framebuffer information pointer + */ +static int mxcfb_set_par(struct fb_info *fbi) +{ + int retval = 0; + int mode; + + if ((retval = wait_event_interruptible(mxcfb_drv_data.suspend_wq, + (mxcfb_drv_data.suspended == + false))) < 0) { + return retval; + } + + mode = mxcfb_set_refresh_mode(fbi, MXCFB_REFRESH_OFF, NULL); + + mxcfb_set_fix(fbi); + + if (mode != MXCFB_REFRESH_OFF) { +#ifdef PARTIAL_REFRESH + mxcfb_set_refresh_mode(fbi, MXCFB_REFRESH_PARTIAL, NULL); +#else + mxcfb_set_refresh_mode(fbi, MXCFB_REFRESH_AUTO, NULL); +#endif + } + return 0; +} + +/* + * Check framebuffer variable parameters and adjust to valid values. + * + * @param var framebuffer variable parameters + * + * @param info framebuffer information pointer + */ +static int mxcfb_check_var(struct fb_var_screeninfo *var, struct fb_info *fbi) +{ + if (var->xres > MXCFB_SCREEN_WIDTH) + var->xres = MXCFB_SCREEN_WIDTH; + if (var->yres > MXCFB_SCREEN_HEIGHT) + var->yres = MXCFB_SCREEN_HEIGHT; + if (var->xres_virtual < var->xres) + var->xres_virtual = var->xres; + if (var->yres_virtual < var->yres) + var->yres_virtual = var->yres; + + if ((var->bits_per_pixel != 32) && (var->bits_per_pixel != 24) && + (var->bits_per_pixel != 16)) { + var->bits_per_pixel = default_bpp; + } + + switch (var->bits_per_pixel) { + case 16: + var->red.length = 5; + var->red.offset = 11; + var->red.msb_right = 0; + + var->green.length = 6; + var->green.offset = 5; + var->green.msb_right = 0; + + var->blue.length = 5; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 0; + var->transp.offset = 0; + var->transp.msb_right = 0; + break; + case 24: + var->red.length = 8; + var->red.offset = 16; + var->red.msb_right = 0; + + var->green.length = 8; + var->green.offset = 8; + var->green.msb_right = 0; + + var->blue.length = 8; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 0; + var->transp.offset = 0; + var->transp.msb_right = 0; + break; + case 32: + var->red.length = 8; + var->red.offset = 16; + var->red.msb_right = 0; + + var->green.length = 8; + var->green.offset = 8; + var->green.msb_right = 0; + + var->blue.length = 8; + var->blue.offset = 0; + var->blue.msb_right = 0; + + var->transp.length = 8; + var->transp.offset = 24; + var->transp.msb_right = 0; + break; + } + + var->height = -1; + var->width = -1; + var->grayscale = 0; + var->nonstd = 0; + + var->pixclock = -1; + var->left_margin = -1; + var->right_margin = -1; + var->upper_margin = -1; + var->lower_margin = -1; + var->hsync_len = -1; + var->vsync_len = -1; + + var->vmode = FB_VMODE_NONINTERLACED; + var->sync = 0; + + return 0; +} + +static inline u_int _chan_to_field(u_int chan, struct fb_bitfield *bf) +{ + chan &= 0xffff; + chan >>= 16 - bf->length; + return chan << bf->offset; +} + +static int +mxcfb_setcolreg(u_int regno, u_int red, u_int green, u_int blue, + u_int trans, struct fb_info *fbi) +{ + unsigned int val; + int ret = 1; + + /* + * If greyscale is true, then we convert the RGB value + * to greyscale no matter what visual we are using. + */ + if (fbi->var.grayscale) + red = green = blue = (19595 * red + 38470 * green + + 7471 * blue) >> 16; + switch (fbi->fix.visual) { + case FB_VISUAL_TRUECOLOR: + /* + * 16-bit True Colour. We encode the RGB value + * according to the RGB bitfield information. + */ + if (regno < 16) { + u32 *pal = fbi->pseudo_palette; + + val = _chan_to_field(red, &fbi->var.red); + val |= _chan_to_field(green, &fbi->var.green); + val |= _chan_to_field(blue, &fbi->var.blue); + + pal[regno] = val; + ret = 0; + } + break; + + case FB_VISUAL_STATIC_PSEUDOCOLOR: + case FB_VISUAL_PSEUDOCOLOR: + break; + } + + return ret; +} + +/* + * mxcfb_blank(): + * Blank the display. + */ +static int mxcfb_blank(int blank, struct fb_info *fbi) +{ + int retval = 0; + struct mxcfb_info *mxc_fbi = fbi->par; + + dev_dbg(fbi->device, "blank = %d\n", blank); + + if ((retval = wait_event_interruptible(mxcfb_drv_data.suspend_wq, + (mxcfb_drv_data.suspended == + false))) < 0) { + return retval; + } + + mxc_fbi->blank = blank; + + switch (blank) { + case FB_BLANK_POWERDOWN: + case FB_BLANK_VSYNC_SUSPEND: + case FB_BLANK_HSYNC_SUSPEND: + case FB_BLANK_NORMAL: + mxcfb_set_refresh_mode(fbi, MXCFB_REFRESH_OFF, NULL); + break; + case FB_BLANK_UNBLANK: + mxcfb_set_refresh_mode(fbi, MXCFB_REFRESH_DEFAULT, NULL); + break; + } + return 0; +} + +/*! + * This structure contains the pointers to the control functions that are + * invoked by the core framebuffer driver to perform operations like + * blitting, rectangle filling, copy regions and cursor definition. + */ +static struct fb_ops mxcfb_ops = { + .owner = THIS_MODULE, + .fb_open = mxcfb_open, + .fb_release = mxcfb_release, + .fb_set_par = mxcfb_set_par, + .fb_check_var = mxcfb_check_var, + .fb_setcolreg = mxcfb_setcolreg, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, + .fb_blank = mxcfb_blank, +}; + +/*! + * Allocates the DRAM memory for the frame buffer. This buffer is remapped + * into a non-cached, non-buffered, memory region to allow palette and pixel + * writes to occur without flushing the cache. Once this area is remapped, + * all virtual memory access to the video memory should occur at the new region. + * + * @param fbi framebuffer information pointer + * + * @return Error code indicating success or failure + */ +static int mxcfb_map_video_memory(struct fb_info *fbi) +{ + u32 msb; + u32 offset; + struct mxcfb_info *mxcfbi = fbi->par; + + fbi->fix.smem_len = fbi->var.xres_virtual * fbi->var.yres_virtual * 4; + + // Set size to power of 2. + msb = fls(fbi->fix.smem_len); + if (!(fbi->fix.smem_len & ((1UL << msb) - 1))) + msb--; // Already aligned to power 2 + if (msb < 11) + msb = 11; + mxcfbi->alloc_size = (1UL << msb) * 2; + + mxcfbi->alloc_start_vaddr = dma_alloc_coherent(fbi->device, + mxcfbi->alloc_size, + &mxcfbi-> + alloc_start_paddr, + GFP_KERNEL | GFP_DMA); + + if (mxcfbi->alloc_start_vaddr == 0) { + dev_err(fbi->device, "Unable to allocate framebuffer memory\n"); + return -ENOMEM; + } + dev_dbg(fbi->device, "allocated fb memory @ paddr=0x%08X, size=%d.\n", + (uint32_t) mxcfbi->alloc_start_paddr, mxcfbi->alloc_size); + + offset = + ((mxcfbi->alloc_size / 2) - 1) & ~((mxcfbi->alloc_size / 2) - 1); + fbi->fix.smem_start = mxcfbi->alloc_start_paddr + offset; + dev_dbg(fbi->device, "aligned fb start @ paddr=0x%08lX, size=%u.\n", + fbi->fix.smem_start, fbi->fix.smem_len); + + fbi->screen_base = mxcfbi->alloc_start_vaddr + offset; + + /* Clear the screen */ + memset(fbi->screen_base, 0, fbi->fix.smem_len); + return 0; +} + +/*! + * De-allocates the DRAM memory for the frame buffer. + * + * @param fbi framebuffer information pointer + * + * @return Error code indicating success or failure + */ +static int mxcfb_unmap_video_memory(struct fb_info *fbi) +{ + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par; + + dma_free_coherent(fbi->device, mxc_fbi->alloc_size, + mxc_fbi->alloc_start_vaddr, + mxc_fbi->alloc_start_paddr); + return 0; +} + +/*! + * Initializes the framebuffer information pointer. After allocating + * sufficient memory for the framebuffer structure, the fields are + * filled with custom information passed in from the configurable + * structures. This includes information such as bits per pixel, + * color maps, screen width/height and RGBA offsets. + * + * @return Framebuffer structure initialized with our information + */ +static struct fb_info *mxcfb_init_fbinfo(struct device *dev, struct fb_ops *ops) +{ + struct fb_info *fbi; + struct mxcfb_info *mxcfbi; + + /* + * Allocate sufficient memory for the fb structure + */ + fbi = framebuffer_alloc(sizeof(struct mxcfb_info), dev); + if (!fbi) + return NULL; + + mxcfbi = (struct mxcfb_info *)fbi->par; + + /* + * Fill in fb_info structure information + */ + fbi->var.xres = fbi->var.xres_virtual = MXCFB_SCREEN_WIDTH; + fbi->var.yres = fbi->var.yres_virtual = MXCFB_SCREEN_HEIGHT; + fbi->var.activate = FB_ACTIVATE_NOW; + mxcfb_check_var(&fbi->var, fbi); + + mxcfb_set_fix(fbi); + + fbi->fbops = ops; + fbi->flags = FBINFO_FLAG_DEFAULT; + fbi->pseudo_palette = mxcfbi->pseudo_palette; + + /* + * Allocate colormap + */ + fb_alloc_cmap(&fbi->cmap, 16, 0); + + return fbi; +} + +/*! + * Probe routine for the framebuffer driver. It is called during the + * driver binding process. The following functions are performed in + * this routine: Framebuffer initialization, Memory allocation and + * mapping, Framebuffer registration, IPU initialization. + * + * @return Appropriate error code to the kernel common code + */ +static int mxcfb_probe(struct platform_device *pdev) +{ + struct fb_info *fbi; + struct mxcfb_info *mxc_fbi; + int ret; + + platform_set_drvdata(pdev, &mxcfb_drv_data); + + /* + * Initialize FB structures + */ + fbi = mxcfb_init_fbinfo(&pdev->dev, &mxcfb_ops); + if (!fbi) { + ret = -ENOMEM; + goto err0; + } + mxcfb_drv_data.fbi = fbi; + mxc_fbi = fbi->par; + + mxcfb_drv_data.suspended = false; + init_waitqueue_head(&mxcfb_drv_data.suspend_wq); + + /* + * Allocate memory + */ + ret = mxcfb_map_video_memory(fbi); + if (ret < 0) { + goto err1; + } + + mxcfb_init_panel(fbi); + + /* + * Register framebuffer + */ + ret = register_framebuffer(fbi); + if (ret < 0) { + goto err2; + } + + dev_info(&pdev->dev, "%s registered\n", MXCFB_NAME); + + return 0; + + err2: + mxcfb_unmap_video_memory(fbi); + err1: + if (&fbi->cmap) + fb_dealloc_cmap(&fbi->cmap); + framebuffer_release(fbi); + err0: + return ret; +} + +#ifdef CONFIG_PM +/*! + * Power management hooks. Note that we won't be called from IRQ context, + * unlike the blank functions above, so we may sleep. + */ + +/*! + * Suspends the framebuffer and blanks the screen. Power management support + * + * @param pdev pointer to device structure. + * @param state state of the device. + * + * @return success + */ +static int mxcfb_suspend(struct platform_device *pdev, pm_message_t state) +{ + uint32_t cmd_param; + struct mxcfb_data *drv_data = platform_get_drvdata(pdev); + struct fb_info *fbi = drv_data->fbi; + struct mxcfb_info *mxc_fbi = fbi->par; + + drv_data->suspended = true; + + if (mxc_fbi->blank == FB_BLANK_UNBLANK) + mxcfb_set_refresh_mode(fbi, MXCFB_REFRESH_OFF, NULL); + /* Display OFF */ + cmd_param = 0x08; + ipu_adc_write_cmd(mxc_fbi->disp_num, CMD, 0x0, &cmd_param, 1); + + return 0; +} + +/*! + * Resumes the framebuffer and unblanks the screen. Power management support + * + * @param pdev pointer to device structure. + * + * @return success + */ +static int mxcfb_resume(struct platform_device *pdev) +{ + uint32_t cmd_param; + struct mxcfb_data *drv_data = platform_get_drvdata(pdev); + struct fb_info *fbi = drv_data->fbi; + struct mxcfb_info *mxc_fbi = fbi->par; + + // Display ON + cmd_param = 0x00; + ipu_adc_write_cmd(mxc_fbi->disp_num, CMD, 0x0, &cmd_param, 1); + drv_data->suspended = false; + + if (mxc_fbi->blank == FB_BLANK_UNBLANK) + mxcfb_set_refresh_mode(fbi, MXCFB_REFRESH_DEFAULT, NULL); + wake_up_interruptible(&drv_data->suspend_wq); + + return 0; +} +#else +#define mxcfb_suspend NULL +#define mxcfb_resume NULL +#endif + +/*! + * This structure contains pointers to the power management callback functions. + */ +static struct platform_driver mxcfb_driver = { + .driver = { + .name = MXCFB_NAME, + }, + .probe = mxcfb_probe, + .suspend = mxcfb_suspend, + .resume = mxcfb_resume, +}; + +/*! + * Device definition for the Framebuffer + */ +static struct platform_device mxcfb_device = { + .name = MXCFB_NAME, + .id = 0, + .dev = { + .coherent_dma_mask = 0xFFFFFFFF, + } +}; + +/*! + * Main entry function for the framebuffer. The function registers the power + * management callback functions with the kernel and also registers the MXCFB + * callback functions with the core Linux framebuffer driver \b fbmem.c + * + * @return Error code indicating success or failure + */ +static int mxcfb_init(void) +{ + int ret = 0; + + ret = platform_driver_register(&mxcfb_driver); + if (ret == 0) { + ret = platform_device_register(&mxcfb_device); + if (ret != 0) { + platform_driver_unregister(&mxcfb_driver); + } + } + return ret; +} + +static void mxcfb_exit(void) +{ + struct fb_info *fbi = dev_get_drvdata(&mxcfb_device.dev); + + if (fbi) { + mxcfb_unmap_video_memory(fbi); + + if (&fbi->cmap) + fb_dealloc_cmap(&fbi->cmap); + + unregister_framebuffer(fbi); + framebuffer_release(fbi); + } + + platform_device_unregister(&mxcfb_device); + platform_driver_unregister(&mxcfb_driver); +} + +module_init(mxcfb_init); +module_exit(mxcfb_exit); + +EXPORT_SYMBOL(mxcfb_set_refresh_mode); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MXC Toshiba QVGA framebuffer driver"); +MODULE_SUPPORTED_DEVICE("fb"); diff --git a/drivers/w1/masters/Kconfig b/drivers/w1/masters/Kconfig index 8236d447adf5..9cc7a6b71530 100644 --- a/drivers/w1/masters/Kconfig +++ b/drivers/w1/masters/Kconfig @@ -34,6 +34,12 @@ config W1_MASTER_DS2482 This driver can also be built as a module. If so, the module will be called ds2482. +config W1_MASTER_MXC + tristate "Freescale MXC driver for 1-wire" + depends on W1 && ARCH_MXC + help + Say Y here to enable MXC 1-wire host + config W1_MASTER_DS1WM tristate "Maxim DS1WM 1-wire busmaster" depends on W1 && ARM diff --git a/drivers/w1/masters/Makefile b/drivers/w1/masters/Makefile index 11551b328186..55b169a6c9a1 100644 --- a/drivers/w1/masters/Makefile +++ b/drivers/w1/masters/Makefile @@ -5,4 +5,6 @@ obj-$(CONFIG_W1_MASTER_MATROX) += matrox_w1.o obj-$(CONFIG_W1_MASTER_DS2490) += ds2490.o obj-$(CONFIG_W1_MASTER_DS2482) += ds2482.o +obj-$(CONFIG_W1_MASTER_MXC) += mxc_w1.o + obj-$(CONFIG_W1_MASTER_DS1WM) += ds1wm.o diff --git a/drivers/w1/masters/mxc_w1.c b/drivers/w1/masters/mxc_w1.c new file mode 100644 index 000000000000..8164951fb39a --- /dev/null +++ b/drivers/w1/masters/mxc_w1.c @@ -0,0 +1,432 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @defgroup MXC_OWIRE MXC Driver for owire interface + */ + +/*! + * @file mxc_w1.c + * + * @brief Driver for the Freescale Semiconductor MXC owire interface. + * + * + * @ingroup MXC_OWIRE + */ + +/*! + * Include Files + */ + +#include <asm/atomic.h> +#include <asm/types.h> +#include <asm/io.h> +#include <linux/delay.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/list.h> +#include <linux/interrupt.h> +#include <linux/spinlock.h> +#include <linux/slab.h> +#include <linux/pci_ids.h> +#include <linux/pci.h> +#include <linux/timer.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <asm/hardware.h> +#include <asm/setup.h> + +#include "../w1.h" +#include "../w1_int.h" +#include "../w1_log.h" + +/* + * mxc function declarations + */ + +static int __devinit mxc_w1_probe(struct platform_device *pdev); +static int __devexit mxc_w1_remove(struct platform_device *pdev); +static DECLARE_COMPLETION(transmit_done); +extern void gpio_owire_active(void); +extern void gpio_owire_inactive(void); + +/* + * MXC W1 Register offsets + */ +#define MXC_W1_CONTROL 0x00 +#define MXC_W1_TIME_DIVIDER 0x02 +#define MXC_W1_RESET 0x04 +#define MXC_W1_COMMAND 0x06 +#define MXC_W1_TXRX 0x08 +#define MXC_W1_INTERRUPT 0x0A +#define MXC_W1_INTERRUPT_EN 0x0C +DEFINE_SPINLOCK(w1_lock); + +/*! + * This structure contains pointers to callback functions. + */ +static struct platform_driver mxc_w1_driver = { + .driver = { + .name = "mxc_w1", + }, + .probe = mxc_w1_probe, + .remove = mxc_w1_remove, +}; + +/*! + * This structure is used to store + * information specific to w1 module. + */ + +struct mxc_w1_device { + char *base_address; + unsigned long found; + unsigned int clkdiv; + struct clk *clk; + struct w1_bus_master *bus_master; +}; + +/* + * this is the low level routine to + * reset the device on the One Wire interface + * on the hardware + * @param data the data field of the w1 device structure + * @return the function returns 0 when the reset pulse has + * been generated + */ +static u8 mxc_w1_ds2_reset_bus(void *data) +{ + volatile u8 reg_val; + u8 ret; + struct mxc_w1_device *dev = (struct mxc_w1_device *)data; + + __raw_writeb(0x80, (dev->base_address + MXC_W1_CONTROL)); + + do { + reg_val = __raw_readb(dev->base_address + MXC_W1_CONTROL); + } while (((reg_val >> 7) & 0x1) != 0); + ret = ((reg_val >> 7) & 0x1); + return ret; +} + +/*! + * this is the low level routine to read/write a bit on the One Wire + * interface on the hardware + * @param data the data field of the w1 device structure + * @param bit 0 = write-0 cycle, 1 = write-1/read cycle + * @return the function returns the bit read (0 or 1) + */ +static u8 mxc_w1_ds2_touch_bit(void *data, u8 bit) +{ + + volatile u8 reg_val; + struct mxc_w1_device *dev = (struct mxc_w1_device *)data; + u8 ret = 0; + + if (0 == bit) { + __raw_writeb((1 << 5), (dev->base_address + MXC_W1_CONTROL)); + + do { + reg_val = + __raw_readb(dev->base_address + MXC_W1_CONTROL); + } while (0 != ((reg_val >> 5) & 0x1)); + } + + else { + __raw_writeb((1 << 4), dev->base_address + MXC_W1_CONTROL); + do { + reg_val = + __raw_readb(dev->base_address + MXC_W1_CONTROL); + } while (0 != ((reg_val >> 4) & 0x1)); + + reg_val = + (((__raw_readb(dev->base_address + MXC_W1_CONTROL)) >> 3) & + 0x1); + ret = (u8) (reg_val); + } + + return ret; +} + +static void mxc_w1_ds2_write_byte(void *data, u8 byte) +{ + struct mxc_w1_device *dev = (struct mxc_w1_device *)data; + INIT_COMPLETION(transmit_done); + __raw_writeb(byte, (dev->base_address + MXC_W1_TXRX)); + __raw_writeb(0x10, (dev->base_address + MXC_W1_INTERRUPT_EN)); + wait_for_completion(&transmit_done); +} +static u8 mxc_w1_ds2_read_byte(void *data) +{ + volatile u8 reg_val; + struct mxc_w1_device *dev = (struct mxc_w1_device *)data; + mxc_w1_ds2_write_byte(data, 0xFF); + reg_val = __raw_readb((dev->base_address + MXC_W1_TXRX)); + return reg_val; +} +static u8 mxc_w1_read_byte(void *data) +{ + volatile u8 reg_val; + struct mxc_w1_device *dev = (struct mxc_w1_device *)data; + reg_val = __raw_readb((dev->base_address + MXC_W1_TXRX)); + return reg_val; +} +static irqreturn_t w1_interrupt_handler(int irq, void *data) +{ + u8 reg_val; + irqreturn_t ret = IRQ_NONE; + struct mxc_w1_device *dev = (struct mxc_w1_device *)data; + reg_val = __raw_readb((dev->base_address + MXC_W1_INTERRUPT)); + if ((reg_val & 0x10)) { + complete(&transmit_done); + reg_val = __raw_readb((dev->base_address + MXC_W1_TXRX)); + ret = IRQ_HANDLED; + } + return ret; +} +void search_ROM_accelerator(void *data, u8 search_type, + w1_slave_found_callback cb) +{ + u64 rn[2], last_rn[2], rn2[2]; + u64 rn1, rom_id, temp, temp1; + int i, j, z, w, last_zero, loop; + u8 bit, reg_val, bit2; + u8 byte, byte1; + int disc, prev_disc, last_disc; + struct mxc_w1_device *dev = (struct mxc_w1_device *)data; + last_rn[0] = 0; + last_rn[1] = 0; + rom_id = 0; + prev_disc = 0; + loop = 0; + disc = -1; + last_disc = 0; + last_zero = 0; + while (!last_zero) { + /* + * Reset bus and all 1-wire device state machines + * so they can respond to our requests. + * + * Return 0 - device(s) present, 1 - no devices present. + */ + if (mxc_w1_ds2_reset_bus(data)) { + pr_debug("No devices present on the wire.\n"); + break; + } + rn[0] = 0; + rn[1] = 0; + __raw_writeb(0x80, (dev->base_address + MXC_W1_CONTROL)); + mdelay(1); + mxc_w1_ds2_write_byte(data, 0xF0); + __raw_writeb(0x02, (dev->base_address + MXC_W1_COMMAND)); + memcpy(rn2, last_rn, 16); + z = 0; + w = 0; + for (i = 0; i < 16; i++) { + reg_val = rn2[z] >> (8 * w); + mxc_w1_ds2_write_byte(data, reg_val); + reg_val = mxc_w1_read_byte(data); + if ((reg_val & 0x3) == 0x3) { + pr_debug("Device is Not Responding\n"); + break; + } + for (j = 0; j < 8; j += 2) { + byte = 0xFF; + byte1 = 1; + byte ^= byte1 << j; + bit = (reg_val >> j) & 0x1; + bit2 = (reg_val >> j); + if (bit) { + prev_disc = disc; + disc = 8 * i + j; + reg_val &= byte; + } + } + rn1 = 0; + rn1 = reg_val; + rn[z] |= rn1 << (8 * w); + w++; + if (i == 7) { + z++; + w = 0; + } + } + if ((disc == -1) || (disc == prev_disc)) + last_zero = 1; + if (disc == last_disc) + disc = prev_disc; + z = 0; + rom_id = 0; + for (i = 0, j = 1; i < 64; i++) { + temp = 0; + temp = (rn[z] >> j) & 0x1; + rom_id |= (temp << i); + j += 2; + if (i == 31) { + z++; + j = 1; + } + + } + if (disc > 63) { + last_rn[0] = rn[0]; + temp1 = rn[1]; + loop = disc % 64; + temp = 1; + temp1 |= (temp << (loop + 1)) - 1; + temp1 |= (temp << (loop + 1)); + last_rn[1] = temp1; + + } else { + last_rn[1] = 0; + temp1 = rn[0]; + temp = 1; + temp1 |= (temp << (loop + 1)) - 1; + temp1 |= (temp << (loop + 1)); + last_rn[0] = temp1; + } + last_disc = disc; + cb(data, rom_id); + } +} + +/*! + * this routine sets the One Wire clock + * to a value of 1 Mhz, as required by + * hardware. + * @param dev the device structure for w1 + * @return The function returns void + */ +static void mxc_w1_hw_init(struct mxc_w1_device *dev) +{ + clk_enable(dev->clk); + + /* set the timer divider clock to divide by 65 */ + /* as the clock to the One Wire is at 66.5MHz */ + __raw_writeb(dev->clkdiv, dev->base_address + MXC_W1_TIME_DIVIDER); + + return; +} + +/*! + * this is the probe routine for the One Wire driver. + * It is called during the driver initilaization. + * @param pdev the platform device structure for w1 + * @return The function returns 0 on success + * and a non-zero value on failure + * + */ +static int __devinit mxc_w1_probe(struct platform_device *pdev) +{ + struct mxc_w1_device *dev; + struct mxc_w1_config *data = + (struct mxc_w1_config *)pdev->dev.platform_data; + int flag, ret_val, irq; + int err = 0; + ret_val = 0; + flag = data->search_rom_accelerator; + dev = kzalloc(sizeof(struct mxc_w1_device) + + sizeof(struct w1_bus_master), GFP_KERNEL); + if (!dev) { + return -ENOMEM; + } + dev->clk = clk_get(&pdev->dev, "owire_clk"); + dev->bus_master = (struct w1_bus_master *)(dev + 1); + dev->found = 1; + dev->clkdiv = (clk_get_rate(dev->clk) / 1000000) - 1; + dev->base_address = (void *)IO_ADDRESS(OWIRE_BASE_ADDR); + + mxc_w1_hw_init(dev); + dev->bus_master->data = dev; + dev->bus_master->reset_bus = &mxc_w1_ds2_reset_bus; + dev->bus_master->touch_bit = &mxc_w1_ds2_touch_bit; + if (flag) { + dev->bus_master->write_byte = &mxc_w1_ds2_write_byte; + dev->bus_master->read_byte = &mxc_w1_ds2_read_byte; + dev->bus_master->search = &search_ROM_accelerator; + irq = platform_get_irq(pdev, 0); + ret_val = + request_irq(irq, w1_interrupt_handler, 0, "mxc_w1", dev); + if (ret_val) { + pr_debug("OWire:request_irq(%d) returned error %d\n", + irq, ret_val); + return -1; + } + } + err = w1_add_master_device(dev->bus_master); + if (err) + goto err_out_free_device; + + platform_set_drvdata(pdev, dev); + return 0; + + err_out_free_device: + + kfree(dev); + return err; +} + +/* + * disassociate the w1 device from the driver + * @param dev the device structure for w1 + * @return The function returns void + */ +static int mxc_w1_remove(struct platform_device *pdev) +{ + struct mxc_w1_device *dev = platform_get_drvdata(pdev); + + clk_disable(dev->clk); + clk_put(dev->clk); + if (dev->found) { + w1_remove_master_device(dev->bus_master); + } + platform_set_drvdata(pdev, NULL); + + return 0; +} + +/* + * initialize the driver + * @return The function returns 0 on success + * and a non-zero value on failure + */ + +static int __init mxc_w1_init(void) +{ + int ret; + + printk(KERN_INFO "Serial: MXC OWire driver\n"); + + gpio_owire_active(); + + ret = platform_driver_register(&mxc_w1_driver); + + return ret; +} + +/* + * cleanup before the driver exits + */ +static void mxc_w1_exit(void) +{ + gpio_owire_inactive(); + platform_driver_unregister(&mxc_w1_driver); +} + +module_init(mxc_w1_init); +module_exit(mxc_w1_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Freescale Semiconductors Inc"); +MODULE_DESCRIPTION("Driver for One-Wire on MXC"); diff --git a/drivers/w1/slaves/Kconfig b/drivers/w1/slaves/Kconfig index 3df29a122f84..b474c333325c 100644 --- a/drivers/w1/slaves/Kconfig +++ b/drivers/w1/slaves/Kconfig @@ -16,6 +16,23 @@ config W1_SLAVE_SMEM Say Y here if you want to connect 1-wire simple 64bit memory rom(ds2401/ds2411/ds1990*) to your wire. +config W1_SLAVE_DS2751 + tristate "Battery Level sensing support (DS2751)" + depends on W1 + help + Say Y here if you want to use a 1-wire + battery level sensing device (DS2751). + +config W1_SLAVE_DS2751_CRC + bool "Protect DS2751 data with a CRC16" + depends on W1_SLAVE_DS2751 + select CRC16 + help + Say Y here to protect DS2751 data with a CRC16. + Each block has 30 bytes of data and a two byte CRC16. + Full block writes are only allowed if the CRC is valid. + + config W1_SLAVE_DS2433 tristate "4kb EEPROM family support (DS2433)" help diff --git a/drivers/w1/slaves/Makefile b/drivers/w1/slaves/Makefile index a8eb7524df1d..92e7768b60e3 100644 --- a/drivers/w1/slaves/Makefile +++ b/drivers/w1/slaves/Makefile @@ -6,4 +6,5 @@ obj-$(CONFIG_W1_SLAVE_THERM) += w1_therm.o obj-$(CONFIG_W1_SLAVE_SMEM) += w1_smem.o obj-$(CONFIG_W1_SLAVE_DS2433) += w1_ds2433.o obj-$(CONFIG_W1_SLAVE_DS2760) += w1_ds2760.o +obj-$(CONFIG_W1_SLAVE_DS2751) += w1_ds2751.o diff --git a/drivers/w1/slaves/w1_ds2751.c b/drivers/w1/slaves/w1_ds2751.c new file mode 100644 index 000000000000..9346a21bdc70 --- /dev/null +++ b/drivers/w1/slaves/w1_ds2751.c @@ -0,0 +1,317 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +/* + * Implementation based on w1_ds2433.c + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/device.h> +#include <linux/types.h> +#include <linux/delay.h> +#ifdef CONFIG_W1_F51_CRC +#include <linux/crc16.h> + +#define CRC16_INIT 0 +#define CRC16_VALID 0xb001 + +#endif + +#include "../w1.h" +#include "../w1_int.h" +#include "../w1_family.h" + +#define W1_EEPROM_SIZE 32 +#define W1_PAGE_SIZE 32 +#define W1_PAGE_BITS 5 +#define W1_PAGE_MASK 0x1F + +#define W1_F51_TIME 300 + +#define W1_F51_READ_EEPROM 0xB8 +#define W1_F51_WRITE_SCRATCH 0x6C +#define W1_F51_READ_SCRATCH 0x69 +#define W1_F51_COPY_SCRATCH 0x48 +#define W1_STATUS_OFFSET 0x0001 +#define W1_EEPROM_OFFSET 0x0007 +#define W1_SPECIAL_OFFSET 0x0008 +#define W1_EEPROM_BLOCK_0 0x0020 +#define W1_EEPROM_BLOCK_1 0x0030 +#define W1_SRAM 0x0080 +struct w1_f51_data { + u8 memory[W1_EEPROM_SIZE]; + u32 validcrc; +}; + +/** + * Check the file size bounds and adjusts count as needed. + * This would not be needed if the file size didn't reset to 0 after a write. + */ +static inline size_t w1_f51_fix_count(loff_t off, size_t count, size_t size) +{ + if (off > size) + return 0; + + if ((off + count) > size) + return (size - off); + + return count; +} + +#ifdef CONFIG_W1_F51_CRC +static int w1_f51_refresh_block(struct w1_slave *sl, struct w1_f51_data *data, + int block) +{ + u8 wrbuf[3]; + int off = block * W1_PAGE_SIZE; + if (data->validcrc & (1 << block)) + return 0; + + if (w1_reset_select_slave(sl)) { + data->validcrc = 0; + return -EIO; + } + wrbuf[0] = W1_F51_READ_EEPROM; + wrbuf[1] = off & 0xff; + wrbuf[2] = off >> 8; + w1_write_block(sl->master, wrbuf, 3); + w1_read_block(sl->master, &data->memory[off], W1_PAGE_SIZE); + + /* cache the block if the CRC is valid */ + if (crc16(CRC16_INIT, &data->memory[off], W1_PAGE_SIZE) == CRC16_VALID) + data->validcrc |= (1 << block); + + return 0; +} +#endif /* CONFIG_W1_F51_CRC */ + +static ssize_t w1_f51_read_bin(struct kobject *kobj, char *buf, loff_t off, + size_t count) +{ + struct w1_slave *sl = kobj_to_w1_slave(kobj); +#ifdef CONFIG_W1_F51_CRC + struct w1_f51_data *data = sl->family_data; + int i, min_page, max_page; +#else + u8 wrbuf[3]; +#endif + + if ((count = w1_f51_fix_count(off, count, W1_EEPROM_SIZE)) == 0) { + return 0; + } + mutex_lock(&sl->master->mutex); +#ifdef CONFIG_W1_F51_CRC + min_page = (off >> W1_PAGE_BITS); + max_page = (off + count - 1) >> W1_PAGE_BITS; + for (i = min_page; i <= max_page; i++) { + if (w1_f51_refresh_block(sl, data, i)) { + count = -EIO; + goto out_up; + } + } + memcpy(buf, &data->memory[off], count); + +#else /* CONFIG_W1_F51_CRC */ + + /* read directly from the EEPROM */ + if (w1_reset_select_slave(sl)) { + count = -EIO; + goto out_up; + } + off = (loff_t) W1_EEPROM_BLOCK_0; + wrbuf[0] = W1_F51_READ_EEPROM; + wrbuf[1] = off & 0xff; + wrbuf[2] = off >> 8; + w1_write_block(sl->master, wrbuf, 3); + if (w1_reset_select_slave(sl)) { + count = -EIO; + goto out_up; + } + + wrbuf[0] = W1_F51_READ_SCRATCH; + wrbuf[1] = off & 0xff; + wrbuf[2] = off >> 8; + w1_write_block(sl->master, wrbuf, 3); + w1_read_block(sl->master, buf, count); + +#endif /* CONFIG_W1_F51_CRC */ + + out_up: + mutex_unlock(&sl->master->mutex); + return count; +} + +/** + * Writes to the scratchpad and reads it back for verification. + * Then copies the scratchpad to EEPROM. + * The data must be on one page. + * The master must be locked. + * + * @param sl The slave structure + * @param addr Address for the write + * @param len length must be <= (W1_PAGE_SIZE - (addr & W1_PAGE_MASK)) + * @param data The data to write + * @return 0=Success -1=failure + */ +static int w1_f51_write(struct w1_slave *sl, int addr, int len, const u8 * data) +{ + u8 wrbuf[4]; + u8 rdbuf[W1_EEPROM_SIZE + 3]; + u8 es = (addr + len - 1) & 0x1f; + /* Write the data to the scratchpad */ + if (w1_reset_select_slave(sl)) + return -1; + wrbuf[0] = W1_F51_WRITE_SCRATCH; + wrbuf[1] = addr & 0xff; + wrbuf[2] = addr >> 8; + + w1_write_block(sl->master, wrbuf, 3); + w1_write_block(sl->master, data, len); + /* Read the scratchpad and verify */ + if (w1_reset_select_slave(sl)) + return -1; + wrbuf[0] = W1_F51_READ_SCRATCH; + w1_write_block(sl->master, wrbuf, 3); + w1_read_block(sl->master, rdbuf, len + 3); + /* Compare what was read against the data written */ + if (memcmp(data, &rdbuf[0], len) != 0) { + printk("Error reading the scratch Pad\n"); + return -1; + } + /* Copy the scratchpad to EEPROM */ + if (w1_reset_select_slave(sl)) + return -1; + wrbuf[0] = W1_F51_COPY_SCRATCH; + wrbuf[3] = es; + w1_write_block(sl->master, wrbuf, 4); + /* Sleep for 5 ms to wait for the write to complete */ + msleep(5); + + /* Reset the bus to wake up the EEPROM (this may not be needed) */ + w1_reset_bus(sl->master); + + return 0; +} + +static ssize_t w1_f51_write_bin(struct kobject *kobj, char *buf, loff_t off, + size_t count) +{ + struct w1_slave *sl = kobj_to_w1_slave(kobj); + int addr; + + if ((count = w1_f51_fix_count(off, count, W1_EEPROM_SIZE)) == 0) + return 0; + off = (loff_t) 0x0020; +#ifdef CONFIG_W1_F51_CRC + /* can only write full blocks in cached mode */ + if ((off & W1_PAGE_MASK) || (count & W1_PAGE_MASK)) { + dev_err(&sl->dev, "invalid offset/count off=%d cnt=%zd\n", + (int)off, count); + return -EINVAL; + } + + /* make sure the block CRCs are valid */ + for (idx = 0; idx < count; idx += W1_PAGE_SIZE) { + if (crc16(CRC16_INIT, &buf[idx], W1_PAGE_SIZE) != CRC16_VALID) { + dev_err(&sl->dev, "bad CRC at offset %d\n", (int)off); + return -EINVAL; + } + } +#endif /* CONFIG_W1_F51_CRC */ + + mutex_lock(&sl->master->mutex); + + /* Can only write data to one page at a time */ + addr = off; + if (w1_f51_write(sl, addr, count, buf) < 0) { + count = -EIO; + goto out_up; + } + + out_up: + mutex_unlock(&sl->master->mutex); + + return count; +} + +static struct bin_attribute w1_f51_bin_attr = { + .attr = { + .name = "eeprom", + .mode = S_IRUGO | S_IWUSR, + .owner = THIS_MODULE, + }, + .size = W1_EEPROM_SIZE, + .read = w1_f51_read_bin, + .write = w1_f51_write_bin, +}; + +static int w1_f51_add_slave(struct w1_slave *sl) +{ + int err; +#ifdef CONFIG_W1_F51_CRC + struct w1_f51_data *data; + data = kmalloc(sizeof(struct w1_f51_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + memset(data, 0, sizeof(struct w1_f51_data)); + sl->family_data = data; + +#endif /* CONFIG_W1_F51_CRC */ + + err = sysfs_create_bin_file(&sl->dev.kobj, &w1_f51_bin_attr); + +#ifdef CONFIG_W1_F51_CRC + if (err) + kfree(data); +#endif /* CONFIG_W1_F51_CRC */ + + return err; +} + +static void w1_f51_remove_slave(struct w1_slave *sl) +{ +#ifdef CONFIG_W1_F51_CRC + kfree(sl->family_data); + sl->family_data = NULL; +#endif /* CONFIG_W1_F51_CRC */ + sysfs_remove_bin_file(&sl->dev.kobj, &w1_f51_bin_attr); +} + +static struct w1_family_ops w1_f51_fops = { + .add_slave = w1_f51_add_slave, + .remove_slave = w1_f51_remove_slave, +}; + +static struct w1_family w1_family_51 = { + .fid = W1_EEPROM_DS2751, + .fops = &w1_f51_fops, +}; + +static int __init w1_f51_init(void) +{ + return w1_register_family(&w1_family_51); +} + +static void __exit w1_f51_fini(void) +{ + w1_unregister_family(&w1_family_51); +} + +module_init(w1_f51_init); +module_exit(w1_f51_fini); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Freescale Semiconductors Inc"); +MODULE_DESCRIPTION + ("w1 family 51 driver for DS2751, Battery Level Sensing Device"); diff --git a/drivers/w1/w1_family.h b/drivers/w1/w1_family.h index ef1e1dafa19a..f77988ae5614 100644 --- a/drivers/w1/w1_family.h +++ b/drivers/w1/w1_family.h @@ -32,6 +32,7 @@ #define W1_THERM_DS18S20 0x10 #define W1_THERM_DS1822 0x22 #define W1_EEPROM_DS2433 0x23 +#define W1_EEPROM_DS2751 0x51 #define W1_THERM_DS18B20 0x28 #define W1_FAMILY_DS2760 0x30 diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 52dff40ec192..c47f7f71a973 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -189,6 +189,18 @@ config PNX4008_WATCHDOG Say N if you are unsure. +config MXC_WATCHDOG + tristate "MXC watchdog" + depends on WATCHDOG && WATCHDOG_NOWAYOUT + depends on ARCH_MXC + help + Watchdog timer embedded into MXC chips. This will + reboot your system when timeout is reached. + + NOTE: once enabled, this timer cannot be disabled. + To compile this driver as a module, choose M here: the + module will be called mxc_wdt. + config IOP_WATCHDOG tristate "IOP Watchdog" depends on PLAT_IOP diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index 87483cc63252..379898b7617d 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -36,6 +36,7 @@ obj-$(CONFIG_S3C2410_WATCHDOG) += s3c2410_wdt.o obj-$(CONFIG_SA1100_WATCHDOG) += sa1100_wdt.o obj-$(CONFIG_MPCORE_WATCHDOG) += mpcore_wdt.o obj-$(CONFIG_EP93XX_WATCHDOG) += ep93xx_wdt.o +obj-$(CONFIG_MXC_WATCHDOG) += mxc_wdt.o obj-$(CONFIG_PNX4008_WATCHDOG) += pnx4008_wdt.o obj-$(CONFIG_IOP_WATCHDOG) += iop_wdt.o obj-$(CONFIG_DAVINCI_WATCHDOG) += davinci_wdt.o |